当前位置: 首页 > news >正文

JAVA基础-NIO

  • 传统IO:使用阻塞IO模型,基于字节流和字符流,进行文件读写,使用Socket和ServerSocket进行网络传输。
    • 存在的问题:
      • 当线程在等待IO完成的时候,是无法执行其他任务,导致资源空等浪费。
      • 一个Socket对应一个线程,Socket是基于字节流进行文件传输,如果大量的Socket,就会存在大量的上下文切换和阻塞,降低系统性能
  • NIO:使用非阻塞模型,基于通道(Channel)和缓冲区(Buffer)进行文件读写,以及使用SocketChannel和ServerSocketChannel进行网络传输
    • 优势:允许线程在等待IO的时候执行其他任务。这种模式通过使用选择器(Selector)来监控多个通道上的事件,实现更高的性能和伸缩性
  • 实际上传统的IO包已经使用NIO重新实现过,即使我们不显示的使用NIO变成,也能从中获益
package org.example.code_case.javaIo;import org.junit.jupiter.api.Test;import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class 传统IO和NIO的对比 {String sourcePath = "/src/main/java/org/example/code_case/javaIo/testFile.iso";String targetPath1 = "/src/main/java/org/example/code_case/javaIo/testFileIO.iso";String targetPath2 = "/src/main/java/org/example/code_case/javaIo/testFileNIO.iso";@Testpublic void testDemo() {Thread ioThread = new Thread(() -> {long startTime = System.currentTimeMillis();try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(sourcePath));BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(targetPath1));) {byte[] bytes = new byte[8192];int len;while ((len = bufferedInputStream.read(bytes)) != -1) {bufferedOutputStream.write(bytes, 0, len);}} catch (IOException e) {throw new RuntimeException(e);}long endTime = System.currentTimeMillis();System.out.println("传统IO time:" + (endTime - startTime));});Thread nioThread = new Thread(() -> {long startTime = System.currentTimeMillis();try (RandomAccessFile readRw = new RandomAccessFile(sourcePath, "rw");RandomAccessFile writeRw = new RandomAccessFile(targetPath2, "rw");) {try (FileChannel readChannel = readRw.getChannel();FileChannel writeChannel = writeRw.getChannel();) {// 创建并使用 ByteBuffer 传输数据ByteBuffer byteBuffer = ByteBuffer.allocate(8192);while (readChannel.read(byteBuffer) > 0) {byteBuffer.flip();writeChannel.write(byteBuffer);byteBuffer.clear();}}} catch (IOException e) {throw new RuntimeException(e);}long endTime = System.currentTimeMillis();System.out.println("NIO time:" + (endTime - startTime));});ioThread.start();nioThread.start();try {ioThread.join();nioThread.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}}

NIO time:24266

传统IO time:24267

当文件越大,传统IO和NIO的速度差异越小

  • 既然如此可以不使用NIO吗?
    • 答案:不行,IO应用的场景:文件IO、网络IO
    • 而NIO的主战场为网络IO
  • NIO设计的目的就是为了解决传统IO在大量并发连接时的性能瓶颈问题。传统IO在网络通信中使用阻塞式IO模型,并且会为每个连接分配一个线程,所以系统资源称为关键瓶颈。而NIO采用非阻塞式IO和IO多路复用,可以在单个线程中处理多个并发连接,从而提高网络传输的性能
  • NIO特点:
    • 非阻塞IO:在等待IO完成时,不会阻塞,可以执行其他任务,节省系统资源
      • NIO的非阻塞性体现在:读或者写的时候,如果通道无数据,则会先返回,当数据准备好后告知selector,不会在读或写时由于数据未准备好而阻塞。
    • IO多路复用:一个线程能够同时监控多个IO通道,,并在IO事件准备好的时处理他们
    • 提供ByteBuffer类,可以高效的管理缓冲区

NIO 和传统 IO 在网络传输中的差异

package org.example.code_case.javaIo.NIO和IO的对比;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class IOServer {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888);) {while (true) {try (Socket accept = serverSocket.accept();InputStream in = accept.getInputStream();OutputStream out = accept.getOutputStream();) {byte[] bytes = new byte[1024];int len;while ((len = in.read( bytes)) != -1){//模拟操作耗时TimeUnit.MILLISECONDS.sleep(10);out.write(bytes, 0, len);}}catch (InterruptedException e) {throw new RuntimeException(e);}}} catch (IOException e) {throw new RuntimeException(e);}
}
package org.example.code_case.javaIo.NIO和IO的对比;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class NIOServer {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);try (ServerSocketChannel open = ServerSocketChannel.open();) {//绑定端口open.bind(new InetSocketAddress(9999));//设置非阻塞模式open.configureBlocking(false);//创建selectorSelector selector = Selector.open();open.register(selector, SelectionKey.OP_ACCEPT);//事件类型:服务端接受客户端连接//处理事件while (true) {//阻塞等待事件发生selector.select();Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//遍历就绪事件while (iterator.hasNext()) {SelectionKey key = iterator.next();//处理完成之后删除iterator.remove();//判断事件类型if (key.isAcceptable()) {//有新连接请求ServerSocketChannel channel = (ServerSocketChannel) key.channel();SocketChannel accept = channel.accept();//设置为非阻塞模式accept.configureBlocking(false);//为新连接附加写队列Queue<ByteBuffer> byteBufferQueue = new ConcurrentLinkedQueue<>();accept.register(selector, SelectionKey.OP_READ, byteBufferQueue);//事件类型:通道可读}if (key.isReadable()) {//通道可读SocketChannel channel = (SocketChannel) key.channel();ByteBuffer allocate = ByteBuffer.allocate(1024);//从SocketChannel中读取数据写入缓冲区,//当通道没有数据可读立即返回0,而读取完返回-1int read = channel.read(allocate);if (read > 0) {//切换读模式allocate.flip();byte[] data = new byte[allocate.remaining()];allocate.get(data);executorService.execute(() -> processData(data, channel, key));}if (read == -1) {//关闭通道closeChannel(key);}}if (key.isValid() && key.isWritable()) {//通道可写SocketChannel channel = (SocketChannel) key.channel();Queue<ByteBuffer> queue = (Queue<ByteBuffer>) key.attachment();synchronized (queue) {ByteBuffer buffer;while ((buffer = queue.peek()) != null) {channel.write(buffer);if (buffer.hasRemaining()) break;//未写完,等待下次写事件queue.poll();//已写完,移除缓冲区}if (queue.isEmpty()) {key.interestOps(SelectionKey.OP_READ);//取消写事件监听,监听读事件closeChannel(key);}}}}}} catch (IOException e) {throw new RuntimeException(e);}}private static void processData(byte[] data, SocketChannel channel, SelectionKey key) {try {//模拟耗时操作TimeUnit.MILLISECONDS.sleep(10);//生成相应数据ByteBuffer wrap = ByteBuffer.wrap(data);Queue<ByteBuffer> queue = (Queue<ByteBuffer>) key.attachment();synchronized (queue) {queue.add(wrap);key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);//同时监听写事件和读事件,OP_READ是因为数据可没有一次性读完}key.selector().wakeup();//唤醒selector立即处理新事件} catch (InterruptedException e) {throw new RuntimeException(e);}}private static void closeChannel(SelectionKey key) {try {key.cancel();key.channel().close();} catch (IOException e) {e.printStackTrace();}}
}
@Test
public void ioTest() throws InterruptedException {ExecutorService ioService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());Runnable ioThread = () -> {try (Socket socket = new Socket("127.0.0.1", 8888);InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();) {out.write("hello world,".getBytes());in.read(new byte[1024]);} catch (IOException e) {throw new RuntimeException(e);}};long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {ioService.execute(ioThread);}ioService.shutdown();ioService.awaitTermination(1, TimeUnit.DAYS);long end = System.currentTimeMillis();System.out.println("传统IO,处理10000个请求 time:" + (end - start));
}@Test
public void nioTest() throws InterruptedException {ExecutorService nioService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());Runnable nioThread = () -> {try (SocketChannel open = SocketChannel.open();) {open.connect(new InetSocketAddress("localhost", 9999));//给服务端发消息ByteBuffer buffer = ByteBuffer.wrap("hello world,".getBytes());open.write(buffer);buffer.clear();//接收服务端消息open.read(buffer);} catch (IOException e) {throw new RuntimeException(e);}};long start1 = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {nioService.execute(nioThread);}nioService.shutdown();nioService.awaitTermination(1, TimeUnit.DAYS);long end1 = System.currentTimeMillis();System.out.println("NIO,处理10000个请求 time:" + (end1 - start1));
}

传统IO,处理10000个请求 time:122803ms

NIO,处理10000个请求 time:23950ms

从运行结果能够很明显的看出NIO在处理网络高并发请求的优势

JavaIO:BIO、NIO、AIO分别是什么?有什么区别?

  • I/O:即输入/输出,通常用于文件读写或网络通信
  • 随着Java的发展,共有3种I/O模型,BIO、NIO、AIO
  • BIO:同步阻塞IO模型,也称传统IO,是面向数据流的,即一个流产生一个字节数据。效率低、网络并发低
    • 同步阻塞IO模型:发送请求,等待对方应答,期间不能做任何事情
    • 适用场景:连接少,并发低的架构,例如文件读写
  • NIO:同步非阻塞IO模型,也称NewIO,是面向数据块的,即每次操作在一步中产生或消费一个数据块。引进Channel通道、BtyeBuffer缓冲区、Selector选择器组件。效率高、网络并发高
    • 同步非阻塞IO模型:发送请求,通过轮询和选择器检查IO状态,期间可以做其他事情。
    • 适用场景:连接数目多,但连接时间短的场景,例如聊天室
  • AIO:异步非阻塞IO模型,建立在NIO的基础上实现的异步
    • 异步非阻塞IO模型:发送请求,然后去其他事情,请求完成自动通知
    • 适用场景:连接数目多,但连接时间长的场景,例如图片服务器
@Test
public void test2() throws IOException, ExecutionException, InterruptedException {//读取文件AsynchronousFileChannel open = AsynchronousFileChannel.open(path, StandardOpenOption.READ);ByteBuffer allocate = ByteBuffer.allocate(1024);open.read(allocate, 0, allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {attachment.flip();System.out.println("读取到的数据" + StandardCharsets.UTF_8.decode(attachment));attachment.clear();try {open.close();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("读取失败");exc.printStackTrace();}});//写入文件AsynchronousFileChannel open1 = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE,StandardOpenOption.WRITE);ByteBuffer wrap = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));Future<Integer> write = open1.write(wrap, 0);write.get();open1.close();System.out.println("写入完成");}

Buffer和Channel

  • 在NIO中有两个重要的组件:Buffer缓冲区和Channel通道
  • 其中Channel就像轨道,只负责运送。Buffer就像火车,负责承载数据
Buffer
  • Buffer:IntBuffer、CharBuffer、ByteBuffer、LongBuffer
  • 缓冲区的核心方法就是put和get方法
  • Buffer类维护了4个核心参数:
    • 容量Capacity:缓冲区的最大容量,在创建时确定,不能改变,底层是数组
    • 上界limit:缓冲区中数据的总数,代表当前缓冲区有多少数据,默认为0,只有在调用flip()方法之后才有意义
    • 位置Position:下一个要被读或写的位置,Position会自动由相应的get和put方法更新
    • 标记mark:用于记录上一次读写的位置
  • 如果想要从Buffer中读取数据,需要调用flip(),此方法将Postion的值给了limit,将Postion置为0
Channel
  • 通道分为两类:文件通道和套接字通道
  • FileChannel:用于文件IO的通道,支持对文件的读写和追加操作。无法设置非阻塞模式
  • SocketChannel:用于TCP套接字的网络通信,支持非阻塞模式,与Selector一同使用提供高效的网络通信。与之匹配的是ServerSocketChannel,用于监听TCP套接字的通道,也支持非阻塞模式,ServerSocketChannel负责监听新的连接请求,接受到请求后创建一个新的SocketChannel处理数据传输
  • DatagramChannel:用于 UDP 套接字 I/O 的通道。DatagramChannel 支持非阻塞模式,可以发送和接收,数据报包,适用于无连接的、不可靠的网络通信。
文件通道FileChannel
  • 使用内存映射文件MappedByteBuffer当方式实现文件复制功能:
 /*** MappedByteBuffer 使用ByteBuffer的子类,它可以直接将文件映射到内存中,以便通过直接操作内存来实现对文件的读写。* 这种方式可以提高文件 I/O 的性能,因为操作系统可以直接在内存和磁盘之间传输数据,无需通过Java 应用程序进行额外的数据拷贝。* 需要注意:MappedByteBuffer进行文件操作时,数据的修改不会立即写入磁盘,可以通过调用force()方法将数据立即写入*/
@Test
public void test3() throws IOException {try (FileChannel sourceChannel = FileChannel.open(path, StandardOpenOption.READ);FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);) {long size = sourceChannel.size();MappedByteBuffer sourceMappedByteBuffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);MappedByteBuffer targetMappedByteBuffer = targetChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);targetMappedByteBuffer.put(sourceMappedByteBuffer);}
}
  • 通道之间通过transferTo()实现数据的传输:
/*** 这种传输方式可以避免将文件数据在用 户空间和内核空间之间进行多次拷贝,提高了文件传输的性能。* transferTo()方法可以将数据从一个通道传输到另一个通道,而不需要通过中间缓冲区。* position:源文件中开始传输的位置* count:传输的字节数* targetChannel:目标通道* <p>* 注意:transferTo()可能无法一次传输所有请求的字节,需要使用循环来确保所有的字节都被传输*/
@Test
public void test4() throws IOException {try (FileChannel sourceChannel = FileChannel.open(path, StandardOpenOption.READ);FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);) {long size = sourceChannel.size();long position = 0;while (position < size) {long offer = sourceChannel.transferTo(position, size - position, targetChannel);position += offer;}}
}
直接缓冲区与非直接缓冲区
  • 直接缓冲区:
    • ByteBuffer.allocateDirect(int size)FileChannel.map()创建的MappedByteBuffer都是直接缓冲区
    • 直接分配在JVM堆内存之外的本地内存中,由操作系统直接管理
    • 在读写时直接操作本地内存,避免了数据复制,提高性能,但是创建和销毁成本高,占用物理内存
    • 适用场景:网络IO操作、频繁的IO操作
  • 非直接缓冲区
    • ByteBuffer.allocate(int size)
    • 分配在JVM的堆内存中,由JVM管理
    • 在读写操作时,需要将数据从堆内存复制到操作系统的本地内存,在进行I/O操作,但是创建成本低,内存管理简单
    • 适用场景:临时数据处理、小文件操作、数据转换处理
异步文件通道AsynchronousFileChannel
  • AsynchronoursFileChannel是一个异步文件通道,提供了使用异步的方式对文件读写、打开关闭等操作
Path file = Paths.get("example.txt");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(file,
StandardOpenOption.READ, StandardOpenOption.WRITE);
  • 两种异步操作:
    • Future方式:使用Future对象来跟踪异步操作的完成情况,当我们调用read()和write()方法的时候,会返回一个Future对象,可以检查任务是否完成,进而操作结果
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> result = fileChannel.read(buffer, position);
while (!result.isDone()) {// 执行其他操作
}
int bytesRead = result.get();
System.out.println("Bytes read: " + bytesRead);
    • CompletionHandler方法:需要实现CompletionHandler接口来定义异步操作成功或失败的操作。相当于回调函数
//读取文件
AsynchronousFileChannel open = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer allocate = ByteBuffer.allocate(1024);AtomicInteger position = new AtomicInteger(0);
CountDownLatch countDownLatch = new CountDownLatch(1);
open.read(allocate, position.get(), allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}if(result > 0){position.addAndGet(result);attachment.flip();System.out.println("读取到的数据" + StandardCharsets.UTF_8.decode(attachment));attachment.clear();open.read(allocate, position.get(), allocate, this);}else{try {countDownLatch.countDown();System.out.println("关闭");open.close();} catch (IOException e) {throw new RuntimeException(e);}}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("读取失败");exc.printStackTrace();}
});countDownLatch.await();

