大家好,我是哪吒。
很多朋友问我,如何才能学好IO流,对各种流的概念,云里雾里的,不求甚解。用到的时候,现百度,功能虽然实现了,但是为什么用这个?不知道。更别说效率问题了~
下次再遇到,再百度,“良性循环”。
今天,我就用一天的时间,整理一下关于Java I/O流的知识点,分享给大家。
每一种IO流,都配有示例代码,大家可以跟着敲一遍,找找感觉~
上一篇介绍了一文搞定Java IO流,输入流、输出流、字符流、缓冲流,附详细代码示例,本篇文章介绍Java NIO以及其它的各种奇葩流。
Java NIO (New I/O) 是 Java 1.4 引入的,在 Java 7 中又进行了一些增强。NIO 可以提高 I/O 操作的效率,它的核心是通道 (Channel) 和缓冲区 (Buffer)。
文末送4本《高并发架构实战:从需求分析到系统设计》
哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师。
Channel 是一种新的 I/O 抽象,它与传统的 InputStream 和 OutputStream 不同,Channel 可以同时进行读和写操作,而且可以对其进行更细粒度的控制。Java NIO 中最基本的 Channel 包括:
使用FileChannel从源文件中读取内容并将其写入到目标文件。
import java.io.FileInputStream; // 引入 FileInputStream 类
import java.io.FileOutputStream; // 引入 FileOutputStream 类
import java.nio.ByteBuffer; // 引入 ByteBuffer 类
import java.nio.channels.FileChannel; // 引入 FileChannel 类
public class FileChannelExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try {
// 使用 FileInputStream 和 FileOutputStream 打开源文件和目标文件
FileInputStream fileInputStream = new FileInputStream(sourceFile);
FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
// 获取 FileChannel 对象
FileChannel sourceChannel = fileInputStream.getChannel();
FileChannel targetChannel = fileOutputStream.getChannel();
// 创建 ByteBuffer 对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从源文件中读取内容并将其写入目标文件
while (sourceChannel.read(buffer) != -1) {
buffer.flip(); // 准备写入(flip buffer)
targetChannel.write(buffer); // 向目标文件写入数据
buffer.clear(); // 缓冲区清空(clear buffer)
}
// 关闭所有的 FileChannel、FileInputStream 和 FileOutputStream 对象
sourceChannel.close();
targetChannel.close();
fileInputStream.close();
fileOutputStream.close();
// 打印成功信息
System.out.println("文件复制成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
用于 UDP 协议的数据读写操作。
使用DatagramChannel从一个端口读取数据并将数据发送到另一个端口。
import java.io.IOException; // 引入 IOException 类
import java.net.InetSocketAddress; // 引入 InetSocketAddress 类
import java.nio.ByteBuffer; // 引入 ByteBuffer 类
import java.nio.channels.DatagramChannel; // 引入 DatagramChannel 类
public class DatagramChannelExample {
public static void main(String[] args) {
int receivePort = 8888;
int sendPort = 9999;
try {
// 创建 DatagramChannel 对象
DatagramChannel receiveChannel = DatagramChannel.open();
// 绑定接收端口
receiveChannel.socket().bind(new InetSocketAddress(receivePort));
System.out.println("接收端口 " + receivePort + " 正在等待数据...");
// 创建数据缓冲区对象
ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
// 从 receiveChannel 接收数据
receiveChannel.receive(receiveBuffer);
// 显示收到的数据
System.out.println("收到的数据是:" + new String(receiveBuffer.array()));
// 关闭 receiveChannel 对象
receiveChannel.close();
// 创建 DatagramChannel 对象
DatagramChannel sendChannel = DatagramChannel.open();
// 创建数据缓冲区对象
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
// 向数据缓冲区写入数据
sendBuffer.clear();
sendBuffer.put("Hello World".getBytes());
sendBuffer.flip();
// 发送数据到指定端口
sendChannel.send(sendBuffer, new InetSocketAddress("localhost", sendPort));
System.out.println("数据已发送到端口 " + sendPort);
// 关闭 sendChannel 对象
sendChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
用于 TCP 协议的数据读写操作。
下面是一个简单的示例,演示如何使用 SocketChannel 和 ServerSocketChannel 进行基本的 TCP 数据读写操作。
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class TCPExample {
public static void main(String[] args) throws Exception {
// 创建 ServerSocketChannel 并绑定端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
// 创建一个 ByteBuffer 用于接收数据
ByteBuffer buf = ByteBuffer.allocate(1024);
// 等待客户端连接
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 客户端已连接,从 SocketChannel 中读取数据
int bytesRead = socketChannel.read(buf);
while (bytesRead != -1) {
// 处理读取到的数据
System.out.println(new String(buf.array(), 0, bytesRead));
// 清空 ByteBuffer,进行下一次读取
buf.clear();
bytesRead = socketChannel.read(buf);
}
}
}
}
}
示例代码说明:
需要注意的点:
如果想要向客户端发送数据,可以使用以下代码:
// 创建一个 ByteBuffer 用于发送数据
ByteBuffer buf = ByteBuffer.wrap("Hello, world!".getBytes());
// 向客户端发送数据
socketChannel.write(buf);
Buffer 是一个对象,它包含一些要写入或要读出的数据。在 NIO 中,Buffer 可以被看作为一个字节数组,但是它的读取和写入操作比直接的字节数组更加高效。
NIO 中最常用的 Buffer 类型包括:
字节缓冲区,最常用的缓冲区类型,用于对字节数据的读写操作。
import java.nio.ByteBuffer;
public class ByteBufferExample {
public static void main(String[] args) {
// 创建一个新的字节缓冲区,初始容量为10个字节
ByteBuffer buffer = ByteBuffer.allocate(10);
// 向缓冲区中写入4个字节
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);
buffer.put((byte) 4);
// 输出缓冲区中的内容
buffer.flip(); // 将缓冲区切换成读模式
System.out.println(buffer.get()); // 输出1
System.out.println(buffer.get()); // 输出2
System.out.println(buffer.get()); // 输出3
System.out.println(buffer.get()); // 输出4
// 将缓冲区清空并重新写入数据
buffer.clear();
buffer.put((byte) 5);
buffer.put((byte) 6);
buffer.put((byte) 7);
buffer.put((byte) 8);
// 输出缓冲区中的内容,方法同上
buffer.flip();
System.out.println(buffer.get()); // 输出5
System.out.println(buffer.get()); // 输出6
System.out.println(buffer.get()); // 输出7
System.out.println(buffer.get()); // 输出8
}
}
示例代码说明:
字符缓冲区,用于对字符数据的读写操作。
import java.nio.CharBuffer;
public class CharBufferExample {
public static void main(String[] args) {
// 创建一个新的字符缓冲区,初始容量为10个字符
CharBuffer buffer = CharBuffer.allocate(10);
// 向缓冲区中写入4个字符
buffer.put('a');
buffer.put('b');
buffer.put('c');
buffer.put('d');
// 输出缓冲区中的内容
buffer.flip(); // 将缓冲区切换成读模式
System.out.println(buffer.get()); // 输出a
System.out.println(buffer.get()); // 输出b
System.out.println(buffer.get()); // 输出c
System.out.println(buffer.get()); // 输出d
// 将缓冲区清空并重新写入数据
buffer.clear();
buffer.put('e');
buffer.put('f');
buffer.put('g');
buffer.put('h');
// 输出缓冲区中的内容,方法同上
buffer.flip();
System.out.println(buffer.get()); // 输出e
System.out.println(buffer.get()); // 输出f
System.out.println(buffer.get()); // 输出g
System.out.println(buffer.get()); // 输出h
}
}
示例代码说明:
import java.nio.*;
public class BasicBufferExample {
public static void main(String[] args) {
// 创建各种基本数据类型的缓冲区,初始容量为10
ShortBuffer shortBuf = ShortBuffer.allocate(10);
IntBuffer intBuf = IntBuffer.allocate(10);
LongBuffer longBuf = LongBuffer.allocate(10);
FloatBuffer floatBuf = FloatBuffer.allocate(10);
DoubleBuffer doubleBuf = DoubleBuffer.allocate(10);
// 向缓冲区中写入数据
shortBuf.put((short) 1);
intBuf.put(2);
longBuf.put(3L);
floatBuf.put(4.0f);
doubleBuf.put(5.0);
// 反转缓冲区,切换到读模式
shortBuf.flip();
intBuf.flip();
longBuf.flip();
floatBuf.flip();
doubleBuf.flip();
// 读取缓冲区中的数据
System.out.println(shortBuf.get()); // 输出1
System.out.println(intBuf.get()); // 输出2
System.out.println(longBuf.get()); // 输出3
System.out.println(floatBuf.get()); // 输出4.0
System.out.println(doubleBuf.get()); // 输出5.0
}
}
示例代码说明:
Selector 是 Java NIO 类库中的一个重要组件,它用于监听多个 Channel 的事件。在一个线程中,通过 Selector 可以监听多个 Channel 的 IO 事件,并实现了基于事件响应的架构。Selector 可以让单个线程处理多个 Channel,因此它可以提高多路复用的效率。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class SelectorExample {
public static void main(String[] args) throws IOException {
// 创建一个ServerSocketChannel,监听本地端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 8080));
serverSocketChannel.configureBlocking(false);
// 创建一个Selector,并将serverSocketChannel注册到Selector上
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port 8080");
while (true) {
// 如果没有任何事件发生,则阻塞等待
selector.select();
// 处理事件
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 处理新的连接请求
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
System.out.println("Accepted connection from " + clientChannel.getRemoteAddress());
clientChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 处理读事件
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
String message = new String(buffer.array(), 0, bytesRead);
System.out.println("Received message from " + clientChannel.getRemoteAddress() + ": " + message);
// 回写数据
ByteBuffer outputBuffer = ByteBuffer.wrap(("Echo: " + message).getBytes());
clientChannel.write(outputBuffer);
}
// 从待处理事件集合中移除当前事件
keyIterator.remove();
}
}
}
}
ZipInputStream 和 ZipOutputStream 可以用于处理 ZIP 文件格式,ZipInputStream 可以从 ZIP 文件中读取数据,ZipOutputStream 可以向 ZIP 文件中写入数据。
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipExample {
public static void main(String[] args) throws IOException {
// 输入文件路径和输出压缩文件路径
String inputFile = "/path/to/input/file";
String outputFile = "/path/to/output/file.zip";
// 创建ZipOutputStream,并设置压缩级别
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(outputFile));
zipOutputStream.setLevel(9);
// 读取需要压缩的文件到文件输入流
FileInputStream fileInputStream = new FileInputStream(inputFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 设置压缩文件内部的名称
ZipEntry zipEntry = new ZipEntry(inputFile);
zipOutputStream.putNextEntry(zipEntry);
// 写入压缩文件
byte[] buf = new byte[1024];
int len;
while ((len = bufferedInputStream.read(buf)) > 0) {
zipOutputStream.write(buf, 0, len);
}
bufferedInputStream.close();
zipOutputStream.closeEntry();
zipOutputStream.close();
System.out.println("File compressed successfully");
}
}
示例代码说明:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class UnzipExample {
public static void main(String[] args) throws IOException {
// 输入压缩文件路径和输出文件路径
String inputFile = "/path/to/input/file.zip";
String outputFile = "/path/to/output/file";
// 创建ZipInputStream
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inputFile));
// 循环读取压缩文件中的条目
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
// 如果是目录,则创建空目录
if (zipEntry.isDirectory()) {
new File(outputFile + File.separator + zipEntry.getName()).mkdirs();
} else { // 如果是文件,则输出文件
FileOutputStream fileOutputStream = new FileOutputStream(outputFile + File.separator
+ zipEntry.getName());
byte[] buf = new byte[1024];
int len;
while ((len = zipInputStream.read(buf)) > 0) {
fileOutputStream.write(buf, 0, len);
}
fileOutputStream.close();
}
zipInputStream.closeEntry();
zipEntry = zipInputStream.getNextEntry();
}
zipInputStream.close();
System.out.println("File uncompressed successfully");
}
}
示例代码说明:
GZIPInputStream 和 GZIPOutputStream 可以用于进行 GZIP 压缩,GZIPInputStream 可以从压缩文件中读取数据,GZIPOutputStream 可以将数据写入压缩文件中。
import java.io.*;
import java.util.zip.GZIPOutputStream;
public class GzipExample {
public static void main(String[] args) throws IOException {
// 输入文件路径和输出压缩文件路径
String inputFile = "/path/to/input/file";
String outputFile = "/path/to/output/file.gz";
// 创建GZIPOutputStream,并设置压缩级别
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(outputFile));
gzipOutputStream.setLevel(9);
// 读取需要压缩的文件到文件输入流
FileInputStream fileInputStream = new FileInputStream(inputFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 写入压缩文件
byte[] buf = new byte[1024];
int len;
while ((len = bufferedInputStream.read(buf)) > 0) {
gzipOutputStream.write(buf, 0, len);
}
bufferedInputStream.close();
gzipOutputStream.close();
System.out.println("File compressed successfully");
}
}
示例代码说明:
import java.io.*;
import java.util.zip.GZIPInputStream;
public class GunzipExample {
public static void main(String[] args) throws IOException {
// 输入压缩文件路径和输出文件路径
String inputFile = "/path/to/input/file.gz";
String outputFile = "/path/to/output/file";
// 创建GZIPInputStream
GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(inputFile));
// 输出文件
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
byte[] buf = new byte[1024];
int len;
while ((len = gzipInputStream.read(buf)) > 0) {
fileOutputStream.write(buf, 0, len);
}
gzipInputStream.close();
fileOutputStream.close();
System.out.println("File uncompressed successfully");
}
}
示例代码说明:
ByteArrayInputStream 和 ByteArrayOutputStream 分别是 ByteArrayInputStream 和 ByteArrayOutputStream 类的子类,它们可以用于对字节数组进行读写操作。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class ByteArrayInputStreamExample {
public static void main(String[] args) throws IOException {
// 用字符串初始化一个字节数组,作为输入数据源
String input = "Hello, world!";
byte[] inputBytes = input.getBytes();
// 创建一个ByteArrayInputStream,使用输入数据源
ByteArrayInputStream inputStream = new ByteArrayInputStream(inputBytes);
// 读取并输出输入流中的数据
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
// 关闭输入流
inputStream.close();
}
}
示例代码说明:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayOutputStreamExample {
public static void main(String[] args) {
String input = "Hello World!";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] output;
try {
outputStream.write(input.getBytes());
output = outputStream.toByteArray();
System.out.println(new String(output));
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
} finally {
try {
outputStream.close();
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
}
示例代码说明:
本文为您讲解了 Java I/O、NIO 以及其他一些流的基本概念、用法和区别。Java I/O 和 NIO 可以完成很多复杂的输入输出操作,包括文件操作、网络编程、序列化等。其他流技术可以实现压缩、读写字节数组等功能。在进行开发时,根据具体需求选择不同的流技术可以提高程序效率和开发效率。
图书数量:本次送出 4 本 !!!⭐️⭐️⭐️⭐️
活动时间:截止到 2023-08-17 12:00:00
抽奖方式:
哪吒会在本文留言区置顶公布中奖名单
名单公布时间:2023-08-17 13点
第1期中奖名单公布:
啥咕啦呛
空圆小生
狮子也疯狂
千子。
很多软件工程师的职业规划是成为架构师,但是要成为架构师很多时候要求先有架构设计经验,而不做架构师又怎么会有架构设计经验呢?那么要如何获得架构设计经验呢?
一方面可以通过工作来学习,观察所在团队的架构师是如何工作的,协助他做一些架构设计和落地的工作。同时,思考如果你是架构师,你将如何完成工作,哪些地方可以做得更好。
另一方面,也可以通过阅读来学习,看看那些典型的、耳熟能详的应用系统是如何设计的。同样,你也可以在阅读的过程中思考:如果你是这个系统的架构师,将如何进行设计?如何输出你的设计结果?哪些关键设计需要进一步优化?
通过这样不断地学习和思考,你就会不断积累架构设计的经验,等你有机会成为架构师的时候,就可以从容不迫地利用你学习与思考获得的经验和方法,开始你的架构师职业生涯。
现在,知名技术畅销书作者李智慧老师的全新力作,基于真实经典案例改编的《高并发架构实战:从需求分析到系统设计》纸书终于出版!
本书精挑细选了18个系统架构案例,这些案例大多是目前大家比较关注的高并发、高性能、高可用系统。它们是高并发架构设计的优秀“课代表”,它们的技术可以解决现有的80%以上的高并发共性问题。所以在阅读文档的过程中,你可以进一步学习与借鉴这些典型的分布式互联网系统架构,构建起自己的系统架构设计方法论,以指导自己的工作实践。
为了避免每篇文档中都出现大量重复、雷同的设计,本书在内容方面进行了取舍,精简了一些常规的、技术含量较低的内容,而尽量介绍那些有独特设计思想的技术点,尽可能做到在遵循设计文档规范的同时,又突出每个系统自己的设计重点。
此外,本书中还有一部分设计是针对大型应用系统的,比如限流器、防火墙、加解密服务、大数据平台等。
但需要强调一点,本书会针对这些知名的大厂应用重新进行设计,而不是分析现有应用是如何设计的。一方面,重新设计完全可以按自己的意愿来,不管是设计方案还是需求分析、性能指标估算,都是一件很有意思的事;另一方面,因为现有应用中的某些关键设计并没有公开,我们要想讨论清楚这些高并发应用的架构设计,没有现成的资料,还是需要自己进行分析并设计。
所以很多案例的设计文档都有需求分析,用于估算重新设计的系统需要承载的并发压力有多大、系统资源需要多少,这些估算大多数都略高于现有大厂的系统指标。希望你在阅读这些内容的时候,能够更真切地体会到架构师的“现场感受”:我评审、设计的这个系统将服务全球数十亿用户;这个系统每年需要的服务器和网络带宽需要几十亿元;这个系统宕机十几分钟,公司就会损失数千万元。
希望你在阅读《高并发架构实战:从需求分析到系统设计》的过程中,能把自己带入真实的系统设计场景中,把文章当成真实的设计文档,把自己想象成文档作者的同事,也就是说,你正在评审我做的设计。
你可以一边阅读一边思考:这个设计哪些地方考虑不周?哪些关键点有缺漏?然后你可以把自己的思考记录下来,当作你的评审意见。
最重要的是,通过这种方式,你拥有了关于每一个软件设计案例的现场感:你不是一个阅读书籍的读者,而是置身于互联网大厂的资深架构师,你在评审同事的设计,也在考虑公司的未来。
本文收录于,Java进阶教程系列。
全网最细Java零基础手把手入门教程,系列课程包括:基础篇、集合篇、Java8新特性、多线程、代码实战,持续更新中(每周1-2篇),适合零基础和进阶提升的同学。
哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师。