Java中的I/O流可以实现数据的输入与输出操作。这里的输入与输出是相对而言的,比如文件与程序之间的交流:
一:流
Java中把不同的输入源(如文件,网络资源等)/输出源(如屏幕灯)抽象为“流“(可以把它看做一个装数据的容器),这里的流指的是一连串流动的字符,流动方式为先进先出。
二:流的分类
1:按流处理的内容
(1)字节流:两个基础的类为InputStream和OutputStream;
(2)字符流:两个基础的类为Reader和Writer。
2:按流的方向
(1)输入流:只能从中读取数据,而不能向其中写入数据。输入流构造方法中参数指定的文件必须存在。如:InputStream、Reader
(2)输出流:只能向其中写入数据,而不能从中读取数据。输出流构造方法中参数指定的文件不一定要存在,不存在可以重新创建。如:OutputStream、Writer
3:按流的角色
(1)基础流:构造方法的参数为介质(比如一个File文件等)。比如InputStream/Reader派生的类,都有一个read()方法,只能用于读取单一的字节或字节数组
(2)包装流:构造方法的参数为基础流,因此有人称它为"基础流的流"。通过多种基础流之间的相互组合,实现的是对一个已经存在的流的封装,通过所封装的流的功能来丰富流的一些操作。比如包装流DataOutputStream通过对基础流OutputStream的封装,可以实现写一些不同于byte类型数据的方法,如writeUTF()
三:流的简单划分
下面的图只是先简单的概括一下,有助于我们以后的学习。
1:从上面的图中可以找到几对儿相呼应的流,你可能会问,我们就是完成一个输入输出,需要这么多复杂的流吗?
答案:是一定的。尽管我们有包装流,可以丰富基础流的操作,但是不要忘记包装流是基础流的流。没有基础流的基本操作,包装流也是没有意义的。
2: 那么这么多的流之间有什么区别呢?
(1)FileInputStream/OutputStream:完成的是文件的字节读取,只能读取一个字节或者一个字节数组,类型比较单一且读写效率不高。
构造方法:InputStream 流的名称 = new FileInputStream/OutputStream(new File("文件路径字符串"));分别实现输入流、输出流的创建。
一般方法有:
a:int read():从此输入流中读取一个数据字节。如果已到达文件末尾,则返回 -1
。
b:int read(byte[] b):从此输入流中将最多 b.length
个字节的数据读入一个 byte 数组中。返回读入的字节总数。如果因为已经到达文件末尾而没有更多的数据,则返回 -1
。
c:int read(byte[] b,int off,int len):从此输入流中将最多 len
个字节的数据读入一个 从off位置坐标开始的byte 数组中。返回读入的字节总数。如果因为已经到达文件末尾而没有更多的数据,则返回 -1
。
d:void write(int b):将指定字节写入此文件输出流。实现 OutputStream
的 write
方法。
e:void write(byte[] b,int off,int len):将指定 byte 数组中从偏移量 off
开始的 len
个字节写入此文件输出流。
f:void write(byte[] b):将 b.length
个字节从指定 byte 数组写入此文件输出流中。
d:voidclose():关闭此文件输入流/输出流,并释放与此流有关的所有系统资源。切记:一定要操作结束后,关闭流。
(2)DataInputStream/DataOutputStream:这一对弥补了上一组中的”类型比较单一“这一问题,他可以实现以读写不同数据类型的数据来操作流。可见他是上一组的包装流。
一般方法有:(1)中的方法在这里依然适用。传递和操作的仍是字节。(为说明问题,这里只给出几对儿特殊的方法,具体请参见帮助文档)
a:readUTF()/writeUTF(String str):以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
b:readChar()/writeChar(Char ch):将一个 char
值以 2-byte 值形式写入基础输出流中,先写入高字节。
(3)BufferedInputStream/BufferedOutputStream:他的出现解决了一中的”读写效率不高“的问题。可见他是第一组的包装流。
一般方法有:(1)中的方法在这里依然适用。传递和操作的仍是字节。这里只给出特殊的方法
a:void flush():刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 前面提到过缓冲区的概念,这一对儿在实现输入或输出时,实现先把数据写到缓冲区中,然后用此方法可以实现冲刷的功能,把存在于缓冲区中的数据”冲刷“到文件中。特殊的几点:
◆close():在关闭流之前也会冲刷缓冲区中的数据到文件中
◆缓存区满了以后,即时不用flush()重刷一下,也会自动写道文件中去
◆不flush,不close则拷贝的文件字节数相对而言较少。因为缓存区满了以后,会自动写道文件中去。假使最后剩了5K,一次flush8K,则最后的5K不够他冲刷一次的,就留在缓冲区中了,所以就少了一点
◆flush不可以写到循环当中,没有意义。因为那相当于读一个字节,就放到缓冲区,就重刷了,会和以前的FileInputStream一样慢。
(4)FileReader/FileWriter:完成的是文件的字符的读取,只能读取一个字符或者一个字符数组。按系统默认的编码方式(gbk)提供字节到字符的转换。
一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。
(5)InputStreamReader/OutputStreamWriter:完成的是文件的字符的读取。提供了不同的编码方式,已解决乱码的问题。对于中文来说读的是2个字节,UTF-8编码为3个字节。可见他是第一组的包装流。
一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。
(6)BufferedReader/BufferedWriter:完成的是文件的字符的读取,实现数据的大量读取。可以readLine()读一行/newLine()重新开一行。可见他是上一组的包装流,当然也可以是其他对应组的。
一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。
a:readLine():读一行
b:newLine():重新开一行。
四:各种流的具体使用
注意:在这里为说明问题,则吧异常均在main方法后向上抛出,在实际的开发中是不允许的。
1:FileInputStream/OutputStream
import java.io.*; public class TestFISFOS { public static void main(String[] args) { InputStream is = null; OutputStream os = null; try { // 输入流的文件必须存在 is = new FileInputStream(new File("d:\\a.txt")); char j = (char)is.read();// 从a.txt中读取一个字节,返回的对应的码值,强转 System.out.println(j); // 输出流的文件不必须存在 // true表示在源文件基础上追加, false表示覆盖。默认是false os = new FileOutputStream(new File("d:\\a.txt"),true); os.write(43);// 向b.txt中写了一个“+”号 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { is.close();// 关闭输入流 os.close();// 关闭输出流 } catch (IOException e) { e.printStackTrace(); } } } } |
测试:测试之前在D盘下建一个a.txt文件,文件中写入hello world
结果:在源文件基础上追加了一个+号。
h |
(2)DataInputStream/DataOutputStream
import java.io.*; public class TestDISDOS { public static void main(String[] args) throws IOException { OutputStream os = new FileOutputStream(new File("d:\\a.txt")); DataOutputStream dos = new DataOutputStream(os);//包装基础流OutputStream dos.writeUTF("中国");//写UTF类型的数据 InputStream is = new FileInputStream(new File("d:\\a.txt")); DataInputStream dis = new DataInputStream(is);//包装基础流putStream String str = dis.readUTF();//用对应的方法读取数据 System.out.println(str); dis.close(); dos.close(); } } |
测试:测试之前在D盘下建一个a.txt文件,文件中写入hello world
结果:在源文件中写了一个乱码的数据
中 |
(3)BufferedInputStream/BufferedOutputStream
实现了大量数据的快速读取。
import java.io.*; public class TestBISBOS { public static void main(String[] args) throws IOException { InputStream is = new FileInputStream(new File("d:/src.rar"));//创建基础输入流 BufferedInputStream bis = new BufferedInputStream(is);//包装基础流InputStream OutputStream os = new FileOutputStream(new File("d:/dest.rar"));//创建基础输出流 BufferedOutputStream bos = new BufferedOutputStream(os);//包装基础流OutputStream int i = 0; while((i = bis.read())!=-1){//判断是不是读到了文件的末尾 //缓存区满了以后,即时不用flush重刷一下。也会自动写道文件中去 bos.write(i); } bos.flush();//把文件从缓冲区冲刷到文件中去 bos.close();//在关闭之前,也flush以下 /** * 1不flush,不close则拷贝的文件字节数相对而言较少,因为,缓冲区满了以后会自动写到 * 文件中. 如果最后剩了5K,不够他flush一次的,则把它留到缓冲区中了,所以少了 * 2:flush写到循环里,没有意义,和以前的FileInputStream一样慢。因为那相当于读一个 * 字节放到缓冲区.就重刷了。 */ } } |
结果:可以试一试,用FileInputStream/OutputStream完成此操作的速度,这个相对而言快很多。
(4)FileReader/FileWriter:
import java.io.*; public class TestFRFW { public static void main(String[] args) throws IOException { FileReader fr = new FileReader(new File("d:\\a.txt"));//创建基本流输入对象 char ch = (char) fr.read();//读的是字符 System.out.println(ch); FileWriter fw = new FileWriter(new File("d:\\a.txt"));//创建基本流输出对象 fw.write("北京欢迎你");//写的是字符串 fw.close(); fr.close(); } } |
测试:测试之前在D盘下建一个a.txt文件,文件中写入"哈尔滨欢迎你"
结果:以正确的编码方式,覆盖了源文件,写入了”北京欢迎你“
哈 |
(5)InputStreamReader/OutputStreamWriter
a:用不同的方式读写
public class TestISROSW { public static void main(String[] args) throws IOException { File file = new File("d:/a.txt"); //utf=8读的是三个字节,gbk读的是两个字节,英文字母都是是一个字节 FileInputStream fis =new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis,"gbk");//包装基础输入流且指定编码方式 char i = (char)isr.read(); //用utf-8的格式去写,3个字节,用gbk去读两个字节,所以出现乱码, System.out.println(i); FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");//包装基础输出流且指定编码方式 osw.write("北京"); osw.close(); isr.close(); } } |
结果:用utf-8的格式去写,3个字节,用gbk去读两个字节,所以出现乱码
鍖 |
b:以相同的方式读取
public class TestISROSW { public static void main(String[] args) throws IOException { File file = new File("d:/a.txt"); //utf=8读的是三个字节,gbk读的是两个字节,英文字母都是是一个字节 FileInputStream fis =new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis,"gbk");//包装基础输入流且指定编码方式 char j = (char)isr.read(); System.out.println(j); //用gbk的格式去写,2个字节,用gbk去读两个字节,所以不出现乱码, FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");//包装基础输出流且指定编码方式 osw.write("北京"); osw.close(); isr.close(); } } |
结果:用gbk的格式去写,2个字节,用gbk去读两个字节,所以不出现乱码
北 |
(6)BufferedReader/BufferedWriter:
public class TestBWBR { public static void main(String[] args) throws IOException { File file = new File("d:\\a.txt");//创建基础文件 FileInputStream fis = new FileInputStream(file);//创建基础输入流 InputStreamReader isr = new InputStreamReader(fis);//创建一层包装流 BufferedReader br = new BufferedReader(isr);//创建二层包装流 String str = br.readLine();//读一行 System.out.println(str); FileOutputStream fos = new FileOutputStream(file);//创建基础输出流 OutputStreamWriter osw = new OutputStreamWriter(fos);//创建一层包装流 BufferedWriter bw = new BufferedWriter(osw);//创建二层包装流 bw.write("马上就出发");//写的字符串 bw.newLine();//回车换行 bw.write("谁也不知道");//在写一行 bw.flush(); bw.close(); br.close(); } } |
测试:测试之前在D盘下建一个a.txt文件,文件中写入"一二三四五六七"
结果:在a.txt中第一行为:马上就出发,第二行为:谁也不知道
马上就出发 |
五:关于脏数据
所谓的脏数据是指不必要的数据。比如读多了,看如下的程序注释
public class TestWriteANDRead { public static void main(String[] args) throws Exception { coppyFile("d:/a.txt", "d:/aa.txt");//拷贝函数的调用 } //自定义拷贝函数 private static void coppyFile(String file1, String file2) throws IOException { InputStream is = null;//定义基础输入流 OutputStream os = null;//定义基础输出流 is = new FileInputStream(new File(file1));// 源文件地址 os = new FileOutputStream(new File(file2)); // 目标文件地址 int i=0;//用来装以后read()的返回值 byte[] b = new byte[8*1024];//定义一个byte数组,实现一次读8个字节 while ((i = is.read(b)) != -1) { // 判断是不是读到了文件末尾 os.write(b,0,i);// 没读到的话,就从a.txt读一个写到aa.txt中一个 //达到了解决脏数据的目的 /** * 一次度八个k,最后很可能就不够8k了,可能就只有5K,那么剩下的8K包含的是: * 剩下的那5K以及最后一个8K的后3K。 所以就出现了后面那3K的脏数据 */ } is.close(); os.close(); } } |
六:重定向输入输出流
我们知道从控制台读入数据用的是System.in,向控制台输出数据用System.out,即默认的输入设备为键盘,默认的输出设备是屏幕。那么我们可以通过System.setIn()方法和System.setOut()方法改变默认的输出与输入设备,下面的列子说明的是如何将默认的输入输出变为文件的:
public class TestSISO { public static void main(String[] args) throws IOException { File file = new File("d:\\a.txt");//创建基本文件 PrintStream ps = new PrintStream(file);//定义包装流,使其写入到文件 System.out.println("输出到控制台");//重定向之前,在控制台输出的 System.setOut(ps);//输入输出流的重定向问题,改变了默认的输出设备 System.out.println("输出到文件");//这句话在a.txt中可以看到 ps.println("hello word");//在后面追加一句 //PrintStream的println方法与BufferedReader的readLine方法经常一起使用 InputStream is = System.in; InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String str = br.readLine();//读一行 System.out.println(str); } } |
结果:
在控制台中的结果如下:
输出到控制台 |
在a.txt文件的结果为
输出到文件 hello word |
Java中虽然提供了这些看起来很麻烦的IO流操作,但是只要细心,常用就不会有问题,我们都一样,加油才是硬道理!!