【JavaSE系列】IO流

news2024/9/28 22:16:37

目录

前言

一、IO流概述

二、IO流体系结构

三、File相关的流

1. FileInputStream

2. FileOutputStream

3. FileReader

4. FileWriter

四、缓冲流

五、转换流

1. InputStreamReader

2. OutputStreamWriter

六、数据流

七、对象流

八、打印流

九、标准输入输出流

十、压缩和解压缩流

十一、字节数组流

1. 基本使用

2. 对象克隆

总结


前言

  在Java编程中,输入输出(I/O)操作是处理数据流的核心部分。无论是从文件读取数据、将数据写入到磁盘,还是通过网络进行通信,都需要使用I/O流来实现。Java提供了丰富的I/O流类库,它们不仅能够高效地处理各种类型的数据,还提供了灵活的方式来装饰和扩展这些基本功能。本篇博客旨在介绍Java I/O流的基础知识,包括其分类、体系结构以及如何使用不同类型的流来完成日常开发中的常见任务。

一、IO流概述

  IO流指的是程序中数据的流动。数据可以从内存流动到硬盘,也可以从硬盘流动到内存。 Java中IO流最基本的作用是完成文件的读和写。

  根据数据流向分为输入流和输出流,输入和输出是相对于内存而言的。 输入流指的是从硬盘到内存(输入又叫做读:read) ;输出流指的是从内存到硬盘(输出又叫做写:write)。根据读写数据形式分为字节流和字符流。字节流指的是一次读取一个字节,适合读取非文本数据,例如图片、声音、视频等文件;字符流指的是一次读取一个字符,只适合读取普通文本,不适合读取二进制文件。

注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流。

  根据流在IO操作中的作用和实现方式来分类可以分为节点流和处理流。 节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分;处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理。 

二、IO流体系结构

三、File相关的流

1. FileInputStream

  文件字节输入流,可以读取任何文件。使用FileInputStream读取的文件中有中文时,有可能读取到中文某个汉字的一半,在将byte[]数组转换为String时可能出现乱码问题,因此FileInputStream不太适合读取纯文本。FileInputStream常用方法如下:

方法描述
FileInputStream(String name);构造方法,创建一个文件字节输入流对象,参数是文件的路径
int read();从文件读取一个字节(8个二进制位),返回值读取到的字节本身,如果读不到任何数据返回-1
int read(byte[] b);一次读取多个字节,如果文件内容足够多,则一次最多读取b.length个字节。返回值是读取到字节总数。如果没有读取到任何数据,则返回 -1
int read(byte[] b, int off, int len);读到数据后向byte数组中存放时,从off开始存放,最多读取len个字节。读取不到任何数据则返回 -1
long skip(long n);跳过n个字节
int available();返回流中剩余的估计字节数量
void close()关闭流

  下面我们在某一路径下创建一个1.txt的文件,在其中写入一串英文字符串,如下图所示:

  我们可以使用FileInputStream一个字节一个字节地读取我们写好的txt文件,代码如下:

public void testFileInputStream() throws IOException {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream("D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\1.txt");
        int readByte;
        while ((readByte = fis.read()) != -1) {
            System.out.print((char) readByte);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (fis != null) {
            fis.close();
        }
    }
}

  我们也可以每次读取多个字节,代码如下:

public void testFileInputStream2() throws IOException {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream("D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\1.txt");
        byte[] bytes = new byte[fis.available()];
        int readBytes;
        while ((readBytes = fis.read(bytes)) != -1) {
            String str = new String(bytes, 0, readBytes);
            System.out.print(str);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (fis != null) {
            fis.close();
        }
    }
}

  两者的运行结果均如下:

2. FileOutputStream

  FileOutputStream 是文件字节输出流。常用方法如下:

方法描述
FileOutputStream(String name);构造方法,创建输出流,先将文件清空,再不断写入
FileOutputStream(String name, boolean append);构造方法,创建输出流,在原文件最后面以追加形式不断写入
write(int b);写一个字节
void write(byte[] b);将字节数组中所有数据全部写出
void write(byte[] b, int off, int len);将字节数组的一部分写出
void close()关闭流
void flush()刷新

  下面是一个示例:

public void testFileOutputStream() {
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream("D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\2.txt");
        String str = "aBcDeFgH";
        fos.write(str.getBytes());
        fos.flush();
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  运行结果如下图所示:

  下面我们利用上面的FileInputStream和FileOutputStram来实现文件复制,这边我复制的是一个视频文件,具体代码如下所示:

public void testFileOutputStream2() throws FileNotFoundException {
    String uri = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\video.mp4"; // 定义资源路径
    FileInputStream fis = new FileInputStream(uri);
    FileOutputStream fos = new FileOutputStream(uri.replace("video", "video_copy"));
    byte[] bytes = new byte[1024]; // 每次读取1KB
    int len;
    try {
        while ((len = fis.read(bytes)) != -1) {;
            fos.write(bytes, 0, len);
        }
        fos.flush();
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        try {
            fis.close();
            fos.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

  运行结果如下图所示,可以看到拷贝后的文件与拷贝前的文件大小相同,说明拷贝没有问题。

补充:我们在使用流的时候,需要手动关闭流,每次这样比较麻烦,代码结构看起来也比较混乱。Java7提供了一个try-with-resources新特性,可以自动关闭资源(凡是实现了AutoCloseable接口的流都可以使用try-with-resources,都会自动关闭)。try-with-resources语法格式如下:

try (ResourceType resource1 = new ResourceType(...);
     ResourceType2 resource2 = new ResourceType2(...)) {
    // 使用资源的代码
} catch (ExceptionType1 e1) {
    // 异常处理代码
} catch (ExceptionType2 e2) {
    // 另一个异常处理代码
} finally {
    // 可选的 finally 块
}

3. FileReader

  FileReader是文件字符输入流,默认采用UTF-8读取文件,一次读取至少一个字符,与FileInputStream类似,不同的是,FileReader读取的是char,FileInputStream读取的是byte。FileReader常用方法如下所示:

方法描述
FileReader(String fileName);构造方法
int read();读取一个字符
int read(char[] cbuf);读取一个字符数组
int read(char[] cbuf, int off, int len);读取某个区间的字符数组
long skip(long n);跳过n个字符
void close()关闭流

  下面是一个示例代码:

public void testReadFile() throws FileNotFoundException {
    try(FileReader fileReader = new FileReader("D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_01_File相关的流\\汉字.txt")) {
        char[] chars = new char[2];
        int len;
        while ((len = fileReader.read(chars)) != -1) {
            System.out.print(new String(chars, 0, len));
        }
    } catch (IOException e) {
        System.out.println(e.getMessage());
    }
}

  运行结果如下图所示:

 

4. FileWriter

  FileWriter是文件字符输出流,默认采用UTF-8,用于对普通文本文件进行输出,常用的方法如下所示:

方法描述
FileWriter(String fileName);构造方法
FileWriter(String fileName, boolean append);构造方法
void write(char[] cbuf);写字符数组
void write(char[] cbuf, int off, int len);将字符数组的某个区间写出
void write(String str);写字符串
void write(String str, int off, int len);将字符串的某个区间写出
void flush();刷新
void close();关闭流
Writer append(CharSequence csq, int start, int end);追加文本

  下面是一个使用示例:

public void testFileWriter() {
    try(FileWriter fileWriter = new FileWriter("D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_01_File相关的流\\文本输出.txt")) {
        fileWriter.write("Hello World!");
        fileWriter.write("I'm a file writer.", 0, 10);
        fileWriter.write("\n人生如戏,喝不喝Java".toCharArray());
        fileWriter.write("\n人生如戏,喝不喝Java".toCharArray(), 0, 5);
        fileWriter.flush();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

  运行结果如下图所示:

补充: 上述读取或者写出文件的时候,我们采用的是绝对路径,除了使用绝对路径,我们还可以使用相对路径和资源文件夹的路径。相对路径指的是从整个项目目录下开始的路径,资源文件夹路径在Maven结构下指的是resources目录下开始的路径,有关这两个路径的使用,如下图所示,可以帮助大家更好地理解:

四、缓冲流

  缓冲流读写速度快,能够提高读写的效率,与上面提到的四种流对应的缓冲流有BufferedInputStream、BufferedOutputStream(适合读写非普通文本文件)、 BufferedReader和BufferedWriter(适合读写普通文本文件)。缓冲流都是处理流/包装流,FileInputStream和FileOutputStream是节点流。

  缓冲流的读写速度快,原理是:在内存中准备了一个缓存。读的时候从缓存中读。写的时候将缓存中的数据一次写出。都是在减少和磁盘的交互次数。如何理解缓冲区?家里盖房子,有一堆砖头要搬在工地100米外,单字节的读取就好比你一个人每次搬一块砖头,从堆砖头的地方搬到工地,这样肯定效率低下。然而聪明的人类会用小推车,每次先搬砖头搬到小车上,再利用小推车运到工地上去,这样你再从小推车上取砖头是不是方便多了呀!这样效率就会大大提高,缓冲流就好比我们的小推车,给数据暂时提供一个可存放的空间。那么,缓冲流的输出效率是如何提高的?在缓冲区中先将字符数据存储起来,当缓冲区达到一定大小或者需要刷新缓冲区时,再将数据一次性输出到目标设备。输入效率是如何提高的? read()方法从缓冲区中读取数据。当缓冲区中的数据不足时,它会自动从底层输入流中读取一定大小的数据,并将数据存储到缓冲区中。大部分情况下,我们调用read()方法时,都是从缓冲区中读取,而不需要和硬盘交互。下面是一个创建缓冲流的示例代码:

public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("relative.txt");
    BufferedInputStream bis = new BufferedInputStream(fis);
    System.out.println(bis);
    bis.close();
}

  从上面可以看到,我们关闭流只需要关闭最外层的处理流即可。当关闭处理流时,底层节点流也会关闭。下面,我们将测试一下节点流和缓冲流的效率,进行一下对比。这里,我采用的是拷贝司马相如的《上林赋》,具体代码如下图所示:

public static void main(String[] args) {
    String inputUrl = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_02_缓冲流\\效率测试文本-上林赋.txt";
    String outputUrl = inputUrl.replace("上林赋", "上林赋_copy");
    try(
        FileInputStream fileInputStream = new FileInputStream(inputUrl);
        FileOutputStream fileOutputStream = new FileOutputStream(outputUrl);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)
    ) {
        /* 使用节点流 */
        long begin = System.currentTimeMillis();
        int readByte;
        while ((readByte = fileInputStream.read()) != -1) {
            fileOutputStream.write(readByte);
        }
        fileOutputStream.flush();
        long end = System.currentTimeMillis();
        System.out.println("使用节点流耗时:" + (end - begin) + "ms");
        /* 使用缓冲流 */
        begin = System.currentTimeMillis();
        while ((readByte = bufferedInputStream.read()) != -1) {
            bufferedOutputStream.write(readByte);
        }
        bufferedOutputStream.flush();
        end = System.currentTimeMillis();
        System.out.println("使用缓冲流耗时:" + (end - begin) + "ms");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

  运行结果如下图所示,可以看到缓冲流的效率比节点流要高很多。

补充:缓冲流的有两个特有方法(输入流),以下两个方法的作用是允许我们在读取数据流时回退到原来的位置(重复读取数据时用)

方法一:void mark(int readAheadLimit); 标记位置(在Java21版本中,参数无意义。低版本JDK中参数表示在标记处最多可以读取的字符数量,如果你读取的字符数超出的上限值,则调用reset()方法时出现IOException)

方法二:void reset(); 重新回到上一次标记的位置 这两个方法有先后顺序:先mark再reset,另外这两个方法不是在所有流中都能用。有些流中有这个方法,但是不能用。

五、转换流

  转换流主要用来解决编解码中出现的乱码问题。

1. InputStreamReader

  InputStreamReader为转换流,属于字符流,作用是将文件中的字节转换为程序中的字符。转换过程是一个解码的过程,主要用来解决读的乱码问题。那么,乱码问题是如何产生的呢?当我们指定的字符集和文件的字符集不一样时就有可能出现乱码。InputStreamReader常用的构造方法有两个:

  • InputStreamReader(InputStream in, String charsetName) // 指定字符集
  • InputStreamReader(InputStream in) // 采用平台默认字符集

  FileReader是InputStreamReader的子类,而InputStreamReader是包装流,所以FileReader也是包装流。 FileReader的出现简化了代码的编写,以下代码本质上是一样的:

Reader reader = new InputStreamReader(new FileInputStream(“file.txt”)); //采用平台默认字符集
Reader reader = new FileReader(“file.txt”); //采用平台默认字符集

  下面是指定字符集的情况: 

Reader reader = new InputStreamReader(new FileInputStream(“file.txt”), “GBK”);
Reader reader = new FileReader("e:/file1.txt", Charset.forName("GBK"));

2. OutputStreamWriter

  OutputStreamWriter是转换流,属于字符流,作用是将程序中的字符转换为文件中的字节。这个过程是一个编码的过程。OutputStreamWriter常用构造方法有两个:

  • OutputStreamWriter(OutputStream out, String charsetName) // 使用指定的字符集
  • OutputStreamWriter(OutputStream out) //采用平台默认字符集

  与InputStreamReader类似,FileWriter是InputStreamReader的子类,以下代码本质是一样的:

Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”), “GBK”);
Writer writer = new FileWriter(“file1.txt”, Charset.forName(“GBK”));

六、数据流

  有两个类与数据流有关,为DataOutputStream和DataInputStream,这两个流都是包装流,读写数据专用的流。DataOutputStream直接将程序中的数据写入文件,不需要转码,效率高。程序中是什么样子,原封不动的写出去。写完后,文件是打不开的。即使打开也是乱码,文件中直接存储的是二进制。 使用DataOutputStream写的文件,只能使用DataInputStream去读取。并且读取的顺序需要和写入的顺序一致,这样才能保证数据恢复原样。两者的构造方法如下:

  • DataInputStream(InputStream in)
  • DataOutputStream(OutputStream out)

  DataOutputStream写的方法如下有writeByte()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble()、writeBoolean()、writeChar()、writeUTF(String) ;DataOutputStream读的方法有readByte()、readShort()、readInt()、readLong()、readFloat()、readDouble()、readBoolean()、readChar()、readUTF()。下面是一个示例代码:

public static void main(String[] args) throws Exception {

    String path = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_04_数据流\\1.txt";

    DataOutputStream dos = new DataOutputStream(new FileOutputStream(path));
    int a = 10;
    double b = 3.144;
    dos.writeInt(a);
    dos.writeDouble(b);
    dos.flush();
    dos.close();

    DataInputStream dis = new DataInputStream(new FileInputStream(path));
    System.out.println(dis.readInt());
    System.out.println(dis.readDouble());
    dis.close();

}

  运行结果如下图所示: 

 

七、对象流

  ObjectOutputStream和ObjectInputStream这两个流,可以完成对象的序列化和反序列化,两者是包装流,其中,ObjectOutputStream用来完成对象的序列化,ObjectInputStream用来完成对象的反序列化。 序列化(Serial)指的是将Java对象转换为字节序列。反序列化(DeSerial)指的是将字节序列转换为Java对象。参与序列化和反序列化的java对象必须实现实现Serializable接口,编译器会自动给该类添加序列化版本号的属性serialVersionUID。如果某对象没有实现该接口就进行序列化,编译器会报如下错误:

  在java中,是通过“类名 + 序列化版本号”来进行类的区分的。 serialVersionUID实际上是一种安全机制。在反序列化的时候,JVM会去检查存储Java对象的文件中的class的序列化版本号是否和当前Java程序中的class的序列化版本号是否一致。如果一致则可以反序列化。如果不一致则报错。 如果一个类实现了Serializable接口,还是建议将序列化版本号固定死,建议显示的定义出来,原因是:类有可能在开发中升级(改动),升级后会重新编译,如果没有固定死,编译器会重新分配一个新的序列化版本号,导致之前序列化的对象无法反序列化。

显示定义序列化版本号的语法:private static final long serialVersionUID = XXL;

  为了保证显示定义的序列化版本号不会写错,建议使用 @java.io.Serial 注解进行标注。并且使用它还可以帮助我们随机生成序列化版本号。 如果我们希望某个属性不参与序列化,需要使用瞬时关键字transient修饰。下面是一个代码示例:

public void test() throws IOException, ClassNotFoundException {
    String path = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_05_对象流\\object";
    // 写对象
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
    Student stu = new Student("张三", 23);
    oos.writeObject(stu);
    oos.flush();
    oos.close();
    // 读对象
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
    Student object = (Student)ois.readObject();
    System.out.println(object.getAge() + object.getName() + object.getGender());
}

  其中提到的Student类如下图所示:

/**
 * 学生类
 */
public class Student implements Serializable {

    @Serial
    private static final long serialVersionUID = 7826613280278208564L;

    private String name;

    private transient int age;

    private String gender;

    public Student() {}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

}

  运行结果如下图所示:

更多序列化与反序列化的知识可以参考这篇博文:序列化与反序列化详解_java序列化和反序列化-CSDN博客

八、打印流

  打印流有PrintStream和PrintWriter,其中PrintStream以字节形式打印,PrintWriter以字符形式打印。下面是一个示例代码:

public void test() throws FileNotFoundException {
    String path = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_06_打印流\\info.log";
    PrintStream printStream = new PrintStream(path); // PrintStream是包装流 
    printStream.append("你好");
    printStream.close();
    PrintWriter printWriter = new PrintWriter(path.replace("log", "log1")); // PrintWriter 是包装流 
    printWriter.append("你好abc");
    printWriter.close();
}

  运行结果如下图所示:

九、标准输入输出流

  System.in获取到的InputStream就是一个标准输入流,标准输入流是用来接收用户在控制台上的输入的。标准输入流不需要关闭,它是一个系统级的全局的流,JVM负责最后的关闭。我们可以使用BufferedReader对标准输入流进行包装。这样可以方便的接收用户在控制台上的输入(这种方式太麻烦了,因此JDK中提供了更好用的Scanner)。我们可以修改输入流的方向(System.setIn()),让其指向文件。

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();

  System.out获取到的PrintStream就是一个标准输出流,标准输出流是用来向控制台上输出的。标准输出流不需要关闭,它是一个系统级的全局的流,JVM负责最后的关闭。 我们可以修改输出流的方向(System.setOut())。让其指向文件。

十、压缩和解压缩流

    在IO体系中,有不同的压缩和解压缩流,这里以GZIPOutputStream(压缩)和GZIPInputStream(解压缩)为例,下面是压缩流的示例代码:

public static void main(String[] args) throws IOException {
    String path = "D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_07_压缩和解压缩流\\待压缩文本.txt";
    FileInputStream fileInputStream = new FileInputStream(path);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(path.replace("待压缩文本.txt", "待压缩文本.txt.gz")));
    byte[] buffer = new byte[1024];
    int read;
    while ((read = fileInputStream.read(buffer)) != -1) {
        gzipOutputStream.write(buffer, 0, read);
    }
    gzipOutputStream.finish(); // 在压缩完所有数据之后调用finish()方法,以确保所有未压缩的数据都被刷新到输出流中,并生成必要的 Gzip 结束标记,标志着压缩数据的结束。
    fileInputStream.close();
    gzipOutputStream.close();
}

  运行结果如下,可以看到在指定的目录下生成了一个压缩文件。

  下面是对应的解压缩的代码:

public static void main(String[] args) throws IOException {
    String path ="D:\\Code\\study\\JavaCode\\JavaSEDemo\\base\\src\\main\\java\\cn\\javase\\base\\io\\_07_压缩和解压缩流\\待压缩文本.txt.gz";
    GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(path));
    FileOutputStream fileOutputStream = new FileOutputStream(path.replace("待压缩文本.txt.gz", "解压缩文本.txt"));
    byte[] buffer = new byte[1024];
    int len;
    while ((len = gzipInputStream.read(buffer)) != -1) {
        fileOutputStream.write(buffer, 0, len);
    }
    gzipInputStream.close();
    fileOutputStream.flush();
    fileOutputStream.close();
}

  运行结果如下,我们压缩后的文件重新解压缩回来了。 

补充:实际上所有的输出流中,只有带有缓冲区的流才需要手动刷新,节点流是不需要手动刷新的,节点流在关闭的时候会自动刷新。

十一、字节数组流

1. 基本使用

  ByteArrayInputStream和ByteArrayOutputStream都是内存操作流,不需要打开和关闭文件等操作。这些流是非常常用的,可以将它们看作开发中的常用工具,能够方便地读写字节数组、图像数据等内存中的数据。 ByteArrayInputStream和ByteArrayOutputStream都是节点流。 ByteArrayOutputStream,将数据写入到内存中的字节数组当中;ByteArrayInputStream,读取内存中某个字节数组中的数据。下面是一个示例代码:

public void test2() throws IOException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 是节点流,默认byte数组大小为32
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeLong(3000L);
    objectOutputStream.writeBoolean(true);
    objectOutputStream.writeBoolean(false);
    objectOutputStream.writeUTF("人生如戏");
    objectOutputStream.flush();
    objectOutputStream.close();
    byte[] byteArray = byteArrayOutputStream.toByteArray(); // 转为byte数组
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    System.out.println(objectInputStream.readLong());
    System.out.println(objectInputStream.readBoolean());
    System.out.println(objectInputStream.readBoolean());
    System.out.println(objectInputStream.readUTF());
    objectInputStream.close();
}

  运行结果如下图所示:

 

2. 对象克隆

对象克隆参考博文:Java中对象的克隆_java 对象克隆-CSDN博客

  我们除了重写clone()方法来完成对象的深克隆,也使用字节数组流也可以完成对象的深克隆。 原理是将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的对象就是一个深克隆。 下面是一个示例代码:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    User user = new User("张三", 23);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(user);
    oos.flush();
    oos.close();
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    User user1 = (User) ois.readObject();
    user1.setName("李四");
    user1.setAge(11);
    System.out.println(user.getAge() + user.getName());
    System.out.println(user1.getAge() + user1.getName());
    ois.close();
}

  运行结果如下图所示,修改克隆后对象中的值,原来的对象中的值没有改变,说明这是一个深克隆。

 

