Java NIO使用和总结

Java NIO使用和总结

0.前言

NIO即New IO,是在java io机制的基础上增加的内容。这篇主要学习和使用它的用法, 主要的学习来自并发编程网http://ifeve.com/java-nio-all/

1.Channel(通道)

Channel其实就是对流进行了改进,使得既可以读也可以写,而一般意义上的流通常是单向的,这就是Channel的产生。下面看一下它的用法:

	/**1.通道基本用法
	 * channel类型
	 * FileChannel 文件中读写数据
	 * DatagramChannel UDP中读写数据
	 * SocketChannel TCP中读写数据
	 * ServerSocketChannel 监听TCP连接信息
	 */
	public static void main(String[] args) throws Exception {
		/*文件输入流*/
		FileInputStream filInputStream = new FileInputStream(new File("channel1.txt"));
		/*从中获取通道*/
		FileChannel channel1 = filInputStream.getChannel();
		/*分配48字节capacity的ByteBuffer*/
		ByteBuffer ByteBuffer = ByteBuffer.allocate(6);
		/*写模式初始*/
//		System.out.println("Channel1.main()position:"+ByteBuffer.position());
//		System.out.println("Channel1.main()limit:"+ByteBuffer.limit());
//		System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());
		/*把数据从channel中读取到ByteBuffer*/
		int ByteRead = channel1.read(ByteBuffer);
		while(-1 != ByteRead){
			/*写模式结束*/
//			System.out.println("Channel1.main()position:"+ByteBuffer.position());
//			System.out.println("Channel1.main()limit:"+ByteBuffer.limit());
//			System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());
			
			System.out.println("Channel1-ByteRead-wri:"+ByteRead);
			/*反转buffer,其实就是从写模式切换到读模式,后面详述*/
			ByteBuffer.flip();
			/*读模式开始*/
//			System.out.println("Channel1.main()position:"+ByteBuffer.position());
//			System.out.println("Channel1.main()limit:"+ByteBuffer.limit());
//			System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());
			
			while(ByteBuffer.hasRemaining()){
				System.out.println("Channel1-ByteRead-get:"+ByteBuffer.get());
			}
			/*读模式结束*/
//			System.out.println("Channel1.main()position:"+ByteBuffer.position());
//			System.out.println("Channel1.main()limit:"+ByteBuffer.limit());
//			System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());
			ByteBuffer.clear();
			ByteRead = channel1.read(ByteBuffer);
		}
	}

2.Buffer

之前我们对流进行操作时,通常会用一个byte或者一个byte数组来接受,但是我们需要更多的功能,比如记录读到哪一个位置,这个时候操作数组很难满足,所以就有了Buffer,下面看一下它的用法:

/**2.buffer基本用法
	 * Buffer的capacity,position和limit
	 * capacity:不管是读模式,还是写模式,都代表缓冲区的长度
	 * limit:
	 * 在写模式下,limit其实就是capacity
	 * 在读模式下,limit代表已经写入元素的数量
	 * position:
	 * 在写模式下,从零开始不管增加,最大值是capacity,position的值就代表已经写入的数据数量
	 * 在读模式下,也是从零开始,按照写入的顺序把数据读取出来,最大值是limit,position的值就代表已经读取的数据数量
	 */

上图的箭头方法分别表示数据的写入和读取的方向。写模式下,position从上往下,读模式下,position也是从上往下。  实际上就是position原本指向3,变成读模式后,就指向1了,如何测试buffer的位置,以及limit和capacity,只要把上面代码的注释去掉即可。 另外buffer还有一些其他的方法:

/**@Description
	 * clear()和compact方法、
	 * clear():如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值
	 * compact():compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。
	 * limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
	 */
	
	/**@Description
	 * 此外,还有其他一些方法
	 * mark(),rewind(),reset(),equals()和compareTo()方法
	 */

3.Scatter和Gather的思想

	/**3.Scatter/Gather(分散/聚集)
	 *  Scattering Reads是指数据从一个channel读取到多个buffer中
	 *  Gathering Writes是指数据从多个buffer写入到同一个channel
	 */

看一下这种思想的用法:

