Java NIO系列使用示例
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 }
勇者无惧,强者无敌。