IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Junit单元测试,BIO、NIO、AIO概念、Buffer类,Channel通道 -> 正文阅读

[开发测试]Junit单元测试,BIO、NIO、AIO概念、Buffer类,Channel通道

单元测试

Junit介绍

Junit是一个Java语言的单元测试框架,简单理解为可以用取代Java的(部分)main方法。Junit属于第三方工具,需导入jar包后使用。

Junit基本使用

/*
    Junit的作用:可以单独的运行某一个方法
    Junit的使用步骤
        1.导入junit的jar包
        2.在要执行的方法上添加一个@Test注解
        3.选择方法左边的绿色三角或者右键选择方法,选择run 方法名称,运行方法
          选择类左边的绿色三角后者右键选择类,选择run 类名,运行类中所有的方法
          右键选择模块,选择run 'All Tests'运行模块中所有类中被Test修饰的方法
 */
public class Demo01Junit {
    @Test
    public void show01(){
        System.out.println("show01方法!");
        System.out.println("show01方法!");
        System.out.println("show01方法!");
        System.out.println("show01方法!");
        System.out.println("show01方法!");
    }
    @Test
    public void show02(){
        System.out.println("show02方法!");
    }
    @Test
    public void show03(){
        System.out.println("show03方法!");
    }
}

Junit注意事项

/*
    Junit的注意事项
        1.没有添加@Test注解的方法,不能使用Junit单元测运行
        2.Junit单元测试只能运行public修饰的,无返回值,无参数的方法
 */
public class Demo02Junit {
    public void show01(){
        System.out.println("show01方法!");
    }
    //java.lang.Exception: Method show02() should be public 方法show02应该是public修饰的
    @Test
    protected void show02(){
        System.out.println("show02方法!");
    }
    //Method show03 should have no parameters 方法show03应该是没有参数的
    @Test
    public void show03(int a){
        System.out.println("show03方法!"+a);
    }
    //java.lang.Exception: Method show04() should be void 方法show04应该是void返回值类型
    @Test
    public String show04(){
        System.out.println("show01方法!");
        return "aaaa";
    }
    //java.lang.Exception: Method show05() should not be static 方法show05应该是没有static
    @Test
    public static void show05(){
        System.out.println("show05方法!");
    }
}

Junit相关注解

/*
    和Junit相关的注解
        * @Test: 可以单独的运行某一个方法
        * @Before:用来修饰方法,该方法会在每一个测试方法执行之前自动执行一次。
        * @After:用来修饰方法,该方法会在每一个测试方法执行之后自动执行一次。
        * @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前自动执行一次。只执行一次
        * @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后自动执行一次。只执行一次
     注意:
         @Before,@After,@BeforeClass,@AfterClass修饰的方法不能单独执行,会自动在@Test修饰的方法前后执行
         @Before,@After:会在每一个@Test修饰的方法前后执行
         @BeforeClass,@AfterClass:会在@Test修饰的方法前后执行,只执行一次
 */
public class Demo03Junit {
    @Test
    public void show01(){
        System.out.println("show01方法");
    }
    @Test
    public void show02(){
        System.out.println("show02方法");
    }
    @Test
    public void show03(){
        System.out.println("show03方法");
    }
    @Before
    public void before(){
        System.out.println("before方法");
    }
    @After
    public void after(){
        System.out.println("after方法");
    }
    @BeforeClass
    public static void beforeClass(){
        System.out.println("beforeClass方法");
    }
    @AfterClass
    public static void afterClass(){
        System.out.println("afterClass方法");
    }
}

BIO、NIO、AIO概述

1).BIO:Block(阻塞的) IO。【同步、阻塞】
2).NIO:Non-Block(非阻塞的(同步)IO——JDK1.4开始的。【同步、非阻塞】
3).AIO:Asynchronous(异步-非阻塞)IO——JDK1.7开始【异步、非阻塞】

阻塞和非阻塞,同步和异步的概念

