1 package com.nio.test;
  2 
  3 import java.io.IOException;
  4 import java.io.RandomAccessFile;
  5 import java.net.InetSocketAddress;
  6 import java.nio.ByteBuffer;
  7 import java.nio.CharBuffer;
  8 import java.nio.channels.DatagramChannel;
  9 import java.nio.channels.FileChannel;
 10 import java.nio.channels.Pipe;
 11 import java.nio.channels.ServerSocketChannel;
 12 import java.nio.channels.SocketChannel;
 13 import java.nio.charset.Charset;
 14 import java.nio.charset.CharsetDecoder;
 15 import java.nio.charset.CoderResult;
 16 import java.nio.file.Files;
 17 import java.nio.file.LinkOption;
 18 import java.nio.file.Path;
 19 import java.nio.file.Paths;
 20 
 21 public class ChannelTest {
 22     public static void main(String[] args) throws Exception {
 23         new ChannelTest().filewrite();
 24         new ChannelTest().byteBufferUtf8();
 25         new ChannelTest().fileread();
 26         new ChannelTest().clientsocket();
 27         new ChannelTest().serverSocket();
 28         new ChannelTest().serverDatagram();
 29         new ChannelTest().clientDatagram();
 30         new ChannelTest().pipe();
 31         new ChannelTest().NIOPath();
 32     }
 33 
 34     private void fileread() {
 35         RandomAccessFile aFile;
 36         Charset charset = Charset.forName("UTF-8");
 37         CharsetDecoder decoder = charset.newDecoder();
 38         try {
 39             // 在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,
 40             // 需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例
 41             aFile = new RandomAccessFile("src/com/nio/test/nio-data.txt", "rw");
 42 
 43             FileChannel inChannel = aFile.getChannel();
 44             // 首先,分配一个Buffer。从FileChannel中读取的数据将被读到Buffer中。
 45             // create buffer with capacity of 48 byte
 46             ByteBuffer byteBuffer = ByteBuffer.allocate(48);// read into buffer.
 47             CharBuffer charBuffer = CharBuffer.allocate(48);
 48 
 49             // 调用多个read()方法之一 从FileChannel中读取数据。
 50             int bytesRead = inChannel.read(byteBuffer);
 51 
 52             char[] tmp = null; // 临时存放转码后的字符
 53             byte[] remainByte = null;// 存放decode操作后未处理完的字节。decode仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,下次再转
 54             int leftNum = 0; // 未转码的字节数
 55             
 56             while (bytesRead != -1) {
 57 
 58                 //System.out.println("Read " + bytesRead);
 59                 byteBuffer.flip(); // make buffer ready for read
 60                 decoder.decode(byteBuffer, charBuffer, false);
 61                 
 62                 charBuffer.flip();
 63 
 64                 remainByte = null;
 65                 leftNum = byteBuffer.limit() - byteBuffer.position();
 66                 if (leftNum > 0) { // 记录未转换完的字节
 67                     remainByte = new byte[leftNum];
 68                     byteBuffer.get(remainByte, 0, leftNum);
 69                 }
 70                 
 71                 // 输出已转换的字符
 72                 tmp = new char[charBuffer.length()];
 73                 while (charBuffer.hasRemaining()) {
 74                     charBuffer.get(tmp);
 75                     System.out.print(new String(tmp));
 76                 }
 77                 
 78                 byteBuffer.clear(); // make buffer ready for writing
 79                 charBuffer.clear();
 80 
 81                 if (remainByte != null) {
 82                     byteBuffer.put(remainByte); // 将未转换完的字节写入bBuf,与下次读取的byte一起转换
 83                 }
 84                 
 85                 bytesRead = inChannel.read(byteBuffer);
 86             }
 87             
 88             aFile.close();
 89         } catch (Exception e) {
 90             // TODO Auto-generated catch block
 91             e.printStackTrace();
 92         }
 93     }
 94 
 95     private void filewrite() throws Exception {
 96         RandomAccessFile accessFile = new RandomAccessFile("src/com/nio/test/nio-data11.txt", "rw");
 97         FileChannel fileChannel = accessFile.getChannel();
 98         String newDate = "New String to write to file" + System.currentTimeMillis();
 99         ByteBuffer buffer = ByteBuffer.allocate(48);
100         buffer.clear();
101         buffer.put(newDate.getBytes());
102         buffer.flip();
103         while (buffer.hasRemaining()) {
104             fileChannel.write(buffer);
105         }
106 
107         /**
108          * FileChannel的truncate方法
109          * 可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:
110          * 
111          * 1 channel.truncate(1024); 这个例子截取文件的前1024个字节。
112          */
113         //fileChannel.truncate(12);
114         /**
115          * FileChannel.force()方法将通道里尚未写入磁盘的数据制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,
116          * 所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。
117          */
118         fileChannel.force(true);// force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。
119 
120         fileChannel.close();
121     }
122 
123     private void clientsocket() throws Exception {
124         SocketChannel socketChannel = SocketChannel.open();
125         //可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。
126         socketChannel.configureBlocking(false);
127         socketChannel.connect(new InetSocketAddress("127.0.0.1", 60000));
128         
129         //为非阻塞模式的判断用
130         while(!socketChannel.finishConnect()){
131             socketChannelRead(socketChannel);
132         }
133     }
134     
135     /**
136      * 
137      * ServerSocketChannel
138      * 
139      * Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 
140      * 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。
141      * 
142      * @throws Exception 
143      * 
144      */
145     private void serverSocket() throws Exception {
146         // 通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel.
147         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
148         serverSocketChannel.socket().bind(new InetSocketAddress(60000));
149         // ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept()
150         // 方法会立刻返回,如果还没有新进来的连接,返回的将是null。
151         // 因此,需要检查返回的SocketChannel是否是null.
152         serverSocketChannel.configureBlocking(false);
153         // 通常不会仅仅只监听一个连接,在while循环中调用 accept()方法.
154         while (true) {
155             // 监听新进来的连接
156             // 通过 ServerSocketChannel.accept() 方法监听新进来的连接。当
157             // accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。
158             // 因此, accept()方法会一直阻塞到有新连接到达。
159             SocketChannel socketChannel = serverSocketChannel.accept();
160 
161             // 非阻塞模式
162             if (socketChannel != null) {
163                 socketChannelRead(socketChannel);
164             }
165             //通过调用ServerSocketChannel.close() 方法来关闭ServerSocketChannel
166             // serverSocketChannel.close();
167         }
168     }
169     
170     private static StringBuilder socketChannelRead(SocketChannel socketChannel) throws Exception {
171         
172         StringBuilder sb = new StringBuilder();
173         
174         Charset charset = Charset.forName("GBK");
175         CharsetDecoder decoder = charset.newDecoder();
176 
177         ByteBuffer byteBuffer = ByteBuffer.allocate(10);
178         CharBuffer charBuffer = CharBuffer.allocate(10);
179 
180         int bytesRead = socketChannel.read(byteBuffer);
181 
182         char[] tmp = null; // 临时存放转码后的字符
183         byte[] remainByte = null;// 存放decode操作后未处理完的字节。decode仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,下次再转
184         int leftNum = 0; // 未转码的字节数
185 
186         while (bytesRead != -1) {
187 
188             // System.out.println("Read " + bytesRead);
189             byteBuffer.flip(); // make buffer ready for read
190             CoderResult result = decoder.decode(byteBuffer, charBuffer, false);
191 //            System.out.println("result:"+ result);
192             charBuffer.flip();
193 
194             remainByte = null;
195             leftNum = byteBuffer.limit() - byteBuffer.position();
196             if (leftNum > 0) { // 记录未转换完的字节
197                 remainByte = new byte[leftNum];
198                 byteBuffer.get(remainByte, 0, leftNum);
199             }
200 
201             // 输出已转换的字符
202             tmp = new char[charBuffer.length()];
203             while (charBuffer.hasRemaining()) {
204                 charBuffer.get(tmp);
205                 //sb.append(tmp);
206                 System.out.print(new String(tmp));
207             }
208 
209             byteBuffer.clear(); // make buffer ready for writing
210             charBuffer.clear();
211 
212             if (remainByte != null) {
213                 byteBuffer.put(remainByte); // 将未转换完的字节写入bBuf,与下次读取的byte一起转换
214             }
215             bytesRead = socketChannel.read(byteBuffer);
216         }
217         return sb;
218     }
219     
220     /**
221      * Java NIO中的DatagramChannel是一个能收发UDP包的通道。
222      * 因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。
223      * 
224      * @throws Exception
225      */
226     private void serverDatagram() throws Exception {
227         /**
228          * 这个例子打开的 DatagramChannel可以在UDP端口9999上接收数据包。
229          */
230         DatagramChannel channel = DatagramChannel.open();
231         channel.socket().bind(new InetSocketAddress(60000));
232         
233         Charset charset = Charset.forName("GBK");
234         CharsetDecoder decoder = charset.newDecoder();
235         
236         //通过receive()方法从DatagramChannel接收数据
237         //receive()方法会将接收到的数据包内容复制到指定的Buffer.
238         //如果Buffer容不下收到的数据,多出的数据将被丢弃。
239         ByteBuffer byteBuffer = ByteBuffer.allocate(48);
240         CharBuffer charBuffer = CharBuffer.allocate(48);
241         byteBuffer.clear();
242         channel.receive(byteBuffer);
243         
244         char[] tmp = null; // 临时存放转码后的字符
245         while(true){
246             byteBuffer.flip();
247             
248             CoderResult result = decoder.decode(byteBuffer, charBuffer, false);
249             
250             charBuffer.flip();
251             tmp = new char[charBuffer.length()];
252             while (charBuffer.hasRemaining()) {
253                 charBuffer.get(tmp);
254                 System.out.print(new String(tmp));
255             }
256             byteBuffer.clear();
257             charBuffer.clear();
258             channel.receive(byteBuffer);
259         }
260     }
261     /**
262      * 可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,
263      * 连接到特定地址并不会像TCP通道那样创建一个真正的连接。
264      * 而是锁住DatagramChannel ,让其只能从特定地址收发数据。
265      * 当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。
266      * 
267      * @throws Exception
268      */
269     private void clientDatagram() throws Exception {
270         DatagramChannel channel = DatagramChannel.open();
271         String newData = "New^啊&ng& to write to fasdfsdafsdfdsfsadf1JLKJL)(&)&*(&&ile..." + System.currentTimeMillis();
272         ByteBuffer buf = ByteBuffer.allocate(480);
273         buf.clear();
274         buf.put(newData.getBytes("GBK"));
275         buf.flip();
276         
277         //通过send()方法从DatagramChannel发送数据  即使下面的地址无法连接也是可以发送数据的。
278         int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1", 60000));
279         //UDP在数据传送方面没有任何保证。
280     }
281     
282     /**
283      * Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。
284      * 数据会被写到sink通道,从source通道读取。
285      * @throws Exception 
286      */
287     private void pipe() throws Exception {
288         
289         Pipe pipe = Pipe.open();
290 
291         //构建一条线程 ,获取管道的SinkChannel,用于数据录入
292         Thread thread = new Thread(() -> {
293             // 向管道写数据
294             // 要向管道写数据,需要访问sink通道。
295             // 通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:
296             Pipe.SinkChannel sinkChannel = pipe.sink();
297             String newData = "New String to write to file..." + System.currentTimeMillis();
298             ByteBuffer buf = ByteBuffer.allocate(48);
299             buf.clear();
300             try {
301                 buf.put(newData.getBytes("GBK"));
302 
303                 buf.flip();
304                 while (buf.hasRemaining()) {
305                     sinkChannel.write(buf);
306                 }
307             } catch (Exception e) {
308                 // TODO Auto-generated catch block
309                 e.printStackTrace();
310             }
311         });
312         
313         //构建一条线程 ,让其去获取到SinkChannel录入的数据并输出
314         Thread thread1 = new Thread(() -> {
315             // 从管道读取数据
316             // 从读取管道的数据,需要访问source通道,
317             // 调用source通道的read()方法来读取数据,
318             Pipe.SourceChannel sourceChannel = pipe.source();
319 
320             Charset charset = Charset.forName("GBK");
321             CharsetDecoder decoder = charset.newDecoder();
322 
323             ByteBuffer byteBuffer = ByteBuffer.allocate(48);
324             CharBuffer charBuffer = CharBuffer.allocate(48);
325 
326             char[] tmp = null; // 临时存放转码后的字符
327             byte[] remainByte = null;// 存放decode操作后未处理完的字节。decode仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,下次再转
328             int leftNum = 0; // 未转码的字节数
329 
330             // read()方法返回的int值会告诉我们多少字节被读进了缓冲区。
331             int bytesRead;
332             try {
333                 bytesRead = sourceChannel.read(byteBuffer);
334 
335                 while (bytesRead != -1) {
336 
337                     // System.out.println("Read " + bytesRead);
338                     byteBuffer.flip(); // make buffer ready for read
339                     CoderResult result = decoder.decode(byteBuffer, charBuffer, false);
340                     // System.out.println("result:"+ result);
341                     charBuffer.flip();
342 
343                     remainByte = null;
344                     leftNum = byteBuffer.limit() - byteBuffer.position();
345                     if (leftNum > 0) { // 记录未转换完的字节
346                         remainByte = new byte[leftNum];
347                         byteBuffer.get(remainByte, 0, leftNum);
348                     }
349 
350                     // 输出已转换的字符
351                     tmp = new char[charBuffer.length()];
352                     while (charBuffer.hasRemaining()) {
353                         charBuffer.get(tmp);
354                         // sb.append(tmp);
355                         System.out.print(new String(tmp));
356                     }
357 
358                     byteBuffer.clear(); // make buffer ready for writing
359                     charBuffer.clear();
360 
361                     if (remainByte != null) {
362                         byteBuffer.put(remainByte); // 将未转换完的字节写入bBuf,与下次读取的byte一起转换
363                     }
364                     bytesRead = sourceChannel.read(byteBuffer);
365                 }
366             } catch (IOException e) {
367                 // TODO Auto-generated catch block
368                 e.printStackTrace();
369             }
370         });
371         
372         thread.run();
373         Thread.sleep(2000L);
374         thread1.run();
375         
376     }
377     
378     /**
379      * Path接口是java NIO2的一部分。首次在java 7中引入。Path接口在java.nio.file包下,
380      * 所以全称是java.nio.file.Path。 java中的Path表示文件系统的路径。可以指向文件或文件夹。
381      * 也有相对路径和绝对路径之分。绝对路径表示从文件系统的根路径到文件或是文件夹的路径。
382      * 相对路径表示从特定路径下访问指定文件或文件夹的路径。相对路径的概念可能有点迷糊,可以自己百度一下。
383      * 不要将文件系统的path和操作系统的环境变量path搞混淆。java.nio.file.Path接口和操作系统的path环境变量没有任何关系。
384      * 在很多方面,java.nio.file.Path接口和java.io.File有相似性,但也有一些细微的差别。
385      * 在很多情况下,可以用Path来代替File类。
386      */
387     private void NIOPath() {
388         //为了使用java.nio.file.Path实例,必须首先创建它。可以使用Paths 类的静态方法Paths.get()来产生一个实例。
389         //请注意例子开头的两个import语句。想要使用Paths类和Path接口,必须首先引入相应包。
390         //其次,注意Paths.get(“c:\\data\\myfile.txt”)的用法。
391         //其使用了Paths.get方法创建了Path的实例。它是一个工厂方法。
392         Path path = Paths.get("c:\\data\\myfile.txt");//绝对路径Path
393         
394         
395         //创建相对路径Path
396         //java NIO Path类也能使用相对路径。可以通过Paths.get(basePath, relativePath)创建一个相对路径Path。
397         Path projects = Paths.get("d:\\data", "projects");
398         //创建了一个指向d:\data\projects文件夹的实例。
399         Path file = Paths.get("d:\\data", "projects\\a-project\\myfile.txt");
400         //创建了一个指向 d:\data\projects\a-project\myfile.txt 文件的实例。
401         //.表示当前路径。例如,如果以如下方式创建一个相对路径:
402         //创建的Path实例对应的路径就是运行这段代码的项目工程目录。
403         Path currentDir = Paths.get(".");
404         System.out.println(currentDir.toAbsolutePath());
405         //..表示父类目录。
406         Path parentDir = Paths.get("..");
407         String path1 = "d:\\data\\projects\\a-project\\..\\another-project";
408         Path parentDir2 = Paths.get(path1);
409         //d:\data\projects\another-project在a-project目录后面的..符号,
410         //将指向的目录修改为projects目录,因此,最终path指向another-project目录。
411         
412         
413         //Path 的normalize()方法可以标准化路径。
414         String originalPath =
415                  "d:\\data\\projects\\a-project\\..\\another-project";
416 
417         Path path3 = Paths.get(originalPath);
418         System.out.println("path3 = " + path3);
419 
420         Path path2 = path3.normalize();
421         System.out.println("path2 = " + path2);
422         //如你所见,标准化后的路径不再包含 a-project\..部分,因为它是多余的。
423         
424         //Files.exists()
425         //Files.exists()方法用来检查文件系统中是否存在某路径。
426         //Path实例对应的路径可能在文件系统中并不存在。例如,如果打算新建一个文件夹,首先需要创建一个对应的Path实例,然后才能创建对应路径下的文件夹。
427         //因为Path实例对应的路径在文件系统的存在性不确定,可以使用Files.exists()方法确认Path对应的路径是否存在 (也就是开发需要自己显式的去调用该方法确认)。
428         //如下是Files.exists()的示例:
429         Path path5 = Paths.get("data/logging.properties");
430 
431         boolean pathExists = Files.exists(path, new LinkOption[] { LinkOption.NOFOLLOW_LINKS });
432         System.out.println(pathExists);
433         //示例中首先创建了一个Path。然后,通过调用Files.exists方法并将path作为第一个参数确认path对应的路径是否存在。
434         //注意下Files.exist()方法的第二个参数。第二个参数数组是评判路径是否存在时使用的规则。
435         //示例中,数组包含LinkOption.NOFOLLOW_LINKS枚举类型,表示Files.exists不会跟进到路径中有连接的下层文件目录。
436         //表示path路径中如果有连接,Files.exists方法不会跟进到连接中去
437     }
438     
439     private void byteBufferUtf8() throws Exception {
440         Charset charset = null;
441         CharsetDecoder decoder = null;
442         String charsetName = "UTF-8";
443         int capacity = 10;
444 
445         charset = Charset.forName(charsetName);
446         decoder = charset.newDecoder();
447 
448         String s = "客户端发送dsad德生科技电脑fdas上考虑迪士尼年少弗拉门发生ofjam打什么的即破发麦克 ‘;打, 饭哦按asdfasfsdfdfsfdsf都客户端发送dsad德生科技电脑fdas上考虑迪士尼年少弗拉门发生ofjam打什么的即破发麦克 ‘;打, 饭哦按asdfasfsdfdfsfdsf都客户端发送dsad德生科技电脑fdas上考虑迪士尼年少弗拉门发生ofjam打什么的即破发麦克 ‘;打, 饭哦按asdfasfsdfdfsfdsf都客户端发送dsad德生科技电脑fdas上考虑迪士尼年少弗拉门发生ofjam打什么的即破发麦克 ‘;打, 饭哦按asdfasfsdfdfsfdsf都";
449         byte[] bytes = s.getBytes(charsetName);
450 
451         // 模拟接收的ByteBuffer size 10
452         ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
453         // 用于临时存放Bytebuffer转换后的字符
454         CharBuffer charBuffer = CharBuffer.allocate(capacity);
455         // 用于连接展示字符串
456         StringBuilder sb = new StringBuilder();
457 
458         int i = 0;
459         while (true) {
460             byteBuffer.put(bytes[i]);
461             i++;
462             if (byteBuffer.remaining() == 0 || i == bytes.length) {
463                 byteBuffer.flip();
464                 CoderResult coderResult;
465                 if (i != bytes.length) {
466                     coderResult = decoder.decode(byteBuffer, charBuffer, false);
467                 } else {
468                     coderResult = decoder.decode(byteBuffer, charBuffer, true);
469                 }
470                 // 有错误
471                 if (coderResult.isError()) {
472                     coderResult.throwException();
473                 }
474                 charBuffer.flip();
475                 sb.append(charBuffer);
476                 charBuffer.clear();
477                 byteBuffer.compact();
478             }
479             // 退出循环
480             if (i == bytes.length) {
481                 break;
482             }
483         }
484         System.out.println(sb);
485     }
486 }