Java NIO

1、NIO概述

  NIO即New IO,它是Java新的I/O技术,要比原来面向流的I/O效率要高,因为它使用的是内存映射文件的方式来处理I/O,Java NIO的类都放在java.nio包及其子包下,相关的类有:Buffer(缓冲)、Channel(通道)、Charset、Selector等。

  Channel通道通过节点(文件、管道、数组、字符串)流的getChannel()方法来获得,成员方法read()、write()用来读写数据,而且只能通过Buffer作为缓冲来读写Channel中的数据。NIO与BIO的一个区别就是,NIO读写数据通过通道,BIO读写数据通过节点的流。通道与流的区别是,通道是全双工的,可以同时进行读写,流是半双工的,只能进行读或写。

  Buffer是一个缓冲区,它是一个抽象类,包含了想要写入的数据或要读取的数据,常用的子类有ByteBuffer、CharBuffer、IntBuffer、MappedByteBuffer等。NIO与BIO的一个区别就是,NIO读写数据需要通过Buffer,而面向流的I/O中可以直接将数据写入或读取到Stream对象中。

   比如我们要向一个文件写入数据,使用NIO的话,可以先获得文件的Channel,然后将数据先写到Buffer中,再将Buffer写入到Channel中。

  Charset可以将Unicode字符串(CharBuffer)和字节序列(ByteBuffer)相互转化。

  Selector是支持非阻塞I/O的相关类。

2、Buffer

  Buffer没有构造器,使用Buffer子类的静态方法allocate()来创建对应的ByteBuffer、CharBuffer、IntBuffer、DoubleBuffer等类型的对象,或者使用Channel实例的map()方法将Channel中的全部数据或部分数据映射到一个Buffer来获得Buffer。

  缓冲区不仅相当于是一个数组,它还提供了对数据结构化访问及维护读写位置等信息。当向Buffer写入数据后,应该调用flip()方法来设置Buffer中的相关数据位置信息以便读取使用,读取Buffer中数据完毕之后一般也是调用clear()方法来恢复数据位置信息到初始化的时候,如下所示:

    

  

import java.nio.*;

public class Test
{	
	public static void main(String[] args)
	{		
		
		//向Buffer写入数据
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		buffer.put("abcd".getBytes());
		buffer.flip();
		
		//从Buffer获取数据
		byte data[] = new byte[1024];
		//buffer.get(data); //获取data大小的数据
		buffer.get(data, 0, buffer.limit()); //获取全部数据
		buffer.clear();
    }
}

  以下为Buffer中的一些成员方法:

  allocate():Buffer子类的静态方法,生成对应的Bufer对象。 ByteBuffer还有一个allocateDirect()方法来创建ByteBuffer,它使用的是系统原生的IO操作,效率会高一点,但占用资源高点,适合用于大型、存活长的ByteBuffer对象。

  Flips():设置当前的数据信息以读取数据。

  clear():恢复数据信息以进行下次写数据。

  wrap():将数组转换为对应的Buffer对象。

  put():向Buffer写入数据,带索引参数的版本不会移动position(即追加写入)。
  get():从Buffer取出数据,带索引参数的版本不会移动position。
  capacity():获得Buffer的大小capacity。
  remaining():获得当前位置position和界限limit之间元素的个数,hasRemaining() 判断当前位置position和界限limit之间是否还有元素可供处理。
  limit():获得或者设置Buffer的界限limit的位置。
  position():获得或者设置Buffer的位置position。
  mark():与reset()配合使用,设置Buffer的mark位置。
  reset():将位置positon转到mark所在的位置。

  compact():将所有的未读数据拷贝到起初处,然后将position设置到未读数据最后一位的后一位。比如业务中一包大小为1024,我们读取了Buffer的1024个数据后发现剩余的数据大小为800,不足1024,那么我们可以对Buffer做compact(),这样剩余的800个未读数据会被复制到Buffer头,而Position位置被设置到801,这样下次写入的时候就会从801处开始写。
  rewind() 将位置position设为0,limit不变,并且取消设置的mark?。

3、Channel
  用于读写的流是半双工的,可以进行读或写,但读写不能同时进行,Channel则是全双工的,读写可以同时进行。

  Channel接口下有用于文件IO的FileChannel,用于UDP通信的DatagramChannel,用于TCP通信的SocketChannel、ServerSocketChannel,用于线程间通信的Pipe.SinkChannel、Pipe.SourceChannel等实现类。

  Channel也不是通过构造器来创建对象,而是通过节点流的getChannel()方法来获得,如通过FileInputStream、FileOutputStream、RandomAccessFile的getChannel()获得对应的FileChannel。也可以使用工具类Channels的静态方法newChannel()来从InputStream、OutputStream建立ReadableByteChannel、WriteableByteChannel。使用Channels的newXXX()来取得指定Channel实例的InputStream、OutputStream、Reader、Writer对象。

  Channel接口是AutoClosable的子接口,可以使用尝试关闭资源语法。

  Channel中常用的方法有map()、read()、write(),write()将Buffer中数据写入到Channel(相当于是读Buffer,所以执行完后应该调用Buffer的clear()方法),read()从Channel中读取数据到Buffer(相当于是写Buffer,所以执行后应该调用flip()方法),map()用来将Channel中全部数据或部分数据映射到一个MappedByteBuffer(ByteBuffer的子类)。FileChannel中还有Position()来获得或者设置当前数据指针。

  下面是使用Channel和Buffer来向一个文件写入数据的示例:

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;

