传统IO是面向流,NIO是面向缓冲区
面向流的传统IO建立的通道是单向的,NIO创建的通道是双向的
NIO的核心在于,通道和缓冲区。通道表示打开到IO设备的连接,若需要使用NIO,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之就是 通道负责传输,缓冲区负责存储。
1> 缓冲区
缓冲区的底层用的就是数组,根据传输数据类型的不同,java为我们提供了相应类型的缓冲区
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
很显然,ByteBuffer是最常用的,因为在磁盘或网络中传输文件都是用的字节。
这些缓冲区的管理方式基本一致,都是通过allocate()方法获取一个缓冲区。
//分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
2>缓冲区中的四个核心属性
capacity:容量,表示缓冲区中最大存储输的容量,一旦声明不能改变
limit:界限,表示缓冲区中可以操作数据的大小。即 limit后面的数据不能读写
position:位置,表示缓冲区中正在操作数据的位置
mark:标记当前position的位置,position位置改变后,可以通过reset方法来使其恢复到mark标记的位置
System.out.println(buf.position());//输出0
System.out.println(buf.limit());//输出1024
System.out.println(buf.capacity());//输出1024
此时缓冲区应该是这样的,下面是一个大小是10的缓冲区
3>缓冲区存取数据
put方法,往缓冲区存数据
get方法,从缓冲区取数据
buf.put("abc".getBytes());
此时缓冲区是下面这个样子,此时的模式是写数据模式。如果此时使用get方法获取数据,是获取不到数据的,因为position之后是没有数据的。
所以我们现在再来输出一下这三个属性的值
System.out.println(buf.position());//输出3 System.out.println(buf.limit());//输出1024 System.out.println(buf.capacity());//输出1024
flip方法,切换到读数据模式,position会回到起点
buf.flip();
此时的缓冲区是这样的,limit界限变成了刚才存入数据的长度
这时候我们再来输出一下三个属性的值
System.out.println(buf.position());//输出0
System.out.println(buf.limit());//输出3
System.out.println(buf.capacity());//输出1024
使用get方法读取内容
byte[] b = new byte[buf.limit()];//创建一个字节数组,长度为缓冲区limit界限的值
buf.get(b);//读出内容到指定的字节数组
System.out.println(new String(b,0,b.length));//输出abc
此时的缓冲区是这样的
输出一下三个属性的值
System.out.println(buf.position());//输出3
System.out.println(buf.limit());//输出3
System.out.println(buf.capacity());//输出1024
rewind方法,重新读数据,三个属性回到执行flip方法后的值
clear方法,清空缓冲区,但是这个方法只会把单个属性的值回到初始状态,而缓冲区中的数据不会删除,只是数据会处于一个被遗忘状态。
hasRemaining方法,判断缓冲区中是否有可操作的数据,
remaining方法,获取可操作数据的数量
if(buf.hasRemaining()){
//如果有可操作的数据,输出可操作数据的数量
System.out.println(buf.remaining());
}
4>直接缓冲区和非直接缓冲区
非直接缓冲区:通过allocate方法分配的缓冲区,将缓冲区直接建立在JVM的内存中。
直接缓冲区:通过allocateDirect方法分配的缓冲区,将缓冲区建立在物理内存中。可以提高效率,但是消耗的资源变大。
isDirect方法用来判断是不是直接缓冲区。
5>通道(Channel)
用于源节点与目标节点的连接,在Java NIO 中负责缓冲区中数据的传输。Channel本身不能存储数据,因此需要配合缓冲区进行传输。就好比铁路需要火车配合一样。
6>通道的主要实现类
java.nio.channels.Chennel接口
|--FileChannel 本地
|--SocketChannel TCP
|--ServerSocketChannel TCP
|--DatagramChannel UDP
7>获取通道
1.java针对支持通道的类提供了getChannel()方法
本地IO:
FileInputStream、FileOutputStream、RandomAccessFile
网络IO:
Socket、ServerSocket、DatagramSocket
2.在JDK 1.7之后针对各个通道提供了静态方法open()
3.在JDK 1.7之后的Files工具类的newByteChannel()方法
利用通道完成文件的复制(非直接缓冲区)
FileInputStream fis = null; FileOutputStream fos = null; FileChannel inChannel = null; FileChannel outChannel = null; try { fis = new FileInputStream("D:\\SVN_WORK\\untitled\\src\\test\\1.png"); fos = new FileOutputStream("D:\\SVN_WORK\\untitled\\src\\test\\2.png"); //获取通道 inChannel = fis.getChannel(); outChannel = fos.getChannel(); //分配一个指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //将通道中的数据放入缓冲区 while (inChannel.read(buf) != -1){ //切换到读取数据模式 buf.flip(); //将缓冲区的数据写入通道 outChannel.write(buf); buf.clear(); } } catch (Exception e) { e.printStackTrace(); }finally { if(inChannel != null){ try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(outChannel != null){ try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
使用直接缓冲区完成文件的复制(内存映射文件),只支持ByteBuffer
FileChannel inChannel = null; FileChannel outChannel = null; MappedByteBuffer inMappedBuf = null; MappedByteBuffer outMappedBuf = null; try{ inChannel = FileChannel.open(Paths.get("D:\\SVN_WORK\\untitled\\src\\test\\1.png"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("D:\\SVN_WORK\\untitled\\src\\test\\3.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //内存映射文件 inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size()); outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size()); //直接对缓冲区进行数据的读写操作 byte[] b = new byte[inMappedBuf.limit()]; inMappedBuf.get(b); outMappedBuf.put(b); }catch (Exception e){ e.printStackTrace(); }finally { if(inChannel != null){ try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(outChannel != null){ try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }
通道之间的数据传输(直接缓冲区)
FileChannel inChannel = null; FileChannel outChannel = null; MappedByteBuffer inMappedBuf = null; MappedByteBuffer outMappedBuf = null; try { inChannel = FileChannel.open(Paths.get("D:\\SVN_WORK\\untitled\\src\\test\\1.png"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("D:\\SVN_WORK\\untitled\\src\\test\\3.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); //方法1 inChannel.transferTo(0, inChannel.size(), outChannel); //方法2 // outChannel.transferFrom(inChannel,0,inChannel.size()); } catch (Exception e) { e.printStackTrace(); } finally { if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel != null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }
8>分散于聚集
分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
RandomAccessFile raf1 = null; RandomAccessFile raf2 = null; try { raf1 = new RandomAccessFile("1.txt", "rw"); //1 获取通道 FileChannel channel1 = raf1.getChannel(); //2 分配指定大小的缓冲区 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); //3 分散读取 ByteBuffer[] bus = {buf1,buf2}; channel1.read(bus); //切换到读数据模式 for (ByteBuffer b : bus) { b.flip(); } //4 聚集写入 raf2 = new RandomAccessFile("2.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bus); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
9>字符集
编码:字符串->字节数组
解码:字节数组->字符串
查看所支持的所有编码
Map<String, Charset> map = Charset.availableCharsets(); Set<Entry<String, Charset>> set = map.entrySet(); for (Entry<String, Charset> entry : set) { System.out.println(entry.getKey()+"---"+entry.getValue()); }
输出结果:
Big5---Big5
Big5-HKSCS---Big5-HKSCS
CESU-8---CESU-8
EUC-JP---EUC-JP
EUC-KR---EUC-KR
GB18030---GB18030
GB2312---GB2312
GBK---GBK
IBM-Thai---IBM-Thai
IBM00858---IBM00858
IBM01140---IBM01140
IBM01141---IBM01141
IBM01142---IBM01142
IBM01143---IBM01143
IBM01144---IBM01144
IBM01145---IBM01145
IBM01146---IBM01146
IBM01147---IBM01147
IBM01148---IBM01148
IBM01149---IBM01149
IBM037---IBM037
IBM1026---IBM1026
IBM1047---IBM1047
IBM273---IBM273
IBM277---IBM277
IBM278---IBM278
IBM280---IBM280
IBM284---IBM284
IBM285---IBM285
IBM290---IBM290
IBM297---IBM297
IBM420---IBM420
IBM424---IBM424
IBM437---IBM437
IBM500---IBM500
IBM775---IBM775
IBM850---IBM850
IBM852---IBM852
IBM855---IBM855
IBM857---IBM857
IBM860---IBM860
IBM861---IBM861
IBM862---IBM862
IBM863---IBM863
IBM864---IBM864
IBM865---IBM865
IBM866---IBM866
IBM868---IBM868
IBM869---IBM869
IBM870---IBM870
IBM871---IBM871
IBM918---IBM918
ISO-2022-CN---ISO-2022-CN
ISO-2022-JP---ISO-2022-JP
ISO-2022-JP-2---ISO-2022-JP-2
ISO-2022-KR---ISO-2022-KR
ISO-8859-1---ISO-8859-1
ISO-8859-13---ISO-8859-13
ISO-8859-15---ISO-8859-15
ISO-8859-2---ISO-8859-2
ISO-8859-3---ISO-8859-3
ISO-8859-4---ISO-8859-4
ISO-8859-5---ISO-8859-5
ISO-8859-6---ISO-8859-6
ISO-8859-7---ISO-8859-7
ISO-8859-8---ISO-8859-8
ISO-8859-9---ISO-8859-9
JIS_X0201---JIS_X0201
JIS_X0212-1990---JIS_X0212-1990
KOI8-R---KOI8-R
KOI8-U---KOI8-U
Shift_JIS---Shift_JIS
TIS-620---TIS-620
US-ASCII---US-ASCII
UTF-16---UTF-16
UTF-16BE---UTF-16BE
UTF-16LE---UTF-16LE
UTF-32---UTF-32
UTF-32BE---UTF-32BE
UTF-32LE---UTF-32LE
UTF-8---UTF-8
windows-1250---windows-1250
windows-1251---windows-1251
windows-1252---windows-1252
windows-1253---windows-1253
windows-1254---windows-1254
windows-1255---windows-1255
windows-1256---windows-1256
windows-1257---windows-1257
windows-1258---windows-1258
windows-31j---windows-31j
x-Big5-HKSCS-2001---x-Big5-HKSCS-2001
x-Big5-Solaris---x-Big5-Solaris
x-euc-jp-linux---x-euc-jp-linux
x-EUC-TW---x-EUC-TW
x-eucJP-Open---x-eucJP-Open
x-IBM1006---x-IBM1006
x-IBM1025---x-IBM1025
x-IBM1046---x-IBM1046
x-IBM1097---x-IBM1097
x-IBM1098---x-IBM1098
x-IBM1112---x-IBM1112
x-IBM1122---x-IBM1122
x-IBM1123---x-IBM1123
x-IBM1124---x-IBM1124
x-IBM1166---x-IBM1166
x-IBM1364---x-IBM1364
x-IBM1381---x-IBM1381
x-IBM1383---x-IBM1383
x-IBM300---x-IBM300
x-IBM33722---x-IBM33722
x-IBM737---x-IBM737
x-IBM833---x-IBM833
x-IBM834---x-IBM834
x-IBM856---x-IBM856
x-IBM874---x-IBM874
x-IBM875---x-IBM875
x-IBM921---x-IBM921
x-IBM922---x-IBM922
x-IBM930---x-IBM930
x-IBM933---x-IBM933
x-IBM935---x-IBM935
x-IBM937---x-IBM937
x-IBM939---x-IBM939
x-IBM942---x-IBM942
x-IBM942C---x-IBM942C
x-IBM943---x-IBM943
x-IBM943C---x-IBM943C
x-IBM948---x-IBM948
x-IBM949---x-IBM949
x-IBM949C---x-IBM949C
x-IBM950---x-IBM950
x-IBM964---x-IBM964
x-IBM970---x-IBM970
x-ISCII91---x-ISCII91
x-ISO-2022-CN-CNS---x-ISO-2022-CN-CNS
x-ISO-2022-CN-GB---x-ISO-2022-CN-GB
x-iso-8859-11---x-iso-8859-11
x-JIS0208---x-JIS0208
x-JISAutoDetect---x-JISAutoDetect
x-Johab---x-Johab
x-MacArabic---x-MacArabic
x-MacCentralEurope---x-MacCentralEurope
x-MacCroatian---x-MacCroatian
x-MacCyrillic---x-MacCyrillic
x-MacDingbat---x-MacDingbat
x-MacGreek---x-MacGreek
x-MacHebrew---x-MacHebrew
x-MacIceland---x-MacIceland
x-MacRoman---x-MacRoman
x-MacRomania---x-MacRomania
x-MacSymbol---x-MacSymbol
x-MacThai---x-MacThai
x-MacTurkish---x-MacTurkish
x-MacUkraine---x-MacUkraine
x-MS932_0213---x-MS932_0213
x-MS950-HKSCS---x-MS950-HKSCS
x-MS950-HKSCS-XP---x-MS950-HKSCS-XP
x-mswin-936---x-mswin-936
x-PCK---x-PCK
x-SJIS_0213---x-SJIS_0213
x-UTF-16LE-BOM---x-UTF-16LE-BOM
X-UTF-32BE-BOM---X-UTF-32BE-BOM
X-UTF-32LE-BOM---X-UTF-32LE-BOM
x-windows-50220---x-windows-50220
x-windows-50221---x-windows-50221
x-windows-874---x-windows-874
x-windows-949---x-windows-949
x-windows-950---x-windows-950
x-windows-iso2022jp---x-windows-iso2022jp
编码解码
Charset cs1 = Charset.forName("GBK"); //获取编码器 CharsetEncoder ce = cs1.newEncoder(); //获取解码器 CharsetDecoder cd = cs1.newDecoder(); CharBuffer cBuf = CharBuffer.allocate(1024); cBuf.put("刘海柱"); cBuf.flip(); try { //编码 ByteBuffer bBuf = ce.encode(cBuf); //解码 bBuf.flip(); CharBuffer cBuf2 = cd.decode(bBuf); System.out.println(cBuf2.toString());//输出刘海柱 //用utf-8解码 Charset.forName("utf-8").newDecoder().decode(bBuf);//输出乱码 } catch (CharacterCodingException e) { e.printStackTrace(); }
10>NIO阻塞式与非阻塞式
这个阻塞式与非阻塞式主要是针对的网络IO,非阻塞式可以让服务端减少不必要的等待
先来看一下使用阻塞式的方式
//客户端 @Test public void client(){ SocketChannel sChannel = null; FileChannel inChannel = null; try { //1,获取通道 sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8998)); inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ); //2,分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //3,读取本地文件,并发送到服务器 while(inChannel.read(buf) != -1){ buf.flip(); sChannel.write(buf); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { //关闭通道 if(sChannel != null){ try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(inChannel != null){ try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } //服务端 @Test public void server(){ ServerSocketChannel ssChannel = null; FileChannel outChannel = null; SocketChannel sChannel = null; try { //1,获取通道 ssChannel = ServerSocketChannel.open(); outChannel = FileChannel.open(Paths.get("2.png"),StandardOpenOption.WRITE,StandardOpenOption.CREATE); //2,绑定连接 ssChannel.bind(new InetSocketAddress(8998)); //3,获取客户端连接的通道 sChannel = ssChannel.accept(); //4,分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //5,接收客户端的数据,并保存到本地 while (sChannel.read(buf) != -1){ buf.flip(); outChannel.write(buf); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { //6,关闭通道 if(ssChannel != null){ try { ssChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(outChannel != null){ try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(sChannel != null){ try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
非阻塞式
//非阻塞式客户端 @Test public void client1() { SocketChannel sChannel = null; try { //1,获取通道 sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8998)); //2,切换到非阻塞式 sChannel.configureBlocking(false); //3,分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //4,发送数据到服务端 buf.put("刘海柱".getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } catch (IOException e) { e.printStackTrace(); } finally { //5,关闭通道 if (sChannel != null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } //非阻塞式服务端 @Test public void server1() { ServerSocketChannel ssChannel = null; SocketChannel sChannel = null; try { //1,获取通道 ssChannel = ServerSocketChannel.open(); //2,切换非阻塞模式 ssChannel.configureBlocking(false); //3,绑定连接 ssChannel.bind(new InetSocketAddress(8998)); //4,获取选择器 Selector selector = Selector.open(); //5,将通道注册到选择器上,并指定“监听接收事件”,监听连接事件 ssChannel.register(selector, SelectionKey.OP_ACCEPT); //6,轮询式的获取选择器上已经准备就绪的事件 while(selector.select() > 0){ //7,获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ //8,获取准备就绪的事件 SelectionKey sk = it.next(); //9,具体判断是什么事件准备就绪 if(sk.isAcceptable()){ //10,如果接收就绪,获取客户端连接的通道 sChannel = ssChannel.accept(); //11,切换到非阻塞模式 sChannel.configureBlocking(false); //12,将该通道注册到选择器,监听读就绪状态 sChannel.register(selector,SelectionKey.OP_READ); }else if(sk.isReadable()){ //13,获取当前选择器上读就绪状态的通道 sChannel = (SocketChannel) sk.channel(); //14,读取数据 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(),0,len)); } } //15,取消选择键(SelectionKey) it.remove(); } } } catch (IOException e) { e.printStackTrace(); } finally { //6,关闭通道 if (ssChannel != null) { try { ssChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (sChannel != null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
上面使用的是TCP方式,下面使用UDP方式传输数据
//发送端 @Test public void send() { DatagramChannel dc = null; try { //获取通道 dc = DatagramChannel.open(); //切换到非阻塞模式 dc.configureBlocking(false); //缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //缓冲区中添加数据 buf.put(new Date().toString().getBytes()); //切换到读取模式 buf.flip(); //发送 dc.send(buf, new InetSocketAddress("127.0.0.1", 8999)); //清空缓冲区 buf.clear(); } catch (IOException e) { e.printStackTrace(); } finally { if (dc != null) { try { //关闭通道 dc.close(); } catch (IOException e) { e.printStackTrace(); } } } } //接收端 @Test public void receive() { DatagramChannel dc = null; try { //获取通道 dc = DatagramChannel.open(); //切换到非阻塞模式 dc.configureBlocking(false); //绑定连接 dc.bind(new InetSocketAddress(8999)); //打开选择器 Selector selector = Selector.open(); //把通道注册到选择器,监听读 dc.register(selector, SelectionKey.OP_READ); //轮询获取数据 while(selector.select() > 0){ Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey sk = it.next(); //如果读就绪 if(sk.isReadable()){ ByteBuffer buf = ByteBuffer.allocate(1024); //接收数据 dc.receive(buf); buf.flip(); System.out.println(new String(buf.array(),0,buf.limit())); buf.clear(); } } it.remove(); } } catch (IOException e) { e.printStackTrace(); }finally { if(dc != null){ try { //关闭通道 dc.close(); } catch (IOException e) { e.printStackTrace(); } } } }
11>管道(Pipe)
Java NIO 管道是两个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
Pipe pipe = null; SinkChannel sinkChannel = null; SourceChannel sourceChannel = null; try { //1,获取管道 pipe = Pipe.open(); //2,缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); buf.put("单向管道发送数据".getBytes()); buf.flip(); //3,获取通道 sinkChannel = pipe.sink(); //4,发送数据 sinkChannel.write(buf); //5,读取数据 sourceChannel = pipe.source(); buf.flip(); int len = sourceChannel.read(buf); System.out.println(new String(buf.array(),0,len)); } catch (IOException e) { e.printStackTrace(); }finally { if(sinkChannel != null){ try { sinkChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(sourceChannel != null){ try { sourceChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }