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

Java 中常用的 IO 流

程序员文章站 2024-03-04 21:01:48
...

什么是流:
在 Java 中所有数据都是使用流来读写的. 流是对数据传输的总称和抽象. 即数据在两个设备间的传输称为流, 流的本质是数据传输, 根据数据传输的特性, 将流抽象为各种类.

Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
为什么需要缓冲流:
当我们 read() 读取文件的时候, 每读取一个字节都会访问一次硬盘, 效率非常低, 当文件过大的时候尤为显著, 因此需要使用 buffered 缓冲流, 当创建 buffered 对象的时候, 会创建一个缓冲区数组, 此时读文件的时候, 会先从硬盘读到缓冲区, 然后直接从缓冲区输出.
Java 中常用的 IO 流
Java 中常用的 IO 流
注意: 使用转换流的时候一定要注意编码方式, 如果不给定字符集, 将使用默认的字符集, 如果java编译的编码和目标文件的编码方式不同, 就会出现乱码
Java 中常用的 IO 流
Java 中常用的 IO 流Java 中常用的 IO 流

字节流与字符流区别:

  1. 字节流操作的基本单元是字节, 字符流操作的基本单位为 Unicode 码元
  2. 字节流在操作的时候本身是不会用到缓冲区的, 是与文件本身直接连接的, 而字符流在操作的时候是会用到缓冲区的
  3. 所有文件的存储都是字节的存储, 在磁盘上保存的是字节
  4. 在使用字节流操作的时候, 即使没有关闭资源(close), 也能输出, 但是字符流不使用 close() 方法关闭资源的话, 是没有任何输出的

下面借助一些代码来理解一下字节流和字符流

