自己工作虽有将近三年了,也算得上是一个程序老手了,但对IO这块一直一知半解,以至于每次写IO相关的代码时,总不能随心所欲的写出自己想要的代码,每次都要百度别人的代码来借鉴。究其原因还是因为对IO这块了解的不够深入。真是如鱼刺在喉,不咽不快,这次一定要把IO这块知识咽下去。
其实对 IO 这块知识之前也有意学习过,但每次都不得要领,学了忘,用的时候再去学,一直无法理解 IO 的精髓,所以也就无法真正的领略IO了。这次学习IO总算没了之前学习的时候云里雾里的感觉。或许有些知识,真的需要时间的积累才能够理解罢!IO这块知识真的挺难理解的,很难初次学习就能对这块知识系统的理解,甚至两次、三次我们会越学越糊涂,但不要放弃,多多接触,多多看相关文章,总有那么一个时刻你会发现柳暗花明了,IO也不过如此了。
此篇文章内容多为我个人本次学习对IO的理解,可能不会太系统和太完善,不喜勿喷哦。
建议读此篇文章前,先读这篇文章:(要都三遍哦!!!)
读了上面的文章,你应该已经知道,Java IO 中一般包含两个部分:
java.io包中堵塞型 IO
java.nio包中的非堵塞型 IO,通常称为New IO
本篇文章主要介绍java.io包中堵塞型 IO
打开你的java.io包你可以看到Java的IO包含大量的类和接口(JDK1.6中包含83个类或者接口),如此众多的类和接口似乎无从下手。下面就将IO简单地分类。
Java的IO主要包含三个部分:
流式部分 -- IO 的主体部分
非流式部分 -- 主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类
文件读取部分的与安全相关的类,如:SerializablePermission类。以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类
其中“流式部分”又可以概括为:两个对应一个桥梁。两个对应指:
字节流(Byte Stream)和字符流(Char Stream)的对应
输入和输出的对应
一个桥梁指:从字节流到字符流的桥梁。对应于输入和输出为InputStreamReader和OutputStreamWriter。
在流的具体类中又可以具体分为:
介质流(Media Stream或者称为原始流Raw Stream)
-- 主要指一些基本的流,他们主要负责从具体的介质上读取数据。
如:从文件、内存缓冲区(Byte数组、Char数组、StringBuffer对象)等,读取数据;
过滤流(Filter Stream)
-- 主要指所有FilterInputStream/FilterOutputStream和FilterReader/FilterWriter的子类,主要是对其包装的类进行某些特定的处理,如:缓存等。
-- 无法单独使用,必须配合其他的一些输入输出流使用,被包装后的对象具有过滤流提供的功能如:被BufferedInputStream包装后对象具有缓存的功能,被DataInputStream包装后的对象具有读取基本数据类型的功能
上面这段的内容来自之前提到的文章,个人觉得总结的十分经典,就直接”拿来主义”了,建议上面的内容也都三遍哦。
实践是检验理论的唯一标准,编码更是如此,上面提到的模糊的概念你可能不甚理解,下面我们结合具体场景来看一下。以 Java IO 中的 字节流 中的 输入流 来具体讲解一下。
我们先来看一张图(网上找来的一张图,经测试发现并不存在ByteArrayReader这个类,图中已标红),这是java io 比较基本的一些处理流,你现在只需要看IO流-->字节流-->InputStream的那部分,结合上面的内容你应该至少知道哪些是介质流?哪些是过滤流?介质流是用来干嘛的?输入流是用来干嘛的?(不知道的话回到上面再读一遍吧)
是的,除了 ObjectInputStream 和 FilterInputStream 下的三个类,其余全是介质流,前面说过了,介质流是主要负责从具体的“介质”上读取数据的,其中“介质”可以是文件、图片、Byte数组、String对象,StringBuffer对象等。从不同“介质”上读取数据,采用的流也不相同。例如:从byte数组中读取数据到流,很显然我们知道应该用 ByteArrayInputStream。同理,从文件中读取数据,我们应该用FileInputStream。
下面我们结合上面的图,依次展示和讲解一下这些流的具体用法。
介质流
ByteArrayInputStream。
我们读取一个byte数组,然后依次打印数组中的成员对应的ascii码。很显然,从byte数组中读取数据,我们应该用ByteArrayInputStream,将数组读取到流中以后,我们就能够以操作流的方式来操作byte数组了。
byte[] b = new byte[] {'j', 'a', 'v', 'a', 'i', 'o'};ByteArrayInputStream bis = new ByteArrayInputStream(b);int c;// read()方法返回int类型0-255之间的数字。读取流结束返回-1// 想想为什么返回 0-255?while ((c = bis.read()) != -1) { System.out.println(c);}
我们知道,计算机的最小存储单位是字节(byte),因此任何类型的数据(图片,文件,英文字符,汉字)都可以用连续的 字节(byte)来表示。任意一个英文字符,用一个字节(byte)即可表示,汉字需要两个字节(byte),一个图片或文件可以 由若干个连续的字节(byte)表示。在计算机中一个字节(byte)可以存储8位二进制数。换算为十进制,范围就是0-255
StringBufferInputStream。
其用法和ByteArrayInputStream类似,不再演示。StringBufferInputStream已经被@Deprecated,原因是StringBufferInputStream不应该属于字节流,推荐使用StringReader。
SequenceInputStream 。
将2个或者多个InputStream 对象转变为一个InputStream,和你想象的一样,SequenceInputStream的构造函数中可以传入两个InputStream,也可以传入一个Enumeration集合
byte[] java = new byte[]{'j', 'a', 'v', 'a'};byte[] io = new byte[]{'i', 'o'};ByteArrayInputStream bais1 = new ByteArrayInputStream(java);ByteArrayInputStream bais2 = new ByteArrayInputStream(io);SequenceInputStream sis = new SequenceInputStream(bais1, bais2);int t;while ((t = sis.read()) != -1) { System.out.println(t);}
PipedInputStream
一般和PipedOutputStream配合使用,读取从对应PipedOutputStream写入的数据。在流中实现了管道的概念。
PipedOutputStream pos = new PipedOutputStream();PipedInputStream pis = new PipedInputStream();pis.connect(pos);String pipedStr = "Hi, PipedInputStream and PipedOutputStream";// 管道输出流写出字节pos.write(pipedStr.getBytes());int t;// 相应管道输入流进行读取while ((t = pis.read()) != -1) { System.out.print((char)t);}pis.close();pos.close();
这是管道流最基本的应用。利用管道流还可以实现线程间的通信。我们做这样一个应用:启动两个线程,一个线程负责 发送数据,一个线程负责接收数据。
// Sender.java 文件public class Sender implements Runnable { PipedOutputStream out = new PipedOutputStream(); /** * PipedOutputStream 和 PipedInputStream 要配对使用,双方进行连接后,write的数据才能被read到 * 故在此返回 PipedOutputStream 对象 */ public PipedOutputStream getPipedOutputStream() { return out; } @Override public void run() { String str = "hi, pipedStream in thread"; try { for (byte b : str.getBytes()) { out.write((char)b); System.out.println("send --> " + (char)b); Thread.sleep(500); } out.close(); } catch (Exception e) { e.printStackTrace(); } }}
// Receiver.javapublic class Receiver implements Runnable { PipedInputStream in = new PipedInputStream(); public PipedInputStream getPipedInputStream() { return in; } @Override public void run() { int t; try { while ((t = in.read()) != -1) { System.out.println("receive --> " + (char)t); Thread.sleep(300); } } catch (Exception e) { e.printStackTrace(); } }}
// 运行此main方法可以看出运行结果public class PipedStream4Thread { public static void main(String[] args) throws Exception { Sender sender = new Sender(); Receiver receiver = new Receiver(); PipedOutputStream out = sender.getPipedOutputStream(); PipedInputStream in = receiver.getPipedInputStream(); in.connect(out); new Thread(sender).start(); new Thread(receiver).start(); }}
FileInputStream
从名称就可以看出,它是用来把文件转换为流的。
public class FileInputStreamTest { public static void main(String[] args) throws Exception { // 不指定具体路径是指当前项目跟路径下 FileInputStream fis = new FileInputStream("file"); byte[] buf = new byte[4]; int bLen = buf.length; int t; // 想想read(buf, 0, bLen)和read()方法的区别? while ((t = fis.read(buf, 0, bLen)) != -1) { // 主意new String(buf, 0, t)一定要带 0,t 参数 // 自己想想为什么吧 System.out.print(new String(buf, 0, t)); } fis.close(); } /** * 这里使用的是read(byte b[], int off, int len)方法进行文件字节读取,没有使用read()方法。 * * read()方法每读取一个字节就要进行一次本地磁盘IO * read(byte b[], int off, int len)方法每次从本地磁盘文件中读取bLen个字节。有效的减少了程序访问本地磁盘的次数,提高了效率, * 其中bLen是b[]数组的长度。读取的字节数组会被放入b[]中,放在b[]中第off个位置先后的len个位置中 * */ }
至此,介质流已经介绍完了,实际应用中我们应该根据从不同介质读取数据,而使用相应的介质流。
下面我们来看过滤流。前面说过,过滤流必须配合其他的一些输入输出流使用,被包装后的对象具有过滤流提供的功能。