[关闭]
@lemonguge 2015-07-01T08:08:16.000000Z 字数 8130 阅读 387

Java I/O系统(二)

I/O


流:代表任何有能力产出数据的数据源对象或者有能力接受数据的接收端对象,“流”屏蔽了实际I/O设备中处理数据的细节。

Java类库中的I/O分出输入和输出两部分,输入流和输出流是相对于内存设备而言。无论是输入还是输出,都有字符流和字节流。所以在学习I/O的过程中,会接触到四个继承体系,查看体系中的顶层类,来了解该体系的基本功能。

不过在学习之前,很有必要了解一下字符流诞生的历史。自从Java 1.0版本以来,在原来面向字节的类中添加了面向字符和基于Unicode的类。简单说“字符流就是:字节流+编码表。”字符流是为了处理文字数据方便而出现的对象,其实这些对象的内部使用的还是字节流(因为文字最终存储的也是字节数据)。只不过,通过字节流读取了相对应的字节数,没有对这些字节直接操作,而是去查了指定的(本机默认的)编码表,获取到了对应的文字。


流的基本方法

任何自InputStreamReader派生而来的类都含有名为read()的方法;任何自OutputStreamWriter派生而来的类都含有名为write()的基本方法。

可以发现它们的返回值都为int类型,很重要的一个原因是:如果已到达流的末尾,则返回-1。

对于上面这四种流,都具有close()方法:关闭此输出流并释放与此流有关的所有系统资源。对于输出流而言,还有一个flush()方法:刷新此输出流并强制写出所有缓冲的输出字节,当输出流调用close方法时,会先调用flush(),刷新缓冲中的数据到目的地。简单而言:当你在写代码的时候,会经常进行保存,这就相当与flush(),在关闭编译器的时候,会提示你进行保存,这就是close()前的flush()


对文件的读写

现在回到一个比较基本的问题,怎么从读写一个文件的数据?Java提供了四个类:FileInputStreamFileOutputStreamFileReaderFileWriter。如果操作的是纯文本文件,应该使用字符流,如果不是的话,只能使用字节流。

纯文本文件的读写操作

读操作

  1. import java.io.FileReader;
  2. import java.io.IOException;
  3. public class TxtReader {
  4. public static void main(String[] args) throws IOException {
  5. read1();
  6. }
  7. public static void read1() throws IOException {
  8. // 当前项目下有一个file.txt文件,文件内容为“abc你好!”
  9. FileReader fr = new FileReader("file.txt");
  10. int ch = 0;
  11. while ((ch = fr.read()) != -1) {
  12. System.out.println(ch);
  13. }
  14. fr.close();
  15. }
  16. } /* Output:
  17. 97
  18. 98
  19. 99
  20. 20320
  21. 22909
  22. 65281
  23. *///:~

可以发现,输出的是ASCⅡ而不是字符,所以应该进行强转操作。

  1. import java.io.File;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. public class TxtReader {
  5. public static void main(String[] args) throws IOException {
  6. read2();
  7. }
  8. public static void read2() throws IOException {
  9. // 当前项目下有一个file.txt文件,内容为“abc你好!”
  10. File file = new File("file.txt");
  11. FileReader fr = new FileReader(file);
  12. int ch = 0;
  13. while ((ch = fr.read()) != -1) {
  14. System.out.print((char) ch);
  15. }
  16. fr.close();
  17. }
  18. } /* Output:
  19. abc你好!
  20. *///:~

