Java NIO笔记(五):通道概论&文件通道

        通道是NIO的一个主要创新,用于在Buffer与通道另一端之间进行有效的数据传输,这点在《Java NIO笔记(一):NIO介绍》有讲过,这里不在赘述。

        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状态。

一、分散读到多个缓冲区 & 从多个缓冲区聚集写入通道

		//test.txt中只有一行数据:whoareu?
		//创建2个缓冲区,组成一个缓冲区数组
		//打开一个文件通道,将test.txt中的数据读到缓冲区数组中
		//缓冲区数组会自动填充buffA和buffB这两个缓冲区
		//buffA填满后,再继续填充buffB
		//达到分散读取数据到多个缓冲区中
		ByteBuffer buffA = ByteBuffer.allocate(6);
		ByteBuffer buffB = ByteBuffer.allocate(5);
		ByteBuffer[] buffArr = { buffA, buffB };
		RandomAccessFile raf = new RandomAccessFile("test.txt", "r");
		FileChannel fc = raf.getChannel();
		fc.read(buffArr);
		System.out.println(new String(buffA.array()));//输出who
		System.out.println(new String(buffB.array()));//输出areu?
		fc.close();
		raf.close();
		//创建两个Buffer,组成一个Buffer Array
		//将Buffer Array的数据写入到文件通道到
		//达到聚集缓冲区其中写入通道目的
		byte[] byteA = "hello ".getBytes("UTF-8");
		ByteBuffer buffC = ByteBuffer.wrap(byteA);
		byte[] byteB = "world!".getBytes("UTF-8");
		ByteBuffer buffD = ByteBuffer.wrap(byteB);
		ByteBuffer[] allBuff = {buffC,buffD};
		RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
		FileChannel fc = raf.getChannel();
		fc.write(allBuff);
		fc.close();
		raf.close();
二、文件通道(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关闭时也会释放文件锁。
        实际应用中,一般使用共享受读文件,使用独占所写文件
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值