经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
java核心技术-NIO
来源:cnblogs  作者:我是派大星  时间:2018/11/7 0:44:53  对本文有异议

1、reactor(反应器)模式

  使用单线程模拟多线程,提高资源利用率和程序的效率,增加系统吞吐量。下面例子比较形象的说明了什么是反应器模式:

  一个老板经营一个饭店,

  传统模式 - 来一个客人安排一个服务员招呼,客人很满意;(相当于一个连接一个线程)

  后来客人越来越多,需要的服务员越来越多,资源条件不足以再请更多的服务员了,传统模式已经不能满足需求。老板之所以为老板自然有过人之处,老板发现,服务员在为客人服务时,当客人点菜的时候,服务员基本处于等待状态,(阻塞线程,不做事)。

  于是乎就让服务员在客人点菜的时候,去为其他客人服务,当客人菜点好后再招呼服务员即可。 --反应器(reactor)模式诞生了

  饭店的生意红红火火,几个服务员就足以支撑大量的客流量,老板用有限的资源赚了更多的money~~~~_

 通道:类似于流,但是可以异步读写数据(流只能同步读写),通道是双向的,(流是单向的),通道的数据总是要先读到一个buffer 或者 从一个buffer写入,即通道与buffer进行数据交互。

  

通道类型:  

  • FileChannel:从文件中读写数据。  
  • DatagramChannel:能通过UDP读写网络中的数据。  
  • SocketChannel:能通过TCP读写网络中的数据。  
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。  

 - FileChannel比较特殊,它可以与通道进行数据交互, 不能切换到非阻塞模式,套接字通道可以切换到非阻塞模式;

缓冲区 - 本质上是一块可以存储数据的内存,被封装成了buffer对象而已!

  

缓冲区类型:

  • ByteBuffer  
  • MappedByteBuffer  
  • CharBuffer  
  • DoubleBuffer  
  • FloatBuffer  
  • IntBuffer  
  • LongBuffer  
  • ShortBuffer  

常用方法:

  • allocate() - 分配一块缓冲区  
  • put() - 向缓冲区写数据
  • get() - 向缓冲区读数据  
  • filp() - 将缓冲区从写模式切换到读模式  
  • clear() - 从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘;  
  • compact() - 从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据
  • mark() - 对position做出标记,配合reset使用
  • reset() - 将position置为标记值    

缓冲区的一些属性:

  • capacity - 缓冲区大小,无论是读模式还是写模式,此属性值不会变;

  • position - 写数据时,position表示当前写的位置,每写一个数据,会向下移动一个数据单元,初始为0;最大为capacity - 1,切换到读模式时,position会被置为0,表示当前读的位置

  • limit - 写模式下,limit 相当于capacity 表示最多可以写多少数据,切换到读模式时,limit 等于原先的position,表示最多可以读多少数据。

非直接缓冲区:通过allocate() 方法 分配缓冲区,将缓冲区建立在JVM内存中

直接缓冲区:通过allocateDirect() 方法直接缓冲区 将缓冲区建立在物理内存中

2.1 关于缓冲区各个属性的测试

  1. String str = "abcde";
  2. //1. 分配一个指定大小的缓冲区
  3. ByteBuffer buf = ByteBuffer.allocate(1024);
  4. System.out.println("--------------allocate()----------------");
  5. System.out.println(buf.position());//0
  6. System.out.println(buf.limit());//1024
  7. System.out.println(buf.capacity());//1024
  8. //2. 利用put存入数据到缓冲区中去
  9. buf.put(str.getBytes());
  10. System.out.println("----------------put()-------------------");
  11. System.out.println(buf.position());//5
  12. System.out.println(buf.limit());//1024
  13. System.out.println(buf.capacity());//1024
  14. //3. 切换到读取模式
  15. buf.flip();
  16. System.out.println("----------------flip()------------------");
  17. System.out.println(buf.position());//0
  18. System.out.println(buf.limit());//5
  19. System.out.println(buf.capacity());//1024
  20. //4. 利用get() 读取缓冲区中的数据
  21. byte[] dst = new byte[buf.limit()];
  22. buf.get(dst);
  23. System.out.println(new String(dst,0,dst.length));
  24. System.out.println("----------------get()------------------");
  25. System.out.println(buf.position());//5
  26. System.out.println(buf.limit());//5
  27. System.out.println(buf.capacity());//1024
  28. //5.可重复读
  29. buf.rewind();
  30. System.out.println("----------------rewind()------------------");
  31. System.out.println(buf.position());//0
  32. System.out.println(buf.limit());//5
  33. System.out.println(buf.capacity());//1024
  34. //6.clear(): 清空缓冲区, 但是缓冲区的数据依然存在, 但是处于被遗忘的状态
  35. buf.clear();
  36. System.out.println("----------------clear()-------------------");
  37. System.out.println(buf.position());//0
  38. System.out.println(buf.limit());//1024
  39. System.out.println(buf.capacity());//1024
  40. byte[] newByte = new byte[buf.limit()];
  41. buf.get(newByte);
  42. System.out.println(new String(newByte,0,newByte.length));

2.2 关于通道的使用

1.利用通道进行 文件的复制 非直接缓冲区

  1. FileInputStream fis = null;
  2. FileOutputStream fos = null;
  3. FileChannel inChannel = null;
  4. FileChannel outChannel = null;
  5. try {
  6. fis = new FileInputStream("1.jpg");
  7. fos = new FileOutputStream("2.jpg");
  8. // ①获取通道
  9. inChannel = fis.getChannel();
  10. outChannel = fos.getChannel();
  11. // ②将通道中的数据存入缓冲区
  12. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  13. // 将通道中的数据存入缓冲区
  14. while (inChannel.read(byteBuffer) != -1) {
  15. byteBuffer.flip(); // 切换读取数据的模式
  16. outChannel.write(byteBuffer);
  17. byteBuffer.clear();
  18. }
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. } finally {
  22. if (inChannel != null) {
  23. try {
  24. inChannel.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. if (outChannel != null) {
  30. try {
  31. outChannel.close();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. if (fis != null) {
  37. try {
  38. fis.close();
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. if (fos != null) {
  44. try {
  45. fos.close();
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. }

2.通道之间的传输

CREATE_NEW:如果文件不存在就创建,存在就报错

CREATE:如果文件不存在就创建,存在创建(覆盖)

  1. FileChannel inChannel = null;
  2. FileChannel outChannel = null;
  3. try {
  4. inChannel = FileChannel.open(Paths.get("hello.txt"), StandardOpenOption.READ);
  5. outChannel = FileChannel.open(Paths.get("hello2.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
  6. inChannel.transferTo(0, inChannel.size(), outChannel);
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. } finally {
  10. if(inChannel != null){
  11. try {
  12. inChannel.close();
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. if(outChannel != null){
  18. try {
  19. outChannel.close();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

3. 使用直接缓冲区完成内存文件的复制

  1. FileChannel inChannel = null;
  2. FileChannel outChannel = null;
  3. try {
  4. inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
  5. outChannel = FileChannel.open(Paths.get("x.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
  6. MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
  7. MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
  8. System.out.println(inMappedBuffer.limit());
  9. byte[] b = new byte[inMappedBuffer.limit()];;
  10. inMappedBuffer.get(b);
  11. outMappedBuffer.put(b);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. } finally {
  15. if(inChannel != null){
  16. try {
  17. inChannel.close();
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. if(outChannel != null){
  23. try {
  24. outChannel.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }

2.3 重点 NIO-非阻塞IO

个人认为 NIO 最难的两点 一个是对于选择器和选择键的理解 其次是对于网络通信模型的理解

本章内容以防过长 只讲解 NIO 的使用方法 上述两点参看下回分解

阻塞IO示例:

  1. //客户端
  2. @Test
  3. public void client() throws IOException{
  4. SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
  5. FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
  6. ByteBuffer buf = ByteBuffer.allocate(1024);
  7. while(inChannel.read(buf) != -1){
  8. buf.flip();
  9. sChannel.write(buf);
  10. buf.clear();
  11. }
  12. sChannel.shutdownOutput();
  13. //接收服务端的反馈
  14. int len = 0;
  15. while((len = sChannel.read(buf)) != -1){
  16. buf.flip();
  17. System.out.println(new String(buf.array(), 0, len));
  18. buf.clear();
  19. }
  20. inChannel.close();
  21. sChannel.close();
  22. }
  23. //服务端
  24. @Test
  25. public void server() throws IOException{
  26. ServerSocketChannel ssChannel = ServerSocketChannel.open();
  27. FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
  28. ssChannel.bind(new InetSocketAddress(9898));
  29. SocketChannel sChannel = ssChannel.accept();
  30. ByteBuffer buf = ByteBuffer.allocate(1024);
  31. while(sChannel.read(buf) != -1){
  32. buf.flip();
  33. outChannel.write(buf);
  34. buf.clear();
  35. }
  36. //发送反馈给客户端
  37. buf.put("服务端接收数据成功".getBytes());
  38. buf.flip();
  39. sChannel.write(buf);
  40. sChannel.close();
  41. outChannel.close();
  42. ssChannel.close();
  43. }

非阻塞IO示例-TCP:

  1. //客户端
  2. @Test
  3. public void client() throws IOException{
  4. //1. 获取通道
  5. SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
  6. //2. 切换非阻塞模式
  7. sChannel.configureBlocking(false);
  8. //3. 分配指定大小的缓冲区
  9. ByteBuffer buf = ByteBuffer.allocate(1024);
  10. //4. 发送数据给服务端
  11. Scanner scan = new Scanner(System.in);
  12. while(scan.hasNext()){
  13. String str = scan.next();
  14. buf.put((new Date().toString() + "\n" + str).getBytes());
  15. buf.flip();
  16. sChannel.write(buf);
  17. buf.clear();
  18. }
  19. //5. 关闭通道
  20. sChannel.close();
  21. }
  22. //服务端
  23. @Test
  24. public void server() throws IOException{
  25. //1. 获取通道
  26. ServerSocketChannel ssChannel = ServerSocketChannel.open();
  27. //2. 切换非阻塞模式
  28. ssChannel.configureBlocking(false);
  29. //3. 绑定连接
  30. ssChannel.bind(new InetSocketAddress(9898));
  31. //4. 获取选择器
  32. Selector selector = Selector.open();
  33. //5. 将通道注册到选择器上, 并且指定“监听接收事件”
  34. ssChannel.register(selector, SelectionKey.OP_ACCEPT);
  35. //6. 轮询式的获取选择器上已经“准备就绪”的事件
  36. while(selector.select() > 0){
  37. //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
  38. Iterator<SelectionKey> it = selector.selectedKeys().iterator();
  39. while(it.hasNext()){
  40. //8. 获取准备“就绪”的是事件
  41. SelectionKey sk = it.next();
  42. //9. 判断具体是什么事件准备就绪
  43. if(sk.isAcceptable()){
  44. //10. 若“接收就绪”,获取客户端连接
  45. SocketChannel sChannel = ssChannel.accept();
  46. //11. 切换非阻塞模式
  47. sChannel.configureBlocking(false);
  48. //12. 将该通道注册到选择器上
  49. sChannel.register(selector, SelectionKey.OP_READ);
  50. }else if(sk.isReadable()){
  51. //13. 获取当前选择器上“读就绪”状态的通道
  52. SocketChannel sChannel = (SocketChannel) sk.channel();
  53. //14. 读取数据
  54. ByteBuffer buf = ByteBuffer.allocate(1024);
  55. int len = 0;
  56. while((len = sChannel.read(buf)) > 0 ){
  57. buf.flip();
  58. System.out.println(new String(buf.array(), 0, len));
  59. buf.clear();
  60. }
  61. }
  62. //15. 取消选择键 SelectionKey
  63. it.remove();
  64. }
  65. }
  66. }

非阻塞IO示例-UDP:

  1. @Test
  2. public void send() throws IOException{
  3. DatagramChannel dc = DatagramChannel.open();
  4. dc.configureBlocking(false);
  5. ByteBuffer buf = ByteBuffer.allocate(1024);
  6. Scanner scan = new Scanner(System.in);
  7. while(scan.hasNext()){
  8. String str = scan.next();
  9. buf.put((new Date().toString() + ":\n" + str).getBytes());
  10. buf.flip();
  11. dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
  12. buf.clear();
  13. }
  14. dc.close();
  15. }
  16. @Test
  17. public void receive() throws IOException{
  18. DatagramChannel dc = DatagramChannel.open();
  19. dc.configureBlocking(false);
  20. dc.bind(new InetSocketAddress(9898));
  21. Selector selector = Selector.open();
  22. dc.register(selector, SelectionKey.OP_READ);
  23. while(selector.select() > 0){
  24. Iterator<SelectionKey> it = selector.selectedKeys().iterator();
  25. while(it.hasNext()){
  26. SelectionKey sk = it.next();
  27. if(sk.isReadable()){
  28. ByteBuffer buf = ByteBuffer.allocate(1024);
  29. dc.receive(buf);
  30. buf.flip();
  31. System.out.println(new String(buf.array(), 0, buf.limit()));
  32. buf.clear();
  33. }
  34. }
  35. it.remove();
  36. }
  37. }
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号