Java服务端(二)- Java标准输入输出NIO

本文介绍了Java NIO的基础知识,对比了NIO与传统IO在流与块处理数据上的差异。详细讲解了NIO中的Buffer、Channel对象,包括Buffer的方法使用、类型,Channel的类型及特点,还介绍了Scatter/Gather操作。此外,给出了从文件读取、写入数据等代码实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础知识:

        NIO即New IO,在之前的Java编程中,一般使用流的方式来处理IO,所有的IO都被视作单个字节的移动,通过stream对象一次移动一个字节。流IO负责把对象转换为字节,然后再转换为对象。

        NIO与IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块。所以NIO的效率要比IO高

流与块的比较:

   IO是以流的方式处理数据,而NIO是以块的方式处理数据。

   面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据创建过滤器就变得非常容易,链接几个过滤器,以便对数据进行处理非常方便而简单,但是面向流的IO通常处理的很慢。

   面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快的多,但面向块的IO缺少了面向流IO所具有的简单性。

对象:

        一个Buffer实质上是一个容器对象,发给Channel的所有对象都必须先放到Buffer中;同样的,从Channel中读取的任何数据都要读到Buffer中。

1、Buffer:

         Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。

         在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。

       使用 Buffer 读写数据一般遵循以下四个步骤:

            1.写入数据到 Buffer;

            2.调用 flip() 方法;

            3.从 Buffer 中读取数据;

            4.调用 clear() 方法或者 compact() 方法。

当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式

在读模式下,可以读取之前写入到 Buffer 的所有数据。

Buffer的类型有:

2、Buffer类中的方法使用:

          capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。

          limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

              当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

                

          position:
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

 Buffer的分配

要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。

ByteBuffer buf = ByteBuffer.allocate(48);

CharBuffer buf = CharBuffer.allocate(1024); // 配一个可存储1024个字符的CharBuffer:

向Buffer中写数据

//写数据到Buffer有两种方式
(从Channel写到Buffer,通过Buffer的put()方法写到Buffer里)
buf.put(127);

(从Channel写到Buffer)
int bytesRead = inChannel.read(buf); //read into buffer.

flip方法

调用flip()方法会将position设回0,并将limit设置成之前position的值。换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

从buffer中读取数据

1、从Buffer读取数据到Channel。
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);

2、使用get()方法从Buffer中读取数据。
byte aByte = buf.get();

clear()与compact()方法
        一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

         如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

         如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

        如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

        compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();  //set position back to mark.

equals()与compareTo()方法
可以使用equals()和compareTo()方法两个Buffer。

2、channel:

     Channel是一个对象,可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:

          1.Channel是双向的,既可以读又可以写,而流是单向的
          2.Channel可以进行异步的读写
          3.对Channel的读写必须通过buffer对象

      由于所有的数据都需要通过Buffer对象处理,所以不能将字节直接写入到Channel中,需要先将数据写入到Buffer中;同样,也不能直接从Channel中直接读取字节,需要先将数据从Channel读入到Buffer,再从Buffer获取这个字节。

    在Java NIO中Channel主要有如下几种类型:

       - FileChannel:从文件读取数据的

       - DatagramChannel:读写UDP网络协议数据

       - SocketChannel:读写TCP网络协议数据

       - ServerSocketChannel:可以监听TCP连接

Scatter/Gather

分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。

Java NIO系列教程(四) Scatter/Gather | 并发编程网 – ifeve.com

代码实例:

           NIO中从通道中读取:创建一个缓冲区,然后让通道读取数据到缓冲区。

           NIO写入数据到通道:创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入。

   从文件读取实例:

     1.从FileInputStream获取Channel

     2.创建Buffer

     3.从Channel读取数据到Buffer

    // 从文件读取数据到缓冲区
	@Test
	public void ChannelRead() throws IOException{
		// 1、获取通道
		FileInputStream fin = new FileInputStream( "readandshow.txt" );
		FileChannel fc = fin.getChannel();  

		// 2、创建缓冲区
		ByteBuffer buffer = ByteBuffer.allocate( 1024 );

		// 3、将数据从通道读到缓冲区
		fc.read( buffer );
	}

写入数据到文件:

    // 写入数据到文件中
	@Test
	public void ChannelWrite() throws IOException{
		// 1、获取一个通道
		FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
		FileChannel fc = fout.getChannel();

		// 2、创建缓冲区,将数据放入缓冲区
		ByteBuffer buffer = ByteBuffer.allocate( 1024 );
		for (int i=0; i<message.length; ++i) {
			buffer.put( message[i] );
		}
		buffer.flip();

		// 3、把缓冲区数据写入通道中
		fc.write( buffer );
	}

读写结合的操作:

        创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。

	// 读写操作的例子
	public void copyFileUseNIO(String src,String dst) throws IOException{
		//声明源文件和目标文件
		FileInputStream fi=new FileInputStream(new File(src));
		FileOutputStream fo=new FileOutputStream(new File(dst));
		//获得传输通道channel
		FileChannel inChannel=fi.getChannel();
		FileChannel outChannel=fo.getChannel();
		//获得容器buffer
		ByteBuffer buffer=ByteBuffer.allocate(1024);
		while(true){
			//判断是否读完文件
			int eof =inChannel.read(buffer);
			if(eof==-1){  // 数据结束时,read()方法就会返回-1
				break;  
			}
			//重设一下buffer的position=0,limit=position
			buffer.flip();
			//开始写
			outChannel.write(buffer);
			//写完要重置buffer,重设position=0,limit=capacity
			buffer.clear();
		}
		inChannel.close();
		outChannel.close();
		fi.close();
		fo.close();
	} 

Selectors学习

Java NIO系列教程(六) Selector | 并发编程网 – ifeve.com

转载参考自:

Java NIO 详解(一) - zailushang1996 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值