NIO基本介绍

  • Java NIO(New IO) 也有人称之为Java non-blocking IO 是从Java1.4版本开始引入的一个新的IO API,可以代替标准的IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的,基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式
  • NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)

NIO和BIO的比较

  • BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高很多
  • BIO是阻塞的,NIO则是非阻塞的
  • BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

NIO三大核心原理

Buffer缓冲区

  • 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存,相比较直接对数组的操作,Buffer API更加容易操作和管理

Channel(通道)

  • Java NIO的通道类似流,但又有些不同 : 既可以从通道中读取数据,又可以写数据到通道。但流的(input或output)读写通常是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步地读写

Selector选择器

  • Selector是一个Java NIO组件,可以能够检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这压根,一个单独的线程可以管理多个channel,从而管理多个网络连接

  • 每个Channel都会对应一个Buffer
  • 一个线程对应Selector,一个Selector对应多个Channel(连接)
  • 程序切换到那个Channel是由事件决定的
  • Selector会根据不同的事件,在各个通道上切换
  • Buffer就是一个内存块,底层是一个数组
  • 数据的读取写入是通过Buffer完成的,BIO中要么是输入流,或者是输出流,不能双向,但是NIO的Buffer时可以读也可以写。
  • Channel负责传输,Buffer负责存取数据

缓冲区Buffer

  • 一个用于特定基本数据类型的容器。由 Java。nio包定义的,所有缓冲区都是Buffer抽象类的子类。Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的

Buffer类及其子类

Buffer就像一个数组,可以保存多个相同类型的数据。根据数据类型不同,有以下Buffer常用子类:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • xxxBuffer(xxx代表八种基本数据类型)

上述Buffer类 他们都采用相似的方法进行管理数据,只是各自管理的数据类型不同而已。都是通过如下方法获取一个Buffer对象:

//创建一个容量为capacity的xxxBuffer对象
static xxxBuffer allocate(int capacity);
|
|
|
IntBuffer buffer = IntBuffer.allocate(10);

Buffer中的重要概念

  • 容量(capacity):创建后不能更改,且容量不能为负

  • 限制(limit):表示缓冲区中可以操作数据的大小.缓冲区的限制不能为负,并且不能大于其容量.

    写入模式,限制等于buffer的容量.读取模式下,limit等于写入的数据量

  • 位置(position):下一个要读取或写入的数据的索引.缓冲区的位置不能为负,并且不能大于其限制

  • 标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法 指定 Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position.

常用API测试

/**
* @PROJECT_NAME: JAVA_Test
* @DESCRIPTION:
* @USER: 罗龙达
* @DATE: 2021/2/10 17:34
*/
public class apiTest { public void print(Buffer buffer){
System.out.println("pos = " + buffer.position());
System.out.println("lim = " + buffer.limit());
System.out.println("cap = " + buffer.capacity());
} @Test
public void test001(){
//1. 分配一个缓冲区,容量设置为10
ByteBuffer buffer = ByteBuffer.allocate(10);
print(buffer);
//2. put往缓冲区中添加数据
System.out.println("--------缓冲区添加数据--------");
buffer.put("LongDa66".getBytes());
print(buffer);
//3. Buffer flip() --> 将缓冲区的界限设置为当前位置,并将当前位置设置为0 可读模式
System.out.println("--------调用flip()方法--------");
buffer.flip();
print(buffer);
//4. get数据的读取
System.out.println("--------缓冲区中读取数据--------");
byte b = buffer.get();
System.out.println("从缓冲区中读取 " + (char)b);
print(buffer);
} @Test
public void test002(){
//1. 分配一个缓冲区,容量设置为10
ByteBuffer buffer = ByteBuffer.allocate(10);
print(buffer);
//2. put往缓冲区中添加数据
System.out.println("--------缓冲区添加数据--------");
buffer.put("LongDa66".getBytes());
print(buffer);
//2. 清除缓冲区中的数据,调用clear方法后只是将pos移到了0.
System.out.println("-------调用clear()方法后--------");
buffer.clear();
byte b = buffer.get();
System.out.println("从缓冲区中读取 " + (char)b);
print(buffer);
} @Test
public void test003(){
//1. 分配一个缓冲区,容量设置为10
ByteBuffer buffer = ByteBuffer.allocate(10);
print(buffer);
//2. put往缓冲区中添加数据
System.out.println("--------缓冲区添加数据--------");
System.out.println("向缓冲区添加 : LongDa66");
buffer.put("LongDa66".getBytes());
print(buffer); buffer.flip();
//3. 从缓冲区中读取前4位
System.out.println("--------缓冲区读取前4位数据--------");
byte[] bytes = new byte[4];
buffer.get(bytes);
String s = new String(bytes);
System.out.println(s);
print(buffer);
System.out.println("-----接着用mark()标记后,读取的数据-----");
buffer.mark();
byte[] bytes2 = new byte[4];
buffer.get(bytes2);
String s2 = new String(bytes2);
System.out.println(s2);
print(buffer);
System.out.println("-----调用reset()回到标记位置-----");
buffer.reset();
print(buffer);
System.out.println("-----调用remaining,看看position和limit之间剩余元素个数-----");
print(buffer);
System.out.println("缓冲区剩余元素个数" + buffer.remaining()); }
}