小结一下对象克隆的方法:

  • 调用Object的clone方法,默认是浅克隆,需要深克隆的话,就需要重写clone方法。
  • 可以通过序列化和反序列化完成对象的克隆。
  • 可以通过ByteArrayInputStream和ByteArrayOutputStream完成深克隆。

总结

  通过本文的详细介绍,我们对Java I/O流有了更深入的理解。从最基本的节点流到复杂的处理流,Java为我们提供了一套强大而灵活的工具集,使得我们可以轻松地处理各种数据流。无论是字节流还是字符流,缓冲流或是转换流,每种流都有其独特的用途和优势。掌握这些知识点不仅可以帮助我们在日常开发中更加得心应手,还能让我们在面对复杂问题时游刃有余。希望这篇博客能成为你学习Java I/O流的一个良好起点,并激发你进一步探索这一领域的兴趣。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2174930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++学习9.28

1> 创建一个新项目,将默认提供的程序都注释上意义 por QT core gui #QT表示引入的类库 core:核心库例如IO操作在该库中 gui:图形化显示库 #如果要使用其他类库中的相关函数,就需要调用相关类库后,才能加以使用greaterThan(Q…

c++926

1.什么是虚函数?什么是纯虚函数? 虚函数:被virtual关键字修饰的成员函数,用于实现多态性,通过基类访问派生类的函数。纯虚函数:在虚函数后面添加0,只有声明而没有实现,需要派生类提…

