Java基础IO总结

发布时间:2023年12月18日

输入和输出的基本概念

将数据输入到计算机内存的过程叫数据输入

在这里插入图片描述

将数据输出到外部存储的过程叫数据输出

在这里插入图片描述

IO流常用类有那些?

IO流常用类大概有40个,按照数据输入输出的单位可分为字节流字符流两个大类

  1. 字节流 InputStream、OutputStream
  2. 字符流 Reader、Writer

字节输入输出流

输入流常用方法

  1. read() :返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 -1 ,表示文件结束。
  2. read(byte b[ ]) : 从输入流中读取数据到数组b中,读取b数组的长度,这一点我们可以通过源码印证
public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
  1. read(byte b[], int off, int len) :读取数据到b数组中,从off开始,读取len个。
  2. skip(long n) :忽略输入流中的 n 个字节 ,返回实际忽略的字节数。
  3. available() :返回输入流中可以读取的字节数。
  4. close() :关闭输入流释放相关的系统资源。

使用示例

 /**
     * 注意中文有可能出现乱码问题
     */
    @Test
    public void readTest() {
        //input.txt内容:JAVAIO_STUDY
        try (InputStream fis = new FileInputStream("input.txt")) {
            int available = fis.available();
            System.out.println("skip前输入流中可以读取的字节数" + available);//skip前输入流中可以读取的字节数12


            long skip = fis.skip(2);
            System.out.println("跳过的字节数 " + skip);//跳过的字节数 2


            available = fis.available();
            System.out.println("skip后输入流中可以读取的字节数" + available);//skip后输入流中可以读取的字节数10

            int content;
            System.out.println("读取input.txt");
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);//VAIO_STUDY
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

输出流常用方法

  1. write(int b) :将特定字节写入输出流。
  2. write(byte b[ ]) : 将b数组的内容写到输出流中。
  3. write(byte[] b, int off, int len) : 将b数组中从off到off+len的内容写到输出流中。
  4. flush() :刷新此输出流并强制写出所有缓冲的输出字节。
  5. close() :关闭输出流释放相关的系统资源。

输出流使用示例

FileOutputStream写到out.txt

 /**
     * 输出流
     *
     * @throws Exception
     */
    @Test
    public void outputStreamTest() throws Exception {
        try (FileOutputStream fis = new FileOutputStream("out.txt")) {
            fis.write("FileOutputStream".getBytes());
        }
    }

数据流

DataInputStream 常用于文本文件有特定格式的场景读取使用,例如我们有一个文本文件内容格式依次是布尔、整形、短整型、浮点型。我们就可以使用DataInputStream,注意格式必须严格遵守,就像下面这样

/**
     * 注意读写顺序必须保持一致
     *
     * @throws Exception
     */
    @Test
    public void dataInputStreamTest() throws Exception {
        FileOutputStream fileOutputStream = new FileOutputStream("abc.txt");
        DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);

        dataOutputStream.writeBoolean(true);
        dataOutputStream.writeInt(55);
        dataOutputStream.writeShort(111);
        dataOutputStream.writeDouble(3.14);

        FileInputStream fileInputStream = new FileInputStream("abc.txt");
        DataInputStream dataInputStream = new DataInputStream(fileInputStream);
        System.out.println(dataInputStream.readBoolean());
        System.out.println(dataInputStream.readInt());
        System.out.println(dataInputStream.readShort());
        System.out.println(dataInputStream.readDouble());

    }

对象流

ObjectInputStream 常用于将文件内容反序列化

/**
     * 对象流
     *
     * @throws Exception
     */
    @Test
    public void objTest() throws Exception {
        Student student = new Student();
        student.setId(1);
        student.setName("小往");
        System.out.println(student);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.data"));
        oos.writeObject(student);

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.data"))) {
            Student student1 = (Student) ois.readObject();
            System.out.println(student1);
        }

    }

字符流

简介

以字符的形式读取或者写入数据,从而避免一些编码问题。我们常见的编码有:

  1. utf8:英文一个字节,一个中文占3个字节。
  2. unicode:无论中文英文都占2个字节。
  3. gbk:一个英文1个字节,中文2个字节。