举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片

  • 同步+阻塞

    这个时候如果我们一直在店里面啥都不干,一直等待商家面前等待它洗完照片,这个过程就叫同步阻塞。

  • 同步+非阻塞

    当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问(轮询),直到照片洗完,这个过程就叫同步非阻塞。

  • 异步+阻塞

    因为店里生意太好了,越来越多的人过来拍,店里面快没地方坐了,老板说你把你手机号留下,我一会洗好了就打电话告诉你过来取,然后你去外面找了一个长凳开始躺着睡觉等待老板打电话,啥不都干,这个过程就叫异步阻塞。

  • 异步+非阻塞

    当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,同时等待老板打电话,这样以来两不耽误,这个过程就叫异步非阻塞。

总结

从上面的描述中我们其实能够看到阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果(阻塞),还是继续做其他的任务(非阻塞)。
而异步和同步,则是对于请求结果的获取是客户端主动等待获取(同步),还是由服务端来通知消息结果(异步)。
从这一点来看同步和阻塞其实描述的两个不同角度的事情,阻塞和非阻塞指的一个是客户端等待消息处理时的本身的状态,是挂起还是继续干别的。同步和异步指的对于消息结果的获取是客户端主动获取,还是由服务端间接推送。

阻塞:等待结果

非阻塞:可以做别的事情

同步:主动获取结果

异步:等待服务器通知结果

NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程
首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)

Buffer类(缓冲区)概述

  • java.nio.Buffer(抽象类):用于特定原始类型(基本类型)的数据的容器。后期在会用Channel进行通信时,底层全部使用Buffer。
  • 它的几个子类:
    1.ByteBuffer:里面可以封装一个byte[]数组。【重点掌握】
    2.ShortBuffer:里面可以封装一个short[]数组。
    3.CharBuffer:里面可以封装一个char[]数组
    4.IntBuffer:里面可以封装一个int[]数组。
    5.LongBuffer:里面可以封装一个long[]数组。
    6.FloatBuffer:里面可以封装一个float[]数组。
    7.DoubleBuffer:里面可以封装一个double[]数组。

创建ByteBuffer

  • 没有构造方法可以创建ByteBuffer,可以通过它的一些“静态方法”获取ByteBuffer对象。
  • 常用三个静态方法: new byte[10]; 默认值 0,0,0…0
    • public static ByteBuffer allocate(int capacity):使用一个“容量”来创建一个“间接字节缓存区”——程序的“堆”空间中创建。
    • public static ByteBuffer allocateDirect(int capacity):使用一个“容量”来创建一个“直接字节缓存区”——系统内存。 {1,2,3,4,5}
    • public static ByteBuffer wrap(byte[] byteArray):使用一个“byte[]数组”创建一个“间接字节缓存区”。
public class Demo01ByteBuffer {
    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.allocate(100);//里边包含一个byte[100] 间接

        ByteBuffer buffer2 = ByteBuffer.allocateDirect(100);//里边包含一个byte[100] 直接

        ByteBuffer buffer3 = ByteBuffer.wrap("我爱java".getBytes());//里边包含一个byte内容是"我爱java" 间接
    }
}

向ByteBuffer添加数据

  • public ByteBuffer put(byte b):向当前可用位置添加数据。
  • public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
  • public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
  • byte[] array()获取此缓冲区的 byte 数组
public class Demo02ByteBuffer {
    public static void main(String[] args) {
        //创建一个长度为10的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println(Arrays.toString(buffer.array()));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        //public ByteBuffer put(byte b):向当前可用位置添加数据。
        buffer.put((byte)1);
        buffer.put((byte)2);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 0, 0, 0, 0, 0, 0, 0, 0]
        //public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
        byte[] bytes = {10,20,30,40,50};
        buffer.put(bytes);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 0, 0, 0]
        buffer.put(bytes,3,2);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 0]
        //put(int index, byte b) 往指定索引处添加一个byte字节
        buffer.put(9,(byte)88);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 88]
    }    
}

容量-capacity

  • Buffer的容量(capacity)是指:Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。