天龙八部怀旧单机微改人面桃花+安装教程+GM工具+虚拟机一键端

今天给大家带来一款单机游戏的架设:天龙八部怀旧单机微改人面桃花。 另外:本人承接各种游戏架设(单机联网) 本人为了学习和研究软件内含的设计思想和原理,带了架设教程仅供娱乐。 教程是本人亲自搭建成功的&#xf…

图说数集相等定义表明“R各元x的对应x+0.0001的全体=R“是几百年重大错误

黄小宁 设集A{x}表A各元均由x代表,{x}中变量x的变域是A。其余类推。因各数x可是数轴上点的坐标故x∈R变为实数yx1的几何意义可是:一维空间“管道”g内R轴上的质点x∈R(x是点的坐标)沿“管道”g平移变为点y…

红队信息搜集扫描使用

红队信息搜集扫描使用 红队行动中需要工具化一些常用攻击,所以学习一下 nmap 等的常规使用,提供灵感 nmap 帮助 nmap --help主机扫描 Scan and no port scan(扫描但不端口扫描)。-sn 在老版本中是 -sP,P的含义是 P…

视频美颜SDK与直播美颜工具API是什么?计算机视觉技术详解

今天,小编将深入探讨视频美颜SDK与直播美颜工具API的概念及其背后的计算机视觉技术。 一、视频美颜SDK的概念 视频美颜SDK是一套用于开发实时美颜效果的工具集,开发者可以利用它在视频流中实现面部特征的优化。这些SDK通常提供了一系列功能&#xff0c…