public static void main(String[] args) throws Exception {
		/*文件输入流*/
		FileInputStream filInputStream = new FileInputStream(new File("channel1.txt"));
		filInputStream.available();
		/*从中获取通道*/
		FileChannel channel1 = filInputStream.getChannel();
		/*分配48字节capacity的ByteBuffer*/
		ByteBuffer bufferHeader = ByteBuffer.allocate(8);
		ByteBuffer bufferBody = ByteBuffer.allocate(8);
		
		ByteBuffer[] bufferArray = {bufferHeader,bufferBody};
		/*把数据从channel中读取到ByteBuffer*/
		channel1.read(bufferArray);
		System.out.println("bufferHeader:"+bufferHeader);
		System.out.println("bufferBody:"+bufferBody);
		bufferHeader.flip();
		while(bufferHeader.hasRemaining()){
			System.out.println("Channel1.main():"+bufferHeader.get());
		}
		
		ByteBuffer bufferHeader2 = ByteBuffer.allocate(8);
		ByteBuffer bufferBody2 = ByteBuffer.allocate(8);
		bufferHeader2.put("a".getBytes());
		bufferBody2.put("b".getBytes());
		/*注意切换成读模式,否则channel2中得不到正确的信息*/
		bufferHeader2.flip();
		bufferBody2.flip();
		System.out.println("bufferHeader2:"+bufferHeader2);
		System.out.println("bufferBody2:"+bufferBody2);
		ByteBuffer[] bufferArray2 = {bufferHeader2,bufferBody2};
		/*文件输入流*/
		FileOutputStream fileOutputStream = new FileOutputStream(new File("channel2.txt"));
		/*从中获取通道*/
		FileChannel channel2 = fileOutputStream.getChannel();
		channel2.write(bufferArray2);
	}

4.通道之间的数据传输

	/**4.通道之间数据传输
	 * 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据
	 * 从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。
	 * */
看一下它们的用法:
	public static void main(String[] args) throws IOException {
		/*测试transferFrom方法*/
		RandomAccessFile fromFile = new RandomAccessFile("channel1.txt", "rw");
		FileChannel      fromChannel = fromFile.getChannel();
		RandomAccessFile toFile = new RandomAccessFile("channel2.txt", "rw");
		FileChannel      toChannel = toFile.getChannel();
		long position = 0;
		long count = fromChannel.size();
		toChannel.transferFrom(fromChannel, position,count);
		System.out.println("Channel1.main()");
	}
public static void main(String[] args) throws IOException {
		/*测试transferTo方法*/
		RandomAccessFile fromFile = new RandomAccessFile("channel1.txt", "rw");
		FileChannel      fromChannel = fromFile.getChannel();
		RandomAccessFile toFile = new RandomAccessFile("channel2.txt", "rw");
		FileChannel      toChannel = toFile.getChannel();
		long position = 0;
		long count = fromChannel.size();
		fromChannel.transferTo(position, count, toChannel);
		System.out.println("Channel1.main()");
	}

5.关于几种流传输速度的比较

传统的流在数据传输方面,可以设置自定义的数组大小,因为一个一个字节的传输肯定是最慢的;下面的测试数据大小为60M

1)数组大小为1024(通常应用中设置的值)时,传输测试:

	public static void main(String[] args) throws IOException {

		/*1.传统的输入输出流,一次读取1024字节*/
		FileInputStream fileInputStream1 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));
		FileOutputStream fileOutputStream1 = new FileOutputStream(new File("E:\\Majintao\\software2.rar"));
		Date dateFirst1 = new Date();
		int readByte1 = 0;
		byte[] byte1 = new byte[1024];
		while(-1 != (readByte1 = fileInputStream1.read(byte1))){
			fileOutputStream1.write(byte1,0,readByte1);
		}
		fileInputStream1.close();
		fileOutputStream1.close();
		Date dateLast1 = new Date();
		Long timeDue1 = dateLast1.getTime()-dateFirst1.getTime();
		System.out.println("传统流传输时间:"+timeDue1);
	}
传统流传输时间:794
2)缓冲流,实际上是对流增加了一层缓冲,传输测试:
/*2.使用缓冲流来传输*/
		FileInputStream fileInputStream2 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));
		FileOutputStream fileOutputStream2 = new FileOutputStream(new File("E:\\Majintao\\software3.rar"));
		BufferedInputStream bufferedInputStream1 = new BufferedInputStream(fileInputStream2);
		BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(fileOutputStream2);
		Date dateFirst2 = new Date();
		int readByte2 = 0;
		byte[] byte2 = new byte[1024];
		while(-1 != (readByte2 = bufferedInputStream1.read(byte2))){
			bufferedOutputStream1.write(byte2,0,readByte2);
		}
		bufferedInputStream1.close();
		bufferedOutputStream1.close();
		fileInputStream2.close();
		fileOutputStream2.close();
		Date dateLast2 = new Date();
		Long timeDue2 = dateLast2.getTime()-dateFirst2.getTime();
		System.out.println("缓冲流传输时间:"+timeDue2);
