Java IO流基础知识详解

Java IO流

文章目录

  • Java IO流
    • 1.java IO流概述
      • 1.1 IO流介绍
        • 1.1.1 什么是IO流?
        • 1.1.2 java中的IO流列表
        • 1.2 IO流分类
        • 1.2.1 按方向分类
        • 1.2.2 按传输单位分类
        • 1.2.3 按功能分类
    • 2.常用 java IO流简单使用
      • 2.1 字符流
        • 2.1.1 FileReader/FileWriter
        • 2.1.2 BufferedReader/BufferedWriter
        • 2.1.3 PiedReader/PiedWriter
        • 2.1.4 InputStreamReader/InputStreamWriter
        • 2.1.5 PrintWriter
      • 2.2 字节流
        • 2.2.1 InputStream/OutputStream
        • 2.2.1 FileInputStream/OutputStream
        • 2.2.2 FilterInputStream/OutputStream
        • 2.2.3 BufferdInputStream/OutputStream
        • 2.2.4 DataInputStream/DataOutputStream
        • 2.2.5 ObjectInpuStream/ObjectOutputStream
        • 2.2.6 PrintStream
    • 3.部分源码分析
      • 3.1字节流
        • 3.1.1 InputStream/OutputStream
        • 3.1.2 FilterInputStream/OutPutStream
        • 3.1.3 DataInputStream/DataOutputStream
    • 4. 总结
      • 4.1 使用总结
      • 4.2 Java中流的继承关系
        • 4.2.1 流类继承关系图

1.java IO流概述

1.1 IO流介绍

1.1.1 什么是IO流?

  • 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流
  • 流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作

1.1.2 java中的IO流列表

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
抽象基类 FilterInputStream FilterOutputStream FilterReader FileWriter
打印流 PrintStream
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

1.2 IO流分类

1.2.1 按方向分类

(1)输入流(InputStream):从文件或网络 资源中将数据输入到程序中的流

  • FileInputStream:从文件读取数据,是最终数据源;

  • ServletInputStream:从HTTP请求读取数据,是最终数据源;

  • Socket.getInputStream():从TCP连接读取数据,是最终数据源;

  • Java IO流基础知识详解_第1张图片

(2)输出流(OutStream):从程序中将数据输出到文件或网络的流

  • Java IO流基础知识详解_第2张图片

Java IO流基础知识详解_第3张图片

1.2.2 按传输单位分类

(1)字符流

  • 字符流以字符为单位,根据码表映射字符,一次读入或写入2个字节,只能用于处理文本,用于读写Unicode编码的字符

(2)字节流

  • 字节流以字节(8bit)为单位,适用于处理所有类型数据,用于读写ASCII字符和二进制数据

1.2.3 按功能分类

(1)节点流:直接与数据源相连,读入或读出

aH

(2)处理流:处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流

  • 处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接

  • 常用的处理流:

    • 缓冲流BufferedInputStreanBufferedOutputStreamBufferedReaderBufferedWriter :增加缓冲功能,避免频繁读写硬盘。
      转换流InputStreamReaderOutputStreamReader:实现字节流和字符流之间的转换。
      数据流DataInputStreamDataOutputStream :提供将基础数据类型写入到文件中,或者读取出来。

      对象流ObjectInputStreamObjectOutputStream:提供将对象转换成二进制比特流写入到输出流中,也可以将对象从输入流中还原对象数据,构建新对象

    Java IO流基础知识详解_第4张图片


2.常用 java IO流简单使用

  • 以下只给出基础IO流的用法,具体的API使用方法请查看文档

2.1 字符流

2.1.1 FileReader/FileWriter

  • 读取单个字符

    • read()方法返回范围为0x00-0xffff正好为两个字节
    • write(int c)方法的参数值会取低16位构建char类型字符
    public class WriterTest {
        public static void main(String[] args) throws IOException {
            try (Reader reader = new FileReader("src/test/resources/text.txt");
                 Writer writer = new FileWriter("src/test/resources/out.txt")) {
                int len;
                while ((len = reader.read()) != -1) {
                    writer.append((char) len);
                }
            } catch (Exception e) {
                e.printStackTrace();
        }
        }
    }
    
  • 读取多个字符存放到数组中,并使用read(char[] chars)方法读取或者将该数组构造成CharSequence对象,使用append(CharSqquence c,[int start,int end])方法追加

    int len;
    char[] chars = new char[2048];
    while ((len = reader.read(chars)) != -1) {
        writer.append(new String(chars,0,len));
        //reader.read(chars);//或者使用
    }
    

