首页 > 编程语言 >Java IO流详解:像流水一样读写数据

Java IO流详解:像流水一样读写数据

时间:2024-09-07 15:29:01浏览次数:5  
标签:字符 Java 字节 文件 读写 写入 IO public 读取

Java IO流详解:像流水一样读写数据

在Java编程世界中,IO流就像水流一样,不断地在内存和外部存储之间搬运数据。这些数据流可以是字节,也可以是字符。不管是文件读写、网络传输,还是数据处理,IO流总是无处不在。下面我们就来揭开IO流的面纱,看看它是如何工作的,以及在实际开发中有哪些应用场景。


IO流到底是什么?

简单来说,IO流(Input/Output Stream)是处理数据输入和输出的工具。无论是文件、网络,还是其他设备,数据的流动都可以通过IO流实现。IO流就像一条条“管道”,将数据从源头流向目的地。你可以想象一下,当你要从文件中读取数据,或者将数据写入文件时,这些操作就像是在往一个水管里倒水,或者从水管中取水。


IO流的分类

Java中的IO流可以按数据类型和方向分为两大类:字节流字符流

1. 字节流

字节流处理的是原始的二进制数据(0和1),适用于处理非文本数据,如图片、音频、视频等。字节流分为字节输入流字节输出流

  • 字节输入流 (InputStream):从外部(文件、网络等)读取字节数据到内存中。
  • 字节输出流 (OutputStream):将内存中的数据以字节形式输出到外部设备(文件、网络等)。

2. 字符流

字符流专门处理文本数据,也就是我们平时使用的字符、字符串等。字符流能够自动处理字符编码问题,避免字节流处理中文时容易出现的乱码问题。字符流分为字符输入流字符输出流

  • 字符输入流 (Reader):从外部读取字符数据到内存中。
  • 字符输出流 (Writer):将内存中的字符数据写出到外部设备。

IO流的应用场景

IO流广泛应用于各类程序中,以下是几个典型的应用场景:

  1. 文件读写:将文件中的数据读取到程序内存,或者将程序数据保存到文件中。
  2. 网络通信:通过字节流或字符流来进行网络数据的传输,比如上传文件或接收服务器响应。
  3. 数据流处理:处理大数据文件时,利用IO流可以按需读取或写入数据,避免内存占用过大。

字节输入流与输出流的常见使用方式

文件字节输入流:FileInputStream

FileInputStream 可以将文件中的数据以字节为单位读取到内存中,常用于读取非文本文件,如图片、视频等。

常用构造方法:

public FileInputStream(File file)
public FileInputStream(String pathname)

常用读取方法:

  • public int read():每次读取一个字节,当没有数据可读时返回-1
  • public int read(byte[] buffer):将数据读取到字节数组中,返回读取的字节数。

为什么每次读取一个字节效率低?

想象一下,你要从一大桶水里一杯一杯地舀水,这样会非常耗时。同样地,每次读取一个字节效率非常低,特别是当处理大文件时,程序的运行速度会显著下降。

优化:一次读取多个字节

为了提高读取效率,我们可以一次读取多个字节,就像用一个大桶取水,性能明显提升。不过,如果处理中文字符,字节流可能会出现乱码,因为中文字符占用多个字节,而字节流不区分字符边界。

一次性读取所有字节

你还可以一次性将整个文件的数据读入内存,通过定义一个与文件大小相同的字节数组,或者使用Java 9引入的readAllBytes()方法。这样就避免了频繁的磁盘读取操作。

public byte[] readAllBytes() throws IOException

注意:如果文件太大,直接读入内存可能导致内存溢出,就像你用太小的水桶去接一场大暴雨一样,装不下的水自然会溢出来。
readAllBytes() 是 Java 9 引入的方法,属于 InputStream 类,用于一次性将输入流中的所有字节读取到一个字节数组中。它解决了之前版本中必须手动循环读取数据的繁琐操作,是对输入流读取操作的一个便捷优化。

该方法的签名是:

public byte[] readAllBytes() throws IOException

在 Java 9 之前的版本如何实现类似功能?

在 Java 9 之前,没有 readAllBytes() 方法,要实现类似功能通常需要手动编写代码,使用循环来读取数据并存入字节数组。例如:

public static byte[] readAllBytesPreJava9(InputStream is) throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    byte[] data = new byte[1024];
    int nRead;
    while ((nRead = is.read(data, 0, data.length)) != -1) {
        buffer.write(data, 0, nRead);
    }
    buffer.flush();
    return buffer.toByteArray();
}

