笔记:
JVM 自身在 I/O方面效率欠佳。操作系统与 Java 基于流的 I/O 模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而 JVM 的 I/O 类喜欢操作小块数据——单个字节、几行文本。结果操作系统送来了整个缓冲区数据,java.io 的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io 类则喜欢一铲子一铲子地加工数据。有了 NIO,就可以轻松地把一卡车数据备份到你能直接使用的地方(ByteBuffer)
采用分页计数的操作系统执行 I/O的全过程可总结为以下几步:
① 确定请求的数据分布在文件系统的哪些页(磁盘扇区组)。磁盘上的文件内容和元素据可能跨越多个文件系统页,而且这些页可能也不连续
② 在内核空间分配足够数量的内存页,以容纳得到确定的文件系统页
③ 在内存页与磁盘上的文件系统页之间建立映射
④ 为每一个内存页产生页错误
⑤ 虚拟内存系统俘获页错误,安排页面调入,从磁盘读取页内容,使页有效
⑥ 一旦页面调入操作完成,文件系统即对原始数据进行解析,获取文件内容或属性信息
需要注意的是,这些文件系统数据也会同其他内存页一样得到高速缓存。对于随后发生的I/O 请求,文件数据的部分或全部可能仍旧位于物理内存当中,无需再从磁盘读取即可重复使用。
内存映射文件
传统的文件I/O 是通过用户进程发布 read() 和 write() 系统调用来传输数据的。为了在内核空间的文件系统页与用户空间的内存区之间移动数据,一次以上的拷贝操作几乎总是免不了的。这是因为,在文件系统页与用户换从之间往往没有一一对应关系。但是,还有一种大多数操作系统都支持的特殊类型的 I/O 操作,允许用户进程最大限度地利用面向页的系统 I/O 特性,并完全摒弃缓冲区拷贝,这就是内存映射 I/O,如图:
内存映射 I/O 使用文件系统建立从用户空间直接到文件系统页的虚拟内存映射。这样做有几个好处:
① 用户进程把文件数据当作内存,所以无需发布 read() 或 write() 系统调用
② 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
③ 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理
④ 数据总是按页对齐的,无需执行缓冲区拷贝
⑤ 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝
虚拟内存和磁盘 I/O 是紧密相连的,从很多方面看来,它们只是同一件事务的两面,在处理大量数据时,尤其要记得这一点。如果数据缓冲区是按页对齐的,且大小是内建页大小的倍数,那么,对于大多数操作系统而言,其处理效率会大幅度提升。
文件锁定
文件锁定机制允许一个进程阻止其他进程存取某个文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事务隔离。在控制多个实体并行访问共同资源方面,文件锁定是必不可少的。数据库等复杂应用严重依赖于文件锁定。
“文件锁定” 从字面上看有锁定整个文件的意思(通常的确是这样),但锁定往往可以发生在更为细微的层面,锁定区域往往可以细致到单个字节。锁定与特定文件相关,开始于文件的某个特定字节地址,包含特定数量的连续字节。这对于协调多个进程互补影响地访问不同区域,是至关重要的。
文件锁定有两种方式:共享和独占。多个共享锁可同时对同一文件文件区域发生作用;独占锁则不同,它要求相关区域不能有其他锁定在其作用。
共享锁和独占锁的经典应用,是控制最初用于读取的共享文件的更新。某个进程要读取文件,会先取得该文件或该文件部分区域的共享锁。第二个希望读取文件区域的进程也会请求共享锁。两个进程可以并行读取,互不影响。但是,假如有第三个进程要更新该文件,它会请求独占锁。该进程会处于阻滞状态,直到既有锁定(共享的、独占的)全部解除。一旦给予独占锁,其它锁的读取进程就会处于阻滞状态,直到独占锁解除。这样,更新进程可以更改文件,而其他读取进程不会因为文件的更改得到前后不一致的结果
文件锁有建议使用和强制使用之分。建议型文件锁会向提出请求的进程提供当前锁定信息,但操作系统并不要求一定这样做,而是由相关进程进行协调并关注锁定信息。多数 Unix 和 类 Unix 操作系统使用建议型锁,有些也使用强制型锁或兼而有之。
强制型锁由操作系统或文件系统强制实施,不管进程对锁的存在知道与否,都会阻止其对文件锁定区域的访问,微软的操作系统往往使用的是强制型锁。假定所有文件锁均为建议型,并在访问共同资源的各个应用程序间使用一定的文件锁定,是明智之举,也是唯一可型的跨平台策略。依赖于强制文件锁定的应用程序,从根本上讲就是不可移植的
缓冲区( Buffers )
新的 Buffer 类是常规 Java 类和通道之间的纽带。原始数据元素组成的固定长度数组,封装在 包含状态信息的对象中,存入缓冲区。缓冲区提供了一个会合点:通道既可提取放在缓冲区中的数
据(写),也可向缓冲区存入数据供读取(读)。此外,还有一种特殊类型的缓冲区,用于内存映 射文件
通道( Channels )
NIO 新引入的最重要的抽象是通道的概念。Channel 对象模拟了通信连接,管道既可以是单向 的(进或出),也可以是双向的(进和出)。可以把通道想象成连接缓冲区和 I/O 服务的捷径。
某些情况下,软件包中的旧类可利用通道。为了能够向与文件或套接字关联的通道进行存取, 适当的地方都增加了新方法。
多数通道可工作在非块模式下,这意味着更好的可伸缩性,尤其是与选择器一同使用的时候。
文件锁定和内存映射文件( File locking and memory-mapped files )
新的 FileChannel 对象包含在 java.nio.channels 软件包内,提供许多面向文件的新特 性,其中最有趣的两个是文件锁定和内存映射文件。
在多个进程协同工作的情况下,要协调各个进程对共享数据的访问,文件锁定是必不可少的工 具。
将文件映射到内存,这样在您看来,磁盘上的文件数据就像是在内存中一样。这利用了操作系 统的虚拟内存功能,无需在内存中实际保留一份文件的拷贝,就可实现文件内容的动态高速缓存
套接字( Sockets )
套接字通道类为使用网络套接字实现交互提供了新方法。套接字通道可工作于非块模式,并可 与选择器一同使用。因此,多个套接字可实现多路传输,管理效率也比 java.net 提供的传统套 接字更高。
三个新套接字通道,即 ServerSocketChannel、SocketChannel 和 DatagramChannel,
选择器( Selectors )
选择器可实现就绪性选择。Selector 类提供了确定一或多个通道当前状态的机制。使用选择 器,借助单一线程,就可对数量庞大的活动 I/O 通道实施监控和维护
字符集( Character sets )
java.nio.charsets 提供了新类用于处理字符与字节流之间的映射关系。您可以对字符转换映射方式进行选择,也可以自己创建映射
@Test
public void NIOTest_copy_1() throws Exception {
long startTime = System.currentTimeMillis();
FileChannel in = new FileInputStream("F:\\temporary\\read.txt").getChannel();
FileChannel out = new FileOutputStream("F:\\temporary\\write.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(in.read(buffer) != -1) {
buffer.flip(); //切换到读取数据模式
out.write(buffer);
buffer.clear();
}
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
}
@Test
public void NIOTest_copy_2() throws Exception {
long startTime = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);
FileChannel outChennel =
FileChannel.open(Paths.get("F:\\temporary\\write1.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//内存映射文件(什么模式 从哪开始 到哪结束)
MappedByteBuffer inMappeBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappeBuf = outChennel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
byte[] dst = new byte[inMappeBuf.limit()];
inMappeBuf.get(dst);
outMappeBuf.put(dst);
inChannel.close();
outChennel.close();
long end = System.currentTimeMillis();
System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
}
package Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.Channel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.DatagramChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import org.junit.jupiter.api.Test;
class NIOTest {
/**
*Position 缓冲区正在操作的位置默认从0开始
*limit 写数据时,limit表示可对Buffer最多写入多少个数据。 读数据时,limit表示Buffer里有多少可读数据(not null的数据),
*capacity 缓冲区最大容量 一旦声明不能改变
* 核心方法:
*put() 往buff存放数据
*get() 获取数据
*/
@Test
void test() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("我会嘤嘤嘤...".getBytes()); // 向 byteBuffer 中存放数据
byteBuffer.flip(); // 读取 buteBuffer 里面的内容
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println("读取内容为: "+new String(bytes,0,bytes.length));
byte[] bytes2 = new byte[byteBuffer.limit()]; // 重复读取
byteBuffer.rewind();
byteBuffer.get(bytes2);
System.out.println("读取内容为: "+new String(bytes2,0,bytes2.length));
}
/**
* @throws Exception
*/
@Test
public void NIOTest_1() throws Exception {
// 读取文件
// 第一步 获取通道
FileInputStream fin = new FileInputStream("F:\\temporary\\read.txt");
FileChannel fc = fin.getChannel();
// 第二步创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 第三步 将数据读到缓冲区
// 注意: 这里不需要告诉通道要读多少数据到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据
// 以及还有多少空间可以容纳更多的数据。我们将在缓冲区内部中介绍更多关于缓冲区统计机制的内容
fc.read(buffer);
// 查看刚刚读入缓冲区的内容
buffer.rewind();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes,0,bytes.length));
}
@Test
public void NIOTest_copy_1() throws Exception {
long startTime = System.currentTimeMillis();
FileChannel in = new FileInputStream("F:\\temporary\\read.txt").getChannel();
FileChannel out = new FileOutputStream("F:\\temporary\\write.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(in.read(buffer) != -1) {
buffer.flip(); //切换到读取数据模式
out.write(buffer);
buffer.clear();
}
in.close();
out.close();
long end = System.currentTimeMillis();
System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
}
@Test
public void NIOTest_copy_2() throws Exception {
long startTime = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);
FileChannel outChennel =
FileChannel.open(Paths.get("F:\\temporary\\write1.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//内存映射文件(什么模式 从哪开始 到哪结束)
MappedByteBuffer inMappeBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappeBuf = outChennel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
byte[] dst = new byte[inMappeBuf.limit()];
inMappeBuf.get(dst);
outMappeBuf.put(dst);
inChannel.close();
outChennel.close();
long end = System.currentTimeMillis();
System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
}
@Test
public void NIOTest_copy_3() throws Exception {
long startTime = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);
FileChannel outChennel = FileChannel.open(Paths.get("F:\\temporary\\write4.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
outChennel.transferFrom(inChannel,0,inChannel.size());
long end = System.currentTimeMillis();
System.out.println("nioCopyTest3耗费时间:"+(end-startTime));
}
/**
* 代码来源: 并发编程网 http://ifeve.com/channels/
* @throws Exception
*/
@Test
public void NIO_Test_1() throws Exception {
RandomAccessFile aFile = new RandomAccessFile("F:\\b\\write4.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print( new String());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
}
/**
*
* 使用 FileChannel 读取 buffer 的示例
* @throws IOException
*/
@Test
public void test_channel() throws Exception {
RandomAccessFile accessFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
FileChannel channel = accessFile.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(128);
byte[] bytes = null;
int length = -1;
while ((length = channel.read(buffer)) != -1) {
buffer.flip(); //切换到读数据
bytes = buffer.array();
System.out.println(new String(bytes,0,length,"UTF-8"));
buffer.clear(); // 要让 buffer 准备好再次被写入,可以通过 clear() 或 compact() 方法来完成
}
channel.close();
accessFile.close();
}
/**
* 1. flip() 方法 将 buffer 从写模式切换到读模式
* 2. 从 buffer 读取数据到 Channel 方法: channel.write(buffer);
* 3. rewind()将 position 设为0,所以你可以重读 Buffer 中的所有数据,limit 保持不变
* 仍表示能从 buffer 中读取多少个元素
* 4. clear() 与 compact() 方法
* 读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear() 或 compact() 方法完成
* 5. mark() 与 reset() 方法
* 通过 mark()方法可以标记 Buffer 中的一个特定 position.之后 可以通过 Buffer.reset 方法恢复这个
* position
* 6. equals() 与 compareTo() 方法
* equals() 满足下面条件时,表示两个 Buffer 相等
* ① 有相同的类型
* ② Buffer 中剩余的 byte char 等的个数
* ③ Buffer 中剩余的 byte char 等都相同
* 如你所见,equals 只是比较 Buffer 的一部分,不是每一个在它里面的元素都比较。实际上,它只比较
* Buffer 中剩余元素
* 7. compareTo() 方法
* 用于比较两个 Buffer 的剩余元素(byte、char)等,如果满足下列条件,则认为一个 Buffer “小于”另一个
* Buffer:
* ① 第一个不相等的元素小于另一个 Buffer 中对应的元素
* ② 所有元素都相等,但第一个 Buffer 比另一个先耗尽(第一个 Buffer 的元素比另一个少)
*
* @throws Exception
*/
public void test_buffer() throws Exception{
}
/**
* 分散(scater): 从 Channel 中读取是指在读取操作时将读取的数据写入多个 buffer 中。因此, Channel
* 将 Channel 中读取的数据分散(scatter) 到多个 Buffer 中
* 聚集(gather): 写入 Channel 是指在写操作时将 多个 buffer 的数据写入同一个 Channel,因此,Channel
* 将多个 Buffer 中的数据"聚集(gather)" 后发送到 Channel
* scatter/gather 经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你
* 可能会将消息和消息头分散到不同的 buffer 中,这样你可以方便的处理消息头和消息体
* @throws Exception
*/
@Test
public void test_ScaterAndGather() throws Exception{
RandomAccessFile accessFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
FileChannel channel = accessFile.getChannel();
ByteBuffer header = ByteBuffer.allocate(16);
ByteBuffer body = ByteBuffer.allocate(64);
ByteBuffer [] byteBuffers = {header , body};
channel.read(byteBuffers);
channel.write(byteBuffers);
channel.close();
accessFile.close();
}
/**
* 通道之间的数据传输
* 在 java NIO 中,如果两个通道中一个是 FileChannel ,那你可以将数据从一个 channel 传输到另一个 channel
*
* @throws Exception
*/
@Test
public void test_transferXXX() throws Exception{
RandomAccessFile readFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
FileChannel readChannel = readFile.getChannel();
RandomAccessFile writeFile = new RandomAccessFile("G:\\temp\\temFile\\write.txt", "rw");
FileChannel writeChannel = writeFile.getChannel();
long position = 0;
long count = writeChannel.size();
// ByteBuffer buffer = ByteBuffer.allocate(1024);
// int length = -1;
// while ((length = readChannel.read(buffer)) != -1) {
// buffer.flip();
//
// buffer.clear();
// }
writeChannel.transferFrom(readChannel, count, position);
readChannel.close();
writeChannel.close();
readFile.close();
writeFile.close();
}
/**
* Selector(选择器) 是 java NIO 中能够检测一到多个 NIO 通道,并且能够知晓是否为 诸如读写事件做好准备的组件。
* 这样,一个单独的线程可以管理多个 channel ,从而管理多个网络连接
* 为什么使用 Selector?(仅用单个线程来处理多个 Channels 的好处)
* 只需要更少的线程来处理通道,事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换开销
* 很大,而且每个线程都要占用系统资源(如内存)。因此,使用的线程越少越好。
* 但是,需要记住,现代的操作系统和 CPU 在多任务方面表现越来越好,所以多线程的开销随着时间的推移,变得越来越小了。
* 实际上,一个 CPU 有多个内核,不使用多任务可能是在浪费 CPU 的能力。不管怎么说,关于那种设计讨论应该放在另一篇不同
* 的文章中。这里只需要知道使用 Selector 能够处理多个通道就足够了
* @throws Exception
* 位置: http://ifeve.com/selectors/
* Java NIO 系列教程(六)
*/
@Test
public void test_Selector() throws Exception{
Channel channel = null;
// Selector 的创建
Selector selector = Selector.open();
// 向 Selector 注册通道
// 与 Selector 一起使用时, Channel 必须处于非阻塞模式下。这意味着不能将 FileChannel 与 Selector 一起
// 使用,因为 FileChannel 不能切换到 非阻塞模式,而 套接字通道都可以
// 注意 register() 方法的第二个参数。这是一个 "interest集合" , 意思是在通过 Selector 监听 Channel 时
// 对什么事件感兴趣。可以监听四种不同类型的事件
// 1. Connect
// 2. Accept
// 3. Read
// 4. Write
// 通道触发了一个事件意思是该事件已经就绪。所以,某个 channel 成功连接到另一个服务器称为"连接就绪". 一个
// Server socket channel 准备好接收新进入的连接称为"接收就绪"。一个有数据可读的通道可以说是“读就绪”。
// 等待写数据的通道可以说是 “写就绪”
// 这四种 事件可以用 SelectionKey 的四个常量表示:
// 1. SelectionKey.OP_CONNECT
// 2. SelectionKey.OP_ACCEPT
// 3. SelectionKey.OP_READ
// 4. SelectionKey.OP_WRITE
// 如果你对不止一种事件感兴趣,那么可以用"位或"操作符将常量连接起来,如下:
// int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
// channel.configureBlocking(false);
// SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
}
/**
* channel.size() 返回该实例所关联的大小
* channel.truncate(1024); 这个例子截取文件的前 1024 个字节,指定长度后面的部分将被删除
* channel.force(true); 将通道里尚未写入磁盘的数据强制写到磁盘上
* @throws Exception
*/
@Test
public void test_FileChannel_1() throws Exception{
RandomAccessFile writeFile = new RandomAccessFile("G:\\temp\\temFile\\write.txt", "rw");
FileChannel writeChannel = writeFile.getChannel();
String newData = "我会嘤嘤嘤123!!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
buffer.put(newData.getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
writeChannel.write(buffer);
}
writeChannel.close();
writeFile.close();
}
/**
* 主要方法:
* 1. position 方法(无参获取当前的位置,有参设置其位置)
* 有时候可能需要在 FileChannel 的某个特定位置进行数据的 读/写操作。可以通过 position() 方法获取 FileChannel 的当前位置
* 2. size 方法
* 返回该实例所关联文件的大小
* 3. truncate 方法,
* 截取文件时,文件将截取指定部分,指定长度后面部分将被删除
* 4. force 方法
* 将通道里尚未写入磁盘的数据强制写到磁盘上。处于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到 FileChannel 里的
* 数据一定会即使写到磁盘上。要保证这一点,需要调节 force() 方法
* @throws Exception
*/
@Test
public void test_FileChannel_2() throws Exception{
// 打开 channel
RandomAccessFile aFile = new RandomAccessFile("F:\\temporary\\write.txt", "rw");
FileChannel channel = aFile.getChannel();
// 从 channel 中读取数据
ByteBuffer buffer = ByteBuffer.allocate(64);
byte [] bytes = null ;
channel.read(buffer);
bytes = buffer.array();
System.out.println("第一次: "+new String( bytes,"utf-8"));
// 向 fileChannel 中写数据
buffer.clear();
String newData = "新增数据, 我会嘤嘤嘤......";
buffer.put(newData.getBytes());
buffer.flip(); // 没有这行代码,不会将数据写入磁盘
while(buffer.hasRemaining()) {
channel.write(buffer);
}
buffer.clear();
// channel.force(true);
// 再次查看内容
buffer.flip();
channel.read(buffer);
bytes = buffer.array();
System.out.println("第二次: "+new String( bytes,"utf-8"));
channel.close();
aFile.close();
}
/**
* NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道,可以通过一下两种方式创建 SocketChannel:
* 1. 打开一个 SocketChannel 并连接到互联网上的某台服务器
* 2. 一个新的连接到达 ServerSocketChannel 时,会创建一个 SocketChannel
* 打开 SocketChannel :
* SocketChannel socketChannel = SocketChannel.open();
* socketChannel.connect(new InetSocketAddress("http://jenkov.com),80);
* 从 SocketChannel 读取数据
* 要从 SocketChannel 中读取数据,调用一个 read() 的方法之一,如下例子:
* ByteBuffer buffer = ByteBuffer.allocate(48);
* int bytesRead = socketChannel.read(buf);
* 首先,分配一个 Buffer .从 SocketChannel 读取到的数据将会放到这个 Buffer 中,read() 方法返回的 int
* 值表示读了多少字节进 Buffer 里,如果返回的是 -1,表示已经读到了流的末尾(连接关闭了)
*
* @throws Exception
*/
@Test
public void test_socketChannel() throws Exception{
}
/**
* NIO 中的 ServerSocketChannel 是一个可以监听新进来的 TCP 连接的通道,就像标准 IO 中的 ServerSocket 一样。
* ServerSocketChannel 类在 java.nio.channels 包中
* 1. 打开 ServerSocketChannel 是通过调用 ServerSocketChannel.open()
* 2.关闭 ServerSocketChannel 是通过调用 serverSocketChannel.close()
* 3.监听新进来的连接 是通过 serverSocketChannel.accept(),当 accep() 方法返回的时候,它返回一个包含新进来的连接的
* SocketChannel。因此 accep() 方法会一直阻塞到有新连接到达
* 4.可以通过 serverSocketChannel.configureBlocking(false);将其设置为 非阻塞模式
*
* @throws Exception
*/
@Test
public void test_serverSocketChanneel() throws Exception{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false); // 将 serverSocketChannel 设置为非阻塞模式
int state = 0;
while(true) {
SocketChannel socketChannel = serverSocketChannel.accept();
// 其它 逻辑
state ++;
if(state > 1000) {
serverSocketChannel.close();
return ;
}
}
}
/**
* DatagramChannel 是一个能收发 UDP 包的通道,因为 UDP 是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接受的是
* 数据包
* @throws Exception
*/
@Test
public void test_datagramChannel() throws Exception{
// 打开 DatagramChannel
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
// 接收数据
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.clear();
channel.receive(buffer);
// 发送数据
String data = "从 Datagram 发送数据";
buffer.clear();
buffer.put(data.getBytes());
buffer.flip();
// 连接特定的地址
int bytesSent = channel.send(buffer, new InetSocketAddress("jenkov.com",80));
int bytesRead = channel.read(buffer);
int bytesWrite = channel.write(buffer);
}
/**
* NIO 管道是 2 个线程之间的单向数据连接。Pipe 有一个 source 通道和一个 sink 通道。数据会被写入 sink 通道,从
* source 通道中读取
* @throws Exception
*/
@Test
public void test_Pipe() throws Exception {
// 创建管道
Pipe pipe = Pipe.open();
// 向管道写数据
Pipe.SinkChannel sinkChannel = pipe.sink();
String data = "测试数据....";
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.clear();
buffer.flip();
while(buffer.hasRemaining()) {
sinkChannel.write(buffer);
break;
}
// 从管道中读取数据
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buffer2 = ByteBuffer.allocate(64);
int bytesRead = sourceChannel.read(buffer2);
System.out.println(new String(buffer2.array(),"utf-8"));
}
/**
* Java NIO 和 IO 的主要区别
* IO NIO
* 面向流 面向缓冲
* 阻塞IO 非阻塞 IO
* 无 选择器
* @throws Exception
*/
@Test
public void test_NIOAndIO() throws Exception{
}
/**
* Path 接口是 java nio2的一部分,全称是 java.nio.file.Path. java 中的 Path 表示文件系统的路径,可以指向文件或文件夹,
* 也有相对路径和绝对路径之分。绝对路径表示从文件系统的根路径到文件或是文件夹的路径。相对路径表示从特定路径下访问指定文件或是文件夹的路径。
* 在很多方面, java.nio.file.Path 接口和 java.io.File 有相似性,但也有一些细微的差别。在很多情况下,可以用 Path 代替。
* @throws Exception
*/
@Test
public void test_NIOPath() throws Exception{
// 绝对路径
Path path = Paths.get("F:\\temporary\\write.txt");
// 相对路径
// 相对路径指一个已确定的路径开始到某一文件或文件夹的路径.将确定路径和相对路径
// 拼接起来就是对应的绝对路径地址
Path projects = Paths.get("F:\\temporary","write.txt");
// .(点) 表示当前路径
Path currentDir = Paths.get(".");
System.out.println(currentDir.toAbsolutePath());
// Path.normalize()
// Path 的 normalize() 方法可以标准化路径。标准化的含义是路径中的 . 和 ..(点) 都被去掉,指向真正的路径目录地址。下面
// Path.normalize() 示例
}
/**
* Files 类( java.nio.file.File)提供了操作文件相关方法
* @throws Exception
*/
@Test
public void test_NIOFiles() throws Exception{
}
/**
* AsynchronousFileChannel 被添加到 Java NIO 中。使用 AsynchronousFileChannel 可以实现异步地
* 读取和写入文件数据,它提供了两种读取数据的方式,都是 调用它本身的 read() 方法。
* @throws Exception
*/
@Test
public void test_AsynchronousFileChannel() throws Exception{
Path path = Paths.get("F:\\temporary\\write.txt");
AsynchronousFileChannel channel =
AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(128);
// 1. 使用 Futrue 读取数据
Future<Integer> operation1 = channel.read(buffer, 0);
while(!operation1.isDone());
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();
// 使用 CompletionHandler 读取数据
// 一旦读取操作完成, CompletionHandler 的 complete() 方法将会被调用。它的第一个参数是 Integer
// 类型,表示读取的字节数。第二个参数 attachment 是 ByteBuffer类型的,用来存储读取的数据,它 其实就是由 read()
// 方法的第三个参数。
int position = 0;
channel.read(buffer, position,buffer,new CompletionHandler<Integer,ByteBuffer>(){
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("result = "+result);
attachment.flip();
byte[] data = new byte[attachment.limit()];
attachment.get(data);
System.out.println(new String(data));
attachment.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
// TODO Auto-generated method stub
}
});
}
}