`
lclcr
  • 浏览: 125461 次
  • 性别: Icon_minigender_1
  • 来自: 山东
社区版块
存档分类
最新评论

NIO第一部分缓冲区和通道

    博客分类:
  • JAVA
阅读更多

       系统运行的性能瓶颈通常在IO读写,包括对端口和文件的操作上,在打 开一个IO通道后,read()将一直等待在端口一边读取字节内容,如果没有内容进来,read()一直等待下去,改进做法就是开设线程,让线程去等待,但是这样做相当耗费资源(传统socket通讯服务器设计模式)。
       在 Java 编程中,所有 IO 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 IO 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。
       从JDK 1.4引入了NIO(new io),NIO 的创建目的是为了让 Java 程序员可以实现高速 IO 而无需编写自定义的本机代码,并提供了异步操作的API(IO提供的则是同步操作的API)。NIO 将最耗时的 IO 操作(即填充和提取缓冲区)转移回操作系统,以块的方式处理数据,因而可以极大地提高速度。
      Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察IO端口,如果有内容进来,会自动通知我们,这样,就不必开启多个线程死等,从外界看,实现了流畅的IO读写,不堵塞了。 
      在JDK 1.4中原来的 IO 包和 NIO 已经很好地集成了。 java.io.* 已经以NIO为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在更面向流的系统中,处理速度也会更快。

     Sun 官方标榜的特性如下:
           – 为所有的原始类型提供 (Buffer) 缓存支持。
           – 字符集编码解码解决方案。
           – Channel :一个新的原始 IO 抽象。
           – 支持锁和内存映射文件的文件访问接口。
           – 提供多路(MultiplexiIIg)、非阻塞式(non-bloking)的高伸缩性网络 IO 。
  
      Buffer是一块连续的内存块,是NIO数据读或写的中转地。在面向流的 IO 中,您将数据直接写入或者将数据直接读到 Stream 对象中。 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
 一个Buffer对象是固定数量的数据的容器。概念上,缓冲区是包在一个对象内的基本数据元素数组。

package java.nio; 
public abstract class Buffer { 
	public final int capacity()
		//储存在缓冲区中的最大数据容量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
	public final int position()
	public final Buffer position (int position)	
		//缓冲区的位置,缓冲区中已经写了多少数据或从缓冲区中获取了多少数据,下一个字节放到数组的哪一个元素中或来自数组的哪一个元素
	public final int limit()
	public final Buffer limit (int newLimit)
		//缓冲区的限制。缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数
	public final Buffer mark()
		//缓冲区的位置设置标记
	public final Buffer reset()
	public final Buffer clear()
		//不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值。(1). 它将limit设置为与capacity相同。(2). 它设置position为0。
	public final Buffer flip()
		
	public final Buffer rewind()
	public final int remaining()
	public final boolean hasRemaining()
	public abstract boolean isReadOnly()
}

     说明: 
           这些函数将引用返回到它们在(this)上被引用的对象。这是一个允许级联调用的类设计方法。级联调用允许这种类型的代码:

buffer.mark( );
buffer.position(5); 
buffer.reset( ); 
被简写为:
buffer.mark().position(5).reset(); 

      java.nio中的类被特意地设计为支持级联调用(java.lang.StringBuffer类也使用了这种级联调用)。

      例如:测试Buffer的方法 

public class TestByteBufferOne {
	public static void main(String[] args) throws Exception {
		ByteBuffer buffer = ByteBuffer.allocate(128);
		showLocationInfo(buffer);	//0-128-128
		buffer.put((byte)'l');
		buffer.put((byte)'c');
		buffer.put((byte)'r');
		showLocationInfo(buffer);	//3-128-128
		printContent(buffer);		//lcr
		buffer.flip();
		buffer.put((byte)'a');
		buffer.put((byte)'b');
		buffer.put((byte)'c');
		showLocationInfo(buffer);	//3-3-128
		printContent(buffer);		//abc
		buffer.put((byte)'d');		//java.nio.BufferOverflowException
		buffer.clear();
		showLocationInfo(buffer);	//0-128-128
	}
	private static void showLocationInfo (ByteBuffer buffer) {
		System.out.println(buffer.position() + "-" + buffer.limit() + "-" + buffer.capacity()); 
	}
	private static void printContent (ByteBuffer buffer) {
		int len = buffer.position();
		for (int i = 0 ; i < len ; i++) {
			System.out.print((char)buffer.get(i));
		}
		System.out.println();
	}
	/**
	 * flip的作用就是将position位置设为0,limit位置设置为原来position的位置值。
	 * 由于原来有三个元素,position为3则执行flip后limit为3,再put时就超过了limit范围,报错
	 */
}

       最常用的缓冲区类型是 ByteBuffer。一个 ByteBuffer 可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。
       因为大多数标准IO操作都使用 ByteBuffer,所以它具有所有共享的缓冲区操作以及一些特有的操作: 

1.ByteBuffer 类中有四个 get() 方法:
	1). byte get();
	2). ByteBuffer get( byte dst[] );
	3). ByteBuffer get( byte dst[], int offset, int length );
	4). byte get( int index );
2.ByteBuffer 类中有五个 put() 方法:
	1). ByteBuffer put( byte b );
	2). ByteBuffer put( byte src[] );
	3). ByteBuffer put( byte src[], int offset, int length );
	4). ByteBuffer put( ByteBuffer src );
	5). ByteBuffer put( int index, byte b );
3.ByteBuffer 还有用于读写不同类型的值的其他方法,如下所示:
	* getByte()
	//......
	* putByte()
	//......
4.缓冲区分配和包装
	ByteBuffer buffer = ByteBuffer.allocate( 1024 );
	allocate()方法分配一个新的字节缓冲区。还可将一个现有的数组转换为缓冲区
	byte array[] = new byte[1024];
	ByteBuffer buffer = ByteBuffer.wrap(array);
5.缓冲区分片
	buffer.position( 3 );
	buffer.limit( 7 );	//缓冲区为从3到6
	ByteBuffer slice = buffer.slice();
	注意:片段和缓冲区共享同一个底层数据数组,对片段数据所作的修改也将反应到缓冲区中
6.只读缓冲区
	可以通过调用缓冲区的 asReadOnlyBuffer() 方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据),只不过它是只读的。 
7.将文件映射到内存
	MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024 );
	将文件的前1024个字节映射到内存中。

       例如:接受用户输入,并将其放到缓冲区中,在结束输入后将刚才输入的数据以String的形式输出 

public class TestByteBufferTwo {
	public static void main(String[] args) throws Exception {
		int c ;
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		//创建一个容量为1024字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.
		while ((c = System.in.read()) != '0') {
			if (c == '\n' || c == '\r') {
				continue;
			}
			System.out.println((char)c);
			buffer.put((byte)c);
		}
		System.out.println((char)c);
		buffer.flip();
		//下面两句等价于buffer.array(),但是输出完正确值后会有空白框的乱码,如下方式则没有
		byte [] bytes = new byte[buffer.limit()];
		buffer.get(bytes);
		String str = new String(bytes);
		System.out.println("str = " + str);
		buffer.clear();
	}
}

       ByteBuffer 不是 NIO 中唯一的缓冲区类型。事实上,对于每一种基本 Java 类型都有一种缓冲区类型: 

* ByteBuffer
	* MappedByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer	

      每一个Buffer类都是Buffer接口的一个实例。除了ByteBuffer,每一个Buffer类都有完全一样的操作,只是它们所处理的数据类型不一样。 

 

      Channel是一个对象,与流的不同之处在于通道是双向的,可以通过它读取和写入数据(一个流必须是 InputStream 或者 OutputStream 的子类)。 

public abstract class FileChannel
	extends AbstractInterruptibleChannel
		implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
public abstract class SocketChannel
	extends AbstractSelectableChannel
		implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
public abstract class DatagramChannel
	extends AbstractSelectableChannel
		implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
public abstract class ServerSocketChannel extends AbstractSelectableChannel

       用于读取、写入、映射和操作文件的通道,多个并发线程可安全地使用文件通道 
       java.io 包的每个 FileInputStream、FileOutputStream 和 RandomAccessFile 类添加了 getChannel 方法获得文件通道FileChannel。
             通过 FileInputStream 实例的 getChannel 方法所获得的通道将允许进行读取操作。
             通过 FileOutputStream 实例的 getChannel 方法所获得的通道将允许进行写入操作。
             如果使用模式 "r" 创建 RandomAccessFile 实例,则通过该实例的 getChannel 方法所获得的通道将允许进行读取操作。
             如果使用模式 "rw" 创建实例,则获得的通道将允许进行读取和写入操作。

      java.nio.channels.FileLock  表示文件区域锁定的标记。
      JDK 1.4引入了文件加锁机制,允许我们同步访问一个共享文件。不过,竞争同一文件的两个线程有可能在不同的java虚拟机上,或者一个是java线程,另一个是操作系统中其他的某个线程,但文件锁对其他线程或其他操作系统进程都是可见的,因为java的文件加锁直接映射到了本地操作系统的加锁机制。
      注意:这里的锁是指锁定其他应用程序,而不是锁定同一虚拟机里访问的同一文件的其他线程 。如果在同一虚拟机两次锁定同一文件或某文件里的同一区域,tryLock()与lock()则会抛出OverlappingFileLockException异常。
      每次通过 FileChannel 类的 lock 或 tryLock 方法获取文件上的锁定时,就会创建一个文件锁定对象。tryLock() 是非阻塞的,它会试着去获取这个锁,但是如果得不到(其它进程已经以独占方式得到这个锁了),那它就直接返回;而lock( )是阻塞的,如果得不到锁,它会在一直处于阻塞状态,除非它得到了锁,或者你打断了调用它的线程,或者关闭了它要lock()的channel,否则它是不会返回的。最后用FileLock.release()释放锁。要想获取整个文件的锁,可以用FileChannel的tryLock( )或lock()方法。

      还可以像这样锁住文件的某一部分 tryLock(long position, long size, boolean shared)  或者 lock(long position, long size, boolean shared),其中第三个参数表示是否是共享锁。锁是独占的还是共享的,这要由操作系统来决定。如果操作系统不支持共享锁,而程序又申请了一个共享锁,那么它会返回一个独占锁。你可以用FileLock.isShared( )来查询锁的类型(共享还是独占)。
      另外锁定写文件通道new FileOutputStream(path).getChannel().tryLock();时,它会清掉原文件中的内容,所以当文件中有内容时最好使用 new FileOutputStream(path, true).getChannel().tryLock(); 以追加方式打开写文件通道。或者使用RandomAccessFile类来创建文件通道然后锁定 new RandomAccessFile(path, "rw").getChannel().tryLock();这样它不会破坏锁定的文件的内容。
      SocketChannel,DatagramChannel,以及 ServerSocketChannel是不需要锁的,因为它们是从单进程实体继承而来;一般来说,你是不会让两个进程去共享一个网络socket的。

      如果使用原来的 IO,那么我们只需创建一个 FileInputStream 并从它那里读取。而在 NIO 中,情况稍有不同:我们首先从 FileInputStream 获取一个 FileInputStream 对象,然后使用这个通道来读取数据。
      在 NIO 系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是 直接从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。

      因此读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer 中。

FileInputStream fis = new FileInputStream(path);
FileChannel fc = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);//分配一个新的字节缓冲区。
fc.read(buffer);	

    写入文件 

FileOutputStream fout = new FileOutputStream(path);
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
	 buffer.put( message[i] );
}
buffer.flip();
fc.write(buffer);

     也可以通过程序读写缓冲区,这个程序不断重复 ― 读、写[读、写][......] ― 直到源文件结束。  

public class TestFileCopy {
	public static void main(String[] args) throws IOException {
		System.out.println((srcFile.length() / (1024 * 1024L)) + "M");	/计算文件大小
//		方法一、
		FileInputStream fis = new FileInputStream(srcFile);
		FileChannel srcChannel =  fis.getChannel();
		FileOutputStream fos = new FileOutputStream(distFile);
		FileChannel distChannel = fos.getChannel();
		distChannel.transferFrom(srcChannel, 0, srcChannel.size());
		//srcChannel.transferTo(distChannel, 0, srcChannel.size());
		distChannel.close();
		srcChannel.close();
//		方法二、
		srcFile.renameTo(new File(distFile));
//		方法三、
		FileInputStream fis = new FileInputStream(srcFile);
		FileOutputStream fos = new FileOutputStream(distFile);
		byte [] bytes = new byte[1024];
		int len = 0;
		while ((len = fis.read(bytes, 0, bytes.length))!= -1) {
			fos.write(bytes, 0, len);
		}
		fos.flush();
		fos.close();
		fis.close();
//		方法四、
		FileInputStream fis = new FileInputStream(srcFile);
		FileOutputStream fos = new FileOutputStream(distFile);
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		FileChannel finc = fis.getChannel();
		FileChannel fouc = fos.getChannel();
		buffer.clear();
		while (finc.read(buffer) != -1) {
			buffer.flip();
			fouc.write(buffer);
			buffer.compact();	//以防写入不完整
		}
		fouc.force(true);	//防止缓存
		fos.flush();
		fos.close();
		finc.close();
		fis.close();
	}
}

    例如:通过在主线程中将file加锁,然后启动一个新的线程模拟对文件的读操作,在锁释放之前读操作无法进行 

public class TestFileChannel {
	public static void main(String[] args) throws IOException, InterruptedException {
		File file = new File("D:\\test\\channel.txt");
		FileOutputStream fos = new FileOutputStream(file, true);
		FileChannel channel = fos.getChannel();
		FileLock lock = channel.tryLock();	//锁定整个文件(主线程中锁定该文件)
		System.out.println("main thread is running ......");
		if (lock.isValid()) {
			System.out.println("get the lock of file " + file.getName());
			FileChannelRunable r = new FileChannelRunable(file);
			Thread thread = new Thread(r);
			thread.start();	
			Thread.sleep(10000);
			System.out.println("main thread is still running ......");			
			fos.write("我刚才被被锁定啦。。。".getBytes()); // 注意:主线程向文件写入数据
			System.out.println("main thread write data to file " + file.getName());			
			lock.release();
			System.out.println("release the lock!");
		}
		fos.close();
	}
}

public class FileChannelRunable implements Runnable {
	private boolean flag = true;
	private File file;
	public FileChannelRunable(File file) {
		this.file = file;
	}
	@Override
	public void run() {
		while (flag) {
			try {
				Thread.sleep(500);
				FileReader fr = new FileReader(file);
				int c;
				//当通过FileReader读取时捕获异常信息因为File已经被锁定了
				while ((c = fr.read()) != -1) {	
					System.out.print("" + (char) c);
				}
				fr.close();
				flag = false;
			} catch (Exception e) {
				System.out.println("error : " + e.getMessage());
			}
		}
	}
}

      public abstract class MappedByteBuffer extends ByteBuffer
     直接字节缓冲区,其内容是文件的内存映射区域,映射的字节缓冲区是通过 FileChannel.map 方法创建的。

 

分享到:
评论

相关推荐

    Java NIO 主要类和方法(Java NIO中文版 附录C)

    Java NIO(New Input/Output)是Java提供的一种新的输入输出处理机制,它与传统IO基于流的处理方式不同,NIO支持面向缓冲区的(Buffer-oriented)、基于通道的(Channel-based)I/O操作。在Java NIO中,Buffer类是...

    Java语言基础教程-Java NIO流篇1

    首先,我们从【第1节】 Java NIO流-简介和概述开始。这一部分主要介绍了NIO的基本理念和相对于BIO的优势。NIO的核心特点是它支持非阻塞I/O,这意味着当数据没有准备好时,线程不会被阻塞,而是可以继续执行其他任务...

    JavaNIO.pdf

    * 限制(Limit):缓冲区中第一个不应该读取或写入的元素的索引,不能为负数,不能大于容量。 * 位置(Position):缓冲区中下一个要读取或写入的元素的索引,不能为负数,不能大于限制。 * 标记(Mark):缓冲区的...

    Java IO, NIO and NIO.2

    2. Limit(限制):表示缓冲区第一个不应读取或写入的元素的基于零的索引,即标识了缓冲区中活跃数据的数量。 3. Position(位置):表示下一个可以读取的数据项的基于零的索引,或数据项可以被写入的位置。 4. Mark...

    网络与nio

    1. **NIO的基本概念**:解释NIO的核心组件,如通道、缓冲区和选择器的工作原理。 2. **NIO的优势**:对比传统I/O,讨论NIO如何提高并发性能,特别是在处理大量连接的服务器场景下。 3. **服务器模型**:介绍基于NIO...

    NIO学习系列:连网和异步IO

    NIO不同于传统的IO模型(即BIO,Blocking IO),它引入了通道(Channel)和缓冲区(Buffer)的概念,允许进行非阻塞的读写操作。在BIO中,读写操作通常是阻塞的,当数据未准备好时,线程会被挂起;而在NIO中,系统会...

    Java语言基础教程-Java NIO流篇2

    本教程将深入讲解Java NIO中的流和通道概念,以帮助开发者更好地理解和利用这一强大的功能。 首先,我们要理解Java NIO的核心组件之一——流。在Java的IO体系中,流是数据传输的抽象,它代表了数据的流向,可以是...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    [第1节] Java NIO流-简介和概述.flv [第2节] Java NIO流-缓冲区.flv [第3节] Java NIO流-缓冲区操作.flv [第4节] JavaNIO流-通道1.flv [第5节] Java NIO流-通道2.flv [第6节] Java NIO流-socket通道操作.flv ...

    nio.rar_Java识别_java nio

    NIO在处理大量并发连接时表现出色,因为它基于通道(Channels)和缓冲区(Buffers)进行数据传输,而非传统的流(Streams)模型。在本主题中,我们将探讨如何利用Java NIO和charset来自动识别字符集。 首先,让我们...

    新IO缓冲区的输入和输出

    核心在于一系列的缓冲区类,它们构成了Java NIO的基础,使得数据在I/O操作中得以高效存储和传输。 Buffer类是所有缓冲区的基础抽象,它定义了通用的操作和属性。Buffer的主要功能包括: 1. **容量(capacity)**:...

    Java NIO 电子书

    本书《Java NIO》由 Ron Hitchens 编写,出版社为 O'Reilly,第一版出版于 2002 年 8 月,共有 312 页。 #### 二、典型挑战与新功能 在传统的 Java IO 处理中,程序员通常会遇到响应性差、可扩展性受限以及可靠性...

    【IT十八掌徐培成】Java基础第26天-04.NIO基础.zip

    总的来说,Java NIO是一种面向缓冲区的、非阻塞的I/O模型,通过选择器和通道提供了一种高效处理并发连接的方式。在设计高性能、高并发的服务器端应用时,理解并熟练掌握NIO是非常重要的。学习徐培成老师的这个Java...

    编写一个简单NIO系统.rar

    在Java中,NIO的核心组件包括选择器(Selector)、通道(Channel)和缓冲区(Buffer)。选择器允许单个线程监控多个通道,当通道准备好读写操作时,选择器会通知我们。通道是连接到I/O设备的途径,而缓冲区则用于...

    开源nio框架cindy源码

    1. **Java NIO基础**:Cindy是建立在Java NIO库之上的,因此首先需要了解Java NIO的基本概念,如选择器(Selector)、通道(Channel)、缓冲区(Buffer)等。理解它们如何协同工作,以及如何通过非阻塞方式处理I/O...

    第11讲 Java提供了哪些IO方式? NIO如何实现多路复用?1

    - NIO的高性能数据操作基于缓冲区(Buffer)和通道(Channel)的原理,如何利用这些机制优化性能。 - 讨论NIO实现可能存在的问题,例如复杂性较高,以及可能的改进方案。 同步与异步、阻塞与非阻塞的概念也是面试...

    【IT十八掌徐培成】Java基础第27天-02.NIO-ServerSocketChannel-SocketChannel.zip

    通过`put()`和`get()`方法,我们可以向缓冲区中写入或读取数据。缓冲区有几种不同的实现,如HeapByteBuffer(基于堆内存)、DirectByteBuffer(直接内存)等,它们各有优缺点,根据具体需求选择合适的类型。 5. **...

    java nio 原理浅析

    - **ChannelBufferFactory**:创建和管理ChannelBuffer的工厂,可以定制不同的缓冲区实现,以满足不同性能需求。 3. **ChannelPipeline**: - ChannelPipeline是Netty中处理I/O事件的通道,它包含一系列...

    032002_【第20章:Java新IO】_通道(Channel)_java_hearing3oc_扣弄你澳大_stoppedh

    在Java NIO中,通道和缓冲区(Buffer)是两个核心概念。通道类似于传统IO中的流,但有以下几个显著区别: 1. **双向性**:通道可以进行读写操作,而流通常是单向的(读或写)。 2. **非阻塞**:通道可以设置为非...

Global site tag (gtag.js) - Google Analytics