当然我们也可以使用缓冲数组的read()方法,将字符读入目标缓冲区数组。

  1. import java.io.File;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. public class TxtReader {
  5. public static void main(String[] args) throws IOException {
  6. read3();
  7. }
  8. public static void read3() throws IOException {
  9. // D盘下有一个file.txt文件,内容为“abc你好!”
  10. FileReader fr = new FileReader("D:" + File.separator + "file.txt");
  11. int length = 0;
  12. char[] chs = new char[4];
  13. while ((length = fr.read(chs)) != -1) {
  14. System.out.println(String.valueOf(chs, 0, length));
  15. }
  16. fr.close();
  17. }
  18. } /* Output:
  19. abc你
  20. 好!
  21. *///:~
  1. import java.io.File;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. public class TxtReader {
  5. public static void main(String[] args) throws IOException {
  6. read4();
  7. }
  8. public static void read4() throws FileNotFoundException, IOException {
  9. // 当前项目下有一个file.txt文件,内容为“abc你好!”
  10. FileReader fr = new FileReader("file.txt");
  11. int length = 0, offset = 2;
  12. char[] chs = new char[10];
  13. while ((length = fr.read(chs, offset, 4)) != -1) {
  14. System.out.println(new String(chs, offset, length));
  15. System.out.println(Arrays.toString(chs));
  16. }
  17. fr.close();
  18. }
  19. } /* Output:
  20. abc你
  21. [ , , a, b, c, 你, , , , ]
  22. 好!
  23. [ , , 好, !, c, 你, , , , ]
  24. *///:~

我们也可以读取文件的一部分。

  1. public static void main(String[] args) throws IOException {
  2. read5();
  3. }
  4. public static void read5() throws IOException {
  5. // 当前项目下有一个file.txt文件,内容为“abc你好!”
  6. FileReader fr = new FileReader("file.txt");
  7. int ch = 0;
  8. while ((ch = fr.read()) != -1) {
  9. if ((char) ch == 'b')
  10. fr.skip(2); // 跳过两个字符
  11. System.out.print((char) ch);
  12. }
  13. fr.close();
  14. }
  15. } /* Output:
  16. ab好!
  17. *///:~

写操作

  1. import java.io.FileWriter;
  2. import java.io.IOException;
  3. public class TxtWriter {
  4. public static void main(String[] args) throws IOException {
  5. // 在创建对象时,就必须明确该文件(用于存储数据的目的地)。
  6. // 如果文件存在,则会被覆盖;如果文件不存在,则会自动创建。
  7. FileWriter fw = new FileWriter("file.txt");
  8. }
  9. } ///:~

如果原先file.txt中有内容,执行程序会发现内部不见了。不过可以在构造函数中加入另一个布尔参数true,来实现对文件进行续写!

  1. import java.io.FileWriter;
  2. import java.io.IOException;
  3. public class TxtWriter {
  4. public static void main(String[] args) throws IOException {
  5. // 续写文件
  6. FileWriter fw = new FileWriter("file.txt", true);
  7. }
  8. } ///:~

当我们想写入数据到纯文本文件中,如果想换行的话,在Windows环境下,换行是\r\n\r的ASCⅡ为13,\n的ASCⅡ为10),而在UNIX环境下,换行是/n。为了代码的可移植性考虑,行分隔符字符串由系统属性line.separator定义。

  1. import java.io.FileWriter;
  2. import java.io.IOException;
  3. public class TxtWriter {
  4. // 换行符
  5. private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  6. public static void main(String[] args) throws IOException {
  7. write1();
  8. }
  9. public static void write1() throws IOException {
  10. FileWriter fw = new FileWriter("file.txt");
  11. fw.write(97);
  12. fw.write(98);
  13. fw.write(99);
  14. fw.write(LINE_SEPARATOR); // 在Windows平台下等价与fw.write("\r\n");
  15. fw.write(20320);
  16. fw.write(22909);
  17. fw.write(65281);
  18. fw.close();
  19. }
  20. } ///:~

可以在当前项目下的file.txt文件中看到两行数据,分别为“abc”和“你好!”。但是这样做明显比较麻烦,我们可以直接写入字符串。

  1. import java.io.FileWriter;
  2. import java.io.IOException;
  3. public class TxtWriter {
  4. // 换行符
  5. private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  6. public static void main(String[] args) throws IOException {
  7. write2();
  8. }
  9. public static void write2() throws IOException {
  10. FileWriter fw = new FileWriter("file.txt", true);
  11. fw.write("Hello World");
  12. fw.write(LINE_SEPARATOR);
  13. fw.write("你好,世界。");
  14. fw.close();
  15. }
  16. } ///:~