直接缓冲区与非直接缓冲区

  • 非直接缓冲区 : 通过allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中。

  • 直接缓冲区 : 通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。

通道Channel

  • 通道(Channel):表示IO源与目标打开的连接。Channel类似于传统的“流”.只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互

Channel与流的区别

  • Channel可以同时进行读写,而流只能读或者写
  • Channel可以实现异步读写数据
  • Channel可以从缓冲读数据,也可以写数据到缓冲

Channel在NIO中是一个接口

Channel常用实现类

  • FileChannel : 用于读取/写入/映射和操作文件的通道
  • DatagramChannel : 通过UDP读写网络中的数据通道
  • SocketChannel : 通过TCP读写网络中的数据
  • ServerSocketChannel : 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel

FileChannel类

常用方法测试

  • 写入文件
@Test
public void test004() {
try {
//1. 字节输出流通向目标文件
FileOutputStream fos = new FileOutputStream("D:\\data2.txt");
//2. 得到字节输出流对应的通道
FileChannel channel = fos.getChannel();
//3. 分配缓冲区
ByteBuffer bufer = ByteBuffer.allocate(1024);
bufer.put("hello,world".getBytes());
//4. 切换成写模式
bufer.flip();
channel.write(bufer);
//5. 关闭通道
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
  • 读取文件
@Test
public void test005(){
try {
//定义一个文件字节输入流与源文件连通
FileInputStream fis = new FileInputStream("D:\\data.txt");
//得到文件字节输入流的文件通道
FileChannel channel = fis.getChannel();
//定义一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据到缓冲区
channel.read(buffer);
buffer.flip();
//读取缓冲区中的数据
String s = new String(buffer.array());
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
  • 文件的复制测试
@Test
public void test006(){
File file = new File("D:\\data.txt");
try {
//得到字节输入/输出流
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("D:\\data3.txt"); //得到输入输出流的通道
FileChannel fisChannel = fis.getChannel();
FileChannel fosChannel = fos.getChannel(); //分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(true){
//清空缓冲区再写入数据
buffer.clear();
//判断文件是否结束
int flag = fisChannel.read(buffer);
if(flag == -1){
break;
}
//切换写模式,写入数据
buffer.flip();
fosChannel.write(buffer); fisChannel.close();
fosChannel.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
  • 分散读取和聚集操作数据
@Test
public void test007(){
File file = new File("D:\\data.txt");
File file2 = new File("D:\\data3.txt"); try {
//字节输入输出流
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file2);
//定义多个缓冲区 --> 数据分散
ByteBuffer buffer1 = ByteBuffer.allocate(4);
ByteBuffer buffer2 = ByteBuffer.allocate(400);
ByteBuffer[] buffers = {buffer1,buffer2};
//从通道中读取数据分散到各个缓冲区
FileChannel fisChannel = fis.getChannel();
FileChannel fosChannel = fos.getChannel();
//从通道中读取数据分散到各个缓冲区
fisChannel.read(buffers);
//从每个缓冲区中查询是否有数据读取到了
for (ByteBuffer buffer : buffers) {
buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.remaining()));
}
//聚集操作缓冲区
fosChannel.write(buffers);
fisChannel.close();
fosChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
  • TransferFrom() & TransferTo()方法
    @Test
public void test008(){
File file = new File("D:\\data.txt");
File file2 = new File("D:\\data3.txt"); try {
//字节输入输出流
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file2);
//从通道中读取数据分散到各个缓冲区
FileChannel fisChannel = fis.getChannel();
FileChannel fosChannel = fos.getChannel();
//复制数据
//从目标通道中复制原通道数据
// fosChannel.transferFrom(fisChannel,fisChannel.position(),fisChannel.size());
//把原通道数据复制到目标通道数据
fisChannel.transferTo(fisChannel.position(),fisChannel.position(),fosChannel); fisChannel.close();
fosChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}

NIO非阻塞式网络通信原理分析

Selector可以实现 : 一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O已连接一线程模型,架构的性能,弹性伸缩能力和可靠性都得到了极大的提升.

入门案例

  • 服务器端
/**
* @PROJECT_NAME: JAVA_Test
* @DESCRIPTION: 目标 : NIO非阻塞通信下的入门案例 : 服务器端
* @USER: 罗龙达
* @DATE: 2021/2/11 0:33
*/
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("---------服务端启动-----------");
//获取通道 --> 接收客户端的连接请求
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//切换为非阻塞模式
ssChannel.configureBlocking(false);
//绑定连接的端口
ssChannel.bind(new InetSocketAddress(9999));
//获取选择器 Selector
Selector selector = Selector.open();
//将通道都注册到选择器上,并且开始指定监听接收事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//使用Selector轮询已经准备就绪的事件
while (selector.select() > 0){
//获取选择器中的所有注册的通道中已经准备就绪的事件
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
//遍历已经准备好的时间
while (it.hasNext()){
System.out.println("开始一轮事件处理");
//提取当前事件
SelectionKey sk = it.next();
//判断这个事件具体是什么
if(sk.isAcceptable()){
//接收事件准备就绪,直接获取当前接入的客户端通道
SocketChannel sChannel = ssChannel.accept();
//切换成非阻塞模式
sChannel.configureBlocking(false);
//将本客户端通道注册到选择器里 服务器端监听读事件
sChannel.register(selector,SelectionKey.OP_READ);
}
//读事件
else if(sk.isReadable()){
//获取当前选择器上的读就绪事件
SocketChannel sChannel = (SocketChannel) sk.channel();
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buffer)) > 0){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
//清除之前的数据
buffer.clear();
}
} //处理完毕移除当前事件 防止重复监听
it.remove();
}
} }
}
  • 客户端
