咱们好,我是哪吒。
很多朋友问我,如何才能学好IO流,对各种流的概念,云里雾里的,不求甚解。用到的时候,现百度,功能虽然实现了,可是为什么用这个?不知道。更甭说功率问题了~
下次再遇到,再百度,“良性循环”。
今天,我就用一天的时刻,收拾一下关于Java I/O流的知识点,分享给咱们。
每一种IO流,都配有示例代码,咱们能够跟着敲一遍,找找感觉~
上一篇介绍了一文搞定Java 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从源文件中读取内容并将其写入到方针文件。
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();
}
}
}
2、DatagramChannel代码示例
用于 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();
}
}
}
3、SocketChannel 和 ServerSocketChannel代码示例
用于 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);
}
}
}
}
}
示例代码阐明:
- 创立一个 ServerSocketChannel 并绑定到本地端口 8888,然后将其设置为非堵塞形式。
- 创立一个 ByteBuffer 用于接纳数据。
- 进入一个死循环,不断等候客户端衔接。
- 当客户端衔接时,从 SocketChannel 中读取数据,并将读取到的数据打印到控制台。
- 清空 ByteBuffer ,进行下一次读取。
需求注意的点:
- 在代码中每次读取完毕都需求清空 ByteBuffer ,不然其 position 特点不会自动归零,或许导致数据读取不正确。
- 因为运用非堵塞形式,假如调用了 accept() 办法但没有当即接纳到客户端衔接,该办法会返回 null,需求继续循环等候。
- 本代码只演示了从客户端读取数据的部分,假如需求向客户端发送数据需求调用SocketChannel.write() 办法
假如想要向客户端发送数据,能够运用以下代码:
// 创立一个 ByteBuffer 用于发送数据
ByteBuffer buf = ByteBuffer.wrap("Hello, world!".getBytes());
// 向客户端发送数据
socketChannel.write(buf);
二、Buffer
Buffer 是一个方针,它包含一些要写入或要读出的数据。在 NIO 中,Buffer 能够被看作为一个字节数组,可是它的读取和写入操作比直接的字节数组愈加高效。NIO 中最常用的 Buffer 类型包含:
1、ByteBuffer示例代码
字节缓冲区,最常用的缓冲区类型,用于对字节数据的读写操作。
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
}
}
示例代码阐明:
- 在上面的示例中,咱们运用ByteBuffer类的allocate()办法创立了一个新的字节缓冲区,然后向缓冲区中写入4个字节的数据。
- 接着,咱们经过调用flip()办法将缓冲区切换成读形式,并运用get()办法读取缓冲区中的数据,并按次序输出每个字节。
- 最终,咱们清空缓冲区并从头写入数据,再次将缓冲区切换成读形式,并运用get()办法读取缓冲区中的数据。
2、CharBuffer示例代码
字符缓冲区,用于对字符数据的读写操作。
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
}
}
示例代码阐明:
- 在上面的示例中,咱们运用CharBuffer类的 allocate() 办法创立了一个新的字符缓冲区,然后向缓冲区中写入4个字符的数据。
- 接着,咱们经过调用**flip()**办法将缓冲区切换成读形式,并运用get()办法读取缓冲区中的数据,并按次序输出每个字符。
- 最终,咱们清空缓冲区并从头写入数据,再次将缓冲区切换成读形式,并运用**get()**办法读取缓冲区中的数据。
3、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 等示例代码
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
}
}
示例代码阐明:
- 在上面的示例中,咱们别离创立了ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer等根本数据类型的缓冲区。
- 接着,咱们向这些缓冲区中写入了对应数据类型的数据。
- 然后咱们经过调用 flip() 办法,将缓冲区切换成读形式,并经过get()办法读取缓冲区中的数据,并按次序输出每一个数据类型的内容。
三、Selector
Selector 是 Java NIO 类库中的一个重要组件,它用于监听多个 Channel 的事情。在一个线程中,经过 Selector 能够监听多个 Channel 的 IO 事情,并实现了依据事情呼应的架构。Selector 能够让单个线程处理多个 Channel,因此它能够进步多路复用的功率。
1、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();
}
}
}
}
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示例代码
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");
}
}
示例代码阐明:
- 首要,咱们创立ZipOutputStream并设置紧缩级别。
- 接着,咱们创立输入文件的FileInputStream,并运用BufferedInputStream包装它。
- 咱们接着设置紧缩文件内部的称号,并运用zipOutputStream.putNextEntry() 办法将其写入ZipOutputStream中。
- 最终,咱们从缓冲区读取文件数据并将其写入ZipOutputStream中。最终封闭输入流和ZipOutputStream。
2、ZipOutputStream示例代码
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");
}
}
示例代码阐明:
- 运用ZipInputStream从指定输入文件中解压文件到指定的输出文件夹中。
- 咱们创立ZipInputStream,然后循环读取紧缩文件中的条目。假如当时条目是目录,则创立空目录,并运用mkdirs()办法创立目录。假如当时条目是文件,则运用FileOutputStream将文件写入到指定的输出文件中。
- 最终封闭当时ZipEntry,并经过getNextEntry()办法获取ZipInputStream中的下一个条目。
五、GZIPInputStream 和 GZIPOutputStream
GZIPInputStream 和 GZIPOutputStream 能够用于进行 GZIP 紧缩,GZIPInputStream 能够从紧缩文件中读取数据,GZIPOutputStream 能够将数据写入紧缩文件中。
1、GZIPInputStream代码示例
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");
}
}
示例代码阐明:
- 运用GZIPOutputStream将指定的输入文件紧缩成输出文件。
- 首要,创立GZIPOutputStream并设置紧缩级别。
- 接着,创立输入文件的FileInputStream,并运用BufferedInputStream包装它。
- 接着从缓冲区读取文件数据并将其写入GZIPOutputStream中。最终封闭输入流和GZIPOutputStream。
2、GZIPOutputStream代码示例
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");
}
}
示例代码阐明:
- 运用GZIPInputStream从指定输入文件中解压文件到指定的输出文件中。
- 首要,咱们创立GZIPInputStream,然后从缓冲区读取文件数据并将其写入到指定的输出文件中。
- 最终,咱们封闭输入流和输出流。
六、ByteArrayInputStream 和 ByteArrayOutputStream
ByteArrayInputStream 和 ByteArrayOutputStream 别离是 ByteArrayInputStream 和 ByteArrayOutputStream 类的子类,它们能够用于对字节数组进行读写操作。
1、ByteArrayInputStream 代码示例
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();
}
}
示例代码阐明:
- 运用“Hello, world!”字符串创立了一个字节数组作为输入数据源,并运用ByteArrayInputStream将其包装成输入流。
- 运用一个循环从输入流中读取数据,并运用new String() 办法将其转换成字符串并输出到控制台。
- 最终,封闭输入流。
2、ByteArrayOutputStream代码示例
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());
}
}
}
}
示例代码阐明:
- 在这个比如中,创立了一个ByteArrayOutputStream方针 outputStream,并向其写入一个字符串”Hello World!”。然后,咱们运用toByteArray() 办法将结果转换为一个字节数组,并打印出来。
- 注意:在运用ByteArrayOutputStream时,要保证在不再需求它时封闭它以保证一切的字节都被刷新到输出流中。
七、总结
本文为您讲解了 Java I/O、NIO 以及其他一些流的根本概念、用法和区别。Java I/O 和 NIO 能够完结很多杂乱的输入输出操作,包含文件操作、网络编程、序列化等。其他流技能能够实现紧缩、读写字节数组等功能。在进行开发时,依据具体需求选择不同的流技能能够进步程序功率和开发功率。