申明:本人于公众号Java筑基期,CSDN先后发当前文章,标明原创,转载二次发文请注明转载公众号,另外请不要再标原创 ,注意违规
字符流和字节流
在Java中,IO(输入输出)操作涉及字符流和字节流。它们是两种不同的抽象类,用于处理不同类型的数据。
下面我会对字符流和字节流进行简单的解释:
-
字节流(Byte Stream):
-
字节流是以字节为单位进行读写数据的IO流。
-
InputStream
和OutputStream
是字节流的抽象类。 -
字节流适用于处理二进制数据,例如图像、音频、视频文件等。
-
示例:
-
FileInputStream
用于从文件读取字节数据public static void main(String[] args) { String filePath = "D:\\xxx\\resources\\readme.txt"; try (FileInputStream fis = new FileInputStream(filePath)) { byte[] buffer = new byte[1024]; // 缓冲区大小,用于存储读取的字节数据 int bytesRead; // 每次读取的字节数 // 使用 while 循环读取文件中的字节数据 while ((bytesRead = fis.read(buffer)) != -1) { // 在这里处理读取的字节数据 // 注意:buffer 中的最后几个字节可能是无效数据,需要根据 bytesRead 的值来确定实际有效的字节数 for (int i = 0; i < bytesRead; i++) { // 处理字节数据,例如打印每个字节的值 System.out.print(buffer[i] + " "); } } } catch (IOException e) { e.printStackTrace(); } }
-
FileOutputStream
用于将字节数据写入文件。public class OutputMain { public static void main(String[] args) { String filePath = "D:\\xxx\\resources\\readme.txt"; byte[] data = "Hello, FileOutputStream!".getBytes(); // 要写入文件的字节数据 try (FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(data); // 将字节数据写入文件 System.out.println("数据写入成功!"); } catch (IOException e) { e.printStackTrace(); } } }
-
用
ByteArrayInputStream
和ByteArrayOutputStream
来读写文件public static void main(String[] args) { String sourceFilePath = "D:\\xxx\\resources\\readme.txt"; String destinationFilePath = "D:\\xxx\\resources\\write.txt"; try (FileInputStream fis = new FileInputStream(sourceFilePath); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { // 使用一个缓冲区来读取文件内容 byte[] buffer = new byte[1024]; int bytesRead; // 读取文件内容并写入 ByteArrayOutputStream 中 while ((bytesRead = fis.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } // 将 ByteArrayOutputStream 中的内容写入另一个文件 try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); FileOutputStream fos = new FileOutputStream(destinationFilePath)) { // 使用一个缓冲区来写入文件内容 byte[] writeBuffer = new byte[1024]; int bytesWritten; // 从 ByteArrayInputStream 中读取内容并写入文件 while ((bytesWritten = bais.read(writeBuffer)) != -1) { fos.write(writeBuffer, 0, bytesWritten); } System.out.println("文件内容写入成功!"); } } catch (Exception e) { e.printStackTrace(); } }
-
-
字符流(Character Stream):
-
字符流是以字符为单位进行读写数据的IO流。
-
Reader
和Writer
是字符流的抽象类。 -
字符流适用于处理文本数据,例如文本文件中的字符数据。
-
字符流会自动处理字符的编码和解码,支持指定字符集进行数据读写。
-
示例:
-
FileReader
用于从文件读取字符数据public static void main(String[] args) { String filePath = "D:\\xxx\\resources\\readme.txt"; try (FileReader fileReader = new FileReader(filePath)) { int data; // 用于存储每次读取的字符数据 // 使用 while 循环读取文件中的字符数据 while ((data = fileReader.read()) != -1) { // 在这里处理读取的字符数据 // 注意:data 是一个 int 值,代表读取的字符的 ASCII 码 char character = (char) data; // 将 int 转换为 char System.out.print(character); } System.out.println(); } catch (IOException e) { e.printStackTrace(); } }
-
FileWriter
用于将字符数据写入文件。public static void main(String[] args) { String filePath = "D:\\xxx\\resources\\readme.txt"; String data = "Hello, FileWriter!"; // 要写入文件的字符数据 try (FileWriter fileWriter = new FileWriter(filePath)) { fileWriter.write(data); // 将字符数据写入文件 System.out.println("数据写入成功!"); } catch (IOException e) { e.printStackTrace(); } }
-
BufferedReader
和BufferedWriter
它们提供缓冲功能,提高IO性能public static void main(String[] args) { String sourceFilePath = "D:\\xxx\\resources\\readme.txt"; String destinationFilePath = "D:\\xxx\\resources\\write.txt"; try (BufferedReader reader = new BufferedReader(new FileReader(sourceFilePath)); BufferedWriter writer = new BufferedWriter(new FileWriter(destinationFilePath))) { String line; // 用于存储每次读取的行数据 // 使用 while 循环读取文件中的每一行数据 while ((line = reader.readLine()) != null) { // 在这里处理读取的行数据 // 例如,可以在写入文件时添加额外的内容 String processedLine = "Processed: " + line; writer.write(processedLine); writer.newLine(); // 写入一个换行符,以分隔不同行的数据 } System.out.println("文件内容写入成功!"); } catch (IOException e) { e.printStackTrace(); } }
-
总结:
-
字节流适用于处理二进制数据,以字节为单位读写。
-
字符流适用于处理文本数据,以字符为单位读写,并自动处理字符的编码和解码。
-
通常情况下,如果处理文本数据,使用字符流更加方便,因为它会自动处理字符编码的问题,而字节流通常用于处理非文本的二进制数据。
对象序列化
对象序列化是将对象转换成字节序列的过程,以便可以将其保存到文件中或通过网络传输。
在Java中,通过实现 Serializable
接口,可以使一个类成为可序列化的,也就是可以被序列化为字节流。序列化后的字节流可以保存到文件、数据库或进行网络传输,而在需要时,可以通过反序列化将字节流还原成原始的Java对象。
实现对象序列化的步骤:
-
让类实现
Serializable
接口,该接口是一个标记接口,没有需要实现的方法。class MyClass implements Serializable { private String name; private int age; private transient String tel; public MyClass(String name, int age,String tel) { this.name = name; this.age = age; this.tel = tel; } public String getName() { return name; } public int getAge() { return age; } public String getTel() { return tel; } }
Serializable
是一个标记接口(marker interface),它没有任何方法需要实现。当一个类实现了Serializable
接口时,表示该类的对象可以被序列化。在某些情况下,我们希望在序列化过程中排除某些敏感信息或不需要序列化的数据。在成员变量声明前加上
transient
关键字,可以将该成员变量标记为瞬态(transient),表示它不参与对象的序列化。在反序列化时,这些成员变量的值将被初始化为默认值(对于基本数据类型是0,对于对象引用是null)。 -
在类中定义需要保存的成员变量和方法。
// 创建一个示例对象,假设它是一个可序列化的类的实例 MyClass obj = new MyClass("John Doe", 30,"123456789");
-
使用
ObjectOutputStream
将对象序列化为字节流。String filePath = "D:\\xxx\\resources\\readme.txt"; FileOutputStream fos = new FileOutputStream(filePath); ObjectOutputStream oos = new ObjectOutputStream(fos); // 将对象写入文件,实现对象的序列化 oos.writeObject(obj);
-
使用
ObjectInputStream
将字节流反序列化为原始的Java对象。FileInputStream fis = new FileInputStream(filePath); ObjectInputStream ois = new ObjectInputStream(fis) // 从文件读取对象,实现对象的反序列化 MyClass obj = (MyClass) ois.readObject(); // 对象已经反序列化为原始的Java对象,可以使用它了 System.out.println("反序列化后的对象:"); System.out.println("Name: " + obj.getName()); System.out.println("Age: " + obj.getAge()); System.out.println("Tel: " + obj.getTel());
输出:
除了tel
以外,其他的确实是序列化进去了。
字符集和编码
在Java中,字符集(Character Set)和编码(Encoding)是涉及文本字符处理的重要概念。在Java中,字符集(Character Set)和编码(Encoding)是涉及文本字符处理的重要概念。
字符集(Character Set): 字符集是一组字符的集合,它将字符映射到唯一的整数值,也称为字符编码点(Code Point)。每个字符都有一个唯一的字符编码点,字符集中的字符编码点是固定的。
在Java中,char
类型代表一个16位的Unicode字符,因此Java的字符集采用的是Unicode字符集,即字符编码点的范围是0x0000至0xFFFF。Unicode字符集允许覆盖几乎所有的世界语言和符号。
编码(Encoding): 编码是将字符编码点映射成字节序列的过程。由于计算机中处理的是二进制数据,因此文本字符需要转换成字节才能在计算机中存储和传输。
UTF-8和UTF-16是两种常见的Unicode编码方案:
-
UTF-8: UTF-8(8-bit Unicode Transformation Format)是一种可变长度的编码方案。它使用1至4个字节表示一个Unicode字符,对于常用的ASCII字符,UTF-8只使用1个字节,而对于非ASCII字符,使用多个字节进行编码。UTF-8编码在存储和传输文本时节省空间,因为它对于英文字符使用较少的字节。
-
UTF-16: UTF-16(16-bit Unicode Transformation Format)是一种固定长度的编码方案。它使用2或4个字节表示一个Unicode字符。UTF-16编码对于大部分字符使用2个字节,而对于一些辅助字符,使用4个字节进行编码。
在Java中,默认的编码方案取决于平台和系统的设置,通常情况下,Java使用UTF-8编码来处理文本数据,但可以通过设置特定的编码方式来控制文本的读写和传输。
例如,在使用 FileReader
和 FileWriter
读写文本文件时,默认使用的是系统的字符集,可以通过指定字符集来明确使用UTF-8或其他编码方式:
// 使用UTF-8编码读写文件
try (FileReader fileReader = new FileReader("file.txt", StandardCharsets.UTF_8);
FileWriter fileWriter = new FileWriter("file.txt", StandardCharsets.UTF_8)) {
// 处理文本数据
} catch (IOException e) {
e.printStackTrace();
}
// 使用UTF-8编码读写文件
try (FileReader fileReader = new FileReader("file.txt", StandardCharsets.UTF_8);
FileWriter fileWriter = new FileWriter("file.txt", StandardCharsets.UTF_8)) {
// 处理文本数据
} catch (IOException e) {
e.printStackTrace();
}
总结:
-
字符集是一组字符的集合,将字符映射到唯一的整数值(字符编码点)。
-
编码是将字符编码点映射成字节序列的过程。
-
Java中采用的字符集是Unicode字符集,字符类型
char
代表一个16位的Unicode字符。 -
UTF-8是可变长度编码,UTF-16是固定长度编码,两者都是Unicode编码方案。
Java IO
Java NIO(New I/O)是Java提供的一种新的I/O操作方式,相较于传统的Java IO(也称为IO流或Stream)更为灵活和高效。
Java NIO引入了通道(Channel)和缓冲区(Buffer)的概念,使得可以通过非阻塞的方式进行数据读写操作。
-
通道(Channel): 通道是Java NIO中用于进行数据传输的抽象。它表示一个连接到文件、套接字或其他IO资源的开放连接。通过通道,可以在缓冲区和IO设备之间直接传输数据,而无需通过中间的IO流。通道是双向的,可以用于读取和写入数据。
在Java NIO中,常用的通道包括
FileChannel
(用于文件IO)、SocketChannel
(用于TCP网络通信)、ServerSocketChannel
(用于TCP服务器)和DatagramChannel
(用于UDP网络通信)。 -
缓冲区(Buffer): 缓冲区是用于存储数据的对象。数据从通道读取到缓冲区,或从缓冲区写入到通道。缓冲区提供了一种高效的方式来管理数据,可以在内存中预先分配空间,并支持直接在内存中进行数据操作。使用缓冲区进行数据传输可以避免频繁的系统调用,提高数据传输的效率。
在Java NIO中,缓冲区是一个数组,可以是基本数据类型的数组(如
ByteBuffer
、CharBuffer
、ShortBuffer
等)或对象数据类型的数组(如ObjectBuffer
)。不同类型的缓冲区适用于不同的数据类型。
通常,数据通过缓冲区来读取和写入。当从通道读取数据时,数据会被读取到缓冲区中;当写入数据到通道时,数据会从缓冲区写入。
Java NIO的基本使用步骤如下:
-
创建一个通道(Channel)对象,连接到数据源(如文件或网络套接字)。
String sourceFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\write.txt"; String destinationFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\readme.txt"; FileInputStream fis = new FileInputStream(sourceFilePath); FileOutputStream fos = new FileOutputStream(destinationFilePath); FileChannel sourceChannel = fis.getChannel(); FileChannel destinationChannel = fos.getChannel()
这里首先是打开源文件和目标文件的通道。
-
创建一个缓冲区(Buffer)对象,用于存储要读取或写入的数据。
// 创建一个缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024);
然后创建一个缓冲区
-
将数据从通道读取到缓冲区中(读操作)或将数据从缓冲区写入到通道中(写操作)。
while (sourceChannel.read(buffer) != -1) { //读取或写入 }
并在循环中从源通道读取数据到缓冲区
-
处理读取或写入的数据。
// 切换缓冲区为读模式 buffer.flip(); // 将缓冲区的数据写入目标通道 destinationChannel.write(buffer);
再将缓冲区的数据写入目标通道
-
关闭通道和缓冲区。
// 清空缓冲区以便继续读取 buffer.clear();
最后关闭通道释放资源
完整代码:
package com.java.CharacterStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class IOMain {
public static void main(String[] args) {
String sourceFilePath = "D:\\xxx\\resources\\write.txt";
String destinationFilePath = "D:\\xxx\\resources\\readme.txt";
try (FileInputStream fis = new FileInputStream(sourceFilePath);
FileOutputStream fos = new FileOutputStream(destinationFilePath);
FileChannel sourceChannel = fis.getChannel();
FileChannel destinationChannel = fos.getChannel()) {
// 创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从源通道读取数据到缓冲区
while (sourceChannel.read(buffer) != -1) {
// 切换缓冲区为读模式
buffer.flip();
// 将缓冲区的数据写入目标通道
destinationChannel.write(buffer);
// 清空缓冲区以便继续读取
buffer.clear();
}
System.out.println("文件复制完成!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在Java NIO(New I/O)中,FileChannel
、SocketChannel
和ServerSocketChannel
是用于文件和网络I/O操作的核心类
它们分别对应文件IO和网络IO的通道。
-
FileChannel:
FileChannel
是用于文件IO的通道,通过它可以直接在文件和缓冲区之间进行数据传输。它提供了高效的文件读写功能,并支持对文件的定位操作。常用的方法包括:
-
read(ByteBuffer dst)
:从通道读取数据到缓冲区。 -
write(ByteBuffer src)
:将缓冲区的数据写入到通道。 -
position(long newPosition)
:设置通道的当前位置。 -
size()
:返回通道关联文件的大小。 -
truncate(long size)
:截断文件大小。 -
transferTo(long position, long count, WritableByteChannel target)
:将通道数据传输到另一个通道。String sourceFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\write.txt"; String destinationFilePath = "D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\readme.txt"; // 使用FileChannel进行文件复制 try (FileInputStream fis = new FileInputStream(sourceFilePath); FileOutputStream fos = new FileOutputStream(destinationFilePath); FileChannel sourceChannel = fis.getChannel(); FileChannel destinationChannel = fos.getChannel()) { ByteBuffer buffer = ByteBuffer.allocate(1024); while (sourceChannel.read(buffer) != -1) { buffer.flip(); destinationChannel.write(buffer); buffer.clear(); } System.out.println("文件复制完成!"); } catch (Exception e) { e.printStackTrace(); }
-
-
SocketChannel:
SocketChannel
是用于TCP网络通信的通道,它可以连接到远程服务器,并实现非阻塞的读写操作。它支持异步非阻塞I/O,可通过Selector
来实现多路复用。常用的方法包括:
-
connect(SocketAddress remote)
:连接到远程服务器。 -
read(ByteBuffer dst)
:从通道读取数据到缓冲区。 -
write(ByteBuffer src)
:将缓冲区的数据写入到通道。 -
finishConnect()
:完成通道连接的操作。
public static void main(String[] args) { try (SocketChannel socketChannel = SocketChannel.open()) { // 连接到服务器端的ServerSocketChannel socketChannel.connect(new InetSocketAddress("localhost", 8080)); System.out.println("连接到服务器端..."); // 向服务器端发送数据 String message = "Hello, server!"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); socketChannel.write(buffer); System.out.println("向服务器端发送数据:" + message); // 接收服务器端的响应 ByteBuffer responseBuffer = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(responseBuffer); if (bytesRead != -1) { responseBuffer.flip(); byte[] responseData = new byte[responseBuffer.remaining()]; responseBuffer.get(responseData); String responseMessage = new String(responseData); System.out.println("从服务器端接收到响应:" + responseMessage); } else { System.out.println("服务器端关闭了连接。"); } } catch (IOException e) { e.printStackTrace(); } }
-
-
ServerSocketChannel:
ServerSocketChannel
是用于TCP服务器的通道,它可以监听客户端的连接请求,并返回对应的SocketChannel
来进行通信。常用的方法包括:
-
bind(SocketAddress local)
:绑定服务器的本地地址。 -
accept()
:接受客户端的连接请求,返回对应的SocketChannel
。// 使用SocketChannel进行网络通信 try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) { serverChannel.bind(new InetSocketAddress(8080)); System.out.println("服务器已启动,监听端口8080..."); while (true) { SocketChannel clientChannel = serverChannel.accept(); System.out.println("接受来自客户端的连接:" + clientChannel.getRemoteAddress()); ByteBuffer buffer = ByteBuffer.allocate(1024); while (clientChannel.read(buffer) != -1) { buffer.flip(); clientChannel.write(buffer); buffer.clear(); } clientChannel.close(); } } catch (Exception e) { e.printStackTrace(); }
发散一下想法,作为思考题:开发一个需求,使用
ServerSocketChannel
建立服务端,使用SocketChannel
建立客户端,由客户端发起连接并且发送内容,服务端收到后,发送一条信息告诉客户端已经收到消息,并且把收到的消息存到本地。代码实现
-