最新要闻
- 【世界播资讯】重庆龙凤胎熊猫宝宝取名“渝可”“渝爱”
- 速看:《小美人鱼》剧照还原动画经典场景:就是肤色不太对
- 头条焦点:中国制造!索尼第四代降噪豆XM5佩戴/音质/快充全面升级:AirPods Pro2劲敌
- 老人墓中藏13万现金全部霉变 专业能手全力抢救:官方科普纸钞如何保管
- 海底捞部分门店取消免费美甲:只能付费购买穿戴甲
- 今热点:五菱电动“吉姆尼” 宝骏悦也续航里程公布:能跑303公里
- 董明珠要玩大的?格力成立房地产新公司 投资数亿元
- 东方甄选称被骗了 虾品供应商发声:不认可甩锅行为
- 热点在线丨百度文心一言申请页被挤爆:预约1小时涌入3万企业
- 环球快讯:现实版GTA!美劫匪偷直升机因操作失误当场坠毁:一地碎片
- 国宝大熊猫的视频_国宝大熊猫的故事
- 每日速看!铁匠用30年给猫和老鼠做铠甲:一套比一套帅!
- 戴尔推出了灵越14 ARM版:骁龙8cx Gen 2、16小时长续航
- 世界播报:卡布达归来!《铁甲小宝》高清修复版将至:已开通B站账号
- 两败俱伤:比亚迪海豹街头铲翻宝马X5
- 【环球时快讯】小伙爆改特斯拉Model 3:加装120寸轮毂、增加“逆天”功能
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
Java中的NIO
最近在研究Java中的IO,感觉这一块很重要,只要涉及到数据传输,不论是本地文件传输,还是远程的网络传输,基本离不开IO。
上一篇文章已经大致的总结了一下Java1.1的传统IO(Java中的IO),看了很多网上的文章,发现知识点很多,自己总结思考过的东西才更容易被消化,所以记录一下NIO相关知识。
【资料图】
1、引言
说到NIO就离不开Channel、Buffer、Selector三个定义,这也是区别于传统IO的地方。简单来说,传统的IO面向的是流处理,NIO面向的是缓冲区(块)处理,也就是下面即将讲到的Buffer。
2、Channel
2.1、Channel介绍
Channel是一个对象,作用于源节点和目标节点的连接,是数据传输的通道,一般和Buffer搭配使用。简单点说,Channel就是铁轨,Buffer就是跑在铁轨上的火车,货物(数据)放在火车上,与铁轨没有关系。
Channel的常用实现类有如下几种,用于文件IO的FileChannel和网络IO的SelectableChannel,其子类有DatagramChannel、SocketChannel、ServerSocketChannel等等。
2.2、获取Channel
简单举例一些常用场景,获取Channel的方式,主要分为文件IO和网络IO。
- 文件IO
1、从流中获取:
FileInputStream inputStream = new FileInputStream(file);FileChannel readChannel = inputStream.getChannel();
2、FileChannel静态方法open()
FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ);FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ,StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
3、RandomAccessFile
RandomAccessFile file1 = new RandomAccessFile("filePath", "r");FileChannel channel = file1.getChannel();
4、Files的静态方法
SeekableByteChannel channel = Files.newByteChannel(Paths.get("filePath"), StandardOpenOption.READ);
- 网络IO
1、SocketChannel
SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
2、ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(9090));
3、DatagramChannel
DatagramChannel datagramChannel = DatagramChannel.open();datagramChannel.bind(new InetSocketAddress(9090));
2.3、代码示例
复制文件一般就三种方式,常规的Channel和Buffer、MappedByteBuffer直接内存映射、
2.3.1、Channel和Buffer复制文件
private static void copyFileByChannelAndBuffer(File file, File newFile) throws IOException { //从流中获取channel try (FileInputStream inputStream = new FileInputStream(file); FileChannel readChannel = inputStream.getChannel(); FileOutputStream outputStream = new FileOutputStream(newFile); FileChannel writeChannel = outputStream.getChannel()) { //分配capacity大小为2048的Buffer ByteBuffer buffer = ByteBuffer.allocate(2048); //循环从channel中获取数据 while (readChannel.read(buffer) != -1) { //因为buffer中实际存储的数据可能并不是满的,所以将limit置为当前position,再将position设置为0,方便读取从下标position-limit的数据 buffer.flip(); //写入数据到channel writeChannel.write(buffer); //因为read和write方法都会操作position,所以如果还需要操作读写,就得重置为初始状态,position为0,limit为capacity buffer.clear(); } }}
2.3.2、内存映射文件的方式复制文件
有如下三种方式读写数据,目前不清楚那种方式更优,网上有的文章说MappedByteBuffer可以类似于将整个文件读取到内存,实际内部也是按需加载到内存,不会占用很多内存。(这个涉及到堆外内存的)
注:MappedByteBuffer映射的文件大小不能超过2G,超过会报错,如下图:
1、直接将ReadBuffer写入WriteBuffer
private static void copyFileByMappedByteBuffer(File file, File newFile) throws IOException { try (FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ); FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { MappedByteBuffer readBuffer = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size()); MappedByteBuffer writeBuffer = writeChannel.map(FileChannel.MapMode.READ_WRITE, 0, readChannel.size()); readBufferAllToWriteBuffer(readBuffer, writeBuffer); }}private static void readBufferAllToWriteBuffer(MappedByteBuffer readBuffer, MappedByteBuffer writeBuffer) { //将buffer中的数据写入 writeBuffer.put(readBuffer); //保存到磁盘 writeBuffer.force();}
2、创建一个中间缓存的数组,分批从ReadBuffer读取数据写入到WriteBuffer
private static void copyFileByMappedByteBuffer(File file, File newFile) throws IOException { try (FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ); FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { MappedByteBuffer readBuffer = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size()); MappedByteBuffer writeBuffer = writeChannel.map(FileChannel.MapMode.READ_WRITE, 0, readChannel.size()); //分批复制 specificByteSizeToSave(file, readBuffer, writeBuffer); }}private static void specificByteSizeToSave(File file, MappedByteBuffer readBuffer, MappedByteBuffer writeBuffer) { //创建一个中间数组 int size = 2048; byte[] bytes = new byte[size]; long total = file.length(); //循环处理 for (long position = 0; position < total; ) { //计算可放入数组的实际数据长度,最大为数组长度 long length = Math.min(total - position, size); //将position-limit之间的数据放入数组 readBuffer.get(bytes, 0, (int) length); //写入数据 writeBuffer.put(bytes, 0, (int) length); //记录position的大小 position = position + length; }}
3、创建一个和文件大小相等的数组,一次性从ReadBuffer中取出数据放入WriteBuffer中
private static void copyFileByMappedByteBuffer(File file, File newFile) throws IOException { try (FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ); FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { MappedByteBuffer readBuffer = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size()); MappedByteBuffer writeBuffer = writeChannel.map(FileChannel.MapMode.READ_WRITE, 0, readChannel.size()); allByteSizeToSave(file, readBuffer, writeBuffer); }}private static void allByteSizeToSave(File file, MappedByteBuffer readBuffer, MappedByteBuffer writeBuffer) { //循环将数据写入 byte[] bytes = new byte[(int) file.length()]; ByteBuffer buffer = readBuffer.get(bytes); //切换到读模式 buffer.flip(); //将buffer中的数据写入 writeBuffer.put(bytes); //保存到磁盘 writeBuffer.force(); //重置buffer buffer.clear();}
2.3.3、transfer方式复制文件
两者没什么大区别,但是transferTo,对于文件大小有2G限制,对于socketChannel有8M的限制
1、transferTo
private static void copyFileByTransferTo(File file, File newFile) throws IOException { try (FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ); FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { readChannel.transferTo(0, readChannel.size(), writeChannel); }}
2、transferFrom
private static void copyFileByTransferFrom(File file, File newFile) throws IOException { try (FileChannel readChannel = FileChannel.open(Paths.get(file.toURI()), StandardOpenOption.READ); FileChannel writeChannel = FileChannel.open(Paths.get(newFile.toURI()), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { writeChannel.transferFrom(readChannel,0, readChannel.size()); }}
3、Buffer
3.1、Buffer介绍
Buffer顾名思义就是缓冲区,在Java NIO里就是存储数据的缓冲区。和Channel的铁轨进行类比,Buffer就是行驶在铁轨上装货物(数据)的火车。简单的描述就是,铁轨(Channel)连接两地(源节点和目标节点),火车(Buffer)装货物(Data)在铁轨上行驶,将数据在两地间运输。
Buffer的子类有ByteBuffer、CharBuffer、IntBuffer、DoubleBuffer等等。最常用和通用的是ByteBuffer,其下面主要有两个子类,一个是操作Java堆内存的HeapByteBuffer和操作本地内存的DirectByteBuffer,两者的区别在于是否由JVM进行内存管理。
3.2、Buffer的核心参数
Buffer类有四个属性,分别是capacity、position、limit和mark,如下图所示:
- capacity:是初始化Buffer时指定的缓冲区大小,即缓冲区的容量。
- position:表示正在操作(读/写)的数据所在的位置。
- limit:表示可操作的(读/写)的数据大小,limit之后的数据不能进行操作。
- mark:就是一个标识位,mark()就会保存当前position的值,reset()将mark记录的值赋给position。
Buffer的子类都是增加了一个数组来存储数据,不论是byte[]还是char[],所以capacity就是数组长度,position和limit都是数组下标,读写操作只能在position-limit之间进行,mark记录的是当前的position。
所以显然,mark<=position<=limit<=capactity的。
3.3、Buffer的核心方法
比较常用的方法有如下几种:
3.3.1、flip()
public final Buffer flip() { limit = position; position = 0; mark = -1; return this;}
读取Buffer中的数据前,需要调用该方法。因为初始值position为0,limit为capacity,每次写入一个字节,position就会加1,所以写完数据时,position的值就在数据的结尾处,真实的数据就是0-position之间的数据
flip()就是设置正确的position和limit值,方便操作position-limit中间的数据。
3.3.2、clear()
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this;}
往Buffer中写数据之前需要调用该方法,目的是让新的数据从0位置开始写入,这个方法没有清除实际数组中的数据,只是重置参数值,将其初始化。新写入的数据会把旧的数据覆盖掉,调用flip()后,每次取到的都是新写入的数据。
其子类(ByteBuffer、IntBuffer等等)一般都是增加了一个数组保存数据,比如ByteBuffer,主要的存取数据的方法就是get()和put(),get()是从Buffer中取数据,put是向Buffer中存数据。例如Channel的read()和write(),内部逻辑也是get()和put()。
3.4、代码示例
1、申请分配堆内存
//分配capacity大小为2048的BufferByteBuffer buffer = ByteBuffer.allocate(2048);
2、申请分配直接内存
ByteBuffer direct = ByteBuffer.allocateDirect(2048);
Channel和Buffer一般是一起搭配使用,代码示例同上面的Channel部分。
4、Selector
Selector是NIO一个核心的组件,称为选择器,也叫多路复用器。单个线程就可以管理多个NIO-Channel,根据Channel不同的状态(可接受、可读、可写)来进行不同的处理,实现多个网络连接的管理,可以说NIO的no-blocking全靠Selector。
4.1、SelectableChannel
不是所有的Channel都能被Selector管理,只有实现了SelectableChannel的Channel才可以(FileChannel就不可以,所以NIO的文件IO其实都是阻塞进行的),SelectableChannel的子类如下:
TCP协议:ServerSocketChannel、SocketChannel;
UDP协议:DatagramChannel;
SCTP协议:SctpChannel、SctpMultiChannel;
NIO Pipe下的SinkChannel和SourceChannel
4.2 SelectionKey
SelectionKey也叫选择键,一种表示SelectableChannel在Selector中注册的令牌。每次SelectableChannel向Selector注册时,就会创建一个SelectionKey。
4.2.1、判断就绪的状态
1、判断可读
isReadable()
2、判断是否可写
isWritable()
3、判断Socket是否可连接
isConnectable()
4、判断Socket是否可接受
isAcceptable()
4.2.2、获取SelectableChannel
channel()返回一个SelectableChannel,可以根据实际强转为需要的Channel类型。
SocketChannel client = (SocketChannel) selectionKey.channel();
4.3、Selector关键参数/方法
有三个存储SelectionKey的Set集合,keys:表示所有注册到这个Selector上的Channel的SelectionKey集合;
selectedKeys:表示IO操作准备就绪的Channel的SelectionKey集合,通过selectedKeys()获取;
cancelledKeys:表示被取消注册关系的Channle的SelectionKey集合;
4.3.1、获取Selector
通过静态方法获取Selector
Selector selector = Selector.open();
4.3.2、Channel注册
Channel的register()方法,通道注册到Selector,并指定“兴趣集”,Selector循环遍历Channel,检查其状态,当状态属于指定的“兴趣集”时,就返回SelectionKey,就是在Channel中选择满足条件的,所以叫选择器,区别于主动上报的“事件”。
socketChannel.register(selector, SelectionKey.OP_READ);
这个“兴趣集”包含四种状态,分别是:
//读操作public static final int OP_READ = 1 << 0;//写操作public static final int OP_WRITE = 1 << 2;//Socket连接操作public static final int OP_CONNECT = 1 << 3;//Socket接受操作public static final int OP_ACCEPT = 1 << 4;
tips:<<表示比特位左移操作,例如00000001,左移两位就是00000100,所以1<<3 = 2^3 = 8,读操作(1)、写操作(4)、连接操作(8)、接受操作(16)
兴趣集表示四种状态的和,例如可以传入9,那就可以拆分成1+8,表示读和连接操作,一般不会直接传入数据,可以采用以下的方式计算:
//采用+的方式socketChannel.register(selector, SelectionKey.OP_READ + SelectionKey.OP_WRITE);//采用或(|)的方式(等同于相加)socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
4.4、代码示例
下面代码演示服务端接受客户端发送的消息,并给客户端回应消息,先启动服务端,然后启动客户端发送消息。
4.4.1、服务端代码
package org.example;import java.io.File;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.nio.charset.StandardCharsets;import java.nio.file.StandardOpenOption;import java.util.Iterator;public class SocketServer { public static void main(String[] args) throws IOException { //打开Socket通道 ServerSocketChannel server = ServerSocketChannel.open(); //设置为非阻塞 server.configureBlocking(false); //绑定连接 server.bind(new InetSocketAddress(8888)); //获取选择器 Selector selector = Selector.open(); //将通道注册到选择器,并监听指定的通道事件 server.register(selector, SelectionKey.OP_ACCEPT); //循环判断是否存在已经做好IO准备的SelectionKey集合 while (selector.select() > 0) { //遍历准备好的SelectionKey集合 Iterator iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); //根据不同的操作执行不同的逻辑 if (selectionKey.isAcceptable()) { //处于可接受状态的Channel要注册到Selector,在其变成其他状态后再处理 SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { //获取本地文件的通道 File file = new File("D:\\serviceFile\\accept.txt"); //获取客户端可读的通道 try (SocketChannel client = (SocketChannel) selectionKey.channel(); FileChannel writeChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { ByteBuffer buffer = ByteBuffer.allocate(2048); //向本地文件写入数据 while (client.read(buffer) > 0) { buffer.flip(); writeChannel.write(buffer); buffer.clear(); } //服务端向客户端返回消息 String returnMsg = "已经收到文件!"; byte[] bytes = returnMsg.getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); byteBuffer.flip(); client.write(byteBuffer); System.out.println(returnMsg); } } //移除已经处理过的Key iterator.remove(); } } server.close(); }}
4.4.2、客户端代码
package org.example;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.nio.charset.StandardCharsets;import java.nio.file.Paths;import java.util.Set;public class SocketClient { public static void main(String[] args) throws IOException { //打开目标地址和端口的Channel try (SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); //本地文件的Channel FileChannel channel = FileChannel.open(Paths.get("D:\\clientFiles\\thisTest.txt"))) { //设置成非阻塞 socketChannel.configureBlocking(false); //获取选择器 Selector selector = Selector.open(); //当前Channel注册到Selector socketChannel.register(selector, SelectionKey.OP_READ); //创建一个1k的Buffer ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //向SocketChannel写入数据 while (channel.read(byteBuffer) != -1) { byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } //选择已经就绪的Channel while (selector.select() > 0) { //取出对应的SelectionKey集合 Set selectionKeys = selector.selectedKeys(); //循环处理集合 for (SelectionKey selectionKey : selectionKeys) { //判断状态是否可读 if (selectionKey.isReadable()) { //从SelectionKey获取SocketChannel try (SocketChannel client = (SocketChannel) selectionKey.channel()) { //复用上面的Buffer while (client.read(byteBuffer) > 0) { //切换到读模式 byteBuffer.flip(); byte[] bytes = new byte[byteBuffer.limit()]; byteBuffer.get(bytes); System.out.println(new String(bytes, StandardCharsets.UTF_8)); byteBuffer.clear(); } } } } break; } } }}
4.4.3、问题记录
问题:数据传输完成后,如果SocketClient主动断开连接,SocketServer代码中的Selector会一直重复出现可读事件。
解决方法:服务端需要主动关闭获取的客户端Channel,通过try resource的方式自动关闭或者调用close()显示关闭,代码如下:
SocketChannel client = (SocketChannel) selectionKey.channel();client.close();
关键词:
每日看点!Prometheus 监控系统1
Java中的NIO
环球快报:带你全方面了解字节 A/B 实验的文化与工具
世界新资讯:【金融街发布】交易商协会发布企业资产证券化基础性制度
【世界播资讯】重庆龙凤胎熊猫宝宝取名“渝可”“渝爱”
速看:《小美人鱼》剧照还原动画经典场景:就是肤色不太对
头条焦点:中国制造!索尼第四代降噪豆XM5佩戴/音质/快充全面升级:AirPods Pro2劲敌
老人墓中藏13万现金全部霉变 专业能手全力抢救:官方科普纸钞如何保管
海底捞部分门店取消免费美甲:只能付费购买穿戴甲
视讯!SQLMap 源码阅读
今日观点!abc285G
环球精选!JVM -Xss
【高端访谈·城市力量】“双碳”背景下农商银行如何“点绿”成金?——访秦农银行党委书记、董事长李彬
今热点:五菱电动“吉姆尼” 宝骏悦也续航里程公布:能跑303公里
董明珠要玩大的?格力成立房地产新公司 投资数亿元
东方甄选称被骗了 虾品供应商发声:不认可甩锅行为
热点在线丨百度文心一言申请页被挤爆:预约1小时涌入3万企业
环球快讯:现实版GTA!美劫匪偷直升机因操作失误当场坠毁:一地碎片
国宝大熊猫的视频_国宝大熊猫的故事
66.类型转换
快讯:【0基础学爬虫】爬虫基础之抓包工具的使用
天天热头条丨制作学生信息管理系统
文心一言正式对标GPT-4,是青铜还是王者?
全球播报:恒生指数16日收跌1.72% 互联网科技股集体走低
每日速看!铁匠用30年给猫和老鼠做铠甲:一套比一套帅!
戴尔推出了灵越14 ARM版:骁龙8cx Gen 2、16小时长续航
世界播报:卡布达归来!《铁甲小宝》高清修复版将至:已开通B站账号
两败俱伤:比亚迪海豹街头铲翻宝马X5
块级元素和行内元素
天天实时:SublimeREPL设置详解——实现代码传递(Eval in REPL)
天天观速讯丨JavaScript 实现异步任务循环顺序执行
自定义 Spring 通用日志注解
精彩看点:日债市场继续消化欧美银行风险扰动 收益率曲线陡峭化下移
即时焦点:【财经分析】债市利空反应钝化 后市操作存分歧
每日快报!【新华500】新华500指数(989001)16日跌1.29%
【环球时快讯】小伙爆改特斯拉Model 3:加装120寸轮毂、增加“逆天”功能
【新要闻】百度文心一言问答测试为视频演示 李彦宏:为保证效果提前录制
全球实时:深蓝SL03车主联合声明:长安雇水军打压车主 要求车辆终身质保加补偿
国内首款天玑9000旗舰平板!OPPO Pad 2官宣:下周发布
【世界时快讯】曾投资一亿美元:马斯克痛批OpenAI成为营利组织
冰墩墩钥匙扣(冰墩墩多少钱一只)
最新消息:76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)
今热点:Markdown用法解析
世界微动态丨得物从0到1自研客服IM系统的技术实践之路
天天看热讯:性能优化搞得好,Tomcat少不了。| 博学谷狂野架构师
世界观天下!笔记本水冷改造记录
世界球精选!创维造车:主打座舱养生续命、碰撞测试0分!真是讽刺他妈给讽刺开门
环球即时:反转!国内油价明晚大概率下跌 预计下调0.07元/升
《暗黑4》本周末开测 世界BOSS刷新时间公布
天天关注:巴奴火锅下架富硒土豆!消费者可获赠500元储值卡:领取有条件
天天热推荐:百度发布文心一言AI模型:可实现文字、图片与视频智能生成
喝的人越来越少!星巴克向中国三四线城市进军:30多一杯咖啡县城青年能爱多久
麦芒11什么时候上市的?麦芒11手机参数配置
华为电视怎么投屏?华为电视怎么下载第三方软件?
孙海洋是湖北哪里人?
鲶鱼效应是什么意思?鲶鱼效应的经典案例是什么?
首店经济是什么意思?首店经济是谁提出的?
快报:权志龙演唱会门票多少钱2020_权志龙演唱会门票多少钱
环球讯息:用图技术搞定附近好友、时空交集等 7 个典型社交网络应用
GPT-4测评,大家先别急,图片输入还没来
全球时讯:C#使用ObjectPool提高StringBuilder性能
今头条!RTX 40笔记本新品翻车?别急 先升级NVIDIA新驱动再说
每日精选:官宣!《炉石传说》被移出杭州亚运会项目:职业选手难过
环球快消息!360发布年度手机安全报告:受骗男性占七成 女性三成
天天最资讯丨800V电气架构打造 全新起亚EV9发布:二三排可面对面乘坐
扣上的安全带会自动打开?本田在美国召回近45万辆汽车
【独家焦点】虹科案例|虹科Visokio商业智能平台在疫后帮酒店业打好翻身仗!
环球今热点:python 二分法查找
环球实时:面试问题-密码
Fortran语言在线代码运行编译工具推荐
Scrapy中的response对象的属性及方法,附加mate属性的使用方法
每日短讯:强冷空气来袭!河南三门峡3月下雪:厚厚一层一夜回冬
环球要闻:爱立信CEO:印度是全球推出5G速度最快的国家之一
上映25周年纪念:3D重制版《泰坦尼克号》国内定档
真凉了!暴雪网易闹掰 《炉石传说》或被移出杭州亚运会项目
世界快看:B站投资 网红爆款:理然男士沐浴露29.9元狂促
更改 ESX 或 ESXi 主机的主机名称
每日快讯!作业DNS服务配置
NOI 2008 志愿者招募 题解 (神奇费用流)
NutUI-React 京东移动端组件库 2月份上新!欢迎使用!
传递“坚持”背后的感人力量
环球新资讯:恒生中国发布2022年ESG报告 持续提升绿色金融产品与服务质量
日本央行削减购债引发政策转向猜测 超长端日债收益率显著回升
天天精选!中银香港完成5亿绿色人民币逆回购交易
世界今日讯!预购玩家可抢先游玩:《暗黑破坏神4》已开启Beta测试预载
天天看点:8999元 联想小新Pro27 2023一体机来了:13代i9、锐炫A370M独显
苦情戏直播涉事公司被查处!央视315点名诱骗老人直播间均已被封
比降价还狠!最帅国产猎装车极氪001限时福利:数万元升级包免费选
398元烫发烫完变成3980元 商家:把头发分成10个区域 每个区域398元
Linux进程通信 | 信号
GPT-4:不了不了,这些我还做不到
每日信息:前端设计模式——迭代器模式
Django-4
每日消息!Minio架构简介
环球观焦点:女子住酒店被毒蛇咬伤 酒店拒担全责有啥能证明引热议:律师发声
世界即时:奇葩创维汽车:碰撞试验0分 开创维汽车寿命延长30岁
环球微头条丨70岁赵雅芝踏青 短裤白衫引网友惊叹:真不老女神
热点!或12万起售对刚比亚迪海豚 大众微型电动车ID.2all概念车首发
世界快报:曾引发隐私争议 谷歌眼睛正式停售:退出科技舞台
【时快讯】海报丨人民武警忠诚党