欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java IO流详解

程序员文章站 2024-03-05 15:30:37
...

Java IO流

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流详解

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

  • Java IO流详解

Java IO流详解

1.2.2 按传输单位分类

(1)字符流

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

(2)字节流

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

1.2.3 按功能分类

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

Java IO流详解

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

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

  • 常用的处理流:

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

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

    Java IO流详解


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 FileInputStream/OutputStream

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

    • read()方法返回范围为0x00-0xff正好为1个字节
    • 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 BufferdInputStream/OutputStream

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

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

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

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

    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.3 DataInputStream/DataOutputStream

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

    • public final void writeBooolean()throws IOException
    • public final void writeByte()throws IOException
    • public final void writeShort()throws IOException*
    • public final void writeInt()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.4 ObjectInpuStream/ObjectOutputStream

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

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

    • 该对象的属性也需要实现Serializable 接口
    • 如果没有实现,会抛出java.io.NotSerializableException异常
  • 该方法可以完成对象的深度拷贝

    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.5 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 注意

  • 使用任何流时都需要调用close()方法,因为JVM垃圾回收机制不会去主动释放系统资源,应主动释放占有的文件资源
  • close方法调用应该在finally块中,防止发生异常不会被调用
  • 可以使用try(流对象) {...}语句简写try{...}catch(){...}finally{...}
    • 使用时需要流对象引用定义在try()

3.2 Java中流的继承关系

3.2.1 流类继承关系图

Java IO流详解