java核心技术

Wesley13
• 阅读 553

[TOC]

1.NIO初识

反应器模式

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

  一个老板经营一个饭店,

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

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

  于是乎就让服务员在客人点菜的时候,去为其他客人服务,当客人菜点好后再招呼服务员即可。 --反应器(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. NIO实战

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

        String str = "abcde";
        
        //1. 分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        System.out.println("--------------allocate()----------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024
        
        //2. 利用put存入数据到缓冲区中去
        buf.put(str.getBytes());
        
        System.out.println("----------------put()-------------------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        
        //3. 切换到读取模式
        buf.flip();
        
        System.out.println("----------------flip()------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //4. 利用get() 读取缓冲区中的数据
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst,0,dst.length));
        
        System.out.println("----------------get()------------------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //5.可重复读
        buf.rewind();
        
        System.out.println("----------------rewind()------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //6.clear(): 清空缓冲区, 但是缓冲区的数据依然存在, 但是处于被遗忘的状态
        buf.clear();
        
        System.out.println("----------------clear()-------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        byte[] newByte = new byte[buf.limit()];
        buf.get(newByte);
        System.out.println(new String(newByte,0,newByte.length));

###2.2 关于通道的使用

2.2.1利用通道进行文件的复制(非直接缓冲区)

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");

            // ①获取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            // ②将通道中的数据存入缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            // 将通道中的数据存入缓冲区
            while (inChannel.read(byteBuffer) != -1) {
                byteBuffer.flip(); // 切换读取数据的模式
                outChannel.write(byteBuffer);
                byteBuffer.clear();
            }

        } catch (IOException 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();
                }
            }

        }
    

2.2.2 通道之间的传输

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

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

        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = FileChannel.open(Paths.get("hello.txt"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("hello2.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
            
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } 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();
                }
            }
        }
    

####2.2.3 内存文件的复制(直接缓冲区)

        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("x.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
            
            MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
            
            System.out.println(inMappedBuffer.limit());
            byte[] b = new byte[inMappedBuffer.limit()];;
            inMappedBuffer.get(b);
            outMappedBuffer.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();
                }
            }
            
        }

2.3 重点 NIO-非阻塞IO

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

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

2.3.1 阻塞IO示例

    //客户端
    @Test
    public void client() throws IOException{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        sChannel.shutdownOutput();
        
        //接收服务端的反馈
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(), 0, len));
            buf.clear();
        }
        
        inChannel.close();
        sChannel.close();
    }
    
    //服务端
    @Test
    public void server() throws IOException{
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        
        ssChannel.bind(new InetSocketAddress(9898));
        
        SocketChannel sChannel = ssChannel.accept();
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        
        //发送反馈给客户端
        buf.put("服务端接收数据成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }

####2.3.2 非阻塞IO示例-TCP

//客户端
    @Test
    public void client() throws IOException{
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);
        
        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);
        
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        //5. 关闭通道
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);
        
        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        
        //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. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();
                    
                    //11. 切换非阻塞模式
                    sChannel.configureBlocking(false);
                    
                    //12. 将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel 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));
                        buf.clear();
                    }
                }
                
                //15. 取消选择键 SelectionKey
                it.remove();
            }
        }
    }

####2.3.3 非阻塞IO示例-UDP

    @Test
    public void send() throws IOException{
        DatagramChannel dc = DatagramChannel.open();
        
        dc.configureBlocking(false);
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        Scanner scan = new Scanner(System.in);
        
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + ":\n" + str).getBytes());
            buf.flip();
            dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
            buf.clear();
        }
        
        dc.close();
    }
    
    @Test
    public void receive() throws IOException{
        DatagramChannel dc = DatagramChannel.open();
        
        dc.configureBlocking(false);
        
        dc.bind(new InetSocketAddress(9898));
        
        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();
        }
    }
点赞
收藏
评论区
推荐文章
十月飞翔 十月飞翔
2年前
CPU虚拟化技术介绍
虚拟化的三个条件:等价性,高效性和资源控制。这三条是针对VMM(VirtualMachineManager)说的。陷入和模拟模型处理器分为两种运行模式:系统模式和用户模式。CPU指令对应分为特权指令和非特权指令。陷入和模拟模型下,虚拟机用户程序仍然运行在用户模式下,虚拟机的内核也运行在用户模式,成为特权级压缩(RingCompression)。这种模式
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
菜园前端 菜园前端
1年前
什么是JavaScript同步模式?
原文链接:什么是同步模式?大部分单线程任务都会排队执行任务,这就称为同步模式(Synchronous)。同步模式执行中,只涉及到调用栈(Callstack)。现实生活举例就像验核酸一样,我们要排队一个个去验(按顺序排队),当只有一条通道(也就是单线程)时,
Stella981 Stella981
3年前
AvctiveMQ和RabbitMQ的区别
ActiveMQ:传统的消息队列,使用Java语言编写。基于JMS(JavaMessageService),采用多线程并发,资源消耗比较大。支持P2P和发布订阅两种模式。RabbitMQ:是使用Erlang语言开发的开源消息队列系统。基于AMQP协议来实现的。AMQP的主要特征是面向消息、队列、路由(
Wesley13 Wesley13
3年前
Java总结:Java多线程
多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。Java给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。这里定义和线程相关的另一个术语进程:一个进程包括由操作系统分配的内存空间,
Wesley13 Wesley13
3年前
Java面试官都爱问的多线程和并发面试题汇总,多刷一题,多份安心!
Java多线程面试问题1、进程和线程之间有什么不同?一个进程是一个独立(selfcontained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进
Wesley13 Wesley13
3年前
Java 一个简单的回调模式
我们需要定义一个接口:packagelinving.test;publicinterfaceMyTestInterface{   publicintgetUpdate(inti);}接下来给这个接口的实现更新packagelinving.test.update;impor
Easter79 Easter79
3年前
TCP客户端与服务器的实现
为了更容易理解,我们举一个小例子来说明服务器与客户端之间的连接过程。有一个饭店,饭店里有服务员,服务员用于招待客人特别要注意的是:要记住相关函数的各个参数都是什么,什么时候返回SOCKET\_ERROR,什么时候返回INVALID\_SOCKET服务器1include<stdio.h2include<winso
Wesley13 Wesley13
3年前
Java并发(二)生产者与消费者
考虑这样一个饭店,它有一个厨师(Chef)和一个服务员(Waiter)。这个服务员必须等待厨师准备好菜品。当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待。这是一个任务协作的示例:厨师代表生产者,而服务员代表消费者。两个任务必须在菜品被生产和消费时进行握手,而系统必须以有序的方式关闭。下面是对这个叙述建模的代码:import ja
Wesley13 Wesley13
3年前
Java线程与多线程
1线程与多线程1.1线程是什么?线程(Thread)是一个对象(Object)。用来干什么?Java线程(也称JVM线程)是Java进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。Java程序采用多线程方式来支持大量的并发请求处理,程序如果在