缓冲流传输时间:189

3)字符流,其实字符流本不应该参与这次的数据比较,因为它的优势是处理字符文件,这里还是把它拿出来测试,传输测试:

		/*3.使用字符流*/
		FileReader fileReader = new FileReader("E:\\Majintao\\software1.rar");
		FileWriter fileWriter = new FileWriter("E:\\Majintao\\software4.rar");
		Date dateFirst3 = new Date();
		char[] char1 = new char[1024];
		int readByte3 = 0;
		while(-1 != (readByte3 = fileReader.read(char1))){
			fileWriter.write(char1);
		}
		fileReader.close();
		fileWriter.close();
		Date dateLast3 = new Date();
		Long timeDue3 = dateLast3.getTime()-dateFirst3.getTime();
		System.out.println("字符流流传输时间:"+timeDue3);
字符流流传输时间:4516
4)自定义接受数组的大小,在使用传统流时,自定义最合适的数组大小,传输测试:

		/*4.在上面1的例子上自定义缓冲数组的大小,调整为最合适的大小*/
		FileInputStream fileInputStream4 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));
		FileOutputStream fileOutputStream4 = new FileOutputStream(new File("E:\\Majintao\\software5.rar"));
		Date dateFirst4 = new Date();
		int readByte4 = 0;
		byte[] byte4 = new byte[204800];
		while(-1 != (readByte4 = fileInputStream4.read(byte4))){
			fileOutputStream4.write(byte4,0,readByte4);
		}
		fileInputStream4.close();
		fileOutputStream4.close();
		Date dateLast4 = new Date();
		Long timeDue4 = dateLast4.getTime()-dateFirst4.getTime();
		System.out.println("传统流传输时间(自定义缓冲数组大小):"+timeDue4);
传统流传输时间(自定义缓冲数组大小):77
5)使用filechannel的transferTo或者transferFrom方法(缺点是只能传输2G以下的数据),传输测试:
		/*5.使用fileChannel*/
		FileInputStream fileInputStream5 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));
		FileOutputStream fileOutputStream5 = new FileOutputStream(new File("E:\\Majintao\\software6.rar"));
		FileChannel inFileChannel = fileInputStream5.getChannel();
		FileChannel outFileChannel = fileOutputStream5.getChannel();
		Date dateFirst5 = new Date();
		inFileChannel.transferTo(0,inFileChannel.size(),outFileChannel);
		fileInputStream5.close();
		fileOutputStream5.close();
		inFileChannel.close();
		outFileChannel.close();
		Date dateLast5 = new Date();
		Long timeDue5 = dateLast5.getTime()-dateFirst5.getTime();
		System.out.println("filechannel传输:"+timeDue5);
filechannel传输:52

可以看到速度比自定义最合适的数组还要快

6)使用filechannel+buffer,传输测试:
		/*6.使用fileChannel 通过buffer */
		FileInputStream fileInputStream6 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));
		FileOutputStream fileOutputStream6 = new FileOutputStream(new File("E:\\Majintao\\software7.rar"));
		Date dateFirst6 = new Date();
		FileChannel inFileChannel6 = fileInputStream6.getChannel();
		FileChannel outFileChannel6 = fileOutputStream6.getChannel();
		
		int readByte6 = 0;
		ByteBuffer buffer = ByteBuffer.allocate(409600);
		while(-1 != (readByte6 = inFileChannel6.read(buffer))){
			buffer.flip();
			outFileChannel6.write(buffer);
			buffer.clear();
		}
		
		fileInputStream6.close();
		fileOutputStream6.close();
		inFileChannel6.close();
		outFileChannel6.close();
		Date dateLast6 = new Date();
		Long timeDue6 = dateLast6.getTime()-dateFirst6.getTime();
		System.out.println("filechannel--buffer传输:"+timeDue6);
filechannel--buffer传输:79

可以看到速度等同或者说略慢于自定义最合适数组

