Java IO流部分源码解析

Java IO流部分源码解析

前言,很多东西实际上是自己闲着没事去看看底层是怎么实现的,很多地方可能讲的并不是很详细或者会出现错误,还是要相信自己的判断(๑╹ヮ╹๑)ノ Studying makes me happy

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<BufferedInputStream, byte[]> 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()方法)

posted @ 2020-04-28 21:16  NIShoushun  阅读(399)  评论(0编辑  收藏  举报