day19【Junit单元测试、NIO同步异步,阻塞与非阻塞,ByteBuffer】

第一章 Junit单元测试

1.1Junit的概念

  • 概述 : Junit是Java语言编写的第三方单元测试框架(工具类)
  • 作用 : 用来做“单元测试”——针对某个普通方法,可以像main()方法一样独立运行,它专门用于测试某个方法。

1.2Junit的使用步骤

  • 1.在模块下创建lib文件夹,把Junit的jar包复制到lib文件夹中

  • 2.选中Junit的jar包,右键选中 add as Library,把JUnit4的jar包添加到classPath中

  • 3.在测试方法上面写上@Test注解

  • 4.执行测试方法

public class Person {

    @Test
    public void test1(){
        System.out.println("Person test1 方法执行了....");
    }

    @Test
    public void test2(){
        System.out.println("Person test2 方法执行了....");
    }

}

执行测试方法

  • 1.选中方法名—>右键—>选中执行 只执行选中的测试方法

  • 2.选中类名----->右键—>选中执行 执行该类中所有的测试方法

  • 3.选中模块---- ->右键—>选中all tests 执行 执行该模块中所有的测试方法

  • 如何查看测试结果

    • 绿色:表示测试通过
    • 红色:表示测试失败,有问题

1.3Junit单元测试的注意实现

  • 1.测试方法的权限修饰符一定是public
  • 2.测试方法的返回值类型一定是void
  • 3.测试方法一定没有参数
  • 4.测试方法 的声明之上一定要使用@Test注解

1.4Junit其他注解

  • @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
  • @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
  • @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次,而且只执行一次。
  • @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次,而且只执行一次。
public class Student {

    @BeforeClass
    public static void beforeClass1(){
        System.out.println("Student beforeClass1静态方法执行了...");
    }

    @BeforeClass
    public static void beforeClass2(){
        System.out.println("Student beforeClass2静态方法执行了...");
    }


    @Before
    public void b1(){
        System.out.println("Student b1方法执行了...");
    }

    @Before
    public void b2(){
        System.out.println("Student b2方法执行了...");
    }

    @Before
    public void b3(){
        System.out.println("Student b3方法执行了...");
    }


    @Test
    public void test1(){
        System.out.println("Student test1 方法执行了....");
    }

    @Test
    public void test2(){
        System.out.println("Student test2 方法执行了....");
    }

    @After
    public void a1(){
        System.out.println("Student a1方法执行了...");
    }

    @After
    public void a2(){
        System.out.println("Student a2方法执行了...");
    }

    @After
    public void a3(){
        System.out.println("Student a3方法执行了...");
    }

    @AfterClass
    public static void afterClass1(){
        System.out.println("Student afterClass1方法执行了...");
    }

    @AfterClass
    public static void afterClass2(){
        System.out.println("Student afterClass2方法执行了...");
    }

}

1.5Junit断言

断言:预先判断某个条件一定成立,如果条件不成立,则直接报错。 使用Assert类中的assertEquals()方法

public class Demo02 {
    @Test
    public void addTest(){
        //测试
        int add = add(3, 6);

        //断言判断结果
        //第一个参数表示期望值
        //第二个参数表示实际值
        //如果结果正确的就测试通过,如果结果错误的,就会报错
        Assert.assertEquals(9,add);
    }

    //加法
    //这个代码的语法没问题,也没有异常。他是逻辑错误,系统不知道你要算的是加法
    public int add(int a, int b){
        int sum = a * b;
        return sum;
    }

}

第二章 NIO

2.1NIO概述

在我们学习Java的NIO流之前,我们都要了解几个关键词

  • 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系
    • 同步: 调用方法之后,必须要得到一个返回值 例如: 买火车票,一定要买到票,才能继续下一步
    • 异步: 调用方法之后,没有返回值,但是会有回调函数,回调函数指的是满足条件之后会自动执行的方法 例如: 买火车票, 不一定要买到票,我可以交代售票员,当有票的话,你就帮我出张票
  • 阻塞与非阻塞:在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取、写入操作完成;而非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理
    • 阻塞:如果没有达到方法的目的,就会一直停在那里(等待) , 例如: ServerSocket的accept()方法
    • 非阻塞: 不管方法有没有达到目的,都直接往下执行(不等待)

在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据,面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,这是一个面向块的I/O系统,系统以块的方式处理数据,每一个操作在一步中产生或者消费一个数据,按块处理要比按字节处理数据快的多。

在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

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

首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)

2.2小结

  • Buffer(缓冲区)、Channel(通道)、Selector(选择器)是NIO的三个部分
  • NIO是在访问个数特别大的时候才使用的 , 比如流行的软件或者流行的游戏中会有高并发和大量连接.

第三章 Buffer类(缓冲区)

3.1Buffer的概述和分类

概述:Buffer是一个对象,它是对某种基本类型的数组进行了封装。

作用: 在NIO中,就是通过 Buffer 来读写数据的。所有的数据都是用Buffer来处理的,它是NIO读写数据的中转池, 通常使用字节数组。

Buffer主要有如下几种:

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

3.2创建ByteBuffer

  • ByteBuffer类内部封装了一个byte[]数组,并可以通过一些方法对这个数组进行操作。

  • 创建ByteBuffer对象

    • 方式一:在堆中创建缓冲区:allocate(int capacity)
public static void main(String[] args) {
    	//创建堆缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
}

在这里插入图片描述

  • 方式二: 在系统内存创建缓冲区:allocatDirect(int capacity)

    public static void main(String[] args) {
        	//创建直接缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
    }
    
    • 在堆中创建缓冲区称为:间接缓冲区

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

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

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

  • 方式三:通过数组创建缓冲区:wrap(byte[] arr)

       public static void main(String[] args) {
              byte[] byteArray = new byte[10];
              ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
       }
    
    • 此种方式创建的缓冲区为:间接缓冲区

3.3添加数据-put

  • public ByteBuffer put(byte b):向当前可用位置添加数据。

  • public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组

  • public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分

    public static void main(String[] args) {
            yteBuffer b1 = ByteBuffer.allocate(10);
            // 添加数据
            b1.put((byte)11);
            b1.put((byte)12);
            b1.put((byte)13);
    
            // ByteBuffer转换为普通字节数组
            byte[] bytes = b1.array();
            System.out.println(Arrays.toString(bytes));
            // 打印结果: [11, 12, 13, 0, 0, 0, 0, 0, 0, 0]
    }
    
    public class Test_添加数据 {
        public static void main(String[] args) {
            ByteBuffer b1 = ByteBuffer.allocate(10);
            // 添加数据
            b1.put((byte)11);
            b1.put((byte)12);
            b1.put((byte)13);
    
            // ByteBuffer转换为普通字节数组
            byte[] bytes = b1.array();
            System.out.println(Arrays.toString(bytes));
            //打印结果: [11, 12, 13, 0, 0, 0, 0, 0, 0, 0]
    
            System.out.println("=======================");
            byte[] b2 = {14,15,16};
            // 添加数据
            b1.put(b2);
            // ByteBuffer转换为普通字节数组
            byte[] b = b1.array();
            System.out.println(Arrays.toString(b));
            //打印结果: [11, 12, 13, 14, 15, 16, 0, 0, 0, 0]
        }
    }
    
    
    public class Test_添加数据 {
        public static void main(String[] args) {
            ByteBuffer b1 = ByteBuffer.allocate(10);
            // 添加数据
            b1.put((byte)11);
            b1.put((byte)12);
            b1.put((byte)13);
    
            // ByteBuffer转换为普通字节数组
            byte[] bytes = b1.array();
            System.out.println(Arrays.toString(bytes));
            // 打印结果: [11, 12, 13, 0, 0, 0, 0, 0, 0, 0]
    
            System.out.println("=======================");
            byte[] b2 = {14,15,16};
            // 添加数据
            b1.put(b2,0,1);
            // ByteBuffer转换为普通字节数组
            byte[] b = b1.array();
            System.out.println(Arrays.toString(b));
            // 打印结果: [11, 12, 13, 14, 0, 0, 0, 0, 0, 0]
        }
    }
    

3.4容量-capacity

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

  • 示例代码:

    public static void main(String[] args) {
        ByteBuffer b1 = ByteBuffer.allocate(10);
        System.out.println("容量:" + b1.capacity());//10。之后不可改变
    
        byte[] byteArray = {97, 98, 99, 100};
        ByteBuffer b2 = ByteBuffer.wrap(byteArray);
        System.out.println("容量:" + b2.capacity());//4。之后不可改变
    }
    
  • 结果:

    容量:10
    容量:4
    

3.5限制-limit

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

  • 有两个相关方法:

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

    public class Test_添加数据 {
        public static void main(String[] args) {
            ByteBuffer b1 = ByteBuffer.allocate(10);
            // 添加数据
            b1.put((byte)10);
    
            // 获取限制
            int limit1 = b1.limit();
            System.out.println("limit1:"+limit1);// 10
    
            // 设置限制
            b1.limit(3);
    
            // 添加元素
            b1.put((byte)20);
          b1.put((byte)30);
            // b1.put((byte)40);// 报异常,因为限制位置索引为3,所以再存14就会报异常:BufferOverflowException
    
        }
    }
    

    图示:
    在这里插入图片描述

3.6位置-position

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

  • 有两个相关方法:

    • public int position():获取当前可写入位置索引。
    • public Buffer position(int p):更改当前可写入位置索引。
public class Test_添加数据 {
    public static void main(String[] args) {
        ByteBuffer b1 = ByteBuffer.allocate(10);
        // 添加数据
        b1.put((byte)11);

        // 获取当前位置索引
        int position = b1.position();
        System.out.println("position:"+position);// 1

        // 设置当前位置索引
        b1.position(5);

        b1.put((byte)22);
        b1.put((byte)33);
        System.out.println("position:"+b1.position());// 7
        System.out.println(Arrays.toString(b1.array()));
        // 打印结果:[11, 0, 0, 0, 0, 22, 33, 0, 0, 0]
    }
}

3.7标记-mark

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

  • 相关方法:

    • public Buffer mark():设置此缓冲区的标记为当前的position位置。
    • public Buffer reset() : 将此缓冲区的位置重置为以前标记的位置。
  • 示例代码:

public static void main(String[] args) {
        ByteBuffer b1 = ByteBuffer.allocate(10);
        // 添加数据
        b1.put((byte)11);

        // 获取当前位置索引
        int position = b1.position();
        System.out.println("position:"+position);// 1

        // 标记当前位置索引
        b1.mark();

        // 添加元素
        b1.put((byte)22);
        b1.put((byte)33);

        // 获取当前位置索引
        System.out.println("position:"+b1.position());// 3
        System.out.println(Arrays.toString(b1.array()));
        // 打印结果:[11, 22, 33, 0, 0, 0, 0, 0, 0, 0]

        // 重置当前位置索引
        b1.reset();
        // 获取当前位置索引
        System.out.println("position:"+b1.position());// 1

        // 添加元素
        b1.put((byte)44);
        System.out.println(Arrays.toString(b1.array()));
        // 打印结果:[11, 44, 33, 0, 0, 0, 0, 0, 0, 0]

    }

3.8其他方法

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

public static void main(String[] args) {
//创建对象
ByteBuffer buffer = ByteBuffer.allocate(10);

    //添加元素
    buffer.put((byte)11);
    buffer.put((byte)22);
    buffer.put((byte)33);

    //限制
    buffer.limit(6);

    //容量是10 位置是3 限制是6
    System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
    

    //还原
    buffer.clear();

    //容量是10 位置是0 限制是10
    System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
}

第四章 Channel(通道)

4.1Channel 的概述

Channel(通道):Channel是一个对象,可以通过它读取和写入数据, 可以把它看做是IO中的流,不同的是:Channel是双向的, Channel对象既可以调用读取的方法, 也可以调用写出的方法 。

输入流: 读

输出流: 写

Channel: 读,写

4.2Channel 的分类

在Java NIO中的Channel主要有如下几种类型:

  • FileChannel:从文件读取数据的 输入流和输出流
  • DatagramChannel:读写UDP网络协议数据 Datagram
  • SocketChannel:读写TCP网络协议数据 Socket
  • ServerSocketChannel:可以监听TCP连接 ServerSocket

4.3FileChannel类的基本使用

获取FileChannel类的对象

  • java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。

  • FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取一个它的子类对象。

    FileInputStream fi=new FileInputStream(new File(src));
    FileOutputStream fo=new FileOutputStream(new File(dst));
    //获得传输通道channel
    FileChannel inChannel=fi.getChannel();
    FileChannel outChannel=fo.getChannel();
    

使用FileChannel类完成文件的复制

  • 我们将通过CopyFile这个示例让大家体会NIO的操作过程。CopyFile执行三个基本的操作:创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。
 public static void main(String[] args) throws Exception{
        FileInputStream fis = new FileInputStream("day19\\aaa\\a.txt");
        FileOutputStream fos = new FileOutputStream("day19\\aaa\\aCopy1.txt");
        // 获得FileChannel管道对象
        FileChannel c1 = fis.getChannel();
        FileChannel c2 = fos.getChannel();

        // 创建ByteBuffer数组
        ByteBuffer b = ByteBuffer.allocate(1000);

        // 循环读取数据
        while ((c1.read(b)) != -1){// 读取的字节会填充postion到limit位置之间
            // 重置 postion为0,limit为postion的位置
            b.flip();
            // 写出数据
            c2.write(b);// 会把postion到limit之间的数据写出
            // 还原
            b.clear();// positon为:0  limit为: capacity 用于下次读取
        }

        // 释放资源
        c2.close();
        c1.close();
        fos.close();
        fis.close();

        /*byte[] bys = new byte[8192];
        int len;
        while ((len = fis.read(bys)) != -1){
            fos.write(bys,0,len);
        }
        fos.close();
        fis.close();*/
    }

4.4 FileChannel结合MappedByteBuffer实现高效读写

4.41MappedByteBuffer类的概述

  • 上例直接使用FileChannel结合ByteBuffer实现的管道读写,但并不能提高文件的读写效率。

  • ByteBuffer有个抽象子类:MappedByteBuffer,它可以将文件直接映射至内存,把硬盘中的读写变成内存中的读写, 所以可以提高大文件的读写效率。

  • 可以调用FileChannel的map()方法获取一个MappedByteBuffer,map()方法的原型:

    MappedByteBuffer map(MapMode mode, long position, long size);

    说明:将节点中从position开始的size个字节映射到返回的MappedByteBuffer中。

4.42复制2GB以下的文件

  • 复制d:\b.rar文件,此文件大概600多兆,复制完毕用时不到2秒。此例不能复制大于2G的文件,因为map的第三个参数被限制在Integer.MAX_VALUE(字节) = 2G。
public static void main(String[] args) throws Exception{
        //java.io.RandomAccessFile类,可以设置读、写模式的IO流类。
        //"r"表示:只读--输入流,只读就可以。
        RandomAccessFile r1 = new RandomAccessFile("day19\\aaa\\a.txt","r");
        //"rw"表示:读、写--输出流,需要读、写。
        RandomAccessFile r2 = new RandomAccessFile("day19\\aaa\\aCopy2.txt","rw");

        // 获得FileChannel管道对象
        FileChannel c1 = r1.getChannel();
        FileChannel c2 = r2.getChannel();

        // 获取文件的大小
        long size = c1.size();

        // 直接把硬盘中的文件映射到内存中
        MappedByteBuffer b1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
        MappedByteBuffer b2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);

        // 循环读取数据
        for (long i = 0; i < size; i++) {
            // 读取字节
            byte b = b1.get();
            // 保存到第二个数组中
            b2.put(b);
        }
        // 释放资源
        c2.close();
        c1.close();
        r2.close();
        r1.close();
    }
  • 代码说明:

  • map()方法的第一个参数mode:映射的三种模式,在这三种模式下得到的将是三种不同的MappedByteBuffer:三种模式都是Channel的内部类MapMode中定义的静态常量,这里以FileChannel举例:
    1). FileChannel.MapMode.READ_ONLY:得到的镜像只能读不能写(只能使用get之类的读取Buffer中的内容);

    2). FileChannel.MapMode.READ_WRITE:得到的镜像可读可写(既然可写了必然可读),对其写会直接更改到存储节点;

    3). FileChannel.MapMode.PRIVATE:得到一个私有的镜像,其实就是一个(position, size)区域的副本罢了,也是可读可写,只不过写不会影响到存储节点,就是一个普通的ByteBuffer了!!

  • 为什么使用RandomAccessFile?

    1). 使用InputStream获得的Channel可以映射,使用map时只能指定为READ_ONLY模式,不能指定为READ_WRITE和PRIVATE,否则会抛出运行时异常!

    2). 使用OutputStream得到的Channel不可以映射!并且OutputStream的Channel也只能write不能read!

    3). 只有RandomAccessFile获取的Channel才能开启任意的这三种模式!

4.43复制2GB以上的文件

  • 下例使用循环,将文件分块,可以高效的复制大于2G的文件
    public static void main(String[] args) throws Exception{
        //java.io.RandomAccessFile类,可以设置读、写模式的IO流类。
        //"r"表示:只读--输入流,只读就可以。
        RandomAccessFile r1 = new RandomAccessFile("H:\\课堂资料.zip","r");
        //"rw"表示:读、写--输出流,需要读、写。
        RandomAccessFile r2 = new RandomAccessFile("H:\\课堂资料2.zip","rw");

        // 获得FileChannel管道对象
        FileChannel c1 = r1.getChannel();
        FileChannel c2 = r2.getChannel();

        // 获取文件的大小
        long size = c1.size();

        // 每次期望复制500M
        int everySize = 1024*1024*500;

        // 总共需要复制多少次
        long count = size % everySize == 0 ? size/everySize : size/everySize+1;

        // 开始复制
        for (long i = 0; i < count; i++) {
            // 每次开始复制的位置
            long start = everySize*i;

            // 每次复制的实际大小
            long trueSize = size - start > everySize ? everySize : size - start;

            // 直接把硬盘中的文件映射到内存中
            MappedByteBuffer b1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);
            MappedByteBuffer b2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);

            // 循环读取数据
            for (long j = 0; j < trueSize; j++) {
                // 读取字节
                byte b = b1.get();
                // 保存到第二个数组中
                b2.put(b);
            }
        }

        // 释放资源
        c2.close();
        c1.close();
        r2.close();
        r1.close();
        
    }

4.5ServerSocketChannel和SocketChannel创建连接

4.51 SocketChannel创建连接

  • 客户端:SocketChannel类用于连接的客户端,它相当于:Socket。

    1). 先调用SocketChannel的open()方法打开通道:

    SocketChannel socket = SocketChannel.open()
    

    2). 调用SocketChannel的实例方法connect(SocketAddress add)连接服务器:

     socket.connect(new InetSocketAddress("localhost", 8888));
    

    示例:客户端连接服务器:

    public class Client {
        public static void main(String[] args) throws Exception {
            SocketChannel socket = SocketChannel.open();
            socket.connect(new InetSocketAddress("localhost", 8888));
    		  System.out.println("后续代码......");
            
        }
    }
    

4.52ServerSocketChanne创建连接

  • 服务器端:ServerSocketChannel类用于连接的服务器端,它相当于:ServerSocket。

  • 调用ServerSocketChannel的静态方法open()就可以获得ServerSocketChannel对象, 但并没有指定端口号, 必须通过其套接字的bind方法将其绑定到特定地址,才能接受连接。

    ServerSocketChannel serverChannel = ServerSocketChannel.open()
    
  • 调用ServerSocketChannel的实例方法bind(SocketAddress add):绑定本机监听端口,准备接受连接。

    ​ 注:java.net.SocketAddress(抽象类):代表一个Socket地址。

    ​ 我们可以使用它的子类:java.net.InetSocketAddress(类)

    ​ 构造方法:InetSocketAddress(int port):指定本机监听端口。

    serverChannel.bind(new InetSocketAddress(8888));
    
  • 调用ServerSocketChannel的实例方法accept():等待连接。

    SocketChannel accept = serverChannel.accept();
    System.out.println("后续代码...");
    

    示例:服务器端等待连接(默认-阻塞模式)

    public class Server {
        public static void main(String[] args) throws Exception{
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(8888));
            System.out.println("【服务器】等待客户端连接...");
            SocketChannel accept = serverChannel.accept();
            System.out.println("后续代码......");
        }
    }
    

    运行后结果:

    【服务器】等待客户端连接...
    
  • 我们可以通过ServerSocketChannel的configureBlocking(boolean b)方法设置accept()是否阻塞

    public class Server {
        public static void main(String[] args) throws Exception {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(8888));
            System.out.println("【服务器】等待客户端连接...");
            //设置非阻塞连接
            ssc.configureBlocking(false);// 写成false叫非阻塞, 写成true叫阻塞
            
             while(true) {
                  //获取客户端连接
                  SocketChannel sc = ssc.accept();
    
                  if(sc != null){
                      //不等于null说明连接上了客户端
                      System.out.println("连接上了。。");
                      //读取数据操作
                      break;
                  }else{
                      //没连接上客户端
                      System.out.println("打会儿游戏~");
                      Thread.sleep(2000);
                  }
              }
        }
    }
    

    运行后结果:

    【服务器】等待客户端连接...
    打会儿游戏~
    有客户端来了就输出: 连接上了。。
    

4.6NIO网络编程收发信息

书写服务器代码
public class Server {
    public static void main(String[] args)  throws IOException{
		//创建对象
        //ServerSocket ss = new ServerSocket(8888);

        //创建
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //服务器绑定端口
        ssc.bind(new InetSocketAddress(8888));

        //连接上客户端
        SocketChannel sc = ssc.accept();
       
        //服务器端接受数据
        //创建数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //接收数据
        int len = sc.read(buffer);
        //打印结构
        System.out.println(new String(buffer.array(),0,len));

        //关闭资源
        sc.close();
    }
}
书写客户端代码
public class Client {
    public static void main(String[] args) {
        //创建对象
        //Socket s = new Socket("127.0.0.1",8888);

        //创建对象
        SocketChannel sc = SocketChannel.open();
        //连接服务器
        sc.connect(new InetSocketAddress("127.0.0.1",8888));

        //客户端发数据
        //创建数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //数组中添加数据
        buffer.put("你好啊~".getBytes());
        //切换
        buffer.flip();
        //发出数据
        sc.write(buffer);

        //关流
        sc.close();
    }
}

总结

  • 能够使用Junit进行单元测试
    - 1.在模块下创建lib文件夹,把Junit的jar包复制到lib文件夹中
    - 2.选中Junit的jar包,右键选中 add as Library,把JUnit4的jar包添加到classPath中
    - 3.在测试方法上面写上@Test注解
    - 4.执行测试方法

  • 能够说出同步和异步的概念
    - 同步: 调用方法之后,必须要得到一个返回值 例如: 买火车票,一定要买到票,才能继续下一步
    - 异步: 调用方法之后,没有返回值,但是会有回调函数,回调函数指的是满足条件之后会自动执行的方法 例如: 买火车票, 不一定要买到票,我可以交代售票员,当有票的话,你就帮我出张票

  • 能够说出阻塞和非阻塞的概念

    • 阻塞:如果没有达到方法的目的,就会一直停在那里(等待) , 例如: ServerSocket的accept()方法
    • 非阻塞:不管方法有没有达到目的,都直接往下执行(不等待)
  • 能够创建和使用ByteBuffer
    方式一:在堆中创建缓冲区:allocate(int capacity)
    方式二: 在系统内存创建缓冲区:allocatDirect(int capacity)
    方式三:通过数组创建缓冲区:wrap(byte[] arr)

  • 能够使用MappedByteBuffer实现高效读写
    1.获得FileChannel对象
    2.使用FileChannel对象调用map方法得到MappedByteBuffer数组 映射硬盘中的数据到内存中
    3.读写
    4.释放资源

  • 能够使用ServerSocketChannel和SocketChannel实现连接并收发信息
    服务器:
    // 获得ServerSocketChannel对象
    // 指定端口号
    // 接收客户端请求
    // 读数据
    // 释放资源
    客户端:
    // 获得SocketChannel对象
    // 连接服务器,指定服务器的ip和端口号
    // 写数据
    // 释放资源

练习模块