PS:来对几种传输做一个比较,同时还会引出两个问题:

	/**
	 *在速度的比较上 
	 *最快的是transferFrom和transferTo方法(示例5)
	 *然后是channel-buffer-channel(示例6)和自定义byte(示例4)
	 *接着是缓冲流(示例2)
	 *接着是(示例1),通常我们没有调整缓冲数组大小,直接设置为1024;
	 *接着是字符流(示例3)
	 *下面有两个问题
	 *1)为什么channel会比较快
	 *因为底层是C操作
	 *2)在自定义缓冲数组时(示例4),如何设置最合适的大小?为什么我们通常设置为1024
	 *这个问题暂时不明白,后面研究时并且补充上;这里提供一个网上的答案:
	 *每次文件读写是以簇为单位,每簇都要消耗时间,如果byte数大于一簇,肯定要多花时间。 不过现在电脑最小的簇也是4K,你的这两个对象没有差别。
	 *如果你的io流是针对网络,那么就不是簇,而是一个包的大小。有些包的载量比1K小,可能会有些差别。
	 */

6.同步(阻塞)与异步(非阻塞)

/**6.NIO中--------------关于同步(阻塞),异步(非阻塞),在数据读取和进程推进这两个方面的应用场景
	 *由于非阻塞,所以线程可以做其他事情,但是这个读写需要监听,你当然可以设置一段时间就尝试读取一次,
	 *但是现在系统中提供了监听的方法,selector; 并且由于理论上可以设置监听多个,因为多个channel同时
	 *变为可读写的几率并不高,这样在一个线程中相比在多个线程中监听多个channel相比,减少了线程上下文
	 *切换的开销。  select与非阻塞channel搭配更好,因为在监听到多个channel就绪时,在处理过程中,可能在
	 *其中一个channel中阻塞,所以这也说明了NIO适合用在"多而小"的情况下,"多"指的是连接数据多,"小"指的
	 *是一次传输的数据量小。
	 *与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,
	 *因为FileChannel不能切换到非阻塞模式,而套接字通道都可以,意思就是Selector适用于网络编程,后面再讲
	 */

7.channel方法拾遗

	/**7.channel方法拾遗
	 * close()方法
	 * position()方法,获得(设置)当前位置
	 * size()方法 返回所关联文件的大小
	 * truncate()方法 截取一个文件
	 */

8.Pipe 管道

	/**8.pipe()操作,用于在线程间数据传输
	 * 多线程管道 是单向的
	 * sinkChannel 数据写入其中
	 * sourceChannel 从中读取数据
	 * 设置完成sinkChannel和sourceChannel之后开启多线程的操作  
	 */
使用示例如下,其中channel1.txt内容为:12345678
	public static void main(String[] args) throws IOException {
		Pipe pipe = Pipe.open();
		SinkChannel sinkChannel = pipe.sink();
		Pipe.SourceChannel sourceChannel = pipe.source();
		
		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				/*文件输入流*/
				try {
					FileInputStream filInputStream = null;
					filInputStream = new FileInputStream(new File("channel1.txt"));
					/*从中获取通道*/
					FileChannel channel1 = filInputStream.getChannel();
					ByteBuffer buf = ByteBuffer.allocate(8);
					buf.clear();
					channel1.read(buf);
					buf.flip();
					while(buf.hasRemaining()) {
						sinkChannel.write(buf);
					}
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
				System.out.println("Channel1.main(...).new Runnable() {...}.run()");
			}
		});
		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				ByteBuffer buf = ByteBuffer.allocate(8);
				try {
					int bytesRead = sourceChannel.read(buf);
					buf.flip();
					while(buf.hasRemaining()){
						System.out.println("Channel1.main():"+buf.get());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				}
			});
		thread1.start();
		thread2.start();
	}
结果如下:
Channel1.main(...).new Runnable() {...}.run()
Channel1.main():49
Channel1.main():50
Channel1.main():51
Channel1.main():52
Channel1.main():53
Channel1.main():54
Channel1.main():55
Channel1.main():56
打印的结果就是12345678的ASCII码

9.NIO和IO总结

	/**9.NIO相对IO的特点
	 * IO             NIO
	 * 面向流         面向缓冲(Buffer)
	 * 阻塞IO         非阻塞IO(Channel)
	 * 无			  选择器(Selector)
	 * 
	 * buffer的设计不仅仅是对原有的缓冲的封装,同时还支持了非阻塞IO(channel),为什么这么说:
	 * 每次读写操作都先把数据放到Buffer里面,然后多次调用Channel的写方法对数据进
	 * 行操作,依靠对Buffer的大小来判断数据的完整性,如果是读方法,则根据read的返回值int判断
	 */

总结:学习和使用了channel的基本用法, 主要是注意它的一些特点,从而合适的应用场景中想到它,不必详记它的语法~  IO的内容有很多,从来没有系统学习过,后面准备复习和总结下IO的用法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值