java网络编程系列之JavaIO的“今生”:NIO非阻塞模型

x33g5p2x  于2021-12-30 转载在 Java  
字(5.5k)|赞(0)|评价(0)|浏览(519)

BIO中的阻塞

非阻塞式NIO

  • Channel: Channel 和 IO 中的 Stream(流)是差不多一个等级的。只不过 Stream 是单向的,譬如:InputStream, OutputStream。并且Channel是非阻塞式的。

Channel与Buffer

通道可以用来读取和写入数据,通道类似于之前的输入/输出流,但是程序不会直接操作通道的,所有的内容都是先读到或写入到缓冲区中,再通过缓冲区中取得获写入的。

剖析Buffer

向Buffer中写入数据

此时读取分为两种情况:

  • 一次性将写入的数据全部读取出来

  • 读取数据时,读取数据到一半,希望转换为写入模式,但是又不希望丢掉还没有读取完毕的数据

剖析channel

  • channel可以通过buffer读取和写入数据
  • 两个channel之间也可以直接进行数据间的传输

几个重要的channel

多方法实现本地文件拷贝

通用的关闭流方法

  1. //通用关闭流和通道的方法
  2. //所有可以被关闭的流和通道都实现了Closeable接口
  3. public static void close(Closeable closeable)
  4. {
  5. if(closeable!=null)
  6. {
  7. try {
  8. closeable.close();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
字节输入流拷贝文件
  1. //字节流拷贝文件实现
  2. public static void noBufferStreamCopy(File src, File tar)
  3. {
  4. InputStream in=null;
  5. OutputStream out=null;
  6. try
  7. {
  8. in=new FileInputStream(src);
  9. out=new FileOutputStream(tar);
  10. //每次读取一个字节
  11. Integer len;
  12. //文件读取完毕返回-1
  13. while((len=in.read())!=-1)
  14. {
  15. out.write(len);
  16. }
  17. }
  18. catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }finally {
  23. close(in);
  24. close(out);
  25. }
  26. }
字节缓冲流拷贝文件
  1. //字节缓冲流拷贝文件实现
  2. public static void bufferStreamCopy(File src,File tar)
  3. {
  4. BufferedInputStream bis=null;
  5. BufferedOutputStream bos=null;
  6. //使用装饰器模式
  7. try {
  8. bis = new BufferedInputStream(new FileInputStream(src));
  9. bos=new BufferedOutputStream(new FileOutputStream(tar));
  10. //准备一个缓存区,大小为1024个字节
  11. byte[] buffer=new byte[1024];
  12. int len;
  13. //一次性读取1024个字节
  14. while((len=bis.read(buffer))!=-1)
  15. {
  16. bos.write(buffer,0,len);
  17. }
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }finally {
  23. close(bis);
  24. close(bos);
  25. }
  26. }
FileChannel拷贝文件
  1. //FileChannel拷贝文件
  2. public static void nioBufferCopy(File src,File tar)
  3. {
  4. FileChannel fin=null;
  5. FileChannel fout=null;
  6. try {
  7. fin=new FileInputStream(src).getChannel();
  8. fout=new FileOutputStream(tar).getChannel();
  9. //准备一个缓冲区
  10. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  11. while(fin.read(byteBuffer)!=-1)//将数据写入缓冲区,一次最多写入1024个字节
  12. {
  13. //将当前缓冲区从写模式转换为读模式
  14. byteBuffer.flip();
  15. //一直读取到缓冲区没有数据剩余为止
  16. while (byteBuffer.hasRemaining())
  17. {
  18. //从缓冲区中读取数据,写入通道中
  19. fout.write(byteBuffer);
  20. }
  21. //将当前缓冲区从读模式转换为写模式
  22. byteBuffer.clear();
  23. }
  24. } catch (FileNotFoundException e) {
  25. e.printStackTrace();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }finally {
  29. close(fin);
  30. close(fout);
  31. }
  32. }
通道间的数据传输完成文件拷贝—transferto,transferfrom
  1. //通道间传输完成文件拷贝
  2. public static void nioTransferCopy(File src,File tar)
  3. {
  4. FileChannel fin=null;
  5. FileChannel fout=null;
  6. try {
  7. fin=new FileInputStream(src).getChannel();
  8. fout=new FileOutputStream(tar).getChannel();
  9. //记录当前总共传输的字节数
  10. Long transferredBytes= 0L;
  11. //记录需要拷贝文件的字节总数
  12. Long size=fin.size();
  13. while(transferredBytes!=size)
  14. {
  15. //transferTo函数每次返回当前总共拷贝了多少个字节
  16. transferredBytes+=fin.transferTo(0,size,fout);
  17. }
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. finally {
  22. close(fin);
  23. close(fout);
  24. }
  25. }

文件拷贝的完整源码

  1. public class Main
  2. {
  3. public static void main(String[] args) {
  4. final String prefix=System.getProperty("user.dir")+System.getProperty("file.separator");
  5. File src=new File(prefix+"src.txt");
  6. File tar=new File(prefix+"tar.txt");
  7. //测试字节流拷贝文件
  8. CopyFile.nioTransferCopy(src,tar);
  9. }
  10. public static class CopyFile
  11. {
  12. //字节流拷贝文件实现
  13. public static void noBufferStreamCopy(File src, File tar)
  14. {
  15. InputStream in=null;
  16. OutputStream out=null;
  17. try
  18. {
  19. in=new FileInputStream(src);
  20. out=new FileOutputStream(tar);
  21. //每次读取一个字节
  22. Integer len;
  23. //文件读取完毕返回-1
  24. while((len=in.read())!=-1)
  25. {
  26. out.write(len);
  27. }
  28. }
  29. catch (FileNotFoundException e) {
  30. e.printStackTrace();
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }finally {
  34. close(in);
  35. close(out);
  36. }
  37. }
  38. //字节缓冲流拷贝文件实现
  39. public static void bufferStreamCopy(File src,File tar)
  40. {
  41. BufferedInputStream bis=null;
  42. BufferedOutputStream bos=null;
  43. //使用装饰器模式
  44. try {
  45. bis = new BufferedInputStream(new FileInputStream(src));
  46. bos=new BufferedOutputStream(new FileOutputStream(tar));
  47. //准备一个缓存区,大小为1024个字节
  48. byte[] buffer=new byte[1024];
  49. int len;
  50. //一次性读取1024个字节
  51. while((len=bis.read(buffer))!=-1)
  52. {
  53. bos.write(buffer,0,len);
  54. }
  55. } catch (FileNotFoundException e) {
  56. e.printStackTrace();
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }finally {
  60. close(bis);
  61. close(bos);
  62. }
  63. }
  64. //FileChannel拷贝文件
  65. public static void nioBufferCopy(File src,File tar)
  66. {
  67. FileChannel fin=null;
  68. FileChannel fout=null;
  69. try {
  70. fin=new FileInputStream(src).getChannel();
  71. fout=new FileOutputStream(tar).getChannel();
  72. //准备一个缓冲区
  73. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  74. while(fin.read(byteBuffer)!=-1)//将数据写入缓冲区,一次最多写入1024个字节
  75. {
  76. //将当前缓冲区从写模式转换为读模式
  77. byteBuffer.flip();
  78. //一直读取到缓冲区没有数据剩余为止
  79. while (byteBuffer.hasRemaining())
  80. {
  81. //从缓冲区中读取数据,写入通道中
  82. fout.write(byteBuffer);
  83. }
  84. //将当前缓冲区从读模式转换为写模式
  85. byteBuffer.clear();
  86. }
  87. } catch (FileNotFoundException e) {
  88. e.printStackTrace();
  89. } catch (IOException e) {
  90. e.printStackTrace();
  91. }finally {
  92. close(fin);
  93. close(fout);
  94. }
  95. }
  96. //通道间传输完成文件拷贝
  97. public static void nioTransferCopy(File src,File tar)
  98. {
  99. FileChannel fin=null;
  100. FileChannel fout=null;
  101. try {
  102. fin=new FileInputStream(src).getChannel();
  103. fout=new FileOutputStream(tar).getChannel();
  104. //记录当前总共传输的字节数
  105. Long transferredBytes= 0L;
  106. //记录需要拷贝文件的字节总数
  107. Long size=fin.size();
  108. while(transferredBytes!=size)
  109. {
  110. //transferTo函数每次返回当前总共拷贝了多少个字节
  111. transferredBytes+=fin.transferTo(0,size,fout);
  112. }
  113. } catch (IOException e) {
  114. e.printStackTrace();
  115. }
  116. finally {
  117. close(fin);
  118. close(fout);
  119. }
  120. }
  121. }
  122. //通用关闭流和通道的方法
  123. //所有可以被关闭的流和通道都实现了Closeable接口
  124. public static void close(Closeable closeable)
  125. {
  126. if(closeable!=null)
  127. {
  128. try {
  129. closeable.close();
  130. } catch (IOException e) {
  131. e.printStackTrace();
  132. }
  133. }
  134. }
  135. }

文件拷贝测试结果

对于小文件拷贝而言,nio优势也还可以,对于大文件而言,nio的优势相对比较明显,并且大家要注意缓冲区大小的选择

剖析Selector

  • 简而言之Selector可以帮助我们监控多个通道的状态
  • 想让Selector监控对应的channel,首先需要把需要被监控的channel注册在Selector上面

在Selector上面注册三个Channel

channel的状态变化

channel可以在上面四个状态中来回切换

在selector上面注册channel

SelectionKey:该对象可以作为当前注册在selector上的channel的唯一标识,通过这个对象也可以获取到当前channel的一些信息

下面展示SelectionKey对象的一些方法:

  • interestOps():返回当前channel在selector上面注册的状态
  • readyOps():返回当前channel的有哪些被监听的状态是处于准备好的,可操作的状态
  • channel():返回selectionkey指代的channel对象
  • selector():返回的是当前channel是在哪一个selector上进行的注册
  • attachment():关于channel的附加信息

使用selector选择channel

  • 第一个channel注册在selector上,并且让selector只去监听当前channel的connect状态
  • 后面两个同理

通过select()函数可以返回当前处于可操作状态下的channel

相关文章