字符输入流Reader

常用方法

  1. read() : 从输入流读取一个字符。
  2. read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,等价于 read(cbuf, 0, cbuf.length)
  3. read(char[] cbuf, int off, int len) :在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
  4. skip(long n) :忽略输入流中的 n 个字符 ,返回实际忽略的字符数。
  5. close() : 关闭输入流并释放相关的系统资源。

使用示例

@Test
    public void fileReaderTest() throws Exception {
        //input.txt内容JAVAIO_STUDY
        try (FileReader fis = new FileReader("input.txt")) {
            int content;
            fis.skip(2);
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);

            }
        }
    }

字符输出流Writer

  1. write(int c) : 写入单个字符。
  2. write(char[] cbuf) :将cbuf内容全部写到输出流。
  3. write(char[] cbuf, int off, int len) :将cbuf的内容从off到off+len写到cbuf中。
  4. write(String str) :写入字符串,等价于 write(str, 0, str.length())
  5. write(String str, int off, int len) :在write(String str) 方法的基础上增加了 off 参数(偏移量)len 参数(要读取的最大字节数)
  6. append(CharSequence csq) :将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象。
  7. append(char c) :将指定的字符附加到指定的 Writer 对象并返回该 Writer 对象。
  8. flush() :刷新此输出流并强制写出所有缓冲的输出字符。
  9. close():关闭输出流释放相关的系统资源。

使用示例

 @Test
    public void writerTest() {
        try (FileWriter writer = new FileWriter("writer.txt")) {
            writer.write("LLJAVA", 2, "LLJAVA".length()-2);//将JAVA写到writer.txt
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

字符流异常处理的标准方式

  1. 操作代码放置到try块中。
  2. finally中关闭流,注意判空。

当然java8的之后我们完全可以使用try-with-resource

@Test
    public void writerTest() {
        FileWriter fw = null;
        try {
            fw = new FileWriter("demo.txt");
            fw.write("abcdefg");

        } catch (IOException e) {
            System.out.println("catch:" + e.toString());
        } finally {
            try {
                if (fw != null)
                    fw.close();
            } catch (IOException e) {
                System.out.println(e.toString());
            }

        }
    }

FileWriter 实现数据追加

//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
		FileWriter fw = new FileWriter("demo.txt",true);

字节缓冲流(文件读写增强器)

BufferedInputStream

BufferedInputStream会将读取到的字节流存到缓冲区中,只有当缓冲区满了,再将数据刷到内存中,注意缓冲区默认大小为8192字节,如有需要我们可以手动调整一下。如下源码所示

public class BufferedInputStream extends FilterInputStream {
    // 内部缓冲区数组
    protected volatile byte buf[];
    // 缓冲区的默认大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 使用默认的缓冲区大小
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    // 自定义缓冲区大小
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

public synchronized int read() throws IOException {
        if (pos >= count) {
        //缓冲空间不足情况下,该函数会通过CAS对缓冲区进行扩容,将count重新计算一下实际大小
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
}




BufferedOutputStream

同样的BufferedOutputStream会将写入的数据优先存放到缓冲区,当前缓冲区空间不足时,才将缓冲区数据写到输出流中,如下源码所示

public synchronized void write(int b) throws IOException {
      if (count >= buf.length) {
            flushBuffer();
                   }
         buf[count++] = (byte)b;
      }

缓冲流与常规性能测试代码

可以看到走了缓冲之后,避免了没必要的IO,程序性能一下子提高不少。

 @Test
    public void readTimeTest() throws Exception {
        long start = System.currentTimeMillis();
        int content;
        try (FileInputStream fis = new FileInputStream("斗破苍穹.txt");
             FileOutputStream fos = new FileOutputStream("fos斗破苍穹.txt")) {
            /**
             * public int read() throws IOException {
             *         return read0();
             *     }
             */
            while ((content = fis.read()) != -1) {
                fos.write(content);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("FileOutputStream:" + (end - start));


        start = System.currentTimeMillis();
        try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream("斗破苍穹.txt"));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("fos斗破苍穹.txt"))) {
            /**
             * public synchronized int read() throws IOException {
             *         if (pos >= count) {
             *             fill();
             *             if (pos >= count)
             *                 return -1;
             *         }
             *         return getBufIfOpen()[pos++] & 0xff;
             *     }
             */
            while ((content = fis.read()) != -1) {
                /**
                 * 可以看到超过了buf才执行写入
                 * public synchronized void write(int b) throws IOException {
                 *         if (count >= buf.length) {
                 *             flushBuffer();
                 *         }
                 *         buf[count++] = (byte)b;
                 *     }
                 */
                bos.write(content);
            }
        }
        end = System.currentTimeMillis();
        System.out.println("BufferedOutputStream:" + (end - start));

     
    }

输出结果

   /**
         * 输出结果
         * FileOutputStream:26886
         * BufferedOutputStream:156
         */

字符流缓冲区

和字节缓冲流差不多,这里就不多介绍了。通过源码我们就知道,字符流是以字符为单位将数据存到数组中,如下源码所示:

public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                //读入缓冲区数组中
                return cb[nextChar++];
            }
        }
    }

