一、流的概念
“对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。”
――《Think in Java》
无论是系统、还是语言的设计中IO的设计都是异常复杂的。面临的最大的挑战一般是如何覆盖所有可能的因素,我们不仅仅要考虑文件、控制台、网络、内存等不同的种类,而且要处理大量的不同的读取方式,如:顺序读取、随机读取,二进制读取、字符读取,按行读取、按字符读取……
为了解决输入/输出问题,创建了Java IO体系,并且提出了流的概念。
如何理解流的概念?联想一下现实生活中水流,自来水公司通过管道将水送入千家万户,数据相当于现实生活中的水,JAVA中的流相当于现实生活中的管道。在Java中,流代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象,流的作用是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
ps:当面试问到你对流的理解时,可以拿生活中的自来水来做对比或者从面对对象的角度来讨论。
二、Java IO体系
1、基于字节的IO操作
2、基于字符的IO操作
从上图可以看到,整个Java IO体系都是基于字节流(InputStream/OutputStream) 和字节流(Reader/Writer)作为基类,根据不同的数据载体或功能派生出来的。
3、分类
按照数据单位来分:字节流、字符流
按照方向分:输入流、输出流
按照功能来分:节点流、处理流&过滤类Filter (直接与底层文件资源连接的称为节点流,对节点流进行包装从而完成更高级功能的称为处理流,处理流在构造时须为其指定一个节点流.)
处理流&过滤类Filter:缓冲(BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter)、转换(InputStreamReader-FileReader, OutputStreamWriter-FileWriter),打印(PrintStream print/println方法支持各种格式数据,PrintWriter功能类似PrintStream ,但是继承的Writer,它两一个字节流一个字符流,带缓冲能力。),
节点流:FileInputStream、FileOutputStream
其它:DataInputStream、DataOutputStream
总结:字符流底层还是通过字节流实现的,因为计算机只能处理二进制数据
三、扩展
下面我们来看一下JDK1.8的源代码
3.1、源代码
3.1.1、InputStream
public abstract int read() throws IOException;//从输入流中读取一个字节(值0-255)并返回,子类必须提供此方法的实现,如果到达了流的末尾,则返回-1
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* 实际上还是调用read()方法,将读到的内容放到字节数组中b,返回-1(已读到流的末尾)或者实际读取的字节数
*/
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
/**
* 跳过n个字节,实际上还是调用read()方法,所以read()方法很关键,返回实际跳过的字节数
*/
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
上面的源码和注释分析了read方法和skip的作用。
3.1.2、OutputStream
public abstract void write(int b) throws IOException;//将指定字节写入输出流,只会写入低8位,高的24位会被忽略,因为一个字节只有8个bit位
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
3.1.3、Reader
abstract public int read(char cbuf[], int off, int len) throws IOException;//将字符读入数组,此方法可能会发生阻塞,返回-1或者读入的字符数
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
/**
* 将字符读入字符缓冲区
*/
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
/**
* 读取单个字符
*/
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
3.1.4、Writer
abstract public void write(char cbuf[], int off, int len) throws IOException;//将字符数组的一部分写入到输出流
/**
* 将指定字符写入到输出流
*/
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
/**
* 将字符数组的所有内容写入到输出流
*/
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
/**
* 将字符串的所有内容写入到输出流
*/
public void write(String str) throws IOException {
write(str, 0, str.length());
}
/**
* 将字符串的部分内容写入到输出流
*/
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
Object lock对象默认指向io对象自身
Reader的skip方法使用了synchronized。
Writer对象为什么有的加锁了有的没加锁?
3.2、NIO
对IO的概念有基本的了解之后,我们再来了解一下JAVA中类似的概念NIO。由于本文主要介绍IO的概念,所以对NIO的介绍会很浅显,后面会有专门的文章来介绍NIO。
NIO( New Input/ Output)一种同步非阻塞的IO模型。同步是指线程不断轮询IO事件是否就绪,非阻塞是指线程在等待IO的时候,可以继续其他任务。同步的核心是Selector,Selector替代了线程本身去轮询IO事件,避免了阻塞的同时也减少了不必要的线程消耗;非阻塞的核心是通道和缓冲区,当IO事件就绪时,可以通过写到缓冲区,保证IO的成功,而无需线程阻塞式的等待。
NIO和IO的主要区别
IO | NIO |
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
关于通道、缓冲区和选择器的实现参考Java NIO?看这一篇就够了!-CSDN博客
参考: