Java IO流部分源码解析
前言,很多东西实际上是自己闲着没事去看看底层是怎么实现的,很多地方可能讲的并不是很详细或者会出现错误,还是要相信自己的判断(๑╹ヮ╹๑)ノ Studying makes me happy
- Java IO流部分源码解析
- 1. Java IO流源码
- 1.1字节流
- 1.1.1 InputStream/OutputStream
- 1.1.2 FileInputStream/FileOutputStream
- 1.1.3 FilterInputStream/OutPutStream
- 1.1.4 DataInputStream/DataOutputStream
- 1.1.5 BufferedInputStream/BufferedOutputStream
- 1.1字节流
- 1. Java IO流源码
1. Java IO流源码
1.1字节流
1.1.1 InputStream/OutputStream
(1)InputStream
-
静态常量
-
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
-
MAX_SKIP_BUFFER_SIZE用于确定跳过时要使用的最大缓冲区大小
-
-
抽象方法
int read()
-
public abstract int read() throws IOException;
-
此方法作用为读取单个字节,返回整数类型,范围为
-1~255
-
实际上该方法只有在FileInputStream中通过本地方法实现
-
-
int read(byte b[], int off, int len)
-
从输入流中读取len个字节,从off位开始,依次存储到数组中
-
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; }
- 该方法首先会对传入
byte数组以及开始位置,长度
进行检查,不合理会抛出相应的异常 - 调用
read()
方法读取第一个字节,如果为没有可读字节直接返回-1,如果有则存入数组起始位off - 循环调用read()方法,将其转换为byte类型存入传入的数组
- 返回读取的字节数
- 该方法首先会对传入
-
-
int read(byte b[])
-
public int read(byte b[]) throws IOException { return read(b, 0, b.length); }
-
0作为起始位,数组.length作为终止位,调用
read(b, 0, b.length)
方法
-
-
long skip(long n)
-
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; }
-
此方法作用为跳过并丢弃掉来自输入流的n字节数据,并返回实际上跳过的字节数量
- 首先方法对传入的跳过的字节数进行检查
- 使用
size=(MAX_SKIP_BUFFER_SIZE, remaining)
的最小值作为要跳过的字节数- 实际上该值只是为了确定之后缓存数组的长度,防止长度过大无法开辟空间
- 之后该方法是采用新建一个数组对象,以0为起始位,以
(size, remaining)
的较小值来作为读取长度,如果实际上读取的字节数已经小于0则直接退出循环,否则remaining -= nr
,记录剩余的还需跳过的字节数 - 返回传入的要跳过的字节数n与剩余还需跳过的字节数remain的差值,即实际上跳过的字节数
-
-
int available()
- 该方法返回可以从输入流中读取或者跳过的字节数的估计值
- 实际上在InpuStream中直接返回0,需要子类去重写该方法
-
void close()
- 该方法作用为关闭输入流,是对Closeable接口的实现
- 实际上该方法方法体并没有内容,需要子类重写该方法
-
void mark(int readlimit)
-
该方法的作用为标记输入流的当前位置,对于rese()方法的后续调用会将该流重新定位在最后一个标记位,以便于重新读取相同的字节
- readlimit参数表示在标记位无效之前可以再读取多少个字节
- 如果输入流读取字节数超过该参数,则流不会存储任何数据
-
public synchronized void mark(int readlimit) {}
- 该方法方法体没有内容
- 该方法被synchronized修饰,对于多个线程来说需要排队访问该方法,保证一致性
-
-
void reset()
-
该方法将此流重新定位到上次调用mark()方法标记的位置
-
如果方法
markSupported()
返回true
,则:- 如果方法
mark
由于上述流创建没有被调用,或从流中读取的字节数,比mark
最后调用的参数大,那么IOException
可能会被抛出。 - 如果这样的
IOException
不抛出,那么流被重置为一个状态,使得所有字节读取自最近一次调用mark
(或自文件开始,如果mark
尚未调用)将被重新提供后续read
方法的呼叫者,之后是到重置到的调用mark标记位的下一个输入数据的字节。
如果方法
markSupported()
返回false
,那么:- 致电
reset
可能会抛出一个IOException
。 - 如果没有抛出
IOException
,则流将重置为固定状态,这取决于输入流的特定类型及其创建方式。 将提供给read方法的后续调用者的read
取决于输入流的特定类型。
- 如果方法
-
在InpuStream中总是抛出异常
IOException("mark/reset not supported")
,需要子类重写该方法
-
-
boolean markSupported()
- 表示是否支持标记,重置功能
- 在InoutStream中返回false,需要子类取根据情况重写
(2)OutputStream
-
接口实现
-
抽象接口void write(int b)
- 写入一个字节,子类去实现,该接口实际上是在FileOutputStream中通过本地方法来实现
-
void write(byte b[],int off,int len)
-
以off为起始位,将数组中len个字节写入到输出流
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]); } }
- 首先对参数进行检测,如果不合要求抛出相应异常
- 采用for循环调用write(int)方法来写入字节
-
-
void write(byte b[])
- 调用
write(byte b[],0,,b.length)
- 调用
-
void flush()
- 将缓冲区的数据写入到输出流,实现了
Flushable
接口 - 在OutputStream中方法体没有内容,子类去实现
- 将缓冲区的数据写入到输出流,实现了
-
void close()
- 关闭输出流,是释放系统资源,实现了
Closeable
接口,进而实现了AutoCloseable
接口,可以用try with resouces语法实现自动关闭 - 在OutputStream中方法体没有内容,子类去实现
- 关闭输出流,是释放系统资源,实现了
1.1.2 FileInputStream/FileOutputStream
作为输入输出流的最终实现类,实际上是通过FileChanel对象来进行文件的读取的,有时间补上
1.1.3 FilterInputStream/OutPutStream
(1)FilterInputStream
-
该类作为所有输入处理流的父类,并没有开放构造方法,因此也不能通过new 来创建该类对象
-
源码:
public class FilterInputStream extends InputStream{ protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } ... }
-
该类中的所有方法均调用的属性
InputStream in
的方法,没有做特别处理,该类也没有增加新的方法
(2)FilterOutputStream
该类作为所有输出处理流的父类,开放构造方法,因此可以通过new 来创建该类对象
重写的方法:
-
public class FilterOutputStream extends OutputStream { protected OutputStream out; public FilterOutputStream(OutputStream out) { this.out = out; } ... public void write(byte b[], int off, int len) throws IOException { if ((off | len | (b.length - (len + off)) | (off + len)) < 0) throw new IndexOutOfBoundsException(); for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } public void close() throws IOException { try (OutputStream ostream = out) { flush(); } } }
close()
方法将在try()
中使用一个引用指向该对象的out
,然后进行flush()
方法输出所有缓存中的数据,当结束后会自动调用out.close()
方法(try with resource 语法)进行关闭
1.1.4 DataInputStream/DataOutputStream
(1)DataInputStream
-
该类为FilterInputStream的直接子类
-
read整数
方法读取整数的实现方式,以readInt
为例public final int readInt() throws IOException { int ch1 = in.read(); int ch2 = in.read(); int ch3 = in.read(); int ch4 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); }
- 该方法读取4个字节,第一个字节左移24位,作为int的第一个字节,第二个字节左移16位,作为整数的第二个字节,...,最后一个字节作为整数的的最低字节,相加得到该整数的值
- 其它整数数据的读取与之类似
-
read浮点数
方法读取浮点数的实现方式public final float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } public final double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); }
- 实际上两者都是以读取相应字节数的整数类型来实现,通过包装类的静态方法(为本地方法)来进行转换
-
readLine()
方法通过循环调用read()方法读入字节,遇到-1或者'\n'或者'\r\n'结束,然后将读取的字节构建String对象实现读取一行的作用,如果没有数据则返回null -
readUTF()
实际上返回了return readUTF(this)
,readeUTF(DataInput in)较为复杂,有兴趣的可以自己看看源码,水平有限,先标记一下 :)//TODO
(2)DataOuputStream
-
该类为FilterOutputStream的直接子类
-
write整数
方法读取整数的实现方式,以writeInt
为例public final void writeInt(int v) throws IOException { out.write((v >>> 24) & 0xFF); out.write((v >>> 16) & 0xFF); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(4); }
- 该方法通过右移的方法依次从高到低获取整数的不同字节,每个字节转为byte,最后写入这4个字节,实现整数的存储
- 其他整数写入的方法与该方法相似
-
write浮点数
public final void writeFloat(float v) throws IOException { writeInt(Float.floatToIntBits(v)); } public final void writeDouble(double v) throws IOException { writeLong(Double.doubleToLongBits(v)); }
- 该类方法通过调用包装类的静态方法将浮点数转为相应的整数,利用写入整数的方法来写入该浮点数的数据
-
writeBytes(String s)
与writeChars(String s)
public final void writeBytes(String s) throws IOException { int len = s.length(); for (int i = 0 ; i < len ; i++) { out.write((byte)s.charAt(i)); } incCount(len); } public final void writeChars(String s) throws IOException { int len = s.length(); for (int i = 0 ; i < len ; i++) { int v = s.charAt(i); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); } incCount(len * 2); }
- 从源码中可以看出
writeBytes
会将字符串的每个字符(2字节)强制转换为byte类型,也就是说超过单个字节的字符会出现乱码现象 - 而writeChars会以一个char对应两个字节的方式正常存入,不会出现乱码问题
- 从源码中可以看出
-
writeUTF(String s)
也是调用其静态方法static int writeUTF(String str, DataOutput out)
情况较为复杂,能力有限,不再此处赘述。
1.1.5 BufferedInputStream/BufferedOutputStream
(1)BufferedInputStream
该类通过维护一个protected volatile byte buf[]
来作为缓冲区,来暂时存储读入的数据,等符合条件后统一输入
-
属性
private static int DEFAULT_BUFFER_SIZE = 8192; // 默认缓冲区大小 private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; // 最大缓冲区大小 protected volatile byte buf[]; // 缓冲数组 // 为buf提供compareAndSet的原子更新程序。这是必要的,因为关闭可以是异步的。我们使用buf[]的空值作为此流已关闭的主要指示器。(关闭时,“in”字段也将为空。) private static final AtomicReferenceFieldUpdater
bufUpdater = AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, "buf"); protected int count; protected int pos; // 从缓冲区要读取的下标位置 protected int markpos = -1; // 标记位 protected int marklimit; // 标记后限制的读取量 -
fill方法
private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; } /** * Check to make sure that buffer has not been nulled out due to * close; if not return it; */ private byte[] getBufIfOpen() throws IOException { byte[] buffer = buf; if (buffer == null) throw new IOException("Stream closed"); return buffer; } private void fill() throws IOException { // 太长而且比较繁琐,只了解功能即可 }
- fill方法使用更多数据填充缓冲区(通过read方法读取到buf数组中),同时考虑到洗牌和处理标记的其他技巧。假设它是由同步方法调用的。此方法还假设所有数据都已读入,因此pos>count。
-
read方法
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; } /** * Read characters into a portion of an array, reading from the underlying * stream at most once if necessary. */ private int read1(byte[] b, int off, int len) throws IOException { int avail = count - pos; if (avail <= 0) { /* If the requested length is at least as large as the buffer, and if there is no mark/reset activity, do not bother to copy the bytes into the local buffer. In this way buffered streams will cascade harmlessly. */ if (len >= getBufIfOpen().length && markpos < 0) { return getInIfOpen().read(b, off, len); } fill(); avail = count - pos; if (avail <= 0) return -1; } int cnt = (avail < len) ? avail : len; System.arraycopy(getBufIfOpen(), pos, b, off, cnt); pos += cnt; return cnt; } public synchronized int read(byte b[], int off, int len) throws IOException { getBufIfOpen(); // Check for closed stream if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int n = 0; for (;;) { int nread = read1(b, off + n, len - n); if (nread <= 0) return (n == 0) ? nread : n; n += nread; if (n >= len) return n; // if not closed but no bytes available, return InputStream input = in; if (input != null && input.available() <= 0) return n; } }
- read()方法首先会检查下标位置,然后读取字节进行填充到buf中
- read方法为synchronized所修饰,该方法会执行过程中会检查标记位,读取位,然后循环调用read1去写入数组,返回写入的字节数量
-
其他
public synchronized int available() throws IOException { int n = count - pos; int avail = getInIfOpen().available(); return n > (Integer.MAX_VALUE - avail) ? Integer.MAX_VALUE : n + avail; } public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; }
- available()方法会检查内部输入流是否有效,长度是否在合理范围内
- mark方法会将标记位设为当前要读取的下标位,将限制读取的字节数设为传入值
- reset方法将要读取的下标位设为标记位
-
close方法
public void close() throws IOException { byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() } }
- 该方法会通过bufUpdater的compareAndSet方法检查执行到该处时,数组是否有写入,如果没变化,就将buf设为null,之后再尝试将in设为null,如果不为null则进行close方法
(2)BufferedOutputStream
该类实际上是通过维护了一个protected byte buf[]
来作为缓冲区,来暂时存储要写入的数据,等到最后统一写出,通过属性int count
来作为当前位置
在构造方法中的size参数实际上被作为创建数组的大小,默认size=8192
重写的方法:
-
write(int b)
方法public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b; }
- 该方法会将传入字节存进buf数组中,并使count+1
- 如果count已经超出buf数组的长度,则直接通过flushBuffer方法来写出数据
-
flush
方法/** Flush the internal buffer */ private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count); count = 0; } } public synchronized void flush() throws IOException { flushBuffer(); out.flush(); }
-
close
方法public void close() throws IOException { try (OutputStream ostream = out) { flush(); } }
- 这是FilterOutputStream的方法,在
BufferedOutputStream
中该方法并没有重写 - 实际上该方法会自动调用flush方法:先在try语句中声明一个引用,在调用BufferedOutputStream的
flush
方法输出buffer中的数据,最后会自动关闭(隐式执行out.close()
方法)
- 这是FilterOutputStream的方法,在