大家好,我是哪吒。
很多朋友问我,如何才能学好IO流,对各种流的概念,云里雾里的,不求甚解。用到的时候,现百度,功能虽然实现了,但是为什么用这个?不知道。更别说效率问题了~
下次再遇到,再百度,“良性循环”。
今天,我就用一天的时间,整理一下关于Java I/O流的知识点,分享给大家。
每一种IO流,都配有示例代码,大家可以跟着敲一遍,找找感觉~
本篇文章介绍Java NIO以及其它的各种奇葩流。
Java NIO (New I/O) 是 Java 1.4 引入的,在 Java 7 中又进行了一些增强。NIO 可以提高 I/O 操作的效率,它的核心是通道 (Channel) 和缓冲区 (Buffer)。
一、Channel
Channel 是一种新的 I/O 抽象,它与传统的 InputStream 和 OutputStream 不同,Channel 可以同时进行读和写操作,而且可以对其进行更细粒度的控制。Java NIO 中最基本的 Channel 包括:
1、FileChannel代码示例
使用FileChannel从源文件中读取内容并将其写入到目标文件。
2、DatagramChannel代码示例
用于 UDP 协议的数据读写操作。
使用DatagramChannel从一个端口读取数据并将数据发送到另一个端口。
3、SocketChannel 和 ServerSocketChannel代码示例
用于 TCP 协议的数据读写操作。
下面是一个简单的示例,演示如何使用 SocketChannel 和 ServerSocketChannel 进行基本的 TCP 数据读写操作。
示例代码说明:
- 创建一个 ServerSocketChannel 并绑定到本地端口 8888,然后将其设置为非阻塞模式。
- 创建一个 ByteBuffer 用于接收数据。
- 进入一个死循环,不断等待客户端连接。
- 当客户端连接时,从 SocketChannel 中读取数据,并将读取到的数据打印到控制台。
- 清空 ByteBuffer,进行下一次读取。
需要注意的点:
- 在代码中每次读取结束都需要清空 ByteBuffer,否则其 position 属性不会自动归零,可能导致数据读取不正确。
- 由于使用非阻塞模式,如果调用了 accept() 方法但没有立即接收到客户端连接,该方法会返回 null,需要继续循环等待。
- 本代码只演示了从客户端读取数据的部分,如果需要向客户端发送数据需要调用SocketChannel.write()方法
如果想要向客户端发送数据,可以使用以下代码:
二、Buffer
Buffer 是一个对象,它包含一些要写入或要读出的数据。在 NIO 中,Buffer 可以被看作为一个字节数组,但是它的读取和写入操作比直接的字节数组更加高效。NIO 中最常用的 Buffer 类型包括:
1、ByteBuffer示例代码
字节缓冲区,最常用的缓冲区类型,用于对字节数据的读写操作。
示例代码说明:
- 在上面的示例中,我们使用ByteBuffer类的allocate()方法创建了一个新的字节缓冲区,然后向缓冲区中写入4个字节的数据。
- 接着,我们通过调用flip()方法将缓冲区切换成读模式,并使用get()方法读取缓冲区中的数据,并按顺序输出每个字节。
- 最后,我们清空缓冲区并重新写入数据,再次将缓冲区切换成读模式,并使用get()方法读取缓冲区中的数据。
2、CharBuffer示例代码
字符缓冲区,用于对字符数据的读写操作。
示例代码说明:
- 在上面的示例中,我们使用CharBuffer类的allocate()方法创建了一个新的字符缓冲区,然后向缓冲区中写入4个字符的数据。
- 接着,我们通过调用flip()方法将缓冲区切换成读模式,并使用get()方法读取缓冲区中的数据,并按顺序输出每个字符。
- 最后,我们清空缓冲区并重新写入数据,再次将缓冲区切换成读模式,并使用get()方法读取缓冲区中的数据。
3、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 等示例代码
示例代码说明:
- 在上面的示例中,我们分别创建了ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer等基本数据类型的缓冲区。
- 接着,我们向这些缓冲区中写入了对应数据类型的数据。
- 然后我们通过调用flip()方法,将缓冲区切换成读模式,并通过get()方法读取缓冲区中的数据,并按顺序输出每一个数据类型的内容。
三、Selector
Selector 是 Java NIO 类库中的一个重要组件,它用于监听多个 Channel 的事件。在一个线程中,通过 Selector 可以监听多个 Channel 的 IO 事件,并实现了基于事件响应的架构。Selector 可以让单个线程处理多个 Channel,因此它可以提高多路复用的效率。
1、Selector让单线程处理多个Channel的代码示例
2、示例代码说明
- 使用ServerSocketChannel监听本地8080端口,并将ServerSocketChannel注册到Selector上。
- 在while循环中,我们通过调用select()方法等待事件发生,如果有事件发生,则从Selector中获取待处理事件集合,然后遍历事件集合,处理每个事件。
- 如果当前事件是新的连接请求,则接受该连接,并将对应的SocketChannel注册到Selector上,使用OP_READ模式表示可以读取数据。
- 如果当前事件是可读的,则读取SocketChannel中的数据并进行回写,回写时使用ByteBuffer包装需要回写的数据,并将其写入到SocketChannel中。
- 最后,我们从待处理事件集合中移除当前事件。
四、ZipInputStream 和 ZipOutputStream
ZipInputStream 和 ZipOutputStream 可以用于处理 ZIP 文件格式,ZipInputStream 可以从 ZIP 文件中读取数据,ZipOutputStream 可以向 ZIP 文件中写入数据。
1、ZipInputStream示例代码
示例代码说明:
- 首先,我们创建ZipOutputStream并设置压缩级别。
- 接着,我们创建输入文件的FileInputStream,并使用BufferedInputStream包装它。
- 我们接着设置压缩文件内部的名称,并使用zipOutputStream.putNextEntry()方法将其写入ZipOutputStream中。
- 最后,我们从缓冲区读取文件数据并将其写入ZipOutputStream中。最后关闭输入流和ZipOutputStream。
2、ZipOutputStream示例代码
示例代码说明:
- 使用ZipInputStream从指定输入文件中解压文件到指定的输出文件夹中。
- 我们创建ZipInputStream,然后循环读取压缩文件中的条目。如果当前条目是目录,则创建空目录,并使用mkdirs()方法创建目录。如果当前条目是文件,则使用FileOutputStream将文件写入到指定的输出文件中。
- 最后关闭当前ZipEntry,并通过getNextEntry()方法获取ZipInputStream中的下一个条目。
五、GZIPInputStream 和 GZIPOutputStream
GZIPInputStream 和 GZIPOutputStream 可以用于进行 GZIP 压缩,GZIPInputStream 可以从压缩文件中读取数据,GZIPOutputStream 可以将数据写入压缩文件中。
1、GZIPInputStream代码示例
示例代码说明:
- 使用GZIPOutputStream将指定的输入文件压缩成输出文件。
- 首先,创建GZIPOutputStream并设置压缩级别。
- 接着,创建输入文件的FileInputStream,并使用BufferedInputStream包装它。
- 接着从缓冲区读取文件数据并将其写入GZIPOutputStream中。最后关闭输入流和GZIPOutputStream。
2、GZIPOutputStream代码示例
示例代码说明:
- 使用GZIPInputStream从指定输入文件中解压文件到指定的输出文件中。
- 首先,我们创建GZIPInputStream,然后从缓冲区读取文件数据并将其写入到指定的输出文件中。
- 最后,我们关闭输入流和输出流。
六、ByteArrayInputStream 和 ByteArrayOutputStream
ByteArrayInputStream 和 ByteArrayOutputStream 分别是 ByteArrayInputStream 和 ByteArrayOutputStream 类的子类,它们可以用于对字节数组进行读写操作。
1、ByteArrayInputStream 代码示例
示例代码说明:
- 使用“Hello, world!”字符串创建了一个字节数组作为输入数据源,并使用ByteArrayInputStream将其包装成输入流。
- 使用一个循环从输入流中读取数据,并使用new String()方法将其转换成字符串并输出到控制台。
- 最后,关闭输入流。
2、ByteArrayOutputStream代码示例
示例代码说明:
- 在这个例子中,创建了一个ByteArrayOutputStream对象 outputStream,并向其写入一个字符串"Hello World!"。然后,我们使用toByteArray()方法将结果转换为一个字节数组,并打印出来。
- 注意:在使用ByteArrayOutputStream时,要确保在不再需要它时关闭它以确保所有的字节都被刷新到输出流中。
七、总结
本文为您讲解了 Java I/O、NIO 以及其他一些流的基本概念、用法和区别。Java I/O 和 NIO 可以完成很多复杂的输入输出操作,包括文件操作、网络编程、序列化等。其他流技术可以实现压缩、读写字节数组等功能。在进行开发时,根据具体需求选择不同的流技术可以提高程序效率和开发效率。
本文转载自微信公众号「哪吒编程」,可以通过以下二维码关注。转载本文请联系哪吒编程公众号。