.NET 红队武器库和资源集合 (第38期)

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

计算机网络自顶向下(1)---网络基础

目录 1.网络的分类 2.网络协议 3.网络分层结构 1.OSI七层模型 2.TCP/IP四层模型 3.网络与OS的关系 4.网络传输基本流程 1.协议报头 5.网络中的地址管理 1.IP地址 2.端口号 6.传输层协议 1.TCP协议 2.UDP协议 3.网络字节序 7.socket 1.网络的分类 局域网&…

excel-VBA知识点记录

1、计算机硬件的组成部分 内存,一旦断电,存储在里面的数据就消失了,而硬盘是永久存储数据的,所以刚开始我们在文件里面编辑没有按保存的时候,数据是在内存里面的,一旦断电数据就没了,但我们点了…

大语言模型知识点分享

1 目前主流的开源模型体系有哪些? Prefix Decoder 系列模型 核心点: 输入采用双向注意力机制,输出为单向注意力。双向注意力意味着输入的每个部分都可以关注到输入的所有其他部分,这在理解上下文时具有很强的优势。 代表模型&a…

六级翻译 高分笔记

第一节 句子的拆分与重组 核心原则:拆主干,补修饰 一、句子的拆分与重组 1.青藏铁路是世界最高最长的高原铁路。(“的”字前面所有去掉,就是句子主干) The Qinghai-Tibet Railway is the highest and longest plate…