public class Test
{	
	public static void main(String[] args)
	{		
		try(
			//以读、写方式打开一个文件,文件不存在就创建
			RandomAccessFile file = new RandomAccessFile("out.txt" , "rw")){
				
				//获得文件对应的Channel
				FileChannel fileChannel = file.getChannel();//RandomAccessFile的getChannel()可以获得文件流的Channel
			
				//向Buffer写入数据
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				buffer.put("测试数据".getBytes());
				buffer.flip();
				
				//将Buffer中数据写入到Channel中
				fileChannel.write(buffer);
		        buffer.clear();
		        
		        //关闭文件也会关闭文件的Channel,相当于也调用了fileChannel.close();
		        file.close();
			}
			catch (IOException ex){
				ex.printStackTrace();
			}
	}
}

  下面为使用RandomAccessFile对应的Channel将文件a.txt中全部数据映射到Buffer后再复制该Buffer中数据到文件末尾的示例:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class RandomFileChannelTest
{
	public static void main(String[] args)
		throws IOException
	{
		File f = new File("a.txt");
		try(
			RandomAccessFile raf = new RandomAccessFile(f, "rw");
			FileChannel randomChannel = raf.getChannel()) 
		{
			// 将Channel中所有数据映射成ByteBuffer
			ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0 , f.length());
			// 把Channel的记录指针移动到最后
			randomChannel.position(f.length());
			// 将buffer中所有数据输出到Channel
			randomChannel.write(buffer);
		}
	}
}

  FileChannel中提供lock()/tryLock()方法来防止其他进程并发修改同一个文件,lock()返回FileLock类型的对象,其release()方法用来释放文件锁。需要注意的是在某些平台下关闭FileChannel时,会释放java虚拟机在该文件上的所有锁。

4、Charset

  Java中默认使用Unicode字符集,可以通过Charset来处理字节序列和字符序列(字符串)之间的转换,其availableCharsets()静态方法可以获得当前JDK支持的所有字符集。调用Charset的静态方法forName()可以获得指定字符集对应的Charset对象,调用该对象的newEncoder()、newDecoder()可以获得对应的编码器、解码器,调用编码器的encode()可以将CharBuffer或String转换为ByteBuffer,调用解码器的decode()可以将ByteBuffer转换为CharBuffer。

  下面的例子没有使用map来将文件映射到内存,而是使用FileChannel每次只从文件test.data读取256字节的内容到ByteBuffer,因为文件内容是GBK格式的,所以通过Charset解码ByteBuffer的内容到CharBuffer里:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

public class ReadFile
{
	public static void main(String[] args)
		throws IOException
	{
		try(
			FileInputStream fis = new FileInputStream("test.data");
			FileChannel fcin = fis.getChannel())
		{
			ByteBuffer bbuff = ByteBuffer.allocate(256);
			while( fcin.read(bbuff) != -1 ) //从Channel读取数据后保存到Buffer
			{
				// 锁定Buffer的空白区:向Buffer写入数据后调整数据位置指针,为从Buffer读取数据做准备
				bbuff.flip();
				
				// 创建Charset对象
				Charset charset = Charset.forName("GBK");
				// 创建解码器(CharsetDecoder)对象
				CharsetDecoder decoder = charset.newDecoder();
				// 将ByteBuffer的内容解码
				CharBuffer cbuff = decoder.decode(bbuff);
				System.out.print(cbuff);
				
				// 将Buffer初始化:从Buffer读取数据后调整数据位置指针,为下一次保存数据做准备
				bbuff.clear();
			}
		}
	}
}

5、Selector

  Selector用来支持多路复用处理,它被称为选择器或多路复用器,可以在seletor上注册多个Channel,当某个Channel上可读或者可写,那么这个Channel就处于就绪状态,就会被Selector监测出来,然后通过一个SelectionKey来表示这个Channel,可以进行后续的I/O操作。

  DatagramChannel、SocketChannel、ServerSocketChannel都是SelectableChannel的子类,可以配合Selector来使用。

  关于Selector的使用详见Java网络编程篇章。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值