/**
* @PROJECT_NAME: JAVA_Test
* @DESCRIPTION: 目标: 客户端案例实现 - 基于NIO非阻塞通信
* @USER: 罗龙达
* @DATE: 2021/2/11 0:56
*/
public class Client {
public static void main(String[] args) throws IOException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));
//切换成非阻塞模式
socketChannel.configureBlocking(false);
//指定缓冲区大小
ByteBuffer buffer = ByteBuffer.allocate(1024);
//发送数据给服务端
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请说:");
String s = scanner.nextLine();
LocalDateTime timeNow = LocalDateTime.now();
buffer.put((timeNow + " 波妞 : " + s).getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
}
}

群聊案例

  • 服务器端
/**
* @PROJECT_NAME: JAVA_Test
* @DESCRIPTION:
* @USER: 罗龙达
* @DATE: 2021/2/11 1:47
*/
public class Server {
//定义选择器,服务端通道,端口
private Selector selector;
private ServerSocketChannel ssChannel;
private static final int PORT = 9999; //初始化
public Server(){
try {
//创建选择器
selector = Selector.open();
//获取通道
ssChannel = ServerSocketChannel.open();
//绑定客户端连接的端口
ssChannel.bind(new InetSocketAddress(PORT));
//设置非阻塞通信模式
ssChannel.configureBlocking(false);
//八通道注册到选择器上,并且开始指定接收事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 监听事件
*/
private void listen(){ try {
while(selector.select() > 0){
//获取选择器中所有注册通道的就绪事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//开始遍历
while (iterator.hasNext()){
SelectionKey sk = iterator.next();
//判断事件的类型
if(sk.isAcceptable()){
//客户端接入请求
//获取当前客户端通道
SocketChannel socketChannel = ssChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
}
else if(sk.isReadable()){
//处理这个客户端的消息,接收它然后实现转发逻辑
readClientData(sk);
}
iterator.remove();//处理完毕,移除当前事件
}
}
} catch (IOException e) {
e.printStackTrace();
} } /**
* 接受当前客户端通道的信息,转发给其他全部客户端通道
* @param sk
*/
private void readClientData(SelectionKey sk) {
SocketChannel socketChannel = null;
try{
//获取当前客户端通道
socketChannel = (SocketChannel) sk.channel();
//创建缓冲区对象开始接受客户端通道的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = socketChannel.read(buffer);
if (count > 0){
buffer.flip();
//提取读取到的信息
String msg = new String(buffer.array(), 0, buffer.remaining());
System.out.println("接收到客户端消息 : " + msg);
sendMsgToAllClient(msg,socketChannel); } }catch (Exception e){
try {
System.out.println("有人离线了 : " + socketChannel.getRemoteAddress());
//当前客户端离线
sk.cancel();
socketChannel.close();
} catch (IOException ioException) { }
}
} /**
* 把当前客户端的消息数据都推送给当前全部在线注册的channel
* @param msg
* @param socketChannel
*/
private void sendMsgToAllClient(String msg, SocketChannel socketChannel) {
System.out.println("服务端开始转发消息, 当前处理的线程 : " + Thread.currentThread().getName());
for (SelectionKey key : selector.keys()) {
Channel channel =key.channel();
if(channel instanceof SocketChannel && socketChannel != channel){
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
try {
((SocketChannel)channel).write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} public static void main(String[] args) {
//创建服务端对象
Server server = new Server();
//开始监听客户端的各种消息事件
server.listen();
}
}
  • 客户端
/**
* @PROJECT_NAME: JAVA_Test
* @DESCRIPTION: 客户端代码逻辑实现
* @USER: 罗龙达
* @DATE: 2021/2/11 17:33
*/
public class Client { private Selector selector;
private static int PORT = 9999;
private static SocketChannel socketChannel; public Client(){
try {
//创建选择器
selector = Selector.open();
//连接服务端
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",PORT));
//设置非阻塞通信模式
socketChannel.configureBlocking(false);
//八通道注册到选择器上,并且开始指定接收事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("当前客户端准备完成");
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
Client client = new Client();
//定义一个线程专门负责监听服务端发送过来的读消息事件
new Thread(client::readInfo).start(); Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()){
String s = sc.nextLine();
Client.sendMsg(s);
}
} private static void sendMsg(String s) {
try {
socketChannel.write(ByteBuffer.wrap(("波仔说:" + s).getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
} private void readInfo() {
try{
while(selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(new String(buffer.array()).trim());
System.out.println("--------分割线-----------");
}
iterator.remove();
} }
}catch (Exception e){
e.printStackTrace();
} }
}

AIO异步非阻塞IO

  • Java AIO : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可,这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统会主动通知应用程序

可以理解为,read/write方法都是异步的,完成后会主动调用回调函数,在JDK1.7中,这部分内容被称作NIO 2

BIO,NIO,AIO三者比较

  • Java BIO :

    同步并阻塞,服务器实现模式为一个链接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善.

  • Java NIO :

    同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用轮询到连接有I/O请求时才启动一个线程进行处理

  • Java AIO :

    异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理

使用场景分析

  • BIO适用于连接数目较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,但程序直观简单易理解
  • NIO方式适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中,编程比较复杂
  • AIO方式适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂

Java中NIO的简单介绍的更多相关文章

  1. java中数据流的简单介绍

    java中的I/O操作主要是基于数据流进行操作的,数据流表示了字符或者字节的流动序列. java.io是数据流操作的主要软件包 java.nio是对块传输进行的支持 数据流基本概念 “流是磁盘或其它外 ...

  2. java 中的多线程简单介绍

    package com.zxf.demo; /* * 多线程的实现方式两种? * 一..实现 runnable 接口 * 2.重写run方法 Run():当一个线程启动后,就会自动执行该方法 * 3. ...

  3. 多线程(三) java中线程的简单使用

    java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...

  4. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  5. java反射机制的简单介绍

    参考博客: https://blog.csdn.net/mlc1218559742/article/details/52754310 先给出反射机制中常用的几个方法: Class.forName (& ...

  6. java基础---->java中nio的使用(一)

    JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的.面向块的 I/O.今天我们就简单的学习一下nio的知识.我笑,便面如春花,定是能感动人的,任他是谁. nio的简 ...

  7. Java中NIO、BIO、AIO相关概念及应用场景

    1.同步阻塞IO(JAVA BIO):同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通 ...

  8. Java泛型使用的简单介绍

    目录 一. 泛型是什么 二. 使用泛型有什么好处 三. 泛型类 四. 泛型接口 五. 泛型方法 六. 限定类型变量 七. 泛型通配符 7.1 上界通配符 7.2 下界通配符 7.3 无限定通配符 八. ...

  9. 史上最全 Java 中各种锁的介绍

    更多精彩原创内容请关注:JavaInterview,欢迎 star,支持鼓励以下作者,万分感谢. 锁的分类介绍 乐观锁与悲观锁 锁的一种宏观分类是乐观锁与悲观锁.乐观锁与悲观锁并不是特定的指哪个锁(J ...

随机推荐

  1. java例题_03 水仙花数

    1 /*3 [程序 3 水仙花数] 2 题目:打印出所有的"水仙花数",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数本身. 3 例如:153 是一个 ...

  2. WebGPU[4] 纹理三角形

    代码见:https://github.com/onsummer/my-dev-notes/tree/master/webgpu-Notes/04-texture-triangle 原创,发布日 202 ...

  3. Vue3教程:Vue 3 + Element Plus + Vite 2 的后台管理系统开源啦

    之前发布过一篇文章<Vue3教程:开发一个 Vue 3 + element-plus 的后台管理系统>,文中提到会开发并开源一个 Vue 3 + Element Plus 的项目供大家练手 ...

  4. [Fundamental of Power Electronics]-PART II-7. 交流等效电路建模-7.3 脉冲宽度调制器建模

    7.3 脉冲宽度调制器建模 我们现在已经达成了本章开始的目标,为图7.1推导了一个有效的等效电路模型.但仍存在一个细节,对脉冲宽度调制(PWM)环节进行建模.如图7.1所示的脉冲宽度调制器可以产生一个 ...

  5. 后续来啦:Winform/WPF中快速搭建日志面板

    后续来啦:Winform/WPF中快速搭建日志面板 继昨天发文ASP.NET Core 可视化日志组件使用(阅读文章,查看视频)后,视频下有朋友留言 "Winform客户端的程序能用它不?& ...

  6. Leedcode算法专题训练(栈和队列)

    1. 用栈实现队列 232. Implement Queue using Stacks (Easy) Leetcode / 力扣 class MyQueue { Stack<Integer> ...

  7. Latex的使用(Ctex+TeXstudio)

    1.下载 CTEX Latex 本来是只支持英文的,但是实在太好用了,遂结合中国的团队以及有识之士,开发了这个 CTEX , CTEX 有 TexLive( TexLive 为 Latex 安装包的名 ...

  8. Ambassador-05-自动重试

    自动重试定义: retry_policy: retry_on: <string> num_retries: <integer> per_try_timeout: <str ...

  9. 现代操作系统原书第3版.mobi

    电子书资源:现代操作系统原书第3版 书籍简介   本书是操作系统领域的经典之作,与第2版相比,增加了关于Linux.Windows Vista和Symbian操作系统的详细介绍.书中集中讨论了操作系统 ...

  10. SpringIOC框架简单实现(注解实现)

    SpringIOC框架简单实现(注解实现) 前情回顾 SpringIOE简单介绍 运用注解的方式来实现IOC 首先,让我们来创建一个Dog类 @Component("dog")// ...