NIO Channel

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/M_Joes_17/article/details/82084891

通道是NIO的一个主要创新,用于在Buffer与通道另一端之间进行有效的数据传输

I/O可以分为文件IO和流IO,那么通道对应的就可以分为文件通道(FileChannel)和流通道(流通道就是套接字通道,SocketChannel),所以NIO中有四种通道实现类:

  • FileChannel:文件通道,用于操作文件I/O
  • ServerSocketChannel:服务器套接字通道,用于TCP连接响应客户端连接
  • SocketChannel:套接字通道,用于TCP协议,客户端连接服务器后,服务器和客户端都会有一个SocketChannel,就可以互相发送数据了
  • DatagramChannel:数据报通道,用于UDP协议

  一、打开一个通道的方法如下:

                //打开一个文件通道,指定为可读写
		RandomAccessFile raf = new RandomAccessFile("d:/test.txt", "rw");
		FileChannel fc = raf.getChannel();
		// 打开一个服务器套接字通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		// 打开一个套接字通道,
		SocketChannel sc = SocketChannel.open();
		// 打开一个数据报通道
		DatagramChannel dc = DatagramChannel.open();

文件通道还可以通过底层文件句柄的的方式获得,但是这样有可能导致不能读写文件

                //不要使用这种方式获取通道实例
		FileInputStream fis = new FileInputStream("d:/test.txt");
		FileChannel fileChannel = fis.getChannel();
		ByteBuffer buff = ByteBuffer.allocate(8192);
		fileChannel.write(buff);

还可以通过java.nio.channels.Channels这个工具类获取通道实例,下面是一个例子:

                // 创建一个可读通道
		ReadableByteChannel rbc = Channels.newChannel(System.in);
		// 创建一个可写通道
		WritableByteChannel wbc = Channels.newChannel(System.out);
		// 创建一个大小为8192字节的字节缓冲区
		ByteBuffer buff = ByteBuffer.allocate(8192);
		// 轮询将可读通道的数据读到缓冲区
		while (rbc.read(buff) != -1) {
			// 翻转缓冲区
			buff.flip()
			String str = new String(buff.array()).trim();
			// 若输入"bye"则关闭通道
			if (str.equals("bye")) {
				rbc.close();
				wbc.close();
				break;
			}
			// 将缓冲区的数据写入到可写通道
			wbc.write(buff);
			// 轮询缓冲区是否还有剩余数据
			while (buff.hasRemaining()) {
				wbc.write(buff);
			}
			// 清空缓冲区
			buff.clear();
		}

 通道可以以阻塞(blocking)或非阻塞(non-blocking)模式运行,阻塞模式会一直等待某个操作直到返回结果;非阻塞不会一直等待,要么返回null,要么返回执行完的结果。只有流通道才能已non-blocking模式运行,如Socket和Pipe。

   Socket通道类继承SelectableChannel,只有SelectableChannel类才能与选择器(Selector)一起使用。

关闭通道使用close()方法,调用close()方法根据操作系统的网络实现不同可能会出现阻塞,可以在任何时候多次调用close();若出现阻塞,第一次调用close()后会一直等待;若第一次调用close()成功关闭后,之后再调用close()会立即返回,不会执行任何操作。

在一个已关闭的通道上进行I/O操作会抛出ClosedChannelException,可以通道isOpen()方法来检查通道时候为打开状态。

 NIO中的通道都实现了InterruptibleChannel,若某个线程上有一个处于阻塞状态的通道,线程被中断会抛出ClosedByInterruptException,并会关闭通道。可以调用isInterrupted()方法检查某个线程的interrupt状态。