public class Demo03capacity {
    public static void main(String[] args) {
        //创建一个长度为10的ByteBuffer,创建完之后长度不能改变,底层是数组
        ByteBuffer buffer1 = ByteBuffer.allocate(10);
        System.out.println("容量:"+buffer1.capacity());//容量:10
        ByteBuffer buffer2 = ByteBuffer.wrap("你好".getBytes());
        System.out.println("容量:"+buffer2.capacity());//容量:6
    }
}

限制 -limit

  • 限制:limit:表示如果设置“限制为某一个位置,那么此位置后的位置将不可用”。
  • 有两个相关方法:
    • public int limit():获取此缓冲区的限制。
    • public Buffer limit(int newLimit):设置此缓冲区的限制。
public class Demo04Limit {
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(10);
        System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:10
        buf.limit(3);//显示3索引位置之后将不可用,包含3

        buf.put((byte)0);
        buf.put((byte)1);
        buf.put((byte)2);
        System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:3
        //buf.put((byte)3);//BufferOverflowException
    }
}

位置 -position

  • 位置position是指:当前可写入的索引。位置不能小于0,并且不能大于"限制"。
  • 有两个相关方法:
    • public int position():获取当前可写入位置索引。
    • public Buffer position(int p):更改当前可写入位置索引。
public class Demo05Position {
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(10);
        System.out.println("初始容量:" + buf.capacity() +//10
                " 初始限制:" + buf.limit() +//10
                " 当前位置:" + buf.position());//0
        buf.put((byte) 10);//position = 1
        buf.put((byte) 20);//position = 2
        buf.put((byte) 30);//position = 3
        System.out.println("当前容量:" + buf.capacity() +
                " 初始限制:" + buf.limit() +
                " 当前位置:" + buf.position());//3
        System.out.println(Arrays.toString(buf.array()));//[10, 20, 30, 0, 0, 0, 0, 0, 0, 0]
        buf.position(1);//当position改为:1
        buf.put((byte) 2);//添加到索引:1
        buf.put((byte) 3);//添加到索引:2
        System.out.println(Arrays.toString(buf.array()));//[10, 2, 3, 0, 0, 0, 0, 0, 0, 0]
        buf.limit(3);
        buf.put((byte) 4);//添加到索引:3 BufferOverflowException  position<limit
    }
}

标记 -mark

  • 标记mark是指:当调用缓冲区的reset()方法时,会将缓冲区的position位置重置为该索引。不能为0,不能大于position。
  • 相关方法:
    • public Buffer mark():设置此缓冲区的标记为当前的position位置。
public class Demo06mark {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte)0);//position=1
        buffer.mark();//设置标记为1
        buffer.put((byte)1);//position=2
        buffer.put((byte)2);//position=3
        System.out.println("position:"+buffer.position());//position:3
        System.out.println(Arrays.toString(buffer.array()));//[0, 1, 2, 0, 0, 0, 0, 0, 0, 0]

        buffer.reset();//设置position的位置为mark标记的位置
        System.out.println("position:"+buffer.position());//position:1
        buffer.put((byte)100);
        System.out.println(Arrays.toString(buffer.array()));//[0, 100, 2, 0, 0, 0, 0, 0, 0, 0]
    }
}

其他方法

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

Channel(通道)

概述

1).java.nio.channels.Channel(接口):用于 I/O 操作的连接。

  • 表示:通道。
  • 可以是“文件通道-FileChannel”、“网络通道-SocketChannel和ServerSockecChannel”。
  • 它类似于IO流,但比IO流更强大。read(byte[]) write(byte[])
  • IO流是“单向”的,Channel是“双向的”。
    2).Channel全部使用Buffer实现读、写。read(ByteBuffer) write(ByteBuffer)

FileChannel类的基本使用(重点)

  • java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。
  • FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取一个它的子类对象。
