申明:本人于公众号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 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
建立客户端,由客户端发起连接并且发送内容,服务端收到后,发送一条信息告诉客户端已经收到消息,并且把收到的消息存到本地。
代码实现