Java的I/O流I/O流分类:理解:思维导图:InputStream流基本方法:InpustStream的继承子类:OutputStream流基本方法:OutputStream的继承子类:字符流字符流的原理字符流、字符集、字符编码、字节流的关系在字节的形式下的判断标准(是否为中文)关于字节编码和字符编码的补充:FileWriter------字符输出流字符输出流中flush和close的区别转换流转换流的作用子类说明字节流通向字符流的桥梁OutputStreamWriterI/O包中的File类File类作用File类信息File类基本方法:RandomAccesseFile类RandomAccessFile类作用RandomAccessFile类的本质常用方法序列化(Serializable)序列化作用序列化的要求序列化流程演示代码Nio(NEW I/O)Nio的作用Nio的本质Nio的特点关于特点的解释nio三大法宝:Path接口作用
Java的I/O流
I/O流分类:
输入流、输出流
理解:
其实从I/O包来讲都是从这四个顶级类InputStream、OutputStream、Reader、Writer来向下进行划分的,而我的理解是从字符流和字节流来进行划分,他们旗下有各种继承类从而衍生出其他的缓冲流什么的但是本质上都是输入输出流的继承类。只不过不同的继承类有自己特殊的地方就比如缓冲流就有着自己内置的缓存在读取文件的时候就会快很多
思维导图:
InputStream流基本方法:
int read() | 读取输入流的下一个字节数据,如果没有数据则返回-1 |
---|---|
int read(byte[] buffer) | 从输入流中读取数据到给定的字节数组 buffer 中。返回实际读取的字节数,如果没有数据可读,返回值为 -1。 |
int read(byte[] buffer, int offset, int length) | 从输入流中读取最多 length 个字节的数据,存储到字节数组 buffer 中,从 offset 位置开始存储。返回实际读取的字节数,如果没有数据可读,返回值为 -1。指定一个长度和起始位置 |
long skip(long n) | 跳过输入流中的 n 个字节。返回实际跳过的字节数。 |
int available() | 返回可从输入流中读取的字节数,不会阻塞。 |
void close() | 关闭输入流,释放资源 |
void mark(int readlimit) | 在当前位置设置标记,允许稍后通过 reset() 方法回到标记处。 |
void reset() | 将流的位置重置为最后设置的标记位置。 |
boolean markSupported() | 检查流是否支持 mark() 和 reset() 方法。 |
InpustStream的继承子类:
FileInputStream | 从文件中读取字节数据的输入流 |
---|---|
ByteArrayInputStream | 从字节数组中读取字节数据的输入流。 |
PipedInputStream | 用于与相关的 PipedOutputStream 进行通信的输入流。 |
FilterInputStream | 抽象类,为输入流提供过滤功能,例如 BufferedInputStream 、DataInputStream 等都是其子类。 |
ObjectInputStream | 从输入流中读取 Java 对象的输入流,用于反序列化。 |
SequenceInputStream | 可以将多个输入流串联起来,按顺序读取数据。 |
PushbackInputStream | 允许一个字节被退回到输入流中,通常与 FilterInputStream 一起使用。 |
DataInputStream | 允许以与机器无关的方式从底层输入流中读取基本数据类型的输入流。 |
BufferedInputStream | 带缓冲的输入流,提高读取效率。 |
LineNumberInputStream | 记录行号的输入流。 |
AudioInputStream | 用于音频数据的输入流。 |
OutputStream流基本方法:
void write(int b) | 将一个字节写入输出流。 |
---|---|
void write(byte[] b) | 将字节数组中的所有字节写入输出流。 |
void write(byte[] b, int off, int len) | 将字节数组中的指定范围字节写入输出流,从 off 位置开始写入 len 字节。 |
void flush() | 刷新输出流,将缓冲区中的数据强制写入目标。 |
void close() | 关闭输出流,释放资源。 |
OutputStream的继承子类:
FileInputStream | 从文件中读取字节数据的输入流 |
---|---|
ByteArrayInputStream | 从字节数组中读取字节数据的输入流。 |
PipedInputStream | 用于与相关的 PipedOutputStream 进行通信的输入流。 |
FilterInputStream | 抽象类,为输入流提供过滤功能,例如 BufferedInputStream 、DataInputStream 等都是其子类。 |
ObjectInputStream | 从输入流中读取 Java 对象的输入流,用于反序列化。 |
BufferedInputStream | 带缓冲的输入流,提高读取效率。 |
DataInputStream | 允许以与机器无关的方式从底层输入流中读取基本数据类型的输入流。 |
PrintStream | 可以将格式化数据写入输出流,通常用于标准输出流。 |
字符流
字符流(Reader、Writer)主要用于处理纯文本文件,但是需要处理中文乱码的问题
字符流的原理
字符流在本质上也是字节流,他的流程是:
-
文本输入: 文本数据在外部源(如文件、网络等)中以字节的形式存储。
-
解码: 当你的程序读取这些字节数据时,你会使用正确的字符编码解码器将字节序列转换为字符。 这个步骤会将字节转换为对应的 Unicode 字符。
-
处理字符: 在程序中,你会使用这些 Unicode 字符进行操作,比如字符串处理、逻辑判断等。
-
编码: 当你需要将字符数据转换为字节序列以进行存储或传输时,你会使用适当的字符编码器将字符编码为字节序列。
-
文本输出: 最终,将这些编码后的字节数据写入目标文本源。
-
总结:从文本中进来对应的是我们设置的字符编码进行传输(二进制--->字符编码),等程序接收到后我们会进行一个解码这个时候就是用之前生成的编码对应unicode字符集来解码(字符编码--->unicode--->字符),这样我们在程序中就能看到原本的字而不是一段字符编码
字符流、字符集、字符编码、字节流的关系
字符编码是一种规则,它规定了字符如何转换成特殊的字节,而这个规则则适用于程序的字符集
一些常见的字符编码:
GBK---->一个中文编译后占2个字节
UTF-8---->一个中文编译后占3个字节
在字节的形式下的判断标准(是否为中文)
字符流底层有一个判断,如果读取到的是中文字符,一次读取2个字节(GBK),如果读取到的是非中文字符,一次读取1个字节, 其中中文字符一般都是负数的字节,部分是第一个字节为负数,后面的字节有可能为正数,非中文字符都是正数。这里的正数负数对应1/0也就是说当我们读到字符是两位且是1开头则通常是中文
关于字节编码和字符编码的补充:
-
字节编码和字符编码都是一张二维数组表,每一个坐标都对应着世界上的某一个特定的子
-
字符编码组成:进制+高位+低位 比如:中 gbk 十六进制(0x) 高位:0xCE 低位:0xD2 最终:0xCED2
-
通过字节编码和字符集的组合使用能够将文件中的字符进行一个传输、解码、编码的操作
-
常用的字符编码(GBK、UTF-8)
-
JAVA默认的字符集和字符编码是unicode和系统默认编码(大多数为UTF-8 )
FileWriter------字符输出流
首先我们要注意的是字符输出流的一个缓存区概念,这个不同于buffered,前者是在输出的时候会先将所有的输出数据放在一个类似系统的缓存区中,而后者则是一个类中内置的缓存数组,可以理解为当我们用burffered输出的时候是先经过buffered的缓存再到系统缓存,当我们执行close和flush后系统缓存的数据才会统一的写到文件中。
字符输出流中flush和close的区别
(1)flush方法是将数据刷出到文件中去,刷出后可以继续调用write方法写出 (2)close方法的主要功能是关闭流释放资源,同时也具有刷出数据的效果 (3)close方法调用结束后,不能再调用write方法写出数据
无论是字节流还是字符流的输出流,在将数据写入文件时,通常需要进行刷新操作才能确保数据被写入文件。这是因为输出流内部往往会维护一个缓冲区,将数据暂时存储在缓冲区中,从而减少实际的磁盘写入次数,提高写入效率。
转换流
转换流的作用
转换流可以按照指定的编码表读写数据
子类说明
在JDK中,提供了两个类用于实现将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter (1)InputStreamReader是Reader的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符。 (2)OutputStreamWriter是Writer的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符。
字节流通向字符流的桥梁
InputStreamReader(InputStream in) InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter
字符流通向字节流的桥梁
I/O包中的File类
File类作用
将磁盘上的文件和目录抽象化成 Java 对象,从而可以通过 Java 代码来操作和管理它们的属性、路径、状态等信息。
File类信息
File类没有子类他是一个对立的类,但是他有很多的方法。
File类基本方法:
RandomAccesseFile类
RandomAccessFile类作用
有着一个特殊的指针能够随机访问文件内容
RandomAccessFile类的本质
RandomAccessFile类于file类的区别是,前者是一个流也可也处理文件,但是他的特殊点是有着一个特殊的指针,指向文件内容的0号位置,每当我们读取流中的n个数据这个指针就会对应的往后移动n个位置。但是其本质是一个流。因为实现了Datainput、Dataoutput两个接口所以说他说一个流,这些接口定义了读取和写入二进制数据的方法,使得 RandomAccessFile
类具备了流的特性。
常用方法
long getFilePointer() 返回当前读写指针所处的位置 void seek(long pos) 设定读写指针的位置,与文件开头相隔pos个字节数 int skipBytes(int n) 使读者指针从当前位置开始,跳过n个字节 void write(byte[] b) 将指定的字节数组写入到这个文件,并从当前文件指针开始 void setLength(long newLength) 设置此文件的长度 final String readLine() 从指定文件当前指针读取下一行内容 说明:seek(long pos)方法可以使RandomAccessFile对象中的记录指针向前、向后自由移动,通过getFilePointer()方法,便可获取文件当前记录指针的位置。
序列化(Serializable)
序列化作用
序列化指的是将我们java程序中的对象数据输出到电脑的磁盘中,通俗点来说就是输出我们的对象数据到本地文件中进行存储。
序列化的要求
序列化要求被序列化的对象必须实现Serializable接口。
序列化流程
首先创建对象--->创建对应的对象流和对应的文件输出流--->我们需要将文件流和对象流关联上这样才能实现完整的序列化
演示代码
import java.io.*; class Student implements Serializable { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } } public class SerializationExample { public static void main(String[] args) { // Serialize try { Student student = new Student("Alice", 20); FileOutputStream fileOut = new FileOutputStream("student.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(student); out.close(); fileOut.close(); System.out.println("Student object serialized."); } catch (IOException e) { e.printStackTrace(); } // Deserialize try { FileInputStream fileIn = new FileInputStream("student.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Student deserializedStudent = (Student) in.readObject(); in.close(); fileIn.close(); System.out.println("Deserialized Student: " + deserializedStudent); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
Nio(NEW I/O)
Nio的作用
减少了底层的读写操作,改进了原本的读写操作
Nio的本质
Nio本质上也是文件流,nio之所以new是以为他的操作与之前的不同,我们之前是直接inputStream进来就read,但是现在我们需要获取inputStream对象的channel(通道),通过通道来进行流内容的输入和输出,存储这边就不再是原本的byte[]数组而是改用了Bytebuffer/CharBuffer数组来进行接收。
Nio的特点
-
非阻塞 I/O:NIO 提供了非阻塞的 I/O 操作方式,允许一个线程处理多个通道的 I/O 操作,提高了系统的并发性能和资源利用率。
-
通道与缓冲区:NIO 的核心组件是通道(Channel)和缓冲区(Buffer)。通道用于读写数据,缓冲区用于在内存中存储数据,实现了高效的数据传输。
-
选择器(Selector):选择器允许一个线程监听多个通道上的事件,从而实现非阻塞的事件驱动模型。选择器可以在一个线程中管理多个通道,提高了系统的并发性能。
-
文件 I/O:NIO 提供了更灵活和高效的文件 I/O 操作,例如通过通道进行文件读写,可以实现零拷贝操作。
-
内存映射文件:NIO 支持将文件映射到内存中,可以通过内存直接读写文件数据,提高了文件 I/O 的效率。
-
字符集编解码:NIO 提供了字符集编解码的支持,使得可以在不同字符集之间进行高效的数据转换。
-
网络编程:NIO 也可以用于网络编程,实现了高效的异步网络操作,适用于需要处理大量连接的场景,如服务器端编程。
-
面向缓冲区:NIO 引入了缓冲区的概念,可以灵活地管理数据,在处理不同类型的数据时更加高效和灵活。
-
零拷贝操作:通过使用通道和内存映射文件,NIO 可以实现零拷贝的数据传输,减少了数据拷贝的开销。
关于特点的解释
-
在正常的io中一个线程会因为未就绪而导致进程阻塞,但是nio使用选择器和通道两个概念解决路这个问题使得会阻塞的io变化从非阻塞的nio。
-
文件IO和内存映射文件、我们普通的io是将文件读入后直接放在内存如果修改的话需要修改内存。这样的操作会导致数据在磁盘和内存之间发生拷贝。但是nio的做法是在内存缓冲区保存一份或者多份文件来和内存区域的文件进行一个映射,当我们修改文件的时候实际是先修改的内存缓冲区的文件这样做的好处就是减少了底层的修改并且实现了零拷贝的特性。
nio三大法宝:
(1)Buffer(Buffer缓冲器) Buffer本质是一个数组缓冲区,读入或写出到Channel中的所有对象都会先放到Buffer中。
(2)Channel(通道) Channel是对传统的输入/输出的模拟,在NIO中,所有的数据都需要通过通道流的形式传输。
(3)Selecter用于监听多个通道的事件(例如:连续打电话、数据到达等),主要用于多线程处理。
BUFFER(缓冲区)
作用
java NIO中的Buffer用于和NIO中的Channel进行交互,交互时数据会从Channel读取到Buffer中,或从Buffer写入到Channel中 说明
Buffer类似于一个数组,它可以保存多个类型相同的数据。 Buffer是一个抽象类,其子类有ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer。 Buffer子类中最常用的是ByteBuffer和CharBuffer 使用
Buffer类的子类中并没有提供构造方法,因此不能通过构造方法来创建对象。 创建Buffer对象,通常会通过子类中的static XxxBuffer allocate(int capacity)方法来实现(其中Xxx表示不同的数据类型,而capacity表示容量)。 Channel(通道) 说明
Channel可以异步的执行I/O的读写操作 Channel的读写操作是双向的,既可以从Channel中读取数据,又可以写数据到Channel,而流的读写操作通常都是单向的。 Channel可以直接将指定文件的部分或者全部直接映射成Buffer Channel只能与Buffer进行交互,程序不能直接读写Channel中的数据 Channel接口的实现类
主要包括:DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、ServerSocketChannel、SocketChannel等 (1)DatagramChannel用于支持UDP网络通信 (2)FileChannel用于从文件中读写数据 (3)Pipe.SinkChannel和Pipe.SourceChannel用于支持线程之间的通信 (4)ServerSocketChannel和SocketChannel用于支持TCP网络通信 使用
(1)Channel对象并不是通过构造方法来创建的,而是通过传统I/O的getChannel()方法来获取对应的Channel (2)不同的流所获取的Channel是不同的,例如FileInputStream和FileOutputStream获取的是FileChannel,同时还可以使用RandomAccessFile获取该对象。 (3)PipedInputStream和PipedOutputStream所获得的是Pipe.SinkChannel和Pipe.SourceChannel. Selector(选择器)
说明
选择器(Selector
)是 Java NIO 中的一个重要概念,用于实现非阻塞 I/O 操作。选择器可以同时监控多个通道的事件,一旦某个通道上的事件发生,选择器就会通知程序来处理这些事件,从而实现了高效的事件驱动模型。
作用
选择器的主要作用是允许一个线程同时管理多个通道,监听多个通道上的事件,而不需要为每个通道分配一个线程。这可以减少线程的数量,提高系统的并发性能和资源利用率。通过选择器,可以实现如下操作:
-
注册通道:将一个或多个通道注册到选择器中,通常是将通道的可读事件、可写事件等感兴趣的事件注册到选择器。
-
选择事件:选择器会在一个循环中检查注册的通道上是否有事件发生。可以使用选择器的方法来阻塞等待事件发生,或者立即返回已经发生的事件。
-
处理事件:一旦选择器检测到通道上有事件发生,会返回一个包含事件的键集合(
SelectionKey
),程序可以通过遍历键集合来处理各个通道的事件。
方法
-
open()
:打开一个选择器。 -
close()
:关闭选择器。 -
register(SelectableChannel channel, int ops)
:将通道注册到选择器中,并指定感兴趣的事件。 -
select()
:阻塞等待至少一个通道上的事件发生。 -
select(long timeout)
:阻塞等待一段时间内是否有事件发生。 -
selectNow()
:非阻塞地检查是否有事件发生。 -
selectedKeys()
:返回一个键集合,表示已经发生了事件的通道。 -
keyFor(SelectableChannel channel)
:返回通道对应的键。 -
wakeup()
:唤醒阻塞在select()
方法上的线程。
Path接口
作用
Path接口是一个共用在文件系统中定位文件的对象,通常表示一个依赖于系统的文件路径
Paths类中提供了两个返回Path的静态方法,通过这两个方法可以创建Path对象。 Files类中提供了大量的静态方法来操作文件。
File和Files类的区别
File
类和 Files
类都与文件和文件系统有关,但它们在 Java 中有不同的作用和功能。
-
File 类:
java.io.File
类是 Java I/O 包中的一个类,用于表示文件或目录的
抽象路径名。它提供了一些方法来操作文件和目录的属性、路径、大小等信息,以及进行基本的文件 I/O 操作。File
类主要用于传统的 I/O 操作,如读写文件,创建目录,判断文件是否存在等。然而,它并不直接提供对文件内容的高级操作。 -
Files 类:
java.nio.file.Files
类属于 Java NIO2(New I/O)包中,提供了一组高级的文件操作方法。Files
类提供了各种静态方法来处理文件和目录,包括复制、移动、删除、创建目录、读写文件内容等。它还支持更多的文件属性操作,以及一些基于流的文件 I/O 操作。相对于File
类,Files
类提供的方法更加灵活、强大,并且适用于更复杂的文件操作场景。
总之,File
类主要用于传统的 I/O 操作,而 Files
类则是 Java NIO2 中的一部分,提供了更丰富的、面向对象的文件操作功能。如果你需要进行更高级的文件操作,特别是在非阻塞的情况下,可以考虑使用 Files
类。
文章是对着Java之I/O流(最详细的I/O流总结)_java中的io流知识总结_熊凯瑞的博客-CSDN博客进行的一个复习所以有直接引用的地方也有自己的思考。