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方法)
单元测试的使用
-
下载
www.junit.org 下载, IDEA中已经有了
-
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
- BeforeEach
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的使用步骤
- 写入缓冲区(把数据保存到数组中)
- 调用
flip()
方法. 用于切换缓冲区的读写模式 - 读缓冲区(从数组中把数据读取出来)
- 调用
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:内存映像文件访问的方式,共三种:
- MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
- MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
- MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
- position:文件映射时的起始位置。
- allocationGranularity:Memory allocation size for mapping buffers,通过native函数initIDs初始化。
- MapMode mode:内存映像文件访问的方式,共三种:
-
-
注意:
-
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
网友评论