css 数字比汉字要靠上

这个问题通常是由于数字字体的下排的问题造成的,也就是数字的底部边缘位置比汉字的顶部边缘位置更靠下。为了解决这个问题,可以尝试以下几种方法: 使用CSS的vertical-align属性来调整对齐方式。例如,可以将数字的对齐方式设置为to…

数组的练习

1.使用函数的递归方法&#xff0c;输出给定字符串的逆序&#xff0c;如"abcdefg"&#xff0c;输出为“gfedcba”. 方法一&#xff1a;首先不采用递归的方法&#xff0c;如何完成上述要求 #include<stdio.h> #include<string.h> int main() {char arr[]…

3.数据结构与算法-基本概念和术语

数据、数据元素、数据项和数据对象 数据 数据元素 学生表-记录 数-节点 图&#xff1a;顶点 数据项 数据对象 数据对象与数据元素的关系 数据结构 数据结构的三个部分 逻辑结构的种类 存储结构分类 顺序存储结构 链式存储结构 索引存储结构 散列存储结构 数据类型和抽象数据类…

Linux中的文件缓冲区

目录 使用 dup2 系统调用 为命令行解释器添加重定向功能 理解缓冲区问题 缓存区的刷新策略 FILE的本质 尝试封装C语言的FILE 小共识&#xff1a; cd->当前路径->当前进程的工作路径->工作路径可以被修改->每个进程都有当前路径->故cd改的是子进程的路径-…