// 字节流读取操作 (1. 使用 FileInputStream 2. 使用 getClassLoader)
    @Test
    public void t1() throws IOException {
//        FileInputStream fis = null;
        InputStream fis = null;
        try {
            // 第一种方式: 使用 FileInputStream + 绝对路径
           //  fis = new FileInputStream(new File("F:\\learnJavaWeb\\io-study\\data\\呵呵.txt"));

            // 第二种方式: 使用 ClassLoader + 相对路径(推荐)
            fis = this.getClass().getClassLoader().getResourceAsStream("呵呵.txt");
            // abcdefg. 读取操作, 从当前位置偏移多少 位(read, new String 中的第二个参数), 读取多长(第三个参数)
            byte[] bytes = new byte[1024];
            int len;
            // 固定写法
            while ((len = fis.read(bytes)) != -1) {
                String s = new String(bytes, 0, len);
                System.out.println(s);
            }
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

// 使用缓冲流来进行字节读取操作
    @Test
    public void t4() throws IOException {
        FileInputStream fis = new FileInputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
        BufferedInputStream bis = new BufferedInputStream(fis); // 缓冲输入流
        byte[] bytes = new byte[1024];
        int len;
        while ((len = fis.read(bytes)) != -1) {
            String s = new String(bytes, 0, len);
            System.out.println(s);
        }
    }
    // 字符流读入操作
    @Test
    public void t2() throws IOException {
        FileReader reader = new FileReader("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
        char[] chars = new char[1024];
        int len;
        // 固定写法
        while ((len = reader.read(chars)) != -1) {
            String s = new String(chars, 0, len);
            System.out.println(s);
        }
    }
  // 字节流转换为字符流(使用缓冲流) 高效
    @Test
    public void t3() throws IOException {
        FileInputStream fis = new FileInputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
        // 转换流操作, 指定编码格式, java 编译的编码如果和目标文件的编码格式不一致, 就会出现乱码
        InputStreamReader isr = new InputStreamReader(fis,"GBK" );
        BufferedReader br = new BufferedReader(isr); // 包装转换的输入流
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }
    // 字节流输出操作(使用 PrintWriter 辅助输出)
    @Test
    public void t5() throws FileNotFoundException {
        // 覆盖的方式
        FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
        // 以追加的形式输出, 追加到文件末尾
//        FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt",true);
        PrintWriter pw = new PrintWriter(fos); // 使用 PrintWriter 打印流辅助输出
        pw.println("追加1");
        pw.println("追加2");
        pw.println("追加3");
        pw.flush(); // 手动刷新缓冲区
//        PrintWriter pw = new PrintWriter(fos, true); // 自动刷新缓冲区
    }
   // 字节流输出操作(使用 BufferedWriter , 因为还要输出成字符形式, 所以需要 OutputStreamWriter 转化流)
    @Test
    public void t6() throws IOException {
        // 覆盖的方式
        FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
        // 追加到文件末尾
//        FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt",true);
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("追加1");
        bw.newLine(); // 换行
        bw.write("追加2");
        bw.newLine(); // 换行
        bw.write("追加3");
        bw.newLine(); // 换行
        bw.flush(); // 手动刷新缓冲区
    }

Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流
注意:
Java 中常用的 IO 流
Java 中常用的 IO 流Java 中常用的 IO 流
Java 中常用的 IO 流
Java 中常用的 IO 流

Java 中常用的 IO 流

案例:简单实现一下对文件的拷贝

// 简单实现一下对文件的拷贝
public class FileCopy {
    public static void main(String[] args) throws IOException {
        copyFile();
    }

    private static void copyFile() throws IOException {
        // 创建输入流对象
        FileInputStream fis = new FileInputStream("D:\\QQ\\386902834\\FileRecv\\IO.png");
        // 创建缓冲输入流对象
        BufferedInputStream bis = new BufferedInputStream(fis);
        // 创建输出流对象
        FileOutputStream fos = new FileOutputStream("F:\\板书\\IO");
        // 创建缓冲输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 准备一个搬运工具
        byte[] bytes = new byte[1024];
        int len = 0;
        // 循环读写数据即可
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }
        // 释放资源
        bos.flush();
        bos.close();
        bis.close();
        System.out.println("复制成功");
    }

Java 中常用的 IO 流
Java 中常用的 IO 流
下面写一段代码理解一下什么是序列化, 什么是反序列化以及深拷贝和浅拷贝的区别

package Java05_31;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SerializableTest implements Serializable {

    private String name;
    private List<Food> foods = new ArrayList<>();

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SerializableTest t = new SerializableTest();
        t.name = "快餐店";
        t.foods.add(new Food("牛排"));
        t.foods.add(new Food("汉堡"));
        t.foods.add(new Food("可乐"));
        // 输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(t); // 序列化 java 对象为二进制数据(拷贝)
        t.name = "慢慢吃";
        t.foods.get(2).name = "牛奶"; // 把可乐改成牛奶
        // 观察打印结果会发现, 这里虽然修改了 t 的某些值,但是 t2 还是打印的是原来的值, 这就是深拷贝
        // 输入流
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        // toByteArray() -> 转成二进制数组
        ObjectInputStream ois = new ObjectInputStream(bais);
        SerializableTest t2 = (SerializableTest) ois.readObject(); // readObject -> 读取对象
        System.out.println(t);
        System.out.println(t2);
    }

    private static class Food implements Serializable{
        private String name;

        public Food(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Food{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    @Override
    public String toString() {
        return "SerializableTest{" +
                "name='" + name + '\'' +
                ", foods=" + foods +
                '}';
    }
}

深拷贝和浅拷贝:

简单来说, 如果拷贝之后, 修改原来的值, 拷贝之后的值也随之变了, 那就是浅拷贝; 如果修改原来的值之后, 拷贝的值没有变, 那就是深拷贝.
浅拷贝不会拷贝堆内存中的对象, 只会拷贝基本类型的属性, 引用类型的属性, 栈中的变量; 而深拷贝则是全部都复制.
如果拷贝对象里的元素只有值, 那深拷贝和浅拷贝是相同的, 都会产生一个新对象, 两个互相分隔;
如果拷贝对象里的元素包含引用, 那就完全不同了: 浅拷贝复制的只是这些引用, 如果对引用中的元素做出改变, 那浅拷贝出的对象引用中的元素也是变化了的; 但是深拷贝不同, 深拷贝会将原对象里的引用也重新创建一份(重新申请一块内存) , 将新对象和原来的对象完全隔离开来!

Java 中常用的 IO 流