- 浏览: 467102 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (371)
- Java (44)
- Flex (25)
- Design Pattern / UML (5)
- JVM (14)
- Software Engineer (4)
- Testing (30)
- Web Backend (60)
- Linux (7)
- Database (11)
- SOA (28)
- English (6)
- FX (5)
- SAP Support (25)
- SAP Development (5)
- SAP Impl & Oprn (8)
- Articles (15)
- Music (4)
- Recipe (14)
- Multi-thread (4)
- Lucene (2)
- CS basic / Algorithm (12)
- Tour (3)
- C C++ (21)
- Others (10)
- Code Repository C++ (1)
- Code Repository Java (1)
- Code Repository Algorithm (1)
- Code Repository SQL (1)
- Web Frontend (4)
最新评论
-
xiangxm:
Java 强引用、 软引用、 弱引用、虚引用 -
nml-lyb:
123
Mule 的第一个例子 -
nml-lyb:
http://www.baidu.com
Mule 的第一个例子 -
white___ant:
...
Java 强引用、 软引用、 弱引用、虚引用 -
joeyon:
...
Java NIO介绍
前言:
可悲的是我参考的这些技术资料是在02~04年的,那时侯还在上学….
服务器在合理的时间之内处理大量客户机请求的能力取决于服务器使用 I/O
流的效率。同时为成百上千个客户机提供服务的服务器必须能够并发地使用 I/O 服务。Java 平台直到 JDK 1.4(也就是
Merlin)才支持非阻塞 I/O 调用。用 Java
语言写的服务器,由于其线程与客户机之比几乎是一比一,因而易于受到大量线程开销的影响,其结果是既导致了性能问题又缺乏可伸缩性。
为了解决这个问题,Java 平台的最新发行版引入了一组新的类。Merlin 的 java.nio
包充满了解决线程开销问题的技巧,包中最重要的是新的 SelectableChannel 类和 Selector 类。
通道(channel)是客户机和服务器之间的一种通信方式。 选择器(selector)与 Windows
消息循环类似,它从不同客户机捕获各种事件并将它们分派到相应的事件处理程序。在本文,我们将向您展示这两个类如何协同工作,从而为 Java
平台创建非阻塞 I/O 机制。
一:传统的IO
在介绍NIO之前,有必要了解传统的I/O操作的方式。以网络应用为例,传统方式需要监听一个ServerSocket,接受请求的连接为其提供服务(服务通常包括了处理请求并发送响应)
可以分析创建服务器的每个具体步骤。首先创建ServerSocket
ServerSocket server=new ServerSocket(10000);
然后接受新的连接请求
Socket newConnection=server.accept();
对于accept方法的调用将造成阻塞,直到ServerSocket接受到一个连接请求为止。一旦连接请求被接受,服务器可以读客户socket中的请求。
InputStream in = newConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader buffer = new BufferedReader(reader);
Request request = new Request();
while(!request.isComplete()) {
String line = buffer.readLine();
request.addLine(line);
}
这样的操作有两个问题,首先BufferedReader类的readLine()方法在其缓冲区未满时会造成线程阻塞,只有一定数据填满了缓
冲区或者客户关闭了套接字,方法才会返回。其次,它会产生大量的垃圾,BufferedReader创建了缓冲区来从客户套接字读入数据,但是同样创建了
一些字符串存储这些数据。虽然BufferedReader内部提供了StringBuffer处理这一问题,但是所有的String很快变成了垃圾需要
回收。
同样的问题在发送响应代码中也存在
Response response = request.generateResponse();
OutputStream out = newConnection.getOutputStream();
InputStream in = response.getInputStream();
int ch;
while(-1 != (ch = in.read())) {
out.write(ch);
}
newConnection.close();
类似的,读写操作被阻塞而且向流中一次写入一个字符会造成效率低下,所以应该使用缓冲区,但是一旦使用缓冲,流又会产生更多的垃圾。传统的解决
方法通常在Java中处理阻塞I/O要用到线程(大量的线程)。一般是实现一个线程池用来处理请求,线程使得服务器可以处理多个连接,但是它们也同样引发
了许多问题。每个线程拥有自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费在阻塞的I/O操作上,没有有效的利用CPU。
问题产生原因:
1。建立连接可能造成阻塞
2。IO处理造成阻塞或者占用大量的cpu和内存
二:改进的IO
1. Buffer
传统的I/O不断的浪费对象资源(通常是String)。新I/O通过使用Buffer读写数据避免了资源浪费。Buffer对象是线性的,有序的数据集合,它根据其类别只包含唯一的数据类型。
java.nio.Buffer 类描述
java.nio.ByteBuffer 包含字节类型。 可以从ReadableByteChannel中读在 WritableByteChannel
中写
java.nio.MappedByteBuffer 包含字节类型,直接在内存某一区域映射
java.nio.CharBuffer 包含字符类型,不能写入通道
java.nio.DoubleBuffer 包含double类型,不能写入通道
java.nio.FloatBuffer 包含float类型
java.nio.IntBuffer 包含int类型
java.nio.LongBuffer 包含long类型
java.nio.ShortBuffer 包含short类型
可以通过调用allocate(int capacity)方法或者allocateDirect(int
capacity)方法分配一个Buffer。特别的,你可以创建MappedBytesBuffer通过调用FileChannel.map(int
mode,long position,int
size)。直接(direct)buffer在内存中分配一段连续的块并使用本地访问方法读写数据。非直接(nondirect)buffer通过使用
Java中的数组访问代码读写数据。有时候必须使用非直接缓冲例如使用任何的wrap方法(如ByteBuffer.wrap(byte[]))在
Java数组基础上创建buffer。
2. 字符编码
向ByteBuffer中存放数据涉及到两个问题:字节的顺序和字符转换。ByteBuffer内部通过ByteOrder类处理了字节顺序问题,但是并没有处理字符转换。事实上,ByteBuffer没有提供方法读写String。
Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。
3. 通道(Channel)
你可能注意到现有的java.io类中没有一个能够读写Buffer类型,所以NIO中提供了Channel类来读写Buffer。通道可以认为是一种连接,可以是到特定设备,程序或者是网络的连接。
ReadableByteChannel和WritableByteChannel分别用于读写。
GatheringByteChannel可以从使用一次将多个Buffer中的数据写入通道,相反的,ScatteringByteChannel则可以一次将数据从通道读入多个Buffer中。你还可以设置通道使其为阻塞或非阻塞I/O操作服务。
为了使通道能够同传统I/O类相容,Channel类提供了静态方法创建Stream或Reader
4. Selector
在过去的阻塞I/O中,我们一般知道什么时候可以向stream中读或写,因为方法调用直到stream准备好时返回。但是使用非阻塞通道,我
们需要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector就是为了这个目的。SelectableChannel可以注册特定的事
件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的任意一个selection方法时,它查看注册了的通道看是否有任
何感兴趣的事件发生。
并不是所有的通道都支持所有的操作。SelectionKey类定义了所有可能的操作位,将要用两次。首先,当应用调用
SelectableChannel.register(Selector sel,int
op)方法注册通道时,它将所需操作作为第二个参数传递到方法中。然后,一旦SelectionKey被选中了,SelectionKey的
readyOps()方法返回所有通道支持操作的数位的和。SelectableChannel的validOps方法返回每个通道允许的操作。注册通道
不支持的操作将引发IllegalArgumentException异常。下表列出了SelectableChannel子类所支持的操作。
ServerSocketChannel OP_ACCEPT
SocketChannel OP_CONNECT, OP_READ, OP_WRITE
DatagramChannel OP_READ, OP_WRITE
Pipe.SourceChannel OP_READ
Pipe.SinkChannel OP_WRITE
三 列一个通讯的例子:简单却实用
服务器:
package
com.sz.nio;
import
java.nio.ByteBuffer;
import
java.nio.IntBuffer;
import
java.nio.channels.SelectionKey;
import
java.nio.channels.Selector;
import
java.nio.channels.ServerSocketChannel;
import
java.nio.channels.SocketChannel;
import
java.nio.channels.spi.SelectorProvider;
import
java.net.InetSocketAddress;
import
java.util.Iterator;
import
java.util.Set;
import
java.io.IOException;
/**
*
SumServer.java
*
Created:
Fri
April
20
13:41:52
2007
*
@author
pineapple
*
@version
1.0
*/
public
class
Server {
private
ByteBuffer
buffer
= ByteBuffer.allocate
(8);
//
新缓冲区的容量,以字节为单位
private
IntBuffer
intBuffer
=
buffer
.asIntBuffer();
private
SocketChannel
socketChannel
=
null
;
private
ServerSocketChannel
serverChannel
=
null
;
/**
*
开启服务
*/
public
void
start() {
try
{
openChannel();
waitForConnection();
}
catch
(IOException e) {
System.
err
.println(e.toString());
}
}
/**
*
打开连接
*
*
@throws
IOException
*/
private
void
openChannel()
throws
IOException {
serverChannel
= ServerSocketChannel.open
();
//
创建Socket连接
serverChannel
.socket().bind(
new
InetSocketAddress(10000));
//
开启端口
serverChannel
.configureBlocking(
false
);
//
设置成为非阻塞模式
System.
out
.println(
"
服务器通道已经打开"
);
}
/**
*
连接接入,处理
*
*
@throws
IOException
*/
private
void
waitForConnection()
throws
IOException {
//
在服务器套接字上注册selector并设置为接受accept方法的通知。
//
这就告诉Selector,套接字想要在accept操作发生时被放在ready表 上,因此,允许多元非阻塞I/O发生。
//Selector selector = Selector.open(); //
这是最简单的创建方法
Selector acceptSelector = SelectorProvider.provider
().openSelector();
//
每个要为客户机请求提供服务的 Channel 都必须接着将自己向Selector 注册。
//Channel
应根据它将处理的事件进行注册。例如,接受传入连接的 Channel应这样注册
//Channel
向 Selector 的注册用 SelectionKey 对象表示。满足以下三个条件之一, Key 就失效:Channel
//
被关闭。Selector 被关闭。通过调用 Key 的 cancel() 方法将 Key 本身取消。
//
如果关键字是可接受(acceptable)的,则接受连接,注册通道,以接受更多的事件(例如:读或写操作)。
//
如果关键字是可读的(readable)或可写的(writable),则服务器会指示它已经就绪于读写本端数据
SelectionKey acceptKey =
serverChannel
.register(acceptSelector,SelectionKey.
OP_ACCEPT
);
int
keysAdded = 0;
//Server
是那个将自己向 Selector 注册以接受所有传入连接的 ServerSocketChannel Server
//
被注册后,我们根据每个关键字(key)的类型以迭代方式对一组关键字进行处理。一个关键字被处理完成后,
//
就都被从就绪关键字(readykeys)列表中除去,如下所示
//select
方法在任何上面注册了的操作发生时返回
//Selector
在 select()调用时阻塞。接着,它开始等待,直到建立了一个新的连接,或者另一个线程将它唤醒,
//
或者另一个线程将原来的阻塞线程中断。某客户已经准备好可以进行I/O操作了,获取其ready键集合
while
((keysAdded = acceptSelector.select()) > 0) {
Set readyKeys = acceptSelector.selectedKeys();
Iterator i = readyKeys.iterator();
//
遍历ready键集合,并处理加法请求
while
(i.hasNext()) {
SelectionKey selectionkey = (SelectionKey) i.next();
i.remove();
ServerSocketChannel nextReady = (ServerSocketChannel) selectionkey.channel();
//
接受加法请求并处理它
// socketChannel = nextReady.accept().socket();
socketChannel
= nextReady.accept();
processRequest();
socketChannel
.close();
}
}
}
private
void
processRequest()
throws
IOException {
buffer
.clear();
socketChannel
.read(
buffer
);
int
result =
intBuffer
.get(0) +
intBuffer
.get(1);
buffer
.flip();
buffer
.clear();
intBuffer
.put(0, result);
socketChannel
.write(
buffer
);
}
public
static
void
main(String[] args) {
new
Server().start();
}
}
客户端:
package
com.sz.nio;
import
java.nio.ByteBuffer;
import
java.nio.IntBuffer;
import
java.nio.channels.SocketChannel;
import
java.net.InetSocketAddress;
import
java.io.IOException;
/**
*
SumClient.java
*
*
*
Created:
Thu
Nov
06
11:26:06
2003
*
*
@author
starchu1981
*
@version
1.0
*/
public
class
Client {
private
ByteBuffer
buffer
= ByteBuffer.allocate
(8);
private
IntBuffer
intBuffer
;
private
SocketChannel
channel
;
/**
*
客户端构造函数
*
*/
public
Client() {
intBuffer
=
buffer
.asIntBuffer();
}
// SumClient constructor
/**
*
连接服务器
发送客户请求
收到服务器响应
*
*
@param
first
*
被计算的数字一
*
@param
second
*
被计算的数字二
*
@return
计算结果
*/
public
int
getSum(
int
first,
int
second) {
int
result = 0;
try
{
channel
= connect();
sendSumRequest(first, second);
result = receiveResponse();
}
catch
(IOException e) {
System.
err
.println(e.toString());
}
finally
{
if
(
channel
!=
null
) {
try
{
channel
.close();
}
catch
(IOException e) {
}
}
}
return
result;
}
/**
*
创建连接
*
*
@return
连接
*
@throws
IOException
*/
private
SocketChannel connect()
throws
IOException {
InetSocketAddress socketAddress =
new
InetSocketAddress(
"localhost"
,
10000);
return
SocketChannel.open
(socketAddress);
}
/**
*
发送请求
*
*
@param
first
*
@param
second
*
@throws
IOException
*/
private
void
sendSumRequest(
int
first,
int
second)
throws
IOException {
buffer
.clear();
intBuffer
.put(0, first);
intBuffer
.put(1, second);
channel
.write(
buffer
);
System.
out
.println(
"
发送加法请求 "
+ first +
"+"
+ second);
}
/**
*
收到响应
*
*
@return
*
@throws
IOException
*/
private
int
receiveResponse()
throws
IOException {
buffer
.clear();
channel
.read(
buffer
);
return
intBuffer
.get(0);
}
public
static
void
main(String[] args) {
Client sumClient =
new
Client();
System.
out
.println(
"
加法结果为 :"
+ sumClient.getSum(100, 324));
}
}
// SumClient
http://blog.csdn.net/PineApple0/archive/2007/04/20/1572394.aspx
发表评论
-
java8 Stream Lazy 解释 (非实现原理)
2015-07-22 07:49 1582http://blog.csdn.net/dm_vincen ... -
java8 系列2 深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)
2015-07-22 07:00 2481http://zh.lucida.me/blog/java ... -
java8 系列1 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
2015-07-22 06:58 1067http://zh.lucida.me/blog/java- ... -
Java 8 Stream探秘 具体实现
2015-07-22 06:31 896http://colobu.com/2014/11/18/J ... -
Arrays.asList() 的了解
2015-07-21 23:05 1161为什么Arrays.asList() 返回的list是 i ... -
java8 Lambda expressions
2015-07-14 13:09 872The single most important chan ... -
java8 new features
2015-07-08 02:06 697http://www.javacodegeeks.com/2 ... -
ReentrantLock与synchronized
2014-09-02 23:34 547http://uule.iteye.com/blog/148 ... -
PriorityQueue的使用
2014-05-29 12:14 957http://blog.csdn.net/yangzhon ... -
java的移位操作详解,左移和右移 << >>, 补码计算
2014-04-17 10:28 804---------------------补码--- ... -
深入Java集合学习系列:ArrayList的实现原理
2014-02-13 09:04 564http://zhangshixi.iteye.com/bl ... -
java集合类 ------------ 实现 细节
2014-02-12 23:12 530深入Java集合学习系列:ArrayList的 ... -
两种简单的方式快速实现hashCode 和 equals方法
2014-02-04 10:46 644http://hi.baidu.com/coolcat_p ... -
集合类 Set
2013-12-30 03:50 501http://www.cnblogs.com/yuchao ... -
java1.5新特性
2013-10-23 10:59 6671.自动包装和解包(Autoboxing and unboxi ... -
正则表达式30分钟入门教程
2013-10-22 00:21 539http://blog.csdn.net/cumt168/a ... -
总结Java方法(函数)传值和传引用的问题
2013-09-16 08:38 612http://java.chinaitlab.com/bas ... -
有状态,无状态对象是什么概念
2013-06-21 06:00 1115http://blog.csdn.net/showwair/ ... -
Dom4j的使用(全而好的文章)
2013-06-09 21:12 538http://xhy0422.iteye.com/blog/ ... -
serialVersionUID 用来表明类的不同版本间的兼容性
2013-06-03 23:12 813http://blog.csdn.net/hulefei29 ...
相关推荐
基于纯verilogFPGA的双线性差值视频缩放 功能:利用双线性差值算法,pc端HDMI输入视频缩小或放大,然后再通过HDMI输出显示,可以任意缩放。 缩放模块仅含有ddr ip,手写了 ram,fifo 代码,可以较为轻松地移植到其他平台。 硬件平台:易灵思 ti60f225 EDA平台:efinity
zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。
基于51单片机的一个智能密码锁设计.7z
《基于STM32的舵机控制系统设计》毕业设计项目 1.STM32单片机+2x180_SG90+2x360_SG90+OLED屏幕 2.OLED屏幕显示舵机的方向、速度、角度各项数据 3.按键1:控制180度舵机正向转动角度 4.按键2:控制180度舵机反向转动角度 5.按键3:控制360度舵机正向转动并且控制舵机速度 6.按键4:控制360度舵机反向转动并且控制舵机速度 7.代码里面含有注释 8.硬件实物接上线就能直接运行
Pyside6+Qml+QtCreator做的桌面app
【资源说明】 基于深度堆叠卷积神经网络的图像融合详细文档+全部资料+优秀项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
【资源说明】 基于卷积神经网络的语音识别声学模型的研究详细文档+全部资料+优秀项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
IndexOutOfBoundsException(解决方案)
Mini-Imagenet数据集文件
时间转换工具(BD时、GPS时、UTC时、北京时间相互转换,可计算日期)
【资源说明】 基于CNN神经网络实现的正方教务系统的验证码识别资料齐全+高分项目+文档+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
西北工业大学 人工智能程序设计 Python NOJ程序源代码.zip
车辆、飞机、船检测12-YOLO(v5至v11)、COCO、CreateML、TFRecord、VOC数据集合集.rar对象检测分配-V2 2024-03-10 7:04 pm ============================= *与您的团队在计算机视觉项目上合作 *收集和组织图像 *了解和搜索非结构化图像数据 *注释,创建数据集 *导出,训练和部署计算机视觉模型 *使用主动学习随着时间的推移改善数据集 对于最先进的计算机视觉培训笔记本,您可以与此数据集一起使用 该数据集包括2992张图像。 以可可格式注释船舶飞机。 将以下预处理应用于每个图像: *像素数据的自动取向(带有Exif-Arientation剥离) *调整大小为640x640(拉伸) 没有应用图像增强技术。
SmartAdmin以「高质量代码」为核心,「简洁、高效、安全」的快速开发平台;基于SpringBoot2/3+Sa-Token+Mybatis-Plus和Vue3 +Ant Design Vue+UniApp (提供JavaScript和TypeScript双版本、Java8和java17双版本);满足三级等保、网络安全、数据安全等功能要求。并重磅开源千余家企业在使用的《高质量代码规范》等
欢迎下载
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;
【资源说明】 基于常规波束形成的时间窗方法以及基于卷积神经网络的时间窗方法水下目标方位估计算法详细文档+全部资料+优秀项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
预览地址:https://blog.csdn.net/qq_42431718/article/details/144633992 html+css网页设计 美食 蛋糕美食7个页面
Python高分毕设——Python&Opencv手势识别系统(完整源码&自定义UI操作界面&视频教程) Python高分毕设——Python&Opencv手势识别系统(完整源码&自定义UI操作界面&视频教程) 使用了OpenCV的视频采集, 图像色域转换, 颜色通道分割, 高斯滤波, OSTU自动阈值, 凸点检测, 边缘检测, 余弦定理计算手势等功能. 准备工作 安装 Python-OpenCV 库 pip install opencv-python -i https://mirrors.ustc.edu.cn/pypi/web/simple 利用 -i 为pip指令镜像源, 这里使用电子科技大学的源, 速度比官方源更快. 安装 Numpy 科学计算库 pip install numpy -i https://mirrors.ustc.edu.cn/pypi/web/simple 安装 PyAutogui 库 pip install pyautogui -i https://mirrors.ustc.edu.cn/pypi/web/simple 代码实现 import nu
Friday night funkin