算力共享平台的控制流程,业务流程

目录 控制流程 业务流程 在提供的计算机网络系统结构示意图和描述中,我们可以区分出控制流程和业务流程的组成部分。 控制流程 控制流程主要涉及系统内部的管理、调度和监控操作,以确保系统能够按照预定的规则和策略运行。在这个例子中,控制流程可能包括但不限于以下部分…

K8S真正删除pod

假设k8s的某个命名空间如&#xff08;default&#xff09;有一个运行nginx 的pod&#xff0c;而这个pod是以kubectl run pod命令运行的 1.错误示范&#xff1a; kubectl delete pod nginx-2756690723-hllbp 结果显示这个pod 是删除了&#xff0c;但k8s很快自动创建新的pod,但是…

【C++算法】5.双指针_乘最多水的容器

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解&#xff1a; 题目链接&#xff1a; 11.盛最多水的容器 题目描述&#xff1a; 解法 7x749 解法一&#xff1a;暴力枚举 输入&#xff1a;[1&#xff0c;8&#xff0c;6&#xff0c;2&#xf…

大厂AI必备数据结构与算法——链表(三)详细文档

冲冲冲&#xff01;开干 神马&#xff01;神马&#xff01;神马&#xff0c;一向让我们学习起来抓耳挠腮的数据结构课程竟然也有教程&#xff1f;还那么详细&#xff1f;&#xff1f;真的假的&#xff1f; 那么好&#xff0c;胡广告诉你是假的&#xff0c;哈哈哈哈哈哈哈哈哈…

付费计量系统通用处理类(下)

普通处理类如下定义&#xff1a; 10.11 Class 11: Customer _Delivery process 用户交付过程 The processes associated with transferring delivered electrical energy to the customer’s load circuit. 是关于将传输的电能传递到用户的负荷电路。 Examples…