前面介绍了如何使用字符流读写文件,并指出字符流工具的处理局限,进而给出随机文件工具加以改进。随机文件工具除了支持访问文件内部的任意位置,更关键的一点是通过字节数组读写文件数据,采取字节方式比起字符方式有下列两个好处:
1、文件长度以字节为单位计量,可以分配等长的字节数组,却无法分配合适长度的字符数组,因此采用字节方式便于从文件中读取数据。
2、字符流工具主要以字符为单位处理数据,意味着它适合用来读写文本文件,不适用于二进制文件(包括图片文件、音频文件、视频文件等等),而字节方式不存在此类限制。
虽说随机文件工具已经实现了以字节方式读写文件,但它更适合大文件的任意位置读写,倘若用于一般文件的处理就显得大材小用了。毕竟杀鸡焉用牛刀,何况牛刀也太笨重了,远不如普通的刀子灵活。可是字符流工具力有不逮,随机文件工具又未恰到好处,难不成还有更方便易用的工具吗?其实文件读写可以抽象为对某个设备的输入输出操作,写文件相当于向文件输出数据,读文件相当于从文件输入数据。类似的操作还有很多,例如打印文件可看作是向打印机输出待打印的文本,敲代码可看作是从键盘输入每个按键对应的字符。Java把这些相关的输入输出操作统一为I/O流,其中字母I表示输入Input,字母O表示输出Output。先前介绍的FileReader和FileWriter属于I/O流中的字符流,而以字节为单位的则是I/O流中的字节流,字节流本身是个大家族,它有两个基类,分别是输入流InputStream和输出流OutputStream,由这两个类派生出丰富多样的输入输出流,各自用于不同的业务场景。
文件字节流是输入输出流当中最常见的一种,它包括文件输出流FileOutputStream和文件输入流FileInputStream,其中FileOutputStream用来将数据写入文件,FileInputStream用来从文件读取数据,并且二者都采取字节数组保存信息。文件输出流的构造方法支持直接填入文件路径,其对象可调用write方法把字节数组写入文件,也可调用close方法关闭文件,用起来FileOutputStream像是File与FileWriter的结合体,当然就更加好用。同时不管是输出流还是输入流,它们都实现了AutoCloseable接口,故而支持try-with-resources方式的资源自动释放。下面是利用文件输出流FileOutputStream写文件的代码例子:
private static String mFileName = "D:/test/aae.txt"; // 利用文件输出流写入文件。注意FileOutputStream处理的是字节信息 private static void writeFile() { String str = "白日依山尽,黄河入海流。 欲穷千里目,更上一层楼。"; // 根据指定路径构建文件输出流对象 try (FileOutputStream fos = new FileOutputStream(mFileName)) { fos.write(str.getBytes()); // 把字节数组写入文件输出流 // 在try(...)里面创建的输入输出流,程序会在处理完成后自动关闭,所以下面的close方法不必显式调用 //fos.close(); // 关闭文件输出流 } catch (Exception e) { e.printStackTrace(); } }
依此类推,文件输入流的构造方法同样支持直接填入文件路径,也拥有read读文件方法和close关闭文件方法,像是File与FileReader的结合体。另外FileInputStream有几个方法值得一提,一个是skip方法,它命令当前位置跳过若干字节,注意该方法跳过的是字节数而非字符数。另一个是available方法,它返回文件当前位置后面的剩余部分大小,刚创建文件输入流对象之时调用available方法,得到的就是文件大小;如果先调用skip方法再调用available方法,得到的数值为文件大小减去跳过的字节数。下面是利用文件输入流读文件的代码例子:
// 利用文件输入流读取文件 private static void readFile() { // 根据指定路径构建文件输入流对象 try (FileInputStream fis = new FileInputStream(mFileName)) { // 分配长度为文件大小的字节数组。available方法返回当前位置后面的剩余部分大小 byte[] bytes = new byte[fis.available()]; fis.read(bytes); // 从文件输入流中读取字节数组 String content = new String(bytes); // 把字节数组转换为字符串 System.out.println("content="+content); // 在try(...)里面创建的输入输出流,程序会在处理完成后自动关闭,所以下面的close方法不必显式调用 //fis.close(); // 关闭文件输入流 } catch (Exception e) { e.printStackTrace(); } }
更多Java技术文章参见《Java开发笔记(序)章节目录》