• 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或者' '或者' '结束,然后将读取的字节构建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()方法)

  • 相关阅读:
    EOJ二月月赛补题
    cf401d
    cf628d
    cf55d
    HDU 6148 Valley Number
    洛谷 P3413 SAC#1
    洛谷 P4127[AHOI2009]同类分布
    洛谷 P2602 [ZJOI2010]数字计数
    bzoj 3679
    函数和循环闭包的理解
  • 原文地址:https://www.cnblogs.com/nishoushun/p/12797305.html
Copyright © 2020-2023  润新知