扫描二维码关注公众号,回复: 2926786 查看本文章

 二、文件通道(FileChannel)

        FileChannel不能直接创建,只能通过创建一个文件对象(RandAccessFile、FileInputStream、FileOutputStream)后调用其getChannel()方法获得。FileChannel是线程安全,多个进程并发操作同一文件不会引起任何问题;兵法行为受低层操作系统或文件系统影响。

   FileChannel类保证同一个JVM上的所有FileChannel实例看到的文件内容是一致的,但不能保证外部的非Java进程看到的该文件视图一致,也可能一致,这取决于低层操作系统的实现。

       打开一个文件通道可以使用下面方式:

                //RandomAccessFile有2中构造方法,下面的构造方法等同于:
		//RandomAccessFile raf = new RandomAccessFile(new File("test.txt"), "rw");
		RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
		FileChannel fc = raf.getChannel();

  RandomAccessFile构造方法的第二个参数含义如下:

  • "rw":对文件可读可写,若文件不存在则会创建该文件
  • "r":只读
  • "rws":对文件可读写,并且文件中数据和元信息(若更新时间等)的每个更新都会写入到磁盘
  • "rwd":对文件可读写,并且文件数据的每个更新会同步写入到磁盘

   文件锁在Jdk1.4时才被提供,当多个程序并发操作同一个文件时,可以使用文件锁来锁定文件同一时刻只能接受一个程序的IO操作。文件锁分为独占锁和共享锁,FileChannel提供了文件锁的API,在FileChannel的文件锁方式中,锁的对象是文件而不是通道或线程,也就是说文件锁不适用于同一个JVM上多个线程并发访问文件的情况。同一个JVM中,一个线程获得了某个文件的独占锁,第二个线程也可以获得这个文件的独占锁;但是,在不同的JVM中,第一个JVM的线程获得的某个文件的独占所,第二个JVM的线程会被阻塞。导致这样的情况的原因是文件锁是由操作系统在进程级上来判优的,而不是在线程级上。

文件锁可以通FileChannel的lock()或tryLock()方法获取,两者的区别如下:

  • lock():阻塞,第一个线程获取文件锁后,第二个线程必须等待
  • tryLock():非阻塞,若不能立即获得文件锁则返回null
                RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
		FileChannel fc = raf.getChannel();
		//--------------------------------------------
		//阻塞获得文件独占锁,并锁定文件所有数据
		FileLock lock0 = fc.lock();
		//阻塞获得文件独占锁,并锁定文件指定数据
		FileLock lock1 = fc.lock(0, 8192, false);
		//阻塞获得文件共享锁,并锁定文件0 ~ 8192字节的数据
		FileLock lock2 = fc.lock(0, 8192, true);
		//--------------------------------------------
		//非阻塞获取文件独占所,等同于lock()
		FileLock lock3 = fc.tryLock();
		//非阻塞获取文件独占所,等同于fc.lock(0, 8192, false);
		FileLock lock4 = fc.tryLock(0, 8192, false);
		//非阻塞获取文件共享锁,等同于fc.lock(0, 8192, true);
		FileLock lock5 = fc.tryLock(0, 8192, true);

FileLock对象关联FileChannel,FileLock API如下:

  • channel():获取关联的FileChannel
  • isShared():判断是共享锁还是独占锁,返回ture是共享锁,返回false是独占所
  • overlaps(long position, long size):判断当前文件锁锁定的区域是否有交叉,也就是是否也被别线程锁定了
  • isValid():判断当前文件锁是否有效
  • release():释放文件锁,通道被关闭或JVM关闭时也会释放文件锁。

        实际应用中,一般使用共享受读文件,使用独占所写文件

、Socket通道(SocketChannel)

     新的socket通道类可以运行非阻塞模式并且是可选择的。这两个性能可以激活大程序(如网络服务器和中间件组件)巨大的可伸缩性和灵活性。NIO模式下再也没有为每个socket连接使用一个线程的必要了,也避免了管理大量线程所需的上下文交换总开销。借助新的NIO类,一个或几个线程就可以管理成百上千的活动socket连接了并且只有很少甚至可能没有性能损失。所有的socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)都继承了位于java.nio.channels.spi包中的AbstractSelectableChannel。这意味着我们可以用一个Selector对象来执行socket通道的就绪选择(readiness selection)。

ServerSocketChannel

public abstract class ServerSocketChannel extends AbstractSelectableChannel
  {
      public static ServerSocketChannel open() throws IOException;
      public abstract ServerSocket socket();
      public abstract ServerSocket accept()throws IOException;
      public final int validOps();
  }  

ServerSocketChannel是一个基于通道的socket监听器。它同我们所熟悉的java.net.ServerSocket执行相同的基本任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。

SocketChannel

​SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));​
socketChannel.close();  

从 SocketChannel 读取数据

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

写入 SocketChannel

String newData = "New String to write to file..." + System.currentTimeMillis();
 
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
    channel.write(buf);
}

注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

DatagramChannel

正如SocketChannel模拟连接导向的流协议(如TCP/IP),DatagramChannel则模拟包导向的无连接协议(如UDP/IP)。

DatagramChannel是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据负载。与面向流的的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址。同样,DatagramChannel对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。

猜你喜欢

转载自blog.csdn.net/M_Joes_17/article/details/82084891