/*
    java.nio.channels.FileChannel抽象类:用于读取、写入、映射和操作文件的通道。
    获取对象的方式:
        FileInputStream类中的方法:
            FileChannel getChannel() 返回与此文件输入流有关的唯一 FileChannel 对象。
        FileOutputStream类中的方法:
            FileChannel getChannel() 返回与此文件输出流有关的唯一 FileChannel 对象。
    成员方法:
        int read(ByteBuffer dst) 读取多个字节到ByteBuffer中,相当于FileInputStream对象中的read(byte[])
        int write(ByteBuffer src)  将ByteBuffer中的数据写入到文件中,相当于FileOutputStream对象中的write(byte[])
 */
public class Demo01FileChannel {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,绑定要读取的数据源
        FileInputStream fis = new FileInputStream("c:\\1.jpg");
        //2.创建FileOutputStream对象,绑定要写入的目的地
        FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
        //3.使用FileInputStream对象中的方法getChannel,获取输入的FileChannel对象
        FileChannel fisChannel = fis.getChannel();
        //4.使用FileOutputStream对象中的方法getChannel,获取输出的FileChannel对象
        FileChannel fosChannel = fos.getChannel();
        //5.使用输入的FileChannel对象中的方法read,读取数据
        //创建一个长度为1024的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = 0;
        while ((len=fisChannel.read(buffer))!=-1){
            //6.使用输出的FileChannel对象中的方法write,把读取到的数据写入到文件中
            //使用flip方法缩小limit的范围:最后一次读取不一定读取1024个
            buffer.flip();/mit设置为当前position位置,position位置设置为0
            fosChannel.write(buffer);//0索引到limit之间的数据,写完之后会修改position的位置limit
            //初始化ByteBuffer的状态
            buffer.clear();//将position设置为:0 ,将限制limit设置为容量capacity(1024)
        }
        //7.释放资源
        fosChannel.close();
        fisChannel.close();
        fos.close();
        fis.close();
    }
}

在这里插入图片描述

FileChannle结合MappedByteBuffer实现高效读写(重点)

  • java.io.RandomAccessFile类
获取FileChannel需要使用RandomAccessFile,可以创建流对象的同时设置读写模式
java.io.RandomAccessFile类,可以设置读、写模式的IO流类
构造方法:
	RandomAccessFile(String name, String mode)
	参数:
		String name:要读取的数据源,或者写入的目的地
		String mode:设置流的读写模式
			"r":只读,必须是小写
			"rw":读写,必须是小写
成员方法:
	FileChannel getChannel() 返回与此文件关联的唯一 FileChannel 对象。
  • 使用FileChannel类中的方法map得到MappedByteBuffer
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)  将此通道的文件区域直接映射到内存中。
	参数:
		FileChannel.MapMode mode:设置读写的模式
			READ_ONLY:只读映射模式。
			READ_WRITE:读取/写入映射模式。
	   long position:文件中的位置,映射区域从此位置开始,一般都是从0开始
	   size - 要映射的区域大小,就是要复制文件的大小,单位字节
  • java.nio.MappedByteBuffer 类
java.nio.MappedByteBuffer:它可以创建“直接缓存区”,将文件的磁盘数据映射到内存。
注意:它最大可以映射:Integer.MAX_VALUE个字节(2G)左右。
eg:磁盘和内存实时映射 硬盘(abc) 内存(abc)  内存修改为(ab) 磁盘也跟着修改(ab)
MappedByteBuffer中的方法:
	byte get(int index)  获取缓冲区中指定索引处的字节
	ByteBuffer put(int index, byte b)  把字节写入到指定的索引处
  • 代码实现:复制2g以下的文件
/*
    FileChannel结合MappedByteBuffer实现高效读写
    注意:
       MappedByteBuffer:直接缓存区,最大设置缓冲区的大小为2G(2048M)
       复制的文件不能超过2G的大小,超过了就需要分块复制
    需求:
        使用MappedByteBuffer复制2G以下的文件
 */