在 Java 9 之后,readAllBytes() 让代码更加简洁,适合一次性读取小型文件或网络数据流。不过,对于大文件的处理,手动分批读取数据仍然更为合适。

小结

readAllBytes() 是 Java 9 之后新增的一个实用方法,用于简化从输入流中读取所有字节数据的操作。


文件字节输出流:FileOutputStream

FileOutputStream 用于将内存中的数据以字节形式写入文件。就像我们在文件中存储图片、视频时,数据实际上是以字节流的形式写入磁盘的。

常用的写入方法:

  • public void write(int b):写入一个字节。
  • public void write(byte[] buffer):写入一个字节数组。
  • public void write(byte[] buffer, int offset, int length):写入字节数组的一部分。

文件复制示例

现在我们来看看一个实际的例子——文件复制。你可以想象这个操作就像用水管将一桶水从一个水缸倒入另一个水缸,全部字节不漏地传输。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("source.txt");
             FileOutputStream fos = new FileOutputStream("destination.txt")) {

            byte[] buffer = new byte[1024];
            int length;

            while ((length = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, length);
            }

            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,我们通过FileInputStream读取文件,再通过FileOutputStream写入另一个文件,完成了文件的复制操作。


如何避免中文读取乱码?

使用字节流直接读取中文时会出现乱码,因为中文字符占用多个字节,而字节流并不识别字符的边界。为了解决这个问题,字符流可以派上用场。字符流可以根据指定的编码方式读取和写入字符数据。

例如,使用InputStreamReader结合FileInputStream可以避免中文乱码:

import java.io.*;

public class ChineseFileReader {
    public static void main(String[] args) {
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream("chinese.txt"), "UTF-8");
             BufferedReader br = new BufferedReader(isr)) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,InputStreamReader根据指定的字符集(如UTF-8)正确地解码字节数据,避免了乱码问题。


文件字符输入流:FileReader

FileReader 是文件字符输入流,用于从文件中读取字符数据。常用于读取文本文件。

构造器:

  • public FileReader(File file):将字符输入流与源文件接通。
  • public FileReader(String pathname):将字符输入流与源文件路径接通。

常用方法:

  • public int read():每次读取一个字符,如果没有数据返回-1
  • public int read(char[] buffer):每次将字符数据读入字符数组,返回读取的字符数。

优点

使用FileReader读取文本文件时,可以避免字节流处理中文时的乱码问题,因为FileReader直接处理字符数据。

读取方式

  • 每次读取一个字符:适合处理小文件,操作简单,但性能较低。
  • 每次读取多个字符:通过字符数组一次读取多个字符,性能提高。

文件字符输出流:FileWriter

FileWriter 是文件字符输出流,主要用于将字符数据写入文件。适合写入文本文件中的字符数据。

构造器:

  • public FileWriter(File file):将字符输出流与源文件接通。
  • public FileWriter(String filepath):将字符输出流与文件路径接通。
  • public FileWriter(File file, boolean append):将字符输出流与源文件接通,支持追加写入。

常用方法:

  • public void write(int c):写一个字符。
  • public void write(String str):写一个字符串。
  • public void write(char[] cbuf):写一个字符数组。
  • public void write(char[] cbuf, int off, int len):写一个字符数组的部分内容。

刷新流与关闭流

在使用文件字符输出流(FileWriter)时,写出的数据会暂时保存在内存的缓冲区中,如果不刷新流关闭流,这些数据可能不会被立即写入文件。因此,写完数据后必须注意以下操作:

刷新流

  • public void flush():将缓冲区中积累的数据立即写入文件。一般在需要保证数据即时写入时使用。

关闭流

  • public void close():关闭流时,系统会自动调用flush(),确保所有数据写入文件后关闭流。

字符缓冲流的作用

为了提高字符流的读写性能,Java 提供了字符缓冲流,它们可以通过一个8KB的缓冲区来加速字符数据的传输。以下是几种常用的缓冲流:

1. 字符缓冲输入流:BufferedReader

作用:BufferedReader内置一个8KB缓冲区,可以加快从字符输入流读取字符数据的效率。相比直接使用ReaderBufferedReader读取时性能更高,还增加了按行读取的功能。

构造方法:

  • public BufferedReader(Reader r):将低级字符输入流包装为高级缓冲字符输入流。

常用方法:

  • public String readLine():按行读取字符数据,如果没有数据可读返回null

2. 字符缓冲输出流:BufferedWriter

作用:BufferedWriter内置一个8KB的缓冲区,可以加快字符输出流的写入效率。与普通字符输出流不同,BufferedWriter可以减少写操作的频率,并提供换行功能。

构造方法:

  • public BufferedWriter(Writer w):将低级字符输出流包装为高级缓冲字符输出流。

常用方法:

  • public void newLine():将换行符写入文件,实现换行。

字符缓冲流的使用场景

  1. 提高性能:缓冲流通过缓冲区减少了与磁盘的直接交互,极大提高了读写效率。
  2. 按行读取数据BufferedReader提供了readLine()方法,可以按行读取文件,适合处理文本文件。
  3. 换行写入数据BufferedWriter提供了newLine()方法,可以方便地在文件中写入换行符。

字节缓冲流:提高文件拷贝性能

字节缓冲流与字符缓冲流类似,也带有一个缓冲区,能够有效提高数据的读写性能。字节缓冲流适用于处理大文件(如视频、图片等)的拷贝操作。

1. 字节缓冲输入流:BufferedInputStream

作用:为字节输入流提供一个8KB的缓冲区,加快读取字节数据的速度。

  • public BufferedInputStream(InputStream is):将低级字节输入流包装成缓冲字节输入流。

2. 字节缓冲输出流:BufferedOutputStream

作用:为字节输出流提供一个8KB的缓冲区,加快写入字节数据的速度。

  • public BufferedOutputStream(OutputStream os):将低级字节输出流包装成缓冲字节输出流。

文件拷贝的优化

为了验证缓冲流的性能提升,我们可以进行文件复制的测试:

测试步骤:

  1. 原始字节流:每次读取一个字节,性能低下。
  2. 原始字节流 + 字节数组:一次读取多个字节,性能提升。
  3. 字节缓冲流:通过缓冲区加速读写,性能显著提高。
  4. 字节缓冲流 + 字节数组:结合缓冲流和字节数组,提供最佳性能。

不同编码导致的乱码问题

当文件的编码与程序使用的编码不一致时,字符流读取文本文件时可能会出现乱码。这是因为不同编码方式对字符的字节数和表示不同。

解决方案:InputStreamReader

InputStreamReader 可以将字节流按照指定的字符集编码转换为字符流,从而解决不同编码导致的乱码问题。

构造器:

  • public InputStreamReader(InputStream is, String charset):指定编码将字节流转换成字符流。

示例:

InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");

字符输出转换流:OutputStreamWriter

在某些场景下,我们希望控制写入文件时使用的字符集编码。OutputStreamWriter 可以将字节输出流转换为字符输出流,并指定写入文件时使用的字符集编码。

构造器:

  • public OutputStreamWriter(OutputStream os, String charset):将字节输出流按照指定编码转换为字符输出流。

示例:

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");

打印流:PrintStreamPrintWriter

打印流是一种特殊的输出流,能够方便地将各种数据类型打印到文件或其他输出设备。常见的打印流有两种:

1. PrintStream

PrintStream继承自字节输出流OutputStream,主要用于打印字节数据,但它也可以处理字符数据。

构造器:

  • public PrintStream(OutputStream out)
  • public PrintStream(String fileName, Charset charset):可以指定字符编码。

常用方法:

  • public void println(String str):打印字符串并换行。

2. PrintWriter

PrintWriter继承自字符输出流Writer,适用于打印字符数据。

构造器:

  • public PrintWriter(OutputStream out)
  • public PrintWriter(String fileName, Charset charset):可以指定字符编码。

常用方法:

  • public void println(String str):打印字符并换行。

打印流的应用场景:

  1. 控制台输出重定向:可以将System.out.println的输出重定向到文件中,保存日志信息。

示例:

PrintStream ps = new PrintStream("log.txt");
System.setOut(ps);

数据流:DataInputStreamDataOutputStream

数据流允许将基本数据类型(如intdoublebyte等)和对应的数据写入和读取文件,能够方便地进行二进制数据的读写。

1. DataOutputStream

用于将数据以二进制格式写入到字节输出流。

构造器:

  • public DataOutputStream(OutputStream out)

常用方法:

  • public void writeInt(int v):写入一个整数。
  • public void writeDouble(double v):写入一个double类型数据。

2. DataInputStream

用于读取由DataOutputStream写入的二进制数据。

构造器:

  • public DataInputStream(InputStream in)

常用方法:

  • public int readInt():读取一个整数。
  • public double readDouble():读取一个double类型数据。

对象流:ObjectOutputStreamObjectInputStream

Java对象可以通过对象流进行序列化和反序列化。对象序列化的过程是将对象转换为字节数据存储到文件中,反序列化则是将字节数据转换为对象。

1. 对象输出流:ObjectOutputStream

将Java对象写入文件。

构造器:

  • public ObjectOutputStream(OutputStream out)

常用方法:

  • public void writeObject(Object obj):将对象序列化并写入文件。

注意:对象必须实现Serializable接口才能被序列化。

2. 对象输入流:ObjectInputStream

从文件中读取Java对象。

构造器:

  • public ObjectInputStream(InputStream in)

常用方法:

  • public Object readObject():将文件中的字节数据反序列化为Java对象。

结语

通过这篇博客,你应该对Java中的IO流有了一个全面的理解。Java IO流像水管一样,不断在内存和外部存储之间传递数据。通过合理地选择和使用字节流、字符流、缓冲流以及数据流和对象流,我们可以高效地实现文件处理、网络通信、对象序列化等操作。

掌握这些知识,不仅让你能够高效处理大规模的数据流,还能帮助你编写出性能优良、健壮的程序。

标签:字符,Java,字节,文件,读写,写入,IO,public,读取
From: https://www.cnblogs.com/itcq1024/p/18401694

相关文章

  • Java 通过aspose.words 把docx文件转成pdf文件后中文变成小方块,aspose转pdf乱码问题的
    Java通过aspose.words把docx文件转成pdf文件后中文变成小方块,aspose转pdf乱码问题的解决方法一、问题描述​在centos服务器使用aspose.word转换word文件为pdf的时候显示中文乱码,但是在win服务器上使用可以正常转换二、问题原因由于linux服务器缺少对应的字库导致文件转换出现......
  • java+vue计算机毕设城市应急救援辅助系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,城市人口密集度不断提高,各类突发灾害与紧急事件的风险也随之增加。自然灾害如地震、洪水,以及人为事故如火灾、交通事故等,均对城......
  • java+vue计算机毕设宠物购物系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当今社会,随着人们生活水平的提高和情感需求的多样化,宠物已成为许多家庭不可或缺的重要成员。宠物的养护与陪伴不仅丰富了人们的情感世界,也带动了宠......
  • java+vue计算机毕设宠物交易平台【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着社会经济的快速发展和人们生活水平的提高,宠物已成为许多家庭不可或缺的重要成员。宠物市场的繁荣不仅体现在宠物数量的激增上,更在于宠物相关服务......
  • java+vue计算机毕设宠物领养系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,人类生活与宠物之间的联系日益紧密,但同时也带来了流浪宠物数量激增的社会问题。流浪宠物不仅面临着生存的挑战,还可能成为疾病传......
  • json字符串转义格式化后再转换处理demo StringEscapeUtils.unescapeJava
    json字符串转义格式化后再转换处理demoStringEscapeUtils.unescapeJava报错关键字:illegalidentifierExpectedBEGIN_OBJECTbutExpectednameatpackagecom.example.core.mydemo;importcom.alibaba.fastjson.JSON;importcom.fasterxml.jackson.core.JsonProcessingE......
  • aspose word指定位置插入图片,借助word模板文件中的书签来定位 及Java 获取网络图片
    asposeword指定位置插入图片,借助word模板文件中的书签来定位 及Java 获取网络图片链接:asposeword模板文件生成pdfhttps://www.cnblogs.com/oktokeep/p/16615900.html在Aspose.Words中,您可以使用DocumentBuilder类在指定位置插入图片。以下是一个简单的示例代码,展示如何实现......
  • 【零基础 快速学Java】韩顺平 零基础30天学会Java--- 常用类(2024JavaReview)
    包装类包装类的分类(针对八种基本数据类型相应的引用类型—包装类)(有了类的特点,就可以调用类中的方法)(实现了接口Serializable【String可以串行化:可以在网络传输】)(实现了接口Comparable[String对象可以比较大小])包装类和基本数据的转换(jdk5前的手动装箱和拆箱方式,jdk5以后(含j......
  • Java 2024年详细面试题合集(持续更新)
    1.java中的数据结构数组、链表、哈希表、栈、堆、队列、树、图2.什么是跨域?跨域的三要素跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制协议、域名、端口注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域3.tomcat三......
  • Java---值传递和引用传递(其实只有值传递)
    在Java中,所有的参数传递都是值传递(pass-by-value)。这意味着方法在调用时,传递给方法的是参数值的副本,而不是参数的实际引用。不同于一些其他语言(如C++),Java不支持引用传递(pass-by-reference)。但是,在理解Java的值传递和对象的行为时,可能会引起一些混淆,尤其是在涉及对象时。下面通过详......