题目一

  1. 定义类:ArrayUtils,定义以下方法:

​ public int getMax(int[] arr){

​ int max = arr[0];

​ for(int i = 1 ;i < arr.length ; i++){

​ max = arr[i] > max ? arr[i] : max;

​ }

​ return max;

​ }

​ 2. 请定义测试方法,测试getMax()方法

package com.day19;

import org.junit.jupiter.api.Test;

public class Task1_2 {
    @Test
    public void test(){
        int [] arr = {1,3,5,7,2,5,9,8,6};
        int max = new ArrayUtils().getMax(arr);
        System.out.println(max);
    }
}
class ArrayUtils{
    public int getMax(int[] arr){

        int max = arr[0];

        for(int i = 1 ;i < arr.length ; i++){

            max = arr[i] > max ? arr[i] : max;

        }

        return max;

    }

}

题目二

请编写程序,实现使用FileChannel结合MappedByteBuffer实现2G以下文件复制的例子。


package com.day19;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class Task3_1 {
    public static void main(String[] args) {
        try {
            //java.io.RandomAccessFile类,可以设置读、写模式的IO流类。
            //"r"表示:只读--输入流,只读就可以。
            RandomAccessFile source = new RandomAccessFile("d:\\b.rar", "r");
            //"rw"表示:读、写--输出流,需要读、写。
            RandomAccessFile target = new RandomAccessFile("d:\b.rar", "rw");
            //分别获取FileChannel通道
            FileChannel in = source.getChannel();
            FileChannel out = target.getChannel();
            //获取文件的大小
            long size = in.size();
            //调用Channel的map方法获取MappedByteBuffer
            MappedByteBuffer mbbi = in.map(FileChannel.MapMode.READ_ONLY, 0, size);
            MappedByteBuffer mbbo = out.map(FileChannel.MapMode.READ_ONLY, 0, size);
            long start = System.currentTimeMillis();
            System.out.println("开始。。。。。");
            for (int i = 0; i < size; i++) {
                byte b = mbbi.get(i);
                ByteBuffer put = mbbo.put(i, b);
            }
            long end = System.currentTimeMillis();
            System.out.println("用时:"+(start- end)+"毫秒!");
            source.close();
            target.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

题目三

请编写程序,实现使用SocketChannel和ServerSocketChannel实现“同步、非阻塞”的例子。

====================================【服务器端】=========================================
package com.day19;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Task3_2Server {
    public static void main(String[] args) {
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open();) {
            serverChannel.bind(new InetSocketAddress("localhost", 8888));
            System.out.println("【服务器】等待连接......");
            SocketChannel accept = serverChannel.accept();
            System.out.println("【服务器】有连接到达.......");

            //1.先发一条
            ByteBuffer outBuffer = ByteBuffer.allocate(100);
            outBuffer.put("您好客户端,我是服务器".getBytes());
            outBuffer.flip();//limit设置为position,position设置为0
            accept.write(outBuffer);//输出从position到limit之间的数据

            //2.再收一条,不确定字数是多少,但最多是100字节。先准备100字节空间
            ByteBuffer inBuffer = ByteBuffer.allocate(100);
            accept.read(inBuffer);
            inBuffer.flip();//limit设置为position,position设置为0
            String msg = new String(inBuffer.array(), 0, inBuffer.limit());
            System.out.println("【服务器】收到信息:" + msg);
            accept.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
======================================【客户端】=========================================
package com.day19;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Task3_2Client {
    public static void main(String[] args) {
        try (SocketChannel socket = SocketChannel.open();) {
            socket.connect(new InetSocketAddress("localhost", 8888));

            //1.先发一条消息
            ByteBuffer buffer = ByteBuffer.allocate(100);
            buffer.put("你好,服务器!我是【客户端】!".getBytes());
            buffer.flip();//limit设置为position,position设置为0
            socket.write(buffer);//输出从position到limit之间的数据

            ByteBuffer inBuffer = ByteBuffer.allocate(100);
            socket.read(inBuffer);
            inBuffer.flip();//limit设置为position,position设置为0

            String msg = new String(inBuffer.array(), 0, inBuffer.limit());
            System.out.println("【客户端】收到信息:" + msg);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值