2.1.2 BufferedReader/BufferedWriter

  • BufferedWriter使用缓冲区将一串字符先存到缓冲区,当读或写的数据达到一定量, 再自动往文件里进行读写 ,效率相比FileWrter提高很多

  • 由于设计时使用装饰者模式对原类的一种功能增强,所以使用方法与FileWriter/Reader相同

  • 使用在finally块调用close()关闭流对象之前:

    • 如果是文件读写完的同时缓冲区刚好装满 , 那么缓冲区会把里面的数据朝目标文件自动进行读或写 , 这种时候你直接调用close()方法不会出现问题 ;
    • 但是如果文件在读写完成时 , 缓冲区没有装满 , 就直接调用close()方法 , 这个时候装在缓冲区的数据就不会自动的朝目标文件进行读或写 , 从而造成缓冲区中的这部分数据丢失 , 所以这个是时候就需要在close()之前先调用flush()方法 , 手动使缓冲区数据读写到目标文件
    public class WriterTest {
        public static void main(String[] args) throws IOException {
            try (Reader reader = new BufferedReader(new FileReader("src/test/resources/text.txt"));
                 Writer writer = new BufferedWriter(new FileWriter("src/test/resources/out.txt",true))) {
                int len;
                char[] chars = new char[2048];
                while ((len = reader.read(chars)) != -1) {
                    writer.append(new String(chars,0,len));
                    //reader.read(chars);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2.1.3 PiedReader/PiedWriter

  • PiedWriter/Reader是字符管道输出输入流,两者采用生产者消费者模式,当一方工作未完成时,另一方处于阻塞状态

  • PipedWriterPipedReader的作用是可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedWriterPipedReader配套使用

  • 在使用时可以通过构造方法传入配套字符管道输出输入流来绑定传输方向;也可以通过connect(配套字符管道输出/输入流对象)方法,连接配套字符管道输出/输入流

    • 如果已经连接,第二次连接时会抛出java.io.IOException
    • 可以通过调用public boolean ready()方法判断是否已经连接
    public class WriterTest {
        public static void main(String[] args) throws IOException {
    
            try(PipedReader reader = new PipedReader();
                PipedWriter writer = new PipedWriter( reader)) {
                //reader.connect(writer);
                int len;
                char[] chars = new char[2048];
                writer.write("begin");
                while ((len = reader.read(chars)) != -1) {
                    writer.append(new String(chars,0,len));
                    System.out.println((char)reader.read());
                    //reader.read(chars);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2.1.4 InputStreamReader/InputStreamWriter

  • 该类通过传入一个字节流对象构造,是字节流与字符流之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符

  • 构造时可以传入编码方式

    public class WriterTest {
        public static void main(String[] args) throws IOException {
    
            try(Reader reader = new InputStreamReader(new FileInputStream("src/test/resources/text.txt"),"UTF-8");
                Writer writer = new BufferedWriter(new FileWriter("src/test/resources/out.txt"))) {
                //reader.connect(writer);
                int len;
                char[] chars = new char[2048];
                while ((len = reader.read(chars)) != -1) {
                    writer.append(new String(chars,0,len));
                    //writer.write(chars,0,len);
                    //System.out.println(new String(chars,0,len));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2.1.5 PrintWriter

  • PrintWriter作为OutputStream的装饰类,通过传入一个OutputStream对象来构造,转化成字符输出流,实现了将基本数据类型,字符串等数据输入到输出流,将数据进行格式化打印功能

    public class WriterTest {
        public static void main(String[] args) throws IOException {
    
            try(PrintWriter writer = new PrintWriter(System.out) ){
                writer.println("hello world");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2.2 字节流

2.2.1 InputStream/OutputStream

  • 所有字节流的抽象父类
  • 实现了Closeable接口

2.2.1 FileInputStream/OutputStream

  • 此类流为字节流的基本类OutputStream的直接子类,可以将数据文件按字节或按存满字节数组大小时读取进输入流,并且通过输出流按字节或按字节数组进行输出

    • read()方法返回范围为0x00-0xff正好为1个字节

    • read(byte[] b,int off,int len)read(byte[] b)

      • 两者作用将读取的字节为从off位开始存储,一直存储len位

      • 很多情况下这两个方法并不能读取到期望的字节数,可以使用以下方法来确保该次读取的字节数目,除非中途遇到IO异常或者到了数据流的结尾(EOFException)

      byte[] b = new byte[count];
      int readCount = 0; // 已经成功读取的字节的个数
      while (readCount < count) {
         readCount += in.read(bytes, readCount, count -readCount);
      }
      
    • write(int c,[boolean append])方法的参数值会取低8位构建char类型字符

  • System.in为一个静态InputStream对象,可以通过System.setIn(InputStream in)来设置

    public class StreamTest {
        public static void main(String[] args) {
            try (FileInputStream in = new FileInputStream("src/test/resources/download.jpg");
                 FileOutputStream out = new FileOutputStream(("src/test/resources/download_copy.jpg"))) {
                int len;
                byte[] bytes = new byte[2048];
                while((len = in.read(bytes))>0){
                    out.write(bytes, 0,len );
                }
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 由于java中Char为2个字节,16个位,所以想要通过字节流来读取文本类文件,需要通过字节数组构造String对象来实现,如下:

    • 如果直接使用read()获取字节,那么对于超出Ascll码的字符来说将会出现乱码现象
    try(FileInputStream in = new FileInputStream("src/test/resources/text.txt")){
        int len;
        byte[] bytes = new byte[1024];
        while((len = in.read(bytes))>-1){
            String input = new String(bytes,0,len);
            System.out.println(input);
        }
    }catch (Exception e){
    
    }
    

2.2.2 FilterInputStream/OutputStream

  • FilterInputStream 是所有处理流的父类,其方法签名与传入的原始流的方法一模一样,只是调用传入的原始流的方法,因此作用也相同

2.2.3 BufferdInputStream/OutputStream

  • BuffredStream 是常用的一种处理流

  • BufferedStream在基础流写入内存中能够提高读取与写入速度。但是缓冲区设置的大小对性能也有影响,默认值是4096字节,并能够根据需求自动增长。并且很多属性都与基础流一致。

    • 构造时可以传入int size,设置缓冲区大小
  • 缓冲数据能够减少对操作系统的调用次数,缓冲数据主要存储在缓冲区中,缓冲区是内存中的字节块。BufferedStream类提供从基础数据源或存储库读取字节以及将字节写入基础数据源或存储库的实现,在不需要缓冲区时可以防止缓冲区降级输入和输出速度。

  • 缓冲类型下,会在后台自动下载定长的内容(read()方法传入数组的大小),读的时候是从缓冲区中拿东西。

    • 这种模式最大的特点是半阻塞式,大部分情况下能大幅度提高处理速度
  • 在程序逻辑速度大大慢于IO速度时,此方法效率明显。最好是在大文件的情况下,分块读,分块写

  • 当使用BuffeInputStream进行文件写入时,最后一定要在close()前使用flush()方法将可能因缓存区中内容不够而没有写入到文件中的数据手动写入到文件中

    public class StreamTest {
        public static void main(String[] args) throws FileNotFoundException {
    
            try (
                BufferedInputStream in = new BufferedInputStream(new FileInputStream("src/test/resources/download.jpg"));
                BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(("src/test/resources/download_copy.jpg")));
            ){
                int len;
                byte[] bytes = new byte[2048];
                while ((len = in.read(bytes)) > 0) {
                    out.write(bytes, 0, len);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

2.2.4 DataInputStream/DataOutputStream

  • 数据输出流允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流

    • public final void writeXX()throws IOException
  • 这些方法将指定的基本数据类型以字节的方式写入到输出流

    • 注意writeXX要与readXX()配套使用,不然因为起写入字节数为基本数据类型大小字节数,而读取时是另一数据类型的字节数,会造成字节数不匹配现象,抛出java.io.EOFException
      • 比如前面用writeInt(1)写入1,后面用read()读取,前面写入4个字节,后面一次只读1个字节,调用read()4次才能全部读完,读取结果为0,0,0,1
      • 再比如前面用write(1)写入1,后面用readInt()读取,前面写入1个字节,后面一次读4个字节,会造成字节数量不够读的现象,抛出java.io.EOFException
    public class StreamTest {
        public static void main(String[] args) throws FileNotFoundException {
    
            try (
                    DataInputStream in = new DataInputStream(new FileInputStream("src/test/resources/text.txt"));
                    DataOutputStream out = new DataOutputStream(new FileOutputStream("src/test/resources/text.txt"));
            ){
                for (int i = 0; i < 99; i++) {
                    out.writeInt(i);
                }
                for (int i = 0; i < 99; i++) {
                    System.out.println(in.readInt());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

2.2.5 ObjectInpuStream/ObjectOutputStream

  • 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作

  • 前提:需要被序列化和反序列化的类必须实现Serializable 接口

    • 该对象的属性也需要实现Serializable 接口
    • 如果没有实现,会抛出java.io.NotSerializableException异常
  • transient关键字

    • 使用transient关键字修饰的属性,在序列化时是不会被读取的
    • 当进行反序列化时,用transient修饰的属性会被赋予默认值,对于对象引用会指向null
  • 静态变量不能被序列化

  • 该方法可以完成对象的深度拷贝

    class Pojo implements Serializable{
        private int ID;
        private String name;
    
        public Pojo(int ID, String name) {
            this.ID = ID;
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Pojo{" +
                    "ID=" + ID +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    public class StreamTest {
        public static void main(String[] args) throws FileNotFoundException {
    
            try (
                    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/test/resources/out.txt"));
                    ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/test/resources/out.txt"));
            ){
                List<Pojo> list = new LinkedList<Pojo>();
                list.add(new Pojo('1',"Alice"));
                list.add(new Pojo('2',"Boom"));
                list.add(new Pojo('3',"Clear"));
                out.writeObject(list);
                List listCopy = (List)in.readObject();
                System.out.println(list.hashCode()+" "+listCopy.hashCode());
                System.out.println(list==listCopy);
                System.out.println(listCopy);
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 控制台输出:
      • 220810805 1932239246
        false
        [Pojo{ID=49, name='Alice'}, Pojo{ID=50, name='Boom'}, Pojo{ID=51, name='Clear'}]

2.2.6 PrintStream

  • 作为输出流使用,其他流不同的是,PrintStream流永远不会抛出异常.因为做了try{}catch(){}会将异常捕获,出现异常情况会在内部设置标识,通过checkError()获取此标识
  • PrintStream流有自动刷新机制,例如当向PrintStream流中写入一个字节数组后自动调用flush()方法
  • PrintStream流打印的字符通过平台默认的编码方式转换成字节,在写入的是字符,而不是字节的情况下,应该使用PrintWriter
  • PrintStream流中基本所有的print(Object obj)重载方法和println(Object obj)重载方法都是通过将对应数据先转换成字符串,然后调用write()方法写到底层输出流中.
    • 常见PrintStream流:
      • System.out就被包装成PrintStream流,
        • 可以通过System.setOut(PrintStream out)设置
      • System.err也是PrintStream流
        • 可以通过void System.setErr(PrintStream err)设置

3.部分源码分析

  • 注:本部分只是自己想练习一下阅读源码的能力,实际上也不会有太多牵扯到原理的东西,会用就行,不想看的话直接跳过

3.1字节流

3.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

  • 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

  • 接口实现

    Java IO流基础知识详解_第5张图片

  • 抽象接口void write(int b)

    • 写入一个字节,子类去实现
  • 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中方法体没有内容,子类去实现

3.1.2 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 来创建该类对象

  • 源码:这里只给出与out的方法内容不同的方法

    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 语法)进行关闭

3.1.3 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)情况较为复杂,也不再此处赘述


4. 总结

4.1 使用总结

  • 使用任何流时都需要调用close()方法,因为JVM垃圾回收机制不会去主动释放系统资源,应主动释放占有的文件资源

  • close方法调用应该在finally块中,防止发生异常不会被调用

  • 可以使用try(流对象) {...}语句简写

    try(声明并new IO流对象() ){
        
        IO流对象的使用
        
    }catch(异常){
        ...
    }finally{
        ...
        后续的处理    
    }
    

4.2 Java中流的继承关系

4.2.1 流类继承关系图

Java IO流基础知识详解_第6张图片

你可能感兴趣的:(Java)