尽管可以通过不同的方式组合 IO 流类, 但我们可能也就只用到其中的几种组合. 下面的例子可以作为典型的 IO 用法的基本参考. 在这些示例中, 异常处理都被简化为将异常传递给控制台, 但是这只有在小型示例和工具中才适用. 在代码中, 你需要考虑更加复杂的错误处理方式.
同样, 本文会包括如下几个方面:
缓冲输入文件
从内存输入
格式化的内存输入
基本的文件输出
存储和恢复数据
读写随机访问文件
实用工具
总结
1. 缓冲输入文件
如果想要打开一个文件用于字符输入, 可以使用以 String 或 File 对象作为文件名的 FileReader. 为了提高速度, 我们可以对那个文件进行缓冲, 那么我们需要将所产生的引用传给一个 BufferedReader 构造器. 通过使用其 readLine()方法来逐行读取文件, 当 readLine()返回 null 时, 就到了文件末尾.
- public class BufferedInputFile {
- public static String read(String fileName) throws Exception {
- BufferedReader br = new BufferedReader(new FileReader(fileName));
- StringBuilder str = new StringBuilder();
- String temp = null;
- while((temp = br.readLine()) != null) {
- str.append(temp + "\n");
- }
- br.close();
- return str.toString();
- }
- public static void main(String[] args) {
- try {
- System.out.println(BufferedInputFile.read("pom.xml"));
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
文件的全部内容都累积在字符串 str 中, 最后记得调用 close()来关闭流.
2. 从内存输入
在下面的示例中, 从上面的 BufferedInputFile.read()读入的 String 结果被用来创建一个 StringReader. 然后调用 read()每次读取一个字符, 并把它打印到控制台.
- public class MemoryInput {
- public static void main(String[] args) {
- try {
- StringReader sr = new StringReader(BufferedInputFile.read("pom.xml"));
- int c;
- while((c = sr.read()) != -1) {
- System.out.print((char)c);
- }
- }catch(Exception e) {
- }
- }
- }
需要注意的是 read()是以 int 形式返回下一个字节, 因此必须将类型强转为 char 才能显示正确结果.
3. 格式化的内存输入
要读取格式化数据, 可以使用 DataInputStream, 它是一个面向字节的 I/O 类, 我们可以用 InputStream 以字节的形式读取任何数据.
- public class FormattedMemoryInput {
- public static void main(String[] args) {
- try {
- DataInputStream di = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read("pom.xml").getBytes()));
- while(di.available() != 0) {
- System.out.print((char)di.readByte());
- }
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
这里需要注意必须为 ByteArrayInputStream 的构造函数提供字节数组, 而 ByteArrayInputStream 传递给 DataInputStream 之后进行了一次 "装饰", 可以进行格式化输入 (比如直接读取 int,double 等类型), 这里我们只是通过 readByte 读取单个字节, 调用该方法时任何字节的值都是合法的结果, 因此返回值是不能用来检测输入是否结束, 这里我们使用 available() 方法查看还有多少可供存取的字符来判断是否结束.
4. 基本的文件输出
FileWriter 对象可以向文件写入数据, 通常会用 BufferedWriter 将其包装起来用以缓冲输出以提高性能. 在本例中为了提供格式化机制, 将其装饰成 PrintWriter:
- public class BasicFileOutput {
- static String file = "BasicFileOutput.out";
- public static void main(String[] args) {
- try {
- BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read("pom.xml")));
- PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
- String temp;
- int count = 0;
- while((temp = in.readLine()) != null) {
- out.println(count++ + temp);
- }
- in.close();
- out.close();
- System.out.println(BufferedInputFile.read(file));
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
这里在读取 BasicFileOutput.out 的内容之前, 先调用了 out 的 close()将其关闭, 一方面是因为流用完之后需要及时关闭以节省资源, 另一方面这里用到了缓冲区, 如果不为所有的输出文件调用 close(), 缓冲区的内容可能不会刷新清空, 这样可能导致信息不完整.
另外 Java SE5 在 PrintWriter 中添加了一个辅助构造器, 可以很方便根据文件名直接构造一个 PrintWriter 而不用执行一系列的装饰工作:
PrintWriter out = new PrintWriter(file);
5. 存储和恢复数据
PrintWriter 可以对数据进行格式化, 以便阅读. 但是为了输出可供另一个 "流" 恢复的数据, 我们需要用 DataOutputStream 写入数据, 并用 DataInputStream 恢复数据. 当然, 这些流可以是任何形式, 在下面的例子中使用的是一个文件.
- public class StoringAndRecoveringData {
- public static void main(String[] args) {
- try {
- DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
- out.writeDouble(3.14159);
- out.writeUTF("That was pi");
- out.writeDouble(1.41413);
- out.writeUTF("Square root of 2");
- out.close();
- DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
- System.out.println(in.readDouble());
- System.out.println(in.readUTF());
- System.out.println(in.readDouble());
- System.out.println(in.readUTF());
- in.close();
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
使用 DataOutputStream 写入数据, Java 可以保证我们可以使用 DataInputStream 准确地读取数据 -- 无论读和写数据的平台多么不同. 当我们使用 DataOutputStream 时, 写字符串并且让 DataInputStream 能够恢复它的唯一可靠的做法就是使用 UTF-8 编码, 在这个例子中是靠 writeUTF()和 readUTF()来实现的.
writeDouble()和 readDouble()方法能够写入和恢复 double 类型的数据. 对于其他类型的数据, 也有类似的方法用于读写. 但是为了保证所有的读写方法都能够正常工作, 我们必须知道流中数据项所在的确切位置, 因为极有可能将保存的 double 数据作为一个简单的字节序列, char 或其他类型读入.
6. 读写随机访问文件
使用 RandomAccessFile, 类似于组合使用了 DataInputStream 和 DataOutputStream, 可以同时对一个文件执行读写操作, 同时可以利用 seek()在文件中到处移动, 非常方便, 关于 RandomAccessFile 的详细用法, 前面有专门写过<<Java I/O 系统: File 和 RandomAccessFile>>.
但是在使用 RandomAccessFile 时, 你需要知道文件的排版, 这样才能正确地操作它, RandomAccessFile 拥有读取基本类型和 UTF-8 字符串的各种具体方法.
- public class UsingRandomAccessFile{
- static String file = "rtest.dat";
- static void display() throws IOException{
- RandomAccessFile rf = new RandomAccessFile(file,"r");
- for(int i = 0; i <7; i++){
- System.out.println("Value" + i + ":" + rf.readDouble());
- }
- System.out.println(rf.readUTF());
- rf.close();
- }
- public static void main(String[] args) throws IOException{
- RandomAccessFile rf = new RandomAccessFile(file,"rw");
- for(int i = 0; i < 7; i++){
- rf.writeDouble(i*1.414);
- }
- rf.writeUTF("The end of the file");
- rf.close();
- display();
- rf = new RandomAccessFile(file,"rw");
- rf.seek(5*8);
- rf.writeDouble(47.0001);
- rf.close();
- display();
- }
- }
我们通过 writeDouble()方法往文件中写入 Double 类型数据并通过 readDouble()方法来读取, 这就是我们需要直到排版的原因, 如果读取的不是 Double 类型的数据有可能出现不是我们想要的结果.
7. 实用工具
到这里我们学习了多种 I/O 流的典型用法, 比如缓冲输入文件, 从内存输入, 基本的文件输出, 存储和恢复数据, 随机读写文件, 这些都是 Java I/O 流比较典型的用法. 这里我们发现读取文件, 修改, 在写出是一个很常见的程序化的任务, 但是 Java I/O 类库的设计有一个问题, 就是我们需要编写很多代码来实现这些操作, 要记住如何打开文件是一件优点困难的事情. 因此, 下面是收集的一些帮助类, 可以很容易为我们完成这些基本任务, 记录在这里, 方便以后查看.
这里收集了两个工具:
一个是 TextFile, 帮助我们读取和写入文件;
另一个是 BinaryFile, 帮助我们简化二进制文件的读取.
7.1 读取文件
TextFile 类包含的 static 方法可以像简单字符串那样读写文本文件, 并且我们可以创建一个 TextFile 对象, 它用一个 ArrayList 来保存文件的若干行, 好处是在我们操纵文件内容时可以使用 ArrayList 的所有功能.
- public class TextFile extends ArrayList<String>{
- // 将文件读取到一行字符串中
- public static String read(String fileName){
- StringBuilder sb = new StringBuilder();
- try{
- BufferedReader in = new BufferedReader(new FileReader(new File(fileName).getAbsoluteFile()));
- try{
- String s;;
- while((s = in.readLine()) != null){
- sb.append(s).append("\n");
- }
- }finally{
- in.close();
- }
- }catch (IOException e){
- throw new RuntimeException(e);
- }
- return sb.toString();
- }
- // 单次调用将一个字符串写入一个文件
- public static void write(String fileName,String text){
- try{
- PrintWriter out = new PrintWriter(new File(fileName).getAbsoluteFile());
- try{
- out.print(text);
- }finally{
- out.close();
- }
- }catch(IOException e){
- throw new RuntimeException(e);
- }
- }
- // 读取文件, 并通过正则表达式将其分离, 保存在 List 中
- public TextFile(String fileName,String splitter){
- super(Arrays.asList(read(fileName).split(splitter)));
- // 因为 split()方法有时会在返回的数组第一个位置产生一个空字符串
- if(get(0).equals(""))
- remove(0);
- }
- // 常规的分行读取
- public TextFile(String fileName){
- this(fileName,"\n");
- }
- // 将该 TextFile 中的内容分行写入指定文件中
- public void write(String fileName){
- try{
- PrintWriter out = new PrintWriter(new File(fileName).getAbsoluteFile());
- try{
- for(String item : this){
- out.println(item);
- }
- }finally{
- out.close();
- }
- }catch(IOException e){
- throw new RuntimeException(e);
- }
- }
- // 简单验证一下
- public static void main(String[] args){
- String file = read("TextFile.java");
- write("test.txt",file);
- TextFile text = new TextFile("test.txt");
- text.write("test2.txt");
- TreeSet<String> words = new TreeSet<String>(new TextFile("TextFile.java","\\W+"));
- System.out.println(words.headSet("a"));
- }
- }
这里利用静态的 read()方法将文件读取到一个字符串中, 再用静态的 write()方法将其写入到文件中. 然后将新写入的文件作为构造参数构造一个 TestFile 对象, 利用其 List 的特性, 将其内容写入文件 test2 中. 这个类的作用是帮我们读取文件, 可以通过静态的 read 方法读取到一个字符串中, 也可以通过构造器读取文件到一个 TextFile 对象中.
7.2 读取二进制文件
- public class BinaryFile{
- public static byte[] read(File bFile)throws IOException{
- BufferedInputStream bf = new BufferedInputStream(new FileInputStream());
- try{
- byte[] data = new byte[bf.available()];
- br.read(data);
- return data;
- }finally{
- bf.close();
- }
- }
- public static byte[] read(String bFile)throws IOException{
- return read(new File(bFile).getAbsoluteFile());
- }
- }
8. 总结
本文没有总结什么新的知识点, 只是总结了一些 Java I/O 的常见用法比如缓冲输入文件, 从内存输入, 基本的文件输出, 存储和恢复数据, 随机读写文件等, 并且搜集了两个工具类用来帮助我们读写文件读取二进制文件, 以提高些代码的效率.
posted on 2019-06-17 19:02 木瓜芒果 阅读(...) 评论(...) 编辑 收藏
来源: https://www.cnblogs.com/volcano-liu/p/10991248.html