【JavaSE系列】IO流

目录

前言

一、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]; // 每次读取1KBint 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 {@Serialprivate 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数组大小为32ObjectOutputStream 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.mzph.cn/bicheng/55126.shtml

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

相关文章

Apache APISIX学习(2):安装Grafana、prometheus

一、Grafana安装 1、介绍 Grafana 是一个监控仪表系统,它是由 Grafana Labs 公司开源的的一个系统监测 (System Monitoring) 工具。它可以大大帮助你简化监控的复杂度,你只需要提供你需要监控的数据,它就可以帮你生成各种可视化仪表。同时它…

Deep Learning for Video Anomaly Detection: A Review 深度学习视频异常检测综述阅读

Deep Learning for Video Anomaly Detection: A Review 深度学习视频异常检测综述阅读 AbstractI. INTRODUCTIONII. BACKGROUNDA. Notation and TaxonomyB. Datasets and Metrics III. SEMI-SUPERVISED VIDEO ANOMALY DETECTIONA. Model InputB. MethodologyC. Network Archite…

基于Python实现的国庆节庆祝小程序

祖国母亲即将迎来75周年华诞,在这个特殊的日子里,我们可以用编程的方式来表达对祖国的祝福。本文将使用Python编写一个简单的国庆节庆祝小程序,通过一些编程技巧和设计为国庆节增添一些程序员的特色。 ⭕️庆祝国庆 ⭐️ 程序设计思路&#x…

828华为云征文|部署个人知识管理系统 SiyuanNote

828华为云征文|部署个人知识管理系统 SiyuanNote 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 SiyuanNote3.1 SiyuanNote 介绍3.2 SiyuanNote 部署3.3 Siyua…

大数据毕业设计选题推荐-重庆旅游景点数据分析系统-Python-Hive-Hadoop-Spark

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

基于微信开发助手企鹅音乐微信小程序的设计与实现(源码+文档+讲解)

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

这条挣钱的路,离我好遥远啊

近日,笔者在发表的《乱篇弹(54)让子弹飞》一文中写道:“ 当然,笔者在《博客中国-狼头长啸的作家专栏》耕耘期间,也赚了一些用以补贴自己养老的‘ 散碎银两’。那么笔者是否可以依照知乎网的‘申请开通权限’…

支付宝远程收款跳转码接口api之工作证跳转收款码

1、在制作工作证跳转收款之前需要在支付宝上开通工作证 2、然后获取支付宝账户信息、收款码等信息 3、将所需信息填入如下代码之中 const axios require(axios); const authCode 从客户端接收到的授权码;axios({method: post,url: https://openapi.alipay.com/alipay.syst…

用通义灵码如何快速合理解决遗留代码问题?

本文首先介绍了遗留代码的概念,并对遗留代码进行了分类。针对不同类型的遗留代码,提供了相应的处理策略。此外,本文重点介绍了通义灵码在维护遗留代码过程中能提供哪些支持。 什么是遗留代码 与过时技术相关的代码: 与不再受支持的…

Python如何配置环境变量详解

一、概述 前提:已安装 Python,如下图: 1.1 检查是否已配置成功(选) 1 2 3 4 5 1. 打开运行窗口 (1) 快捷键 : Win r,并输入 cmd (2) 直接输入: Python 2. 若有下列提示,即为 安装成功…

星辰计划04-深入理解kafka的消息存储和索引设计

消息存储 提到存储不得不说消息的读写,那么kafka他是如何读写数据的呢? 读取消息 1.通过debug(如何debug) 我们可以得到下面的调用栈,最终通过FileRecords来读取保存的数据 写入消息 1.通过debug(如何debug) 我们可以得到下面的调用栈&am…

【HTTP 和 HTTPS详解】3

HTTP 状态代码 HTTP 状态代码是服务器发送给客户端的三位数字,用于指示客户端请求的结果。它们分为五类:信息性(100-199)、成功(200-299)、重定向(300-399)、客户端错误&#xff08…

怎么不用付费直接编辑pdf?5款pdf在线编辑器免费推荐给你!

在我们日常工作中,可能会经常需要直接编辑修改pdf内容,例如,在将文档发送给其它人之前,您可能需要进行一些修改;或者当扫描的文本出现错误时,您也需要进行修正。此时,如果有一款在线编辑器&…

【C++笔记】初始模版和STL简介

【C笔记】初始模版和STL简介 🔥个人主页:大白的编程日记 🔥专栏:C笔记 文章目录 【C笔记】初始模版和STL简介前言一.初始模版1.1泛型编程1.2函数模版1.3类模板 二.STL简介2.1什么是STL2.2STL的版本2.3STL的六大组件2.4STL的重要…

Vue项目之Element-UI(Breadcrumb)动态面包屑效果 el-breadcrumb

效果预览 需要导航的页面Vue.js 最笨的方法就是在每个需要面包屑的页面中固定写好 <template><div class="example-container"><el-breadcrumb separator="/"

Tableau数据可视化入门

目录 一、实验名称 二、实验目的 三、实验原理 四、实验环境 五、实验步骤 1、Tableau界面引导 2、数据来源 3、数据预处理操作 4、制作中国各个地区的利润图表 4.1条形图 4.2气泡图 5、制作填充地球图 一、实验名称&#xff1a; 实验一&#xff1a;Tableau数据可视…

RTE大会报名丨 重塑语音交互:音频技术和 Voice AI,RTE2024 技术专场第一弹!

Voice AI 实现 human-like 的最后一步是什么&#xff1f; AI 视频爆炸增长&#xff0c;新一代编解码技术将面临何种挑战&#xff1f; 当大模型进化到实时多模态&#xff0c;又将诞生什么样的新场景和玩法&#xff1f; 所有 AI Infra 都在探寻规格和性能的最佳平衡&#xff0…

美畅物联丨GB/T 28181系列之TCP/UDP被动模式和TCP主动模式

GB/T 28181《安全防范视频监控联网系统信息传输、交换、控制技术要求》作为我国安防领域的重要标准&#xff0c;为视频监控系统的建设提供了全面的技术指导和规范。该标准详细规定了视频监控系统的信息传输、交换和控制技术要求&#xff0c;在视频流传输方面&#xff0c;GB/T 2…

考研数据结构——C语言实现插入排序

插入排序是一种简单直观的比较排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常采用in-place&#xff08;原地排序&#xff09;&#…

Git 与远程分支

90.远程仓库和分支 我们经常需要对远程仓库里的分支进行更新。 ‍ 当从远程库 clone 时&#xff0c;默认情况下&#xff0c;只会拉取 master ​分支&#xff0c;并且会将本地的 master 分支和远程的 master 分支关联起来&#xff1a; $ git branch * master‍ ‍ 推送本地…