手写一个bufferReader实现readLine

手动实现一个bufferReader,实际上readLine底层就是调用单字符读取的read方法,一旦遇到\n停止指针偏移,输出读取到的字符串,如下图:

在这里插入图片描述

代码示例

public class MyBufferReader implements Closeable {

    private Reader reader;

    public MyBufferReader(Reader reader) {
        this.reader = reader;
    }

    public MyBufferReader() {
    }


    public String readLine() throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        int readCode;
        while ((readCode = reader.read()) != -1) {
            char c = (char) readCode;
            if (c == '\n') {
                break;
            }
            if (c == '\r') {
                continue;
            }

            stringBuilder.append(c);
        }
        return stringBuilder.toString();
    }




    @Override
    public void close() throws IOException {
        reader.close();
    }
}

测试代码

@Test
    public void myReaderTest(){
        /**
         * 文本内容
         * JAVAIO_STUDY
         * 123
         */
        try (MyBufferReader reader = new MyBufferReader(new FileReader("input.txt"))) {
            System.out.println(reader.readLine());
            System.out.println(reader.readLine());
            /**
             * 输出结果
             * JAVAIO_STUDY
             * 123
             */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如何确定使用那种流对象

明确是否有源和目的

如果有输入则要用到InputStream 或者Reader
如果有输出则要用到OutputStreamWriter

确定是读取文本还是媒体文件等

如果是文本文件则是字符流,反之其他媒体文件或者对格式没有要求的文件都用字节流

是否需要提高性能

如果需要考虑性能就使用缓冲流包装一下

案例1

  1. 将一个文本文件中数据存储到另一个文件中,复制文件,请确定要使用的流。

按照题目所说因为有源,所以要使用InputStream Reader
读取的是字符所以用Reader
原设备是硬盘的文本文件,所以用FileReader
我们需要提高效率所以要用BufferedReader
所以代码为
FileReader fr = new FileReader(“a.txt”);
BufferedReader bufr = new BufferedReader(fr);
输出流同理得出
FileWriter fw = new FileWriter(“b.txt”);
BufferedWriter bufw = new BufferedWriter(fw);

案例2

将键盘录入的数据保存到一个文件中。

源是键盘,但是要转成文本所以用System.in转换成Reader
不需要提高效率所以InputStreamReader足矣
输出到文本且要效率所以的BufferedWriter套FileWriter

补充:随机访问流

随机访问流支持操作偏移量灵活改动或者读取数据如下所示

 @Test
    public void randomAccessFile() throws Exception {
        RandomAccessFile randomAccessFile = new RandomAccessFile("input.txt", "rw");

        System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
        System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
        System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());

        randomAccessFile.seek(1);
        System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
        randomAccessFile.seek(0);
//        覆盖已有字符串
        randomAccessFile.write(new byte[]{'J', 'A', 'V', 'A'});
    }

场景设计——大文件上传

Java实现文件分片上传

参考文献

Java IO基础知识总结

文章来源:https://blog.csdn.net/shark_chili3007/article/details/107317711
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。