目录:
Java NIO 学习笔记(一)---- 概述, Channel/Buffer
Java NIO 学习笔记(二)---- 聚集和分散, 通道到通道
Java NIO 学习笔记(三)----Selector
Java NIO 学习笔记(四)---- 文件通道和网络通道
FileChannel 文件通道
FileChannel 是连接到文件的通道, 可以从文件中读取数据, 并将数据写入文件, 可以替代使用标准 IO 读写文件的操作.
注意 FileChannel 无法设置为非阻塞模式. 它始终以阻塞模式运行.
打开 FileChannel
在使用 FileChannel 之前必须先将其打开. 无法直接打开 FileChannel , 必须通过 InputStream,OutputStream 或 RandomAccessFile 获取 FileChannel . 以下是通过 RandomAccessFile 打开 FileChannel 的方法:
- RandomAccessFile aFile = new RandomAccessFile("D:\\test\\input.txt", "rw");
- FileChannel inChannel = aFile.getChannel();
从 FileChannel 读写数据
这是通过调用 read()/write() 方法之一完成的, 读和写都是针对一个 ByteBuufer 对象的.
这是一个例子:
- // 这里省略 2 个 Channel 的定义...
- ByteBuffer buffer = ByteBuffer.allocate(48);
- buffer.clear(); // 清除缓冲区, 准备写入数据
- int bytesRead = inChannel.read(buffer); // 将 inChannel 的数据读入缓冲区
- buffer.flip(); // 反转缓冲区, 就是把指针放到开头, 并设置 limit 标记结尾
- while(buffer.hasRemaining()) { // 只要缓冲区还有数据
- outChannel.write(buffer); // 就将缓冲区数据写入通道
- }
分配缓冲区后, 调用 FileChannel 对象的 read() 方法将 FileChannel 中的数据读入 Buffer , 返回的 int 代表读取的字节数. 如果返回 -1, 则到达文件结尾.
使用 write() 方法将数据写入 FileChannel , 该方法同样将 Buffer 作为参数, 注意在 while 循环中调用 write() 方法. 这是因为无法保证 write() 方法写入 FileChannel 的字节数. 因此, 我们重复调用 write() 方法, 直到 Buffer 中没有要写入通道的字节.
关闭 FileChannel
使用 FileChannel 后, 必须将其关闭:
channel.close();
FileChannel Position 指定操作位置
可以调用 position() 方法获取 FileChannel 对象的当前位置, 调用 position(long pos) 方法来设置 FileChannel 的位置, 这样就可以在特定位置开始读取或写入 FileChannel .
一个例子:
- // 获取当前位置
- long pos = channel.position();
- // 指定通道的位置, 0 <= position <= limit
- channel.position(pos +123);
如果位置设置在文件结束后面:
读取操作将得到 -1 ---- 文件结束标记.
写入操作, 文件将扩大到指定位置并写入数据. 这可能导致 "文件漏洞", 磁盘上的物理文件数据中存在间隙.(比如开始顺序写入 123 后, 此时 position=3, 手动指定 position = 6, 写入 4, 那此时磁盘物理文件应该是['1', '2', '3', '~', '~', '~', '4'], 中间有 3 个位置是空的)
获取 FileChannel 的大小信息
FileChannel 对象的 size() 方法返回通道所连接文件的文件大小. 这是一个简单的例子:
- long fileSize = channel.size();
- FileChannel Truncate(截断)
可以通过调用 FileChannel 对象的 truncate() 方法截断文件. 截断文件时, 会以给定长度将其剪切掉, 即指定长度后面部分被删除. 这是一个例子, 将文件长度截断为 1024 字节:
channel.truncate(1024);
将 FileChannel 数据强制保存到磁盘
FileChannel 对象的 void force(boolean metaData) 方法强制将所有未写入磁盘的数据从通道刷新到磁盘. 出于性能原因, 操作系统可能会将数据缓存在内存中, 因此在调用 force() 方法之前, 无法保证写入通道的数据实际写入磁盘.
boolean 参数指定是否应该刷新文件元数据 (权限信息等) 到磁盘.
- channel.force(true);
- channel.force(flase);
SocketChannel 套接字通道
SocketChannel 是连接到 TCP 网络套接字的通道, 相当于 Java 网络编程的套接字. 可以通过两种方式创建:
打开 SocketChannel 并连接到 Internet 上的某个服务器, 即向服务器发出连接.
当传入连接到达 ServerSocketChannel 时, 创建 SocketChannel , 即接收客户端发来的连接.
打开和关闭 SocketChannel
以下是打开 SocketChannel 的方法:
- SocketChannel socketChannel = SocketChannel.open();
- socketChannel.connect(new InetSocketAddress("http://baidu.com", 80));
- socketChannel.close();
InetSocketAddress 是 SocketAddress 类的子类, 为 (IP 地址 + 端口号) 类型, 也就是端口地址类型, 可以使用静态方法 createUnresolved(String host, int port) 获取对象, 另外也能由构造函数 InetSocketAddress(InetAddress addr, int port) 创建, 其中 InetAddress 对象可省略, 也可用字符串代替.
通过 SocketChannel 读写数据
- // 这里省略 2 个 Channel 的定义...
- ByteBuffer buffer = ByteBuffer.allocate(48);
- buffer.clear(); // 清除缓冲区, 准备写入数据
- int bytesRead = socketChannel.read(buffer); // 将 socketChannel 的数据读入缓冲区
- buffer.flip(); // 反转缓冲区, 就是把指针放到开头, 并设置 limit 标记结尾
- while(buffer.hasRemaining()) { // 只要缓冲区还有数据
- channel.write(buffer);// 就将缓冲区数据写入通道
- }
可以看到, 基本和 FileChannel 的读写方式一致, 首先分配缓冲区, 然后调用 read() 方法. 此方法将数据从 SocketChannel 读入 Buffer . read() 方法返回的 int 代表写入了多少字节数据. 如果返回 -1 , 则到达流的末尾(连接已关闭). 调用 SocketChannel 对象的 write(Buffer buffer) 方法则可以将 Buffer 的数据写入 SocketChannel.
SocketChannel 的非阻塞模式
可以将 SocketChannel 设置为非阻塞模式, 可以在异步模式下调用 connect(),read() 和 write() 方法.
connect()
如果 SocketChannel 处于非阻塞模式调用 connect() 方法, 则可能会在建立连接之前返回. 要确定是否成功建立了连接, 可以调用 finishConnect() 方法, 它当且仅当已连接此通道的套接字时才返回 true .
如下所示:
- socketChannel.configureBlocking(false);
- socketChannel.connect(new InetSocketAddress("http://baidu.com", 80));
- while(! socketChannel.finishConnect() ){
- //wait, or do something else...
- }
- write()
在非阻塞模式下, write() 方法可能在没有写入任何内容的情况下返回. 因此, 需要在循环中调用 write() 方法.
- while(buffer.hasRemaining()) { // 只要缓冲区还有数据
- channel.write(buffer);// 就将缓冲区数据写入通道
- }
- read()
在非阻塞模式下, read() 方法可能在没有读取任何数据的情况下返回. 因此, 需要注意返回的 int , 它代表读取了多少字节.
带有选择器的非阻塞模式
使用 Selector 时, SocketChannel 的非阻塞模式效果更好. 通过使用选择器注册一个或多个 SocketChannel , 可以向选择器询问已准备好进行读取, 写入的通道. 后面会有更详细的提起, 这里先不讲.
ServerSocketChannel
ServerSocketChannel 是一个可以侦听传入 TCP 连接的通道, 就像标准 ServerSocket 一样. 这是一个例子:
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.socket().bind(new InetSocketAddress(9999));
- while (true) {
- SocketChannel socketChannel = serverSocketChannel.accept(); // 监听传入的连接
- //do something with socketChannel...
- }
- serverSocketChannel.close();
ServerSocketChannel 可以设置为非阻塞模式. 在非阻塞模式下, accept() 方法调用后会立即返回, 如果有连接传入, 则返回 SocketChannel 对象, 如果没有连接传入, 则返回 null. 因此, 必须检查返回的 SocketChannel 是否为空. 如下:
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.socket().bind(new InetSocketAddress(9999));
- serverSocketChannel.configureBlocking(false);
- while (true) {
- SocketChannel socketChannel = serverSocketChannel.accept(); // 监听传入的连接
- if(socketChannel != null){
- //do something with socketChannel...
- }
- }
DatagramChannel 数据包通道
DatagramChannel 是可以发送和接收 UDP 数据包的通道. 由于 UDP 是一种无连接的网络协议, 因此不能像在其他通道中那样读取和写入 DatagramChannel . 而是通过发送和接收数据包的方式通信.
接收数据
DatagramChannel 是通过 receive() 方法接收数据的. receive() 方法将接收到的数据包的内容复制到给定的 Buffer 中. 如果接收的数据包包含的数据多于缓冲区可以包含的数据, 则会悄悄丢弃多出的数据. 一个示例如下:
- ByteBuffer receiveBuffer = ByteBuffer.allocate(128);
- DatagramChannel serverChannel = DatagramChannel.open();
- serverChannel.socket().bind(new InetSocketAddress(9999));
- receiveBuffer.clear(); // 清除缓冲区, 准备写入数据
- serverChannel.receive(receiveBuffer);
- receiveBuffer.flip(); // 反转缓冲区以准备被读取
- System.out.println(new String(receiveBuffer.array(), 0, receiveBuffer.limit()));
发送数据
- ByteBuffer sendBuffer = ByteBuffer.allocate(128);
- sendBuffer.clear(); // 清除缓冲区, 准备写入数据
- byte[] sendData = "string from cilent".getBytes();
- sendBuffer.put(sendData);
- DatagramChannel clientChannel = DatagramChannel.open();
- int sendSuccess = clientChannel.send(sendBuffer, new InetSocketAddress("127.0.0.1", 9999));
- System.out.println("sendSuccess:" + sendSuccess);
- clientChannel.close();
此示例将字符串发送到 UDP 本机的 9999 端口, 由于 UDP 不对数据传送做出任何保证, 因此不会通知对方是否收到了发送的数据包.
连接到特定地址
可以将 DatagramChannel"连接" 到网络上的特定地址. 由于 UDP 是无连接的, 因此这种连接到地址的方式不会像 TCP 通道那样创建真正的连接. 它只会锁定 DatagramChannel , 让其只能从一个特定地址发送和接收数据包. 一个示例:
channel.connect(new InetSocketAddress("baidu.com", 80));
连接后, 还可以使用 read() 和 write() 方法, 就像使用传统通道一样, 只是在数据传送方面没有任何保证. 例子:
- int bytesRead = channel.read(buf);
- int bytesWritten = channel.write(buf);
来源: https://www.cnblogs.com/czwbig/p/10046987.html