@lemonguge
2015-07-01T08:08:16.000000Z
字数 8130
阅读 387
I/O
流:代表任何有能力产出数据的数据源对象或者有能力接受数据的接收端对象,“流”屏蔽了实际I/O设备中处理数据的细节。
Java类库中的I/O分出输入和输出两部分,输入流和输出流是相对于内存设备而言。无论是输入还是输出,都有字符流和字节流。所以在学习I/O的过程中,会接触到四个继承体系,查看体系中的顶层类,来了解该体系的基本功能。
不过在学习之前,很有必要了解一下字符流诞生的历史。自从Java 1.0版本以来,在原来面向字节的类中添加了面向字符和基于Unicode的类。简单说“字符流就是:字节流+编码表。”字符流是为了处理文字数据方便而出现的对象,其实这些对象的内部使用的还是字节流(因为文字最终存储的也是字节数据)。只不过,通过字节流读取了相对应的字节数,没有对这些字节直接操作,而是去查了指定的(本机默认的)编码表,获取到了对应的文字。
任何自InputStream或Reader派生而来的类都含有名为read()的方法;任何自OutputStream或Writer派生而来的类都含有名为write()的基本方法。
InputStream字节输入流的三个重载read()方法 int read()方法:从输入流中读取数据的下一个字节。int read(byte[] b)方法:从输入流中读取一定数量的字节,并将其存储在目标缓冲区数组b中。以整数形式返回读入缓冲区的总字节数。int read(byte[] b, int off, int len)方法:off为目标缓冲数组b中将写入数据的初始偏移量,将输入流中最多len个数据字节读入byte数组,返回实际读取的字节数。Reader字符输入流的四个重载read()方法 int read()方法:读取单个字符。(支持高效的单字符输入的子类应重写此方法)int read(char[] cbuf)方法:将字符读入目标缓冲区数组cbuf,返回读取的字符数。int read(char[] cbuf, int off, int len)方法:off为开始存储字符处的偏移量,将输入流中最多len个数据字节读入char目标缓冲数组,返回实际读取的字符数。int read(CharBuffer target)方法:将字符读入指定的字符缓冲区target,返回添加到缓冲区的实际字符数量。可以发现它们的返回值都为int类型,很重要的一个原因是:如果已到达流的末尾,则返回-1。
OutputStream字节输出流的三个重载write()方法 void write(int b)方法:将指定的字节写入此输出流,写入的字节是b的八个低位,b的24个高位将被忽略。 void write(byte[] b)方法:将b.length个字节从指定的byte数组写入此输出流。 void write(byte[] b, int off, int len)方法:将指定byte数组中从偏移量off开始的len个字节按顺序写入此输出流。元素b[off]是此操作写入的第一个字节,b[off+len-1]是此操作写入的最后一个字节。 Writer字符输出流的五个重载write()方法 void write(int c)方法:写入单个字符,要写入的字符包含在给定整数值的16个低位中,16高位被忽略。(支持高效单字符输出的子类应重写此方法)void write(char[] cbuf)方法:要写入的字符数组写入此输出流。void write(char[] cbuf, int off, int len)方法:写入字符数组的某一部分。off为开始写入字符处的偏移量,len要写入的字符数。void write(String str)方法:要写入的字符串。void write(String str, int off, int len)方法:写入字符串的某一部分。off为相对初始写入字符的偏移量,len要写入的字符数。对于上面这四种流,都具有close()方法:关闭此输出流并释放与此流有关的所有系统资源。对于输出流而言,还有一个flush()方法:刷新此输出流并强制写出所有缓冲的输出字节,当输出流调用close方法时,会先调用flush(),刷新缓冲中的数据到目的地。简单而言:当你在写代码的时候,会经常进行保存,这就相当与flush(),在关闭编译器的时候,会提示你进行保存,这就是close()前的flush()。
现在回到一个比较基本的问题,怎么从读写一个文件的数据?Java提供了四个类:FileInputStream、FileOutputStream、FileReader和FileWriter。如果操作的是纯文本文件,应该使用字符流,如果不是的话,只能使用字节流。
import java.io.FileReader;import java.io.IOException;public class TxtReader {public static void main(String[] args) throws IOException {read1();}public static void read1() throws IOException {// 当前项目下有一个file.txt文件,文件内容为“abc你好!”FileReader fr = new FileReader("file.txt");int ch = 0;while ((ch = fr.read()) != -1) {System.out.println(ch);}fr.close();}} /* Output:979899203202290965281*///:~
可以发现,输出的是ASCⅡ而不是字符,所以应该进行强转操作。
import java.io.File;import java.io.FileReader;import java.io.IOException;public class TxtReader {public static void main(String[] args) throws IOException {read2();}public static void read2() throws IOException {// 当前项目下有一个file.txt文件,内容为“abc你好!”File file = new File("file.txt");FileReader fr = new FileReader(file);int ch = 0;while ((ch = fr.read()) != -1) {System.out.print((char) ch);}fr.close();}} /* Output:abc你好!*///:~
当然我们也可以使用缓冲数组的read()方法,将字符读入目标缓冲区数组。
import java.io.File;import java.io.FileReader;import java.io.IOException;public class TxtReader {public static void main(String[] args) throws IOException {read3();}public static void read3() throws IOException {// D盘下有一个file.txt文件,内容为“abc你好!”FileReader fr = new FileReader("D:" + File.separator + "file.txt");int length = 0;char[] chs = new char[4];while ((length = fr.read(chs)) != -1) {System.out.println(String.valueOf(chs, 0, length));}fr.close();}} /* Output:abc你好!*///:~
import java.io.File;import java.io.FileReader;import java.io.IOException;public class TxtReader {public static void main(String[] args) throws IOException {read4();}public static void read4() throws FileNotFoundException, IOException {// 当前项目下有一个file.txt文件,内容为“abc你好!”FileReader fr = new FileReader("file.txt");int length = 0, offset = 2;char[] chs = new char[10];while ((length = fr.read(chs, offset, 4)) != -1) {System.out.println(new String(chs, offset, length));System.out.println(Arrays.toString(chs));}fr.close();}} /* Output:abc你[ , , a, b, c, 你, , , , ]好![ , , 好, !, c, 你, , , , ]*///:~
我们也可以读取文件的一部分。
public static void main(String[] args) throws IOException {read5();}public static void read5() throws IOException {// 当前项目下有一个file.txt文件,内容为“abc你好!”FileReader fr = new FileReader("file.txt");int ch = 0;while ((ch = fr.read()) != -1) {if ((char) ch == 'b')fr.skip(2); // 跳过两个字符System.out.print((char) ch);}fr.close();}} /* Output:ab好!*///:~
import java.io.FileWriter;import java.io.IOException;public class TxtWriter {public static void main(String[] args) throws IOException {// 在创建对象时,就必须明确该文件(用于存储数据的目的地)。// 如果文件存在,则会被覆盖;如果文件不存在,则会自动创建。FileWriter fw = new FileWriter("file.txt");}} ///:~
如果原先file.txt中有内容,执行程序会发现内部不见了。不过可以在构造函数中加入另一个布尔参数true,来实现对文件进行续写!
import java.io.FileWriter;import java.io.IOException;public class TxtWriter {public static void main(String[] args) throws IOException {// 续写文件FileWriter fw = new FileWriter("file.txt", true);}} ///:~
当我们想写入数据到纯文本文件中,如果想换行的话,在Windows环境下,换行是\r\n(\r的ASCⅡ为13,\n的ASCⅡ为10),而在UNIX环境下,换行是/n。为了代码的可移植性考虑,行分隔符字符串由系统属性line.separator定义。
import java.io.FileWriter;import java.io.IOException;public class TxtWriter {// 换行符private static final String LINE_SEPARATOR = System.getProperty("line.separator");public static void main(String[] args) throws IOException {write1();}public static void write1() throws IOException {FileWriter fw = new FileWriter("file.txt");fw.write(97);fw.write(98);fw.write(99);fw.write(LINE_SEPARATOR); // 在Windows平台下等价与fw.write("\r\n");fw.write(20320);fw.write(22909);fw.write(65281);fw.close();}} ///:~
可以在当前项目下的file.txt文件中看到两行数据,分别为“abc”和“你好!”。但是这样做明显比较麻烦,我们可以直接写入字符串。
import java.io.FileWriter;import java.io.IOException;public class TxtWriter {// 换行符private static final String LINE_SEPARATOR = System.getProperty("line.separator");public static void main(String[] args) throws IOException {write2();}public static void write2() throws IOException {FileWriter fw = new FileWriter("file.txt", true);fw.write("Hello World");fw.write(LINE_SEPARATOR);fw.write("你好,世界。");fw.close();}} ///:~
对当前项目下的file.txt文件进行续写,可以看到有三行数据,分别为“abc”、“你好!Hello World”和“你好,世界。”如果我们要写入大量的纯文本数据时,不建议读一个字符再写一个字符,应该使用将字符读入目标缓冲区数组的read()方法,在循环中调用flush()方法来时不时地进行保存。
import java.io.FileWriter;import java.io.IOException;// 复制文本public class TxtWriter {public static void main(String[] args) throws IOException {copy();}public static void copy() throws IOException {System.out.println(LINE_SEPARATOR.length()); // 在Windows平台下长度为2File file = new File("file.txt");FileReader fr = new FileReader(file);FileWriter fw = new FileWriter("D:" + File.separator + "copy_file.txt");int length = 0;char[] chs = new char[10];while ((length = fr.read(chs)) != -1) {System.out.println("----------");String temp = new String(chs, 0, length);System.out.println(temp+":"+temp.length());fw.write(temp);fw.flush(); // 将每次读入的数据进行写出并保存}fr.close();fw.close();}} /* Output:2----------abc你好!He:10----------llo World:10 // 其中把换行符分开了,此次为\r(长度为1)----------你好,世界。:7 // \n(长度为1)和后面6个字符,长度为7*///:~
对于非纯文本文件的操作,不要使用读取一个字节的方式,这样做没有效率,应该使用目标缓冲区数组的读方法。在文件输入流中有一个int available()方法,返回该文件的大小。
import java.io.File;import java.io.FileInputStream;import java.io.IOException;public class FileStream {public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");System.out.println(String.valueOf(fis.available()>>>10)+"KB");}} /* Output:12121KB*///:~
对非纯文本文件的操作其实差别不大,只是读取和写入的不是字符而是字节。
import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileStream {public static void main(String[] args) throws IOException {avaiStream();}public static void avaiStream() throws IOException {FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");FileOutputStream fos = new FileOutputStream("I:"+File.separator+"copy_Destroyer.mp3");// 创建与文件大小刚好相等的缓冲区数组byte[] bytes = new byte[fis.available()];fis.read(bytes);fos.write(bytes);fis.close();fos.close();}} ///:~
不过对于上面这种做法,并不应该值得提倡。因为一旦文件很大的话,容易造成内存溢出的错误,所以应该定义一个合适大小的缓冲区数组。
import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileStream {public static void main(String[] args) throws IOException {copyStream();}public static void copyStream() throws FileNotFoundException, IOException {FileInputStream fis = new FileInputStream("D:"+File.separator+"Destroyer.mp3");FileOutputStream fos = new FileOutputStream("G:"+File.separator+"copy_Destroyer.mp3");byte[] bytes = new byte[1024]; // 自定义缓存数组大小int length = 0;while((length=fis.read(bytes))!=-1){fos.write(bytes, 0, length);}fis.close();fos.close();}} ///:~
对于操作文件的四个基本流,上面的例子已经很详细的进行了讲解。当读者看到这里的话,可以很荣幸的告诉大家,Java I/O系统的学习应该可以达到了70%。虽然本节只是讲诉了四个基本流和四个文件流,但是I/O其他的类的学习很大程度都依赖与它们,相信大家也不再会对Java I/O系统感到害怕了。