1. I/O对程序的吞吐率有着决定性的因素。java
2. Java中旧的IO接口,对文件操做只能一个字节一个字节或一行一行的读,对Socket IO会阻塞,能够为每个Socket建立一个Thread,可是这样的系统开销和资源浪费都太大,不是合理选择;而NIO对Socket IO能够实现非阻塞,能够用单线程管理多个通道,而且NIO有了缓冲区的概念,不论是File IO仍是Socket IO都是在和Buffer相互读取。程序员
如图:数组
因此NIO能够先将通道数据读到缓冲区中再进行操做,避免了逐字节或逐行读取的性能开销。网络
NIO有三个核心模块:Selector(选择器)、Channel(通道)、Buffer(缓冲区),另外java.nio.charsets包下新增的字符集类也是nio一个重要的模块,但我的以为不算是NIO的核心,只是一个供NIO核心类使用的工具类。app
a. Selector容许单个线程处理多个Channel,相比旧的IO为防止阻塞而为每个Channel建立一个Thread来讲,性能高出很是多;工具
只用SelectableChannel才能注册到Selector,单线程处理多个Channel.性能
将Channel注册到Selector中,轮询调用select(),这个方法会阻塞,当注册的某个通道准备好要进行IO操做时,这个便返回已选择键的个数,此时经过selectedKeys得到已选择的键,就能够进行相关的IO操做了;选择键(SelectionKey)是用来链接Selector和Channel.大数据
b.Channel主要有:FileChannel、ServerSocketChannel、SocketChannel、DatagramChannel,这四种通道涵盖了文件IO、TCP套接字IO、UDP数据报IO。this
c.Buffer主要有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,涵盖了IO操做的基本数据类型。编码
3. 缓冲区(Buffer)
概念:缓冲区(Buffer)就是在内存中预留指定字节数的存储空间用来对输入/输出(I/O)的数据做临时存储,这部分预留的内存空间就叫作缓冲区。
在Java NIO中,缓冲区的做用也是用来临时存储数据,能够理解为是I/O操做中数据的中转站。
缓冲区直接为通道(Channel)服务,写入数据到通道或从通道读取数据,这样的操利用缓冲区数据来传递就能够达到对数据高效处理的目的。
NIO中主要有八种缓冲区类(其中MappedByteBuffer是专门用于内存映射的一种ByteBuffer):
缓冲区是包在一个对象内的基础数据的数组,Buffer类相比通常简单数组而言其优势是将数据的内容和相关信息放在一个对象里面,这个对象提供了处理缓冲区数据的丰富的API。
全部缓冲区都有4个属性:capacity、limit、position、mark,并遵循:capacity>=limit>=position>=mark>=0,下表格是对着4个属性的解释:
属性 | 描述 |
Capacity | 容量,便可以容纳的最大数据量;在缓冲区建立时被设定而且不能改变 |
Limit | 上界,缓冲区中当前数据量 |
Position | 位置,下一个要被读或写的元素的索引 |
Mark | 标记,调用mark()来设置mark=position,再调用reset()可让position恢复到标记的位置即position=mark |
1、建立缓冲区
全部的缓冲区类都不能直接使用new关键字实例化,它们都是抽象类,可是它们都有一个用于建立相应实例的静态工厂方法,以ByteBuffer类为例子:
//建立一个容量为10的byte缓冲区 ByteBuffer buff = ByteBuffer.allocate(10);
上面代码将会从堆空间中分配一个容量大小为10的byte数组做为缓冲区的byte数据存储器。对于其余缓冲区类上面方式也适用,如建立容量为10的CharBuffer:
//建立一个容量为10的char缓冲区 CharBuffer buff = CharBuffer.allocate(10);
若是想用一个指定大小的数组做为缓冲区的数据的存储器,可使用wrap()方法:
//使用一个指定数组做为缓冲区的存储器 byte[] bytes = new byte[10]; ByteBuffer buff = ByteBuffer.wrap(bytes);
上面代码中缓冲区的数据会存放在bytes数组中,bytes数组或buff缓冲区任何一方中数据的改动都会影响另外一方。还能够建立指定初始位置(position)和上界(limit)的缓冲区:
//使用一个指定数组做为缓冲区的存储器 //并建立一个position=3,limit=8,capacity=10的缓冲区 byte[] bytes = new byte[10]; ByteBuffer buff = ByteBuffer.wrap(bytes, 3, 8);
下图是新建立的一个容量为10的字节缓冲区的内存图:
位置为0,上届和容量都为10,初始标记为-1,4个属性中,capacity固定不变,mark limit position会在对缓冲区的操做中变化。
2、操做缓冲区
一、存取(Buffer.get() & Buffer.put())
使用get()从缓冲区中取数据,使用put()向缓冲区中存数据。
// 建立一个容量为10的byte数据缓冲区 ByteBuffer buff = ByteBuffer.allocate(10); // 存入4次数据 buff.put((byte) 'A'); buff.put((byte) 'B'); buff.put((byte) 'C'); buff.put((byte) 'D'); // 翻转缓冲区 buff.flip(); // 读取2次数据 System.out.println((char)buff.get()); System.out.println((char)buff.get());
上面有提过缓冲区四个属性值必定遵循capacity>=limit>=position>=mark>=0,put()时,若position超过limit则会抛出BufferOverflowException;get()时,若position超过limit则会抛出BufferUnderflowException。
buff.flip()是将缓冲区翻转,翻转将在下面来讲。
调用put()或get()时,每调用一次position的值会加1,指示下次存或取开始的位置;
上面代码put()四次后的缓冲区内存示意图:
上面代码执行buff.flip()将缓冲区翻转后的内存示意图:
上面代码执两次get()后的缓冲区内存示意图:
再向Buffer中读写数据时有2个方法也很是有用:
Buffer.remaining():返回从当前位置到上界的数据元素数量;
Buffer.hasRemaining():告诉咱们从当前位置到上界是否有数据元素;
二、翻转(Buffer.flip())
翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态,使用flip()方式实现翻转。Buffer.flip()的源码以下:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
相信看到了实现的源码应该就会清楚flip()的做用了。rewind()方法与flip()很类似,区别在于rewind()不会影响limit,而flip()会重设limit属性值,Buffer.rewind()的源码以下:
public final Buffer rewind() { position = 0; mark = -1; return this; }
三、压缩(Buffer.compact())
压缩就是将已读取了的数据丢弃,保留未读取的数据并将保留的数据从新填充到缓冲区的顶部,而后继续向缓冲区写入数据。
// 建立一个容量为10的byte数据缓冲区 ByteBuffer buff = ByteBuffer.allocate(10); // 填充缓冲区 buff.put((byte)'A'); buff.put((byte)'B'); buff.put((byte)'C'); buff.put((byte)'D'); System.out.println("first put : " + new String(buff.array())); //翻转 buff.flip(); //释放 System.out.println((char)buff.get()); System.out.println((char)buff.get()); //压缩 buff.compact(); System.out.println("compact after get : " + new String(buff.array())); //继续填充 buff.put((byte)'E'); buff.put((byte)'F'); //输出全部 System.out.println("put after compact : " + new String(buff.array()));
以上代码打印结果:
first put : ABCD A B compact after get : CDCD put after compact : CDEF
控制台中输出内容中有正方形的乱码,是正常。由于字节缓冲区中没有赋值的内存块默认值是0,而Unicode编码中没有0编码,因此乱码。
四、标记(Buffer.mark())
标记就是记住当前位置(使用mark()方法标记),以后能够将位置恢复到标记处(使用reset()方法恢复),mark()和reset()源码以下:
public final Buffer mark() { mark = position; return this; } public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
五、比较两个缓冲区是否相等
比较两个缓冲区是否相等有2种方法:equals(Object ob) 和compareTo(ByteBuffer that),这两个方法都是在Buffer的子类中实现的。
equals比较的两个缓冲区中的每一个值,因此容许不一样的Buffer对象进行比较;compareTo有类型限制,ByteBuffer只能和ByteBuffer进行比较;比较两个缓冲区其实是比较两个缓冲区中每一个缓冲区position到limit之间(不包括limit)的缓冲值。以下图:
六、批量移动缓冲区的数据
缓冲区的目的就是高效传输数据,高效传输数据就应杜绝一个一个的传输,因此Buffer API提供了相应的方法来进行批量移动。下面是个例子:
byte[] bytes = "hello world!".getBytes(); // 建立一个容量等bytes容量的byte数据缓冲区 ByteBuffer buff = ByteBuffer.allocate(bytes.length); //将byte数据写入缓冲区,下面代码和buff.put(bytes)效果一致 buff.put(bytes, 0, bytes.length); //翻转缓冲区 buff.flip(); //轮询判断是否有数据,有则将缓冲区的数据批量读到array中 byte[] array = new byte[bytes.length]; while(buff.hasRemaining()){ buff.get(array, 0, buff.remaining()); } //输出冲缓冲区读出来的数据 System.out.println(new String(array));
以上面代码为例,
写数据到缓冲区时,若bytes.length > buff.capacity()则会抛出java.nio.BufferOverflowException;
从缓冲区中读数据时,若array.length < buff.limit()则会抛出java.lang.IndexOutOfBoundsException。
七、复制缓冲区
复制一个与源缓冲区共享数据的缓冲区,各自管理本身的属性
asReadOnlyBuffer():复制一个只读缓冲区
duplicate():复制一个可读可写的缓冲区
slice():复制一个从源缓冲position到limit的新缓冲区
NIO缓冲区有八种缓冲区实现类,在文件IO、套接字(Socket)IO都得使用ByteBuffer来操做数据,能够说NIO中,ByteBuffer是很是核心和经常使用的缓冲区实现类,对ByteBuffer的实现由更深刻的了解才能灵活正确的运用。
字节(byte)是操做系统和全部I/O设备使用的基本数据类型,基本数据类型是供咱们程序员来直接操做的,实际是以bit(比特位)存储在内存;那么1字节等于多少位呢?相信你们都应该知道等于八位。
1byte等于8bit的背景:在计算机刚问世时,每一个字节能够是3~12个比特位(bit),在市场的推进和前辈们的实践下,最终决定使用8位做为一个字节,由于8位足够表达英文字符集中任何一个字符,使用8位表明一个字节能够简化硬件设计,而且8位刚好能够容纳2个十六进制数字,因此8的倍数能够提供足够的组合来存储有效的数值;IBM在1960年率先推出的IBM360大型机使用的就是8位表示1字节。在这样的背景下,最终统一使用8比特位表示1字节。
1、字节顺序
Java中有八种基本数据类型,其中除byte和boolean类型外的其余的基本数据都是由组合在一块儿的几个字节组成(byte是一个字节,boolean的值是true或false,而1字节的值范围是-128~127,boolean不能对应到一个或多个字节上),每一个基本数据类型都是以连续字节顺序的形式存储在内存中,字节顺序分为:大端字节顺序和小端字节顺序
什么是字节顺序?:
多字节的数值被存储在内存的方式就是字节顺序。数值的最高字节位于低位地址,系统使用的就是大端字节顺序;数值的最低字节位于低位地址,系统使用就是小端字节顺序。例如一个int类型的数值58700999,其十六进制表示为0x037fb4c7,内存图以下:
网络协议(Internet Protocol,即IP)规定数据必须使用大端字节顺序传输,可是有硬件使用的是小端字节顺序,因此若本地系统是小端字节顺序,就须要将多字节数值先转换成大端字节顺序后才能正确传输数据。
在Java NIO中使用ByteOrder类来封装字节顺序,使用ByteOrder.nativeOrder()能够获取本地系统的字节顺序;ByteOrder的toString()将返回BIG_ENDIAN(大端字节顺序)或LITTLE_ENDIAN(小端字节顺序)。ByteBuffer默认使用的是大端字节顺序,和本地系统字节顺序无关,其余全部的Buffer类使用的字节顺序都和本地系统的字节顺序一致。
NIO中全部的缓冲区类都有order()方法,其返回ByteOrder对象,经过调用ByteOrder的toString()方法即可查看某个Buffer类的字节顺序。只有ByteBuffer才能够自定义设置字节顺序,经过调用ByteBuffer的order(ByteOrder bo)方法实现。
2、直接缓冲区
直接缓冲区是不使用JVM堆栈而是经过操做系统来建立内存块用做缓冲区。因此可使用操做系统底层I/O,其处理数据性能要高于基于JVM堆栈的非直接缓冲区;可是直接缓冲区的建立和销毁的性能开销要低于基于JVM堆栈的非直接缓冲区。
NIO的八种缓冲区中,只有ByteBuffer才能建立直接缓冲区(还有MappedByteBuffer?MappedByteBuffer继承ByteBuffer),经过调用ByteBuffer.allocateDirect(int capacity)来建立直接缓冲区。
全部的缓冲区类均可以经过调用isDirect()方法来判断是不是直接缓冲区。
3、视图缓冲区
视图缓冲区就是新建立其余的基础数据类型缓冲区,新缓冲区和源缓冲区共享数据,但各自维护本身的属性(capacity、limit、position、mark)。
只有ByteBuffer才能建立视图缓冲区,而且能够将缓冲区的数据转换。
视图缓冲区是基础缓冲区的一部分,由基础缓冲区的position和limit限制。建立一个ByteBuffer的CharBuffer视图,以下图:
ByteBuffer byteBuff = ByteBuffer.allocate(6); byteBuff.position(2); byteBuff.limit(4); System.out.println(); CharBuffer charBuff = byteBuff.asCharBuffer();
asCharBuffer() asShortBuffer() asIntBuffer() asLongBuffer() asFloatBuffer() asDoubleBuffer()
4、使用ByteBuffer提供的API直接存取基础数据
putInt(int value) putChar(char value) putLong(long value) putDouble(double value) putShort(short value) putFloat(float value) getInt()
ByteBuffer buff = ByteBuffer.allocate(100); buff.putShort((short)100); buff.putInt(200); buff.putLong(300L); buff.putFloat(400.5f); buff.putDouble(500.56); buff.putChar('A'); buff.flip(); buff.getShort();//return 100 buff.getInt();//return 200 buff.getLong();//return 300 buff.getFloat();//return 400.5 buff.getDouble();//return 500.56 buff.getChar();//return 'A'
Java中除了char类型外,其余基本数据类型并无提供用来处理无符号数值的API,可能有时候须要将无符号的数据转成数据流或文件,但是ByteBuffer并无提供API来作这样的处理,咱们能够本身实现使用ByteBuffer存取无符号数据。
下面是向ByteBuffer对象中存取无符号数值的工具类:
import java.nio.ByteBuffer; public class Unsigned { public static short getUnsignedByte(ByteBuffer buff) { return (short) (buff.get() & 0xff); } public static short getUnsignedByte(ByteBuffer buff, int position) { return (short) (buff.get(position) & (short) 0xff); } public static void putUnsignedByte(ByteBuffer buff, int value) { buff.put((byte) (value & 0xff)); } public static void putUnsignedByte(ByteBuffer buff, int position, int value) { buff.put(position, (byte) (value & 0xff)); } // -------------------------------------------- public static int getUnsignedShort(ByteBuffer buff) { return buff.getShort() & 0xffff; } public static int getUnsignedShort(ByteBuffer buff, int position) { return buff.getShort(position) & (short) 0xffff; } public static void putUnsignedShort(ByteBuffer buff, int value) { buff.putShort((short) (value & 0xffff)); } public static void putUnsignedShort(ByteBuffer buff, int position, int value) { buff.putShort(position, (short) (value & 0xffff)); } // -------------------------------------------- public static long getUnsignedInt(ByteBuffer buff) { return buff.getInt() & 0xffffffffL; } public static long getUnsignedInt(ByteBuffer buff, int position) { return buff.getInt(position) & 0xffffffffL; } public static void putUnsignedInt(ByteBuffer buff, int value) { buff.putInt((int) (value & 0xffffffffL)); } public static void putUnsignedInt(ByteBuffer buff, int position, int value) { buff.putInt(position, (int) (value & 0xffff)); } }
由于Java不提供无符号的byte类型,因此取出来的无符号数值要转成比它大一级的基本数据类型,getUnsignedByte()返回short类型,getUnsignedShort()返回int类型,getUnsignedInt()返回long类型。没有比long更大的基本数据类型,因此不能存取long类型的无符号数值(能够用int替代)。