public class Demo02MappedByteBuffer {
    public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();
        //1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和设置只读模式
        RandomAccessFile inRAF = new RandomAccessFile("c:\\748m.rar","r");
        //2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和设置读写模式
        RandomAccessFile outRAF = new RandomAccessFile("d:\\748m.rar","rw");
        //3.使用读取文件的RandomAccessFile对象中的getChannel方法,获取读取文件的FileChannel
        FileChannel inRAFChannel = inRAF.getChannel();
        //4.使用写入文件的RandomAccessFile对象中的getChannel方法,获取写入文件的FileChannel
        FileChannel outRAFChannel = outRAF.getChannel();
        //5.使用读取文件FileChannel中的方法size,获取要读取文件的大小(字节)
        long size = inRAFChannel.size();
        System.out.println(size);//785042177 字节
        //6.使用读取文件FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
        MappedByteBuffer inMap = inRAFChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
        //7.使用写入文件FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
        MappedByteBuffer outMap = outRAFChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
        //8.创建for循环,循环size次
        for (int i = 0; i < size; i++) {
            //9.使用读取文件的直接缓冲区MappedByteBuffer中的方法get,读取指定索引处的字节
            byte b = inMap.get(i);
            //10.使用写入文件直接缓冲区MappedByteBuffer中的方法put,把读取得到的字节写入到指定索引处
            outMap.put(i,b);
        }
        //11.释放资源
        outRAF.close();
        inRAF.close();
        long e = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:2397毫秒!
    }
}

在这里插入图片描述

  • 代码实现 :复制2g以上的文件
    在这里插入图片描述
/*
    需求:
        使用MappedByteBuffer复制2G以上的文件
    注意:
        MappedByteBuffer:直接缓存区,最大设置缓冲区的大小为2G(2048M)
       复制的文件不能超过2G的大小,超过了就需要分块复制
 */
public class Demo03MappedByteBuffer {
    public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();
        //1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和设置只读模式
        RandomAccessFile inRAF = new RandomAccessFile("c:\\2g.rar","r");
        //2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和设置读写模式
        RandomAccessFile outRAF = new RandomAccessFile("d:\\2g.rar","rw");
        //3.使用读取文件的RandomAccessFile对象中的getChannel方法,获取读取文件的FileChannel
        FileChannel inRAFChannel = inRAF.getChannel();
        //4.使用写入文件的RandomAccessFile对象中的getChannel方法,获取写入文件的FileChannel
        FileChannel outRAFChannel = outRAF.getChannel();
        //5.使用读取文件FileChannel中的方法size,获取要读取文件的大小(字节)
        long size = inRAFChannel.size();
        long count = 1;//共复制文件的块数,默认值为1
        long startIndex = 0;//复制文件的开始索引
        long copySize = size;//每次复制文件的大小,默认等于文件的总大小
        long everSize = 512*1024*1024;//分块,每块文件大小 512M
        //判断要复制的文件大小是否大于每块文件的大小
        if(size>everSize){
            //计算共需要复制的总块数
            count = size%everSize==0 ? size/everSize : size/everSize+1;
            //第一次复制文件的大小等于每块大小
            copySize = everSize;
        }
        //分几块就复制几次
        for (int i = 0; i < count; i++) {
            //6.使用读取文件FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
            MappedByteBuffer inMap = inRAFChannel.map(FileChannel.MapMode.READ_ONLY, startIndex, copySize);
            //7.使用写入文件FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
            MappedByteBuffer outMap = outRAFChannel.map(FileChannel.MapMode.READ_WRITE, startIndex, copySize);
            System.out.println("每块文件的开始索引:"+startIndex);
            System.out.println("每次复制每块文件的大小:"+copySize);
            System.out.println("-------------------------------------------");
            //一读一写复制文件
            for (int j = 0; j < copySize; j++) {
                byte b = inMap.get(j);
                outMap.put(j,b);
            }
            //计算下一块的起始索引
            startIndex += copySize;
            //计算下一块复制文件大小
            copySize = size-startIndex > everSize ? everSize : size-startIndex;
        }
        //释放资源
        inRAFChannel.close();
        outRAFChannel.close();
        long e = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:5111毫秒!
    }
}
  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-09-02 11:40:46  更:2021-09-02 11:42:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/12 17:40:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码