Paths类

  • Paths类主要用于操作文件和目录的路径。
//获取当前工作路径
String currentPath = System.getProperty("user.dir");
System.out.println("当前工作目录:" + currentPath);
//获取指定路径
Path resolve = Paths.get(currentPath).resolve("src/main/resources/不同IO的使用方式.txt");
System.out.println("获取文件名:" + resolve.getFileName());
System.out.println("获取父目录:" + resolve.getParent());
System.out.println("获取根目录:" + resolve.getRoot());
System.out.println("简化路径:" + resolve.normalize());
System.out.println("将相对路径转化为绝对路径:" + resolve.toAbsolutePath());

Files类

  • Files类提供了大量的静态方法,用于处理文件系统中的文件和目录。包括文件的创建、删除、复制、移动等操作,以及读取和设置文件属性
  • exists(Path path, LinkOption... options);检查文件或目录是否存在
    • LinkOption是一个枚举类,定义了如何处理文件系统的符号链接的选项。符号链接是一种特殊类型的文件,在Unix和类Unix系统常见,在Win系统中类似快捷方式
  • createFile(Path path, FileAttribute<?>... attrs);创建一个新的空文件
    • FileAttribute是一个泛型接口,用于处理各种不同类型的属性
Path parentPath = Paths.get(System.getProperty("user.dit")).resolve("src/main/resources");
Path resolve = parentPath.resolve("FileTest.txt");//设置文件权限:
//rw- 文件所有者可读写不能执行
//r-- 文件所在组可读不能写不能执行
//--- 其他用户不能读写不能执行
Set<PosixFilePermission> posixFilePermissions = PosixFilePermissions.fromString("rw-r-----");
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(posixFilePermissions);
Files.createFile(resolve, fileAttribute);
  • createDirectory(Path dir, FileAttribute<?>... attrs);创建一个新的文件夹
  • delete(Path path);删除文件或目录
  • copy(Path source, Path target, CopyOption... options);复制文件或目录
    • 在Java NIO中,有两个实现了CopyOption接口的枚举类:StandardCopyOption和LinkOption
    • StandardCopyOption:REPLACE_EXISTING如果文件已存在,copy会替换目标文件,如果不指定,则抛异常。COPY_ATTRIBUTES复制时同样复制文件属性,如果不指定,则文件具有默认属性
  • move(Path source, Path target, CopyOption... options);移动或重命名文件或目录
  • readAllLines(Path path, Charset cs);读取文件的所有行到一个字符串列表
  • write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options);将字符串列表写入文件
    • OpenOption是一个用于配置文件操作的接口,它提供了在使用 Files.newByteChannel() 、 Files.newInputStream() 、 Files.newOutputStream() 、 AsynchronousFileChannel.open() 和 FileChannel.open() 方法时定制行为的选项。
    • StandardOpenOption实现了OpenOption接口的枚举类:
      • read、write、append、truncate_existing、create、create_new、delete_on_close、sparse、sync、dsync
  • newBufferedReader(Path path, Charset cs) 和 newBufferedWriter(Path path, Charset cs, OpenOption... options);创建BufferedrReader和BufferedWrite
  • Files.walkFileTree();递归访问目录结构中的所有文件和目录,并允许对这些文件和目录执行自定义操作