对当前项目下的file.txt文件进行续写,可以看到有三行数据,分别为“abc”、“你好!Hello World”和“你好,世界。”如果我们要写入大量的纯文本数据时,不建议读一个字符再写一个字符,应该使用将字符读入目标缓冲区数组的read()方法,在循环中调用flush()方法来时不时地进行保存。

  1. import java.io.FileWriter;
  2. import java.io.IOException;
  3. // 复制文本
  4. public class TxtWriter {
  5. public static void main(String[] args) throws IOException {
  6. copy();
  7. }
  8. public static void copy() throws IOException {
  9. System.out.println(LINE_SEPARATOR.length()); // 在Windows平台下长度为2
  10. File file = new File("file.txt");
  11. FileReader fr = new FileReader(file);
  12. FileWriter fw = new FileWriter("D:" + File.separator + "copy_file.txt");
  13. int length = 0;
  14. char[] chs = new char[10];
  15. while ((length = fr.read(chs)) != -1) {
  16. System.out.println("----------");
  17. String temp = new String(chs, 0, length);
  18. System.out.println(temp+":"+temp.length());
  19. fw.write(temp);
  20. fw.flush(); // 将每次读入的数据进行写出并保存
  21. }
  22. fr.close();
  23. fw.close();
  24. }
  25. } /* Output:
  26. 2
  27. ----------
  28. abc
  29. 你好!He:10
  30. ----------
  31. llo World
  32. :10 // 其中把换行符分开了,此次为\r(长度为1)
  33. ----------
  34. 你好,世界。:7 // \n(长度为1)和后面6个字符,长度为7
  35. *///:~

文件字节流

对于非纯文本文件的操作,不要使用读取一个字节的方式,这样做没有效率,应该使用目标缓冲区数组的读方法。在文件输入流中有一个int available()方法,返回该文件的大小。

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. public class FileStream {
  5. public static void main(String[] args) throws IOException {
  6. FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");
  7. System.out.println(String.valueOf(fis.available()>>>10)+"KB");
  8. }
  9. } /* Output:
  10. 12121KB
  11. *///:~

对文件的复制操作

对非纯文本文件的操作其实差别不大,只是读取和写入的不是字符而是字节。

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. public class FileStream {
  6. public static void main(String[] args) throws IOException {
  7. avaiStream();
  8. }
  9. public static void avaiStream() throws IOException {
  10. FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");
  11. FileOutputStream fos = new FileOutputStream("I:"+File.separator+"copy_Destroyer.mp3");
  12. // 创建与文件大小刚好相等的缓冲区数组
  13. byte[] bytes = new byte[fis.available()];
  14. fis.read(bytes);
  15. fos.write(bytes);
  16. fis.close();
  17. fos.close();
  18. }
  19. } ///:~

不过对于上面这种做法,并不应该值得提倡。因为一旦文件很大的话,容易造成内存溢出的错误,所以应该定义一个合适大小的缓冲区数组。

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. public class FileStream {
  6. public static void main(String[] args) throws IOException {
  7. copyStream();
  8. }
  9. public static void copyStream() throws FileNotFoundException, IOException {
  10. FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");
  11. FileOutputStream fos = new FileOutputStream("G:"+File.separator+"copy_Destroyer.mp3");
  12. byte[] bytes = new byte[1024]; // 自定义缓存数组大小
  13. int length = 0;
  14. while((length=fis.read(bytes))!=-1){
  15. fos.write(bytes, 0, length);
  16. }
  17. fis.close();
  18. fos.close();
  19. }
  20. } ///:~

对于操作文件的四个基本流,上面的例子已经很详细的进行了讲解。当读者看到这里的话,可以很荣幸的告诉大家,Java I/O系统的学习应该可以达到了70%。虽然本节只是讲诉了四个基本流和四个文件流,但是I/O其他的类的学习很大程度都依赖与它们,相信大家也不再会对Java I/O系统感到害怕了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注