一、IO流的概念
流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出
- IO是Input和Output的简写,也就是输入和输出的含义。
- IO流是指读写数据时像流水一样从一端流到另外一端,因此得名为"流"。
二、流的分类
Java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
按数据的流向不同分为:输入流和输出流
--------->输入流:把数据从其他设备上读取到内存中的流。
通常以InputStream、Reader结尾
---------->输出流:把数据从内存中写到其他设备上的流
通常以OutputStream、Write结尾
按操作数据单位的不同分为:字节流和字符流
--------->字节流:以字节为单位,读写数据的流
通常以InputStream、OutputStream结尾
---------->字符流:以字符为单位,读写数据的流
通常以Reader、Write结尾
根据IO流的角色不同可分为:节点流和处理流
-------->节点流:直接从数据源或目的地读写数据
--------->处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
三、流的API
JAVA的IO流共涉及40多个类,实际上非常规则,都是从如下类中派生:
(抽象基类) | 输入流 | 输出流 |
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
四、FileReader/FileWriter使用
执行步骤:
创建读取或写出的File类的对象
2.创建输入流或者输出流
3.具体的读入或者写出的过程
读入:read(char[] cbuffer)
写出:write(String str) / write(char[] cbuffer,0,len)
4.关闭流资源,避免内存泄漏
注意事项:
1.涉及到了流资源的关闭操作,出现异常的话,需要使用try-catch-finally来处理异常
2.对于输入流,要求file类的对象对应的物理磁盘上的文件必须存在。否则会报
FileNotFoundException的异常。
3.对于输出流,file类的对象对应的物理磁盘上的文件可以不存在。
-----》如果文件不存在,输出过程中,会自动创建此文件,并写出数据到此文件中
-----》如果文件存在,使用FileWriter(File file)或FileWriter(File file,false)
------>FileWriter(File file,false):输出过程,会创建同名的文件对现有的文件进行覆盖
------->FileWriter(File file,true):输出过程,会在现有的文件末尾追加写出的内容
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class FileReadWriteExample { public static void main(String[] args) { String filePath = "path/to/file.txt"; // 文件路径 // 使用FileReader读取文件内容 try (FileReader fileReader = new FileReader(filePath)) { int character; while ((character = fileReader.read()) != -1) { // 处理读取的字符 System.out.print((char) character); } } catch (IOException e) { e.printStackTrace(); } // 使用FileWriter写入文件内容 try (FileWriter fileWriter = new FileWriter(filePath)) { String content = "Hello, world!"; fileWriter.write(content); } catch (IOException e) { e.printStackTrace(); } } }
在上面的代码示例中,我们首先定义了文件的路径 filePath,你需要将其替换为你要读取或写入的实际文件路径。
然后我们使用FileReader来读取文件内容。在此示例中,我们使用read()方法来一个字符一个字符地读取文件内容,并将其转换为字符类型 char 进行处理。读取的循环会一直执行直到文件的末尾(read()方法返回-1)。
接下来,我们使用FileWriter
来写入文件内容。在此示例中,我们使用write()
方法将字符串内容写入文件。你可以根据需要写入不同的内容。
五、基础的IO流框架
缓冲流:缓冲流也称为处理流,对文件或者其他目标频繁的操作,效率低,性能差。缓冲流目的是提高程序读取和写出的性能。缓冲流也分为字节缓冲流(如FileInputStream与FileOutputStream)和字符缓冲流(如FileReader与FileWriter)
基本原理:
是创建流对象时候,会创建一个内置的默认大小的缓冲区数组,通过缓冲区书写.使得性能大大提升。
使用方法:
处理非文本文件的字节流:
BufferedInputStream read(byte[] buffer)
BufferedOutputStream write(byte[] buffer,0,len)
处理文本文件的字符流:
BufferedReader read(char[] cBuffer) / readLine()
BufferedWriter write(char[] cBuffer,0,len)
六、FileInputStream使用
执行步骤:
1.创建读取或写出的File类的对象
2.创建输入流或者输出流
3.具体的读入或者写出的过程
读入:read(byte[] buffer)
写出:write(byte[] buffer,0,len)
4.关闭流资源,避免内存泄漏
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileReadWriteExample { public static void main(String[] args) { String sourcePath = "path/to/source_file.txt"; // 源文件路径 String destPath = "path/to/dest_file.txt"; // 目标文件路径 // 使用FileInputStream读取文件内容 try (FileInputStream fis = new FileInputStream(sourcePath)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { // 处理读取的数据 // 此处示例将读取的数据直接写入目标文件中 try (FileOutputStream fos = new FileOutputStream(destPath, true)) { fos.write(buffer, 0, bytesRead); } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } }
在上面的代码示例中,我们假设有一个源文件和一个目标文件,你需要将 sourcePath 和 destPath 替换为实际的文件路径。源文件将从 sourcePath 读取,然后将读取的内容写入目标文件中。
我们使用 FileInputStream 来读取文件的内容。在示例中,我们使用一个字节数组 buffer 来存储每次读取的数据。通过调用 read() 方法来读取数据,并将读取的字节数保存在 bytesRead 中。如果 read() 方法返回 -1,表示已经读取到文件末尾。
然后,我们使用 FileOutputStream 来将读取的数据写入目标文件中。我们通过调用 write() 方法将读取的数据写入文件中,写入的字节数为 bytesRead。
七、对象流的使用
ObjectOutoutStream:将内存中的对象———>磁盘中的文件、通过网络传输的数据。:序列化
ObjectInputStream:磁盘中的文件或通过网络接受的数据———>内存中的对象。:反序列化
- 序列化是将对象转换为字节流的过程,反序列化是将字节流转换为对象的过程。
- 序列化和反序列化需要使用相同的序列化算法,否则无法正确地进行反序列化。
- 常见的序列化算法有JSON、XML、Protobuf、MsgPack等。
- 序列化和反序列化的性能对系统的影响非常大,需要根据实际情况选择合适的序列化算法。
- 在redis中,可以使用自定义的序列化算法,也可以使用redis自带的序列化算法。
- 序列化和反序列化的过程中需要注意对象的版本兼容性问题,避免出现反序列化失败的情况。
序列化示范:
try { // 创建一个对象输出流 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); // 创建一个对象 Person person = new Person("Tom", 18); // 将对象序列化到文件中 out.writeObject(person); // 关闭输出流 out.close(); } catch (IOException e) { e.printStackTrace(); }
反序列化示范:
try { // 创建一个对象输出流 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); // 创建一个对象 Person person = new Person("Tom", 18); // 将对象序列化到文件中 out.writeObject(person); // 关闭输出流 out.close(); } catch (IOException e) { e.printStackTrace(); }
其中,Person类需要实现Serializable接口,示例代码如下:
public class Person implements Serializable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
在使用序列化和反序列化时,需要注意以下几点:
- 序列化和反序列化的对象必须是可序列化的,即对象必须实现Serializable接口或Externalizable接口。
- 序列化和反序列化的对象的类必须存在,否则会抛出ClassNotFoundException异常。
- 序列化和反序列化的对象的类的serialVersionUID必须一致,否则会抛出InvalidClassException异常。
- 序列化和反序列化的对象的属性必须是基本类型或可序列化的类型,否则会抛出NotSerializableException异常。
- 序列化和反序列化的对象的属性如果是transient修饰的,则不会被序列化和反序列化。
- 序列化和反序列化的对象的属性如果是static修饰的,则不会被序列化和反序列化。
- 序列化和反序列化的对象的属性如果是final修饰的,则在序列化时会被直接写入,反序列化时不会重新赋值。
- 序列化和反序列化的对象的属性如果是枚举类型,则在序列化时会写入枚举常量的名称,反序列化时会根据名称获取枚举常量。
- 序列化和反序列化的对象的属性如果是集合类型,则需要保证集合中的元素也是可序列化的。
八、关于字符集的理解
字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,就需要进行字符编码,以便计算机能够识别和存储各种文字。
- 1.ASCII(American Standard Code for Information Interchange,美国信息互换标准编码)是基于罗马字母表的一套电脑编码系统。它是最通用的单字节编码系统,并等同于国际标准ISO 646。
- 2.GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,GB2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。双字节表示:两个字节中前面的字节为第一字节,后面的字节为第二字节。习惯上称第一字节为“高字节” ,而称第二字节为“低字节”。
- 3.Big5字符集共收录13,053个中文字,Big5码使用了双字节储存方法,以两个字节来编码一个字。
- 4.GB 18030标准采用单字节、双字节和四字节三种方式对字符编码
- 5.Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”
- 6.UTF-8是Unicode的其中一个使用方式。 UTF是 Unicode Tranformation Format,即把Unicode转做某种格式的意思。UTF-8便于不同的计算机之间使用网络传输不同语言和编码的文字,使得双字节的Unicode能够在现存的处理单字节的系统上正确传输。