@Test
public void test4() throws IOException {Path parentPathDir = Paths.get(System.getProperty("user.dir")).resolve("src/main/java/org/example/code_case");Files.walkFileTree(parentPathDir, new SimpleFileVisitor<Path>() {//在访问目录之前调用的@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println("访问目录之前:" + dir);return super.preVisitDirectory(dir, attrs);}//在访问目录之后调用的@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {System.out.println("访问目录之后:" + dir);return super.postVisitDirectory(dir, exc);}//在访问文件时调用的@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println("访问文件:" + file);return super.visitFile(file, attrs);}//在访问文件时发生异常时调用的@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {System.out.println("访问文件时发生异常:" + file);return super.visitFileFailed(file, exc);}});
}
    • 其中FileVisitResult枚举包括:continue、terminate、skip_siblings(跳过兄弟节点,然后继续)、skip_subtree(跳过子树,然后继续)仅在preVisitDirectory方法返回时才有意义
@Test
public void test5() throws IOException {//文件搜索Path parentPathDir = Paths.get(System.getProperty("user.dir")).resolve("src/main/java/org/example/code_case");class MyFileVisitor extends SimpleFileVisitor<Path> {public boolean exsits = false;final String fileName = "README.txt";@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {String currentFileName = file.getFileName().toString();if (fileName.equalsIgnoreCase(currentFileName)) {exsits = true;return FileVisitResult.TERMINATE;}return super.visitFile(file, attrs);}};MyFileVisitor myFileVisitor = new MyFileVisitor();Files.walkFileTree(parentPathDir, myFileVisitor);if(myFileVisitor.exsits){System.out.println("文件存在");}else {System.out.println("文件不存在");}}
http://www.lryc.cn/news/614276.html

相关文章:

  • flutter TLS protocol versions: (TLSv1.2, TLSv1.3)
  • 【数据结构】排序(sort) -- 计数排序
  • 在 Elasticsearch/Kibana (ELK Stack) 中搜索包含竖线 (|)​​ 这类特殊字符的日志消息 (msg 字段) ​确实需要转义
  • 软件包管理、缓存、自定义 YUM 源
  • Vulnhub drippingblues 靶场复现 详细攻略
  • 强光干扰下误报率↓82%!陌讯多模态融合算法在高空抛物检测的实战优化
  • 自适应反步控制:理论与设计
  • 分布式微服务--GateWay的断言以及如何自定义一个断言
  • MySQL 配置性能优化赛:核心策略与实战技巧
  • 分布式系统性能优化实战:从瓶颈定位到架构升级
  • 前端后端之争?JavaScript和Java的特性与应用场景解析
  • Microsoft Dynamics AX 性能优化解决方案
  • 用JOIN替代子查询的查询性能优化
  • 深入解析基于Zookeeper分布式锁在高并发场景下的性能优化实践指南
  • DataFun联合开源AllData社区和开源Gravitino社区将在8月9日相聚数据治理峰会论坛
  • AI漫画翻译器-上传图片自动翻译,支持多语言
  • 分享超图提供的、很不错的WebGIS学习资源
  • 从安卓兼容性困境到腾讯Bugly的救赎:全链路崩溃监控解决方案-卓伊凡|bigniu
  • 什么是局放?局放在线智能传感器,敏锐洞察电气设备中的隐形故障!
  • bytearray和bytes
  • 进程管理、系统高负载、cpu超过800%等实战问题处理
  • 【Mybatis入门】配置Mybatis(IDEA)
  • scratch笔记和练习-第11课:穿越峡谷
  • [Linux]学习笔记系列 -- [arm[kernel]
  • Godot ------ 中级人物血条制作02
  • ABP VNext + Fody AOP:编译期织入与性能监控
  • 当服务器多了时,如何管理?
  • 服务器快照与备份的本质区别及正确使用指南 (2025)
  • Linux 内核发包流程与路由控制实战
  • VMwareWorkstation17Pro安装CentOS8无法连接外网问题