美文网首页
3/26day19_JUnit单元测试_NIO

3/26day19_JUnit单元测试_NIO

作者: 蹦蹦跶跶的起床啊 | 来源:发表于2020-03-27 20:47 被阅读0次

    day19_JUnit单元测试_NIO

    复习

    1.网络编程三要素
        协议(TCP),IP地址,端口号
    2.Socket类
       构造方法:
            public Socket(服务器的IP地址,服务器的端口号);
       成员方法:
            public void close();
            public OutputStream getOutputStream();//从客户端 到 服务器的流
            public InputStream getInputStream();//从服务器 到 客户端流
            
            public void shutDownOutput(); // 关闭输出流
            public void shutDownInput(); // 关闭输入流
    3.ServerSocket类    
            public ServerSocket(服务器的端口号);
            public Socket accept(); //接收连接到服务器的客户端对象
    
    4.重点案例【TCP的双向通信】
    

    今日内容


    Junit单元测试[重点]

    单元测试概念

    • 什么叫单元测试
      • 单元, 在JAVA中一个单元可以指某个方法, 或者某个类
      • 测试, 写一段代码对象单元进行测试
    • Junit 是专门对单元测试提供一个第三方框架
      • 作用: 让一个普通方法独立运行(替换了main方法)

    单元测试的使用

    • Junit单元测试框架的使用步骤

      • 编写业务类,在业务类中编写业务方法。比如增删改查的方法
      • 编写测试类,在测试类中编写测试方法,在测试方法中编写测试代码来测试。
      • 测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法
        • 每一个单词首字母大写,称为大驼峰命名法,比如类名,接口名...
        • 从第二单词开始首字母大写,称为小驼峰命名法,比如方法命名
        • 比如业务类类名:ProductDao,那么测试类类名就应该叫:TestProductDao
      • 测试方法的命名规则:以test开头,以业务方法名结尾
        • 比如业务方法名为:save,那么测试方法名就应该叫:testSave
    • 运行测试

      • 选中方法名 --> 右键 --> Run '测试方法名' 运行选中的测试方法

      • 选中测试类类名 --> 右键 --> Run '测试类类名' 运行测试类中所有测试方法

      • 选中模块名 --> 右键 --> Run 'All Tests' 运行模块中的所有测试类的所有测试方法

    • 测试方法注意事项

      • 必须是public修饰的,没有返回值,没有参数

      • 必须使用JUnit的注解@Test修饰

    Junit常用注解

    • Junit4.x

      • @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。(在@Test方法之前)
      • @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。(在@Test方法之后)
      • @BeforeClass:只能用来静态修饰方法,该方法会在所有测试方法之前执行一次,而且只执行一次。
      • @AfterClass:只能用来静态修饰方法,该方法会在所有测试方法之后执行一次,而且只执行一次。
    • Junit5.x(方法作用通4.x, 只是更换了名字)

      • BeforeEach
        • AfterEach
        • BeforeAll
          • AfterAll

    NIO 介绍

    阻塞与非阻塞

    • 阻塞: 在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如Socket新连接建立完毕,或者数据读取、写入操作完成;

    • 非阻塞: 是不管IO操作是否结束,直接返回,相应操作在后台继续处理

    同步与异步

    可参考
    https://www.cnblogs.com/IT-CPC/p/10898871.html

    • 同步:
      • 同步可能是阻塞的, 也可能是非阻塞的
      • 同步阻塞: 是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;
      • 同步非阻塞:完成某个任务时, 不需要等待任务结束, 当前线程立即可以向下执行.后期需要我们自己写其他代码获取结果
    • 异步:
      • 异步一般来说都是非阻塞的
      • 异步非阻塞: 完成某个任务时, 不需要等待任务结束, 当前线程立即可以继续向下执行. 后期不需要自己写其他代码来获取结果, 任务完成之后自动会通过其他机制把结果返回.
    public class TestDogDemo {
        @BeforeClass
        public void aa() {
            System.out.println("Before的aaa");
        }
        @AfterClass
        public void bb() {
            System.out.println("After的bbb");
        }
    /*    @Before
        public void aa() {
            System.out.println("Before的aaa");
        }
        @After
        public void bb() {
            System.out.println("After的bbb");
        }*/
    
        @org.junit.Test
        public void test01() {
            Dog dd = new Dog();
            System.out.println("第一个"+dd.getSum(1, 2, 3));
        }
        @org.junit.Test
        public void test02() {
            Dog dd = new Dog();
            System.out.println("第二个"+dd.getSum(1, 2, 3));
        }
    
    }
    

    BIO, NIO, AIO的介绍

    • BIO(传统的IO): 同步阻塞的IO

    • NIO(New新的IO): 同步阻塞的, 也可以是同步非阻塞.

      NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程

    • NIO2(也叫AIO)(Asynchronous IO): 异步非阻塞的IO.可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

    NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)


    NIO-Buffer类(缓冲区)

    Buffer的介绍和种类

    • Buffer概念

      Buffer称为缓冲区, 本质是一个数组.

      Buffer是一个对象,它对某种基本类型的数组进行了封装。NIO开始使用的Channel(通道)就是通过 Buffer 来读写数据的。
      在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。

    • Buffer的使用步骤

      1. 写入缓冲区(把数据保存到数组中)
      2. 调用flip() 方法. 用于切换缓冲区的读写模式
      3. 读缓冲区(从数组中把数据读取出来)
      4. 调用claer()或者compact()方法(清空缓冲区/清除已经读过的数据)

      步骤解释:当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
      一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或
      compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被
      移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

    • Buffer 的种类

      • ByteBuffer 字节缓冲区(字节数组)[最常用]
      • CharBuffer 字符缓冲区(字符数组)
      • DoubleBuffer Double缓冲区(小数数组)
      • FloatBuffer Float缓冲区(小数数组)
      • mIntBuffer 整型缓冲区(整型数组)
      • LongBuffer 长整形(长整形数组)
      • Sho rtBuffer 短整型(短整型数组)

    ByteBuffer的三种创建方式(最好配合Channel使用)

    • 方式一 public static allocate(int capaticy); 在堆内存区申请一个固定字节大小的ByteBuffer缓冲区(分配速度快, 读写速度慢)

      public static void main(String[] args) {
              //创建堆缓冲区
              ByteBuffer byteBuffer = ByteBuffer.allocate(10);
      }
      
    • 方式二 public static allocatDirect(int capaticy); 在系统内存(直接内存)中申请一个固定字节大小的ByteBuffer缓冲区(分配速度慢, 读写速度快)

      public static void main(String[] args) {
              //创建直接缓冲区
              ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
      }
      

      在堆中创建缓冲区称为:间接缓冲区

      在系统内存创建缓冲区称为:直接缓冲区

      间接缓冲区的创建和销毁效率要高于直接缓冲区

      间接缓冲区的工作效率要低于直接缓冲区

    • 方式三 public static wrap(byte[] arr); 把一个字节数组直接包装成此种方式创建的缓冲区为:间接缓冲区

       public static void main(String[] args) {
              byte[] byteArray = new byte[10];
              ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
       }
      

    ByteBuffer的三种添加数据方式

    • public ByteBuffer put(byte b); 添加单个字节
    • public ByteBuffer put(byte[] byteArray); 添加字节数组
    • public ByteBuffer put(byte[] byteArray,int offset,int len); 添加一个byte[]数组的一部分
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println(Arrays.toString(buffer.array()));
        buffer.put((byte) 12);
        System.out.println(Arrays.toString(buffer.array()));
        byte[] bs = {20, 50, 22};
        buffer.put(bs);
        System.out.println(Arrays.toString(buffer.array()));
        byte[] bs1 = {88, 33, 11};
        buffer.put(bs1, 0, 2);
        System.out.println(Arrays.toString(buffer.array()));
    }
    

    ByteBuffer的容量- capacity

    • Buffer的容量(capacity)是指:Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。

    • public int capacity(); 获取Buffer的容量

    ByteBuffer的限制-limit

    • 限制limit是指:第一个不应该读取或写入元素的index索引。缓冲区的限制(limit)不能为负,并且不能大于容量。(返回第一个不能操作的数据的索引)

    • 可以传入参数(参数范围为底层数组长度0~capacity), 指定从哪个索引处开始不能再进行操作

    • 有两个相关方法

      public int limit():获取此缓冲区的限制。
      public Buffer limit(int newLimit):设置此缓冲区的限制。

    ByteBuffer的位置-position

    • 位置position是指:当前可写入的索引。位置不能小于0,并且不能大于"限制"。(最小为0 ,最大为capacity/limit)

    • 两个方法:

      public int position():获取当前可写入位置索引。
      public Buffer position(int p):更改当前可写入位置索引。

    ByteBuffer的标记-mark

    • 标记mark是指:当调用缓冲区的reset()方法时,会将缓冲区的position位置重置为该mark处索引。不能为0,不能大于position。

    ByteBuffer的其他方法

    • public int remaining():获取position与limit之间的元素数。
    • public boolean isReadOnly():获取当前缓冲区是否只读。
    • public boolean isDirect():获取当前缓冲区是否为直接缓冲区。
    • public Buffer clear():还原缓冲区的状态。
      • 将position设置为:0
      • 将限制limit设置为容量capacity;
      • 丢弃标记mark。
    • public Buffer flip():缩小limit的范围。
      • 将limit设置为当前position位置;
      • 将当前position位置设置为0;
      • 丢弃标记。
    • public Buffer rewind():重绕此缓冲区。
      • 将position位置设置为:0
      • 限制limit不变。
      • 丢弃标记。

    Channel(通道)(相当于取代了输入/输出流)

    Channel的概述

    • Channel(通道):Channel是一个对象类,可以通过它读取和写入数据。可以把它看做是IO中的流类似. IO流油Input和Output之分, 通道没有输入输出之分,只有一种就是Channel通道

    Channel的分类

    • FileChannel 文件通道, 读写文件的
    • DatagramChannel UDP协议通道(通过UDP协议收发数据)
    • SocketChannel TCP协议中客户端的通道(给客户端读写数据用)
    • ServerSocketChannel TCP协议中服务器端通道(给服务器端读写数据用)

    FileChannel类的基本使用

    • java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。
    • FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取
      一个它的子类对象。
    public static void main(String[] args) throws Exception{
        //复制文件
        //创建文件对象
        File file = new File("1.png");
        File newFile = new File("new1.png");
        //创建文件的输入输出流
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(newFile);
    
        FileChannel inchannel = fis.getChannel();
        FileChannel outchannel = fos.getChannel();
    
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = 0;
        while ((len = inchannel.read(buffer)) != -1) {
            //切换模式
            buffer.flip();
            //读取缓冲区数据,写入到文件中
            outchannel.write(buffer);
            //清空
            buffer.clear();
        }
        outchannel.close();
        inchannel.close();
        fos.close();
        fis.close();
    }
    

    FileChannel结合MappedByteBuffer实现高效读写

    • MappedByteBuffer继承自ByteBuffer

    • MappedByteBuffer 好处, 对大文件读写效率高(内部采用的就是直接内存(系统内存)

    • 获取MappedByteBuffer采用 FileChannel提供的map()方法

    • FileChannel提供了map()方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。

      • map()方法中的参数详解

        FileChannel中的几个变量

        • MapMode mode:内存映像文件访问的方式,共三种:
          1. MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
          2. MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
          3. MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
        • position:文件映射时的起始位置。
        • allocationGranularity:Memory allocation size for mapping buffers,通过native函数initIDs初始化。
    • 注意:

      • FileInputStream从头读, 一直读到尾巴

      • RandomAccessFile, 从中间读, 也就是根据指定的下标进行读取, 既可以读, 也可以写

    • 代码实现

      public static void main(String[] args) throws Exception{
          //创建文件, 只读模式, 和读写模式
          RandomAccessFile srcFile = new RandomAccessFile("1.txt", "r");
          RandomAccessFile destFile = new RandomAccessFile("new1.txt", "rw");
          //获取通道
          FileChannel inChannel = srcFile.getChannel();
          FileChannel outChannel = destFile.getChannel();
          int size = (int)inChannel.size();
      
          MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
          MappedByteBuffer outMap = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
      
          for (int i = 0; i < size; i++) {
              byte b = inMap.get(i);
              outMap.put(i,b);
          }
      
          outChannel.close();
          inChannel.close();
      }
      

    ServerSocketChannel和SocketChannel创建连接

    • ServerSocketChannel 提供两种模式, 阻塞和非阻塞.

    • ServerSocketChannel 主要是接受连接使用, 读写操作更多的是使用SocketChannel

    • 注意 socketChanne.read(buffer). 这句话相当于是 从channel读, 向创建好的buffer中写

    • ServerSocketChannel的创建方式

      • 方式一, 创建同步阻塞

        //创建阻塞的
        public static void main(String[] args) throws IOException {
            //创建ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //绑定本地端口
            serverSocketChannel.bind(new InetSocketAddress(888));
            System.out.println("服务器启动了");
            //接收客户端通道
            SocketChannel socketChannel = serverSocketChannel.accept();
        }
        
    • 方式二, 创建同步非阻塞

      public static void main(String[] args) throws IOException, InterruptedException {
          //创建ServerSocketChannel
          ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
          //设置为同步非阻塞服务器通道
          serverSocketChannel.configureBlocking(false);
          //绑定本地端口
          serverSocketChannel.bind(new InetSocketAddress(888));
          System.out.println("服务器启动了");
          while (true) {
              //接收客户端通道
              SocketChannel socketChannel = serverSocketChannel.accept();
              if (socketChannel != null) {
                  System.out.println("连接上了客户端");
              } else {
                  System.out.println("无客户端连接");
                  Thread.sleep(2000);
              }
          }
      }
      
    • SocketChannel的创建方式

      • 方式一 创建阻塞式客户端

        public static void main(String[] args) throws IOException {
            //创建对象
            SocketChannel socketChannel = SocketChannel.open();
            //连接服务器IP和端口号
            socketChannel.connect(new InetSocketAddress("localhost", 888));
            System.out.println("接下来写后续代码");
        }
        
    • 方式二 创建非阻塞式客户端

      public static void main(String[] args) throws IOException, InterruptedException {
          //创建对象
          SocketChannel socketChannel = SocketChannel.open();
          while (true) {
              try {
                  //连接服务器IP和端口号
                  boolean b = socketChannel.connect(new InetSocketAddress("localhost", 888));
                  if (b) {
                      System.out.println("可以和服务器进行交互");
                  }
              } catch (Exception e) {
                  System.out.println("两秒后再次尝试和服务器连接");
                  Thread.sleep(2000);
              }
          }
      }
      

    ServerSocketChannel和SocketChannel收发信息

    public class SocketChannelDemo {
        public static void main(String[] args) throws IOException {
            SocketChannel socketChannel = SocketChannel.open();
            if (socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999))) {
                //发送数据
                System.out.println("连接服务器成功");
                ByteBuffer bb = ByteBuffer.wrap("哈哈哈, 我是客户端第一次连接哦".getBytes());
                socketChannel.write(bb);
                //读取数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int len = socketChannel.read(buffer);
                buffer.flip();
                System.out.println(new String(buffer.array(), 0, len));
                //释放资源
                socketChannel.close();
            }
        }
    }
    
    
    public class ServerSocketChannelDemo {
        public static void main(String[] args) throws IOException {
            //创建对象
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(9999));
            //接受客户端
            SocketChannel socketChannel = serverSocketChannel.accept();
            System.out.println("客户端连接上了");
            //读取数据
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(byteBuffer);
            //打印数据
            byteBuffer.flip();
            String str = new String(byteBuffer.array(), 0, len);
            System.out.println("客户端写的为"+str);
            //回数据
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("我是服务器哦".getBytes());
            socketChannel.write(buffer.flip());
            //释放资源
            socketChannel.close();
        }
    }
    

    今日总结

    能够使用Junit进行单元测试【重点】
        a.Junit是第三方的单元测试框架
        b.@Test 将一个普通方法可以独立运行
        c.@Before @After @BeforeClass @AfterClass
        
    能够说出阻塞和非阻塞的概念
        阻塞: 任务没有执行完毕,线程无法向下继续执行
        非阻塞: 无论任务是否执行完毕,线程继续向下执行    
    能够说出同步和异步的概念
        同步,可以是阻塞的,可以非阻塞
            同步阻塞的,任务没有执行完毕,线程无法向下继续执行
            同步非阻塞,无论任务是否执行完毕,线程继续向下执行,后期我们需要自己去写代码来获取任务的结果
        异步,一般都是非阻塞的
            异步非阻塞,无论任务是否执行完毕,线程继续向下执行.后期任务执行完毕会想办法通知我们
    能够创建和使用ByteBuffer
       创建:
            allocate(字节数); //JVM的堆中申请的空间,间接
            allocatDirect(字节数); // 系统的内存中申请的空间,直接
            wrap(字节数组); // JVM的堆中申请的空间,间接
        使用:
            put(字节/字节数组/字节数组的一部分);
            capacity,limit,position,mark,reset,rewind,flip,clear
            
    能够使用MappedByteBuffer实现高效读写
         使用文件对象 RandomAccessFile 不能使用普通的File
                
    能够使用ServerSocketChannel和SocketChannel实现连接并收发信息
         ServerSocketChannel: 服务器端,可以是同步阻塞的也可以是同步非阻塞的
         SocketChannel: 客户端,可以是同步阻塞的也可以是同步非阻塞的
         通过configureBlocking 方式可以设置阻塞还是非阻塞    
    

    具体的MappedByteBuffer可以看:https://blog.csdn.net/qq_41969879/article/details/81629469

    相关文章

      网友评论

          本文标题:3/26day19_JUnit单元测试_NIO

          本文链接:https://www.haomeiwen.com/subject/pniruhtx.html