阻塞式和非阻塞式udp传输_NIO非阻塞网络编程三大核心理念

本次开始NIO网络编程,之前已经说过BIO,对于阻塞IO里面的问题一定有了清晰的认识,在JDK1.4版本后,提供了新的JAVA IO操作非阻塞API,用意替换JAVA IO 和JAVA NetWorking相关的API。NIO其实有个名称叫new IO。

c7962571e2b5c31eb52e32beda8d9b6f.png

(一)NIO

  • ① 介绍

java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。

  • ② 三大核心组件

高性能网络编程的基础组件,Buffer缓存区、Channel 通道、Selector 选择器。

(二) Buffer缓存区

  • ① 介绍

缓存区本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。
相比较直接对数组的操作。Buffer API 更加容易操作和管理。

  • ② 使用Buffer进行数据写入与读取,需要进行如下四个步骤

  1. 将数据写入缓冲区。

  2. 调用buffer.flip(),转换为读取模式。

  3. 缓冲区读取数据。

  4. 调用buffer.clear() 或 buffer.compact() 消除缓冲区

  • ③ Buffer工作原理

BUffer三个重要属性,通过完成了数组的封装。

1.capacity 容量:作为一个内存块,Buffer具有一定的固定大小,也称为【容量】。
2.position 位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置。
3.limit 限制:写入模式,限制等于buffer的容量,读取模式下,limit等于写入的数据量。

99a075d5e18bd7a2ae5bc5f87ee092a9.png

  • ④ 源码

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;

public class BufferDemo {
public static void main(String[] args) {
// 构建一个byte字节缓冲区,容量是4
//堆内存
ByteBuffer byteBuffer = ByteBuffer.allocate(4);

//堆外内存
// ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4);
// 默认写入模式,查看三个重要的指标
System.out.println(String.format("初始化:capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
byteBuffer.position(), byteBuffer.limit()));
// 写入2字节的数据
byteBuffer.put((byte) 1);
byteBuffer.put((byte) 2);
byteBuffer.put((byte) 3);
// 再看数据
System.out.println(String.format("写入3字节后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
byteBuffer.position(), byteBuffer.limit()));

// 转换为读取模式(不调用flip方法,也是可以读取数据的,但是position记录读取的位置不对)
System.out.println("#######开始读取");
byteBuffer.flip();
byte a = byteBuffer.get();
System.out.println(a);
byte b = byteBuffer.get();
System.out.println(b);
System.out.println(String.format("读取2字节数据后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
byteBuffer.position(), byteBuffer.limit()));

// 继续写入3字节,此时读模式下,limit=3,position=2.继续写入只能覆盖写入一条数据
// clear()方法清除整个缓冲区。compact()方法仅清除已阅读的数据。转为写入模式
byteBuffer.compact(); // buffer : 1 , 3
byteBuffer.put((byte) 3);
byteBuffer.put((byte) 4);
byteBuffer.put((byte) 5);
System.out.println(String.format("最终的情况,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),
byteBuffer.position(), byteBuffer.limit()));

// rewind() 重置position为0
// mark() 标记position的位置
// reset() 重置position为上次mark()标记的位置

}
}

01b0f80b63dec5037fea6dc7a0b9b9ef.png

  • ⑤ ByteBuffer 内存类型

ByteBuffer 为性能关键型代码提供了直接内存(direct堆外)和非直接内存(heap堆)两种实现,堆外内存获取的方式

ByteBuffer directBytebuffer = ByteBuffer.allocateDirect(noBytes);

好处

  1. 进行网络IO 或者 文件IO时比heapBuffer 少一次拷贝,(file/socket —— OS memory —— jvm heap )GC会移动对象内存,在写file 或 socket的过程中,JVM的实现中,会先把数据复制到堆外,在进行写入。

  2. GC范围之外,降低GC压力,但实现了自动管理。DirectByteBuffer 中 有一个Cleaner 对象(PhantomReference) ,Cleaner被GC前会执行clean 方法,触发DirectByteBuffer 中定义Deallocator

建议

  1. 性能确实可观的时候才去使用,分配给大型,长寿命(网络传输,文件读写场景)

  2. 通过虚拟机参数MaxDirectMemorySize限制大小,防止耗尽整个机器的内存,在JVM之外的内存无法监控。

(三)Channel 通道

  • ① 介绍

Channel的API 涵盖了UDP、TCP网络和文件IO,FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel。

  • ② 和标准IO Stream操作的区别

在一个通道内进行读取和写入stream通常是单向的(input 或 output),可以非堵塞读取和写入通道,通道中读取或写入缓冲区。

ba67bfdc53495d6a03ae16846438c5af.png

8ec04265e828ce8330ff75b9cf579a6d.png

  • ③ SocketChannel

SocketChannel用于建立TCP网络连接,类似java.net.Socket。有两种创建socketChannel形式

1.客户端主动发起和服务器的连接
2.服务器获取的新连接

write写

在尚未写入任何内容时可能就返回了。需要在循环中调用write()

read读

read() 方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节。

  • ④ ServerSocketChannel

ServerSocketChannel 可能监听新建立的TCP连接通道,类似ServerSocket。

ServerSocketChannel.accepta()

如果该通道处于飞度赛模式,那么如何没有挂起的连接,该方法将立即返回null。必须检查返回的SocketChannel是否为null。

  • ⑤ 源码

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {

public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
while (!socketChannel.finishConnect()) {
// 没连接上,则一直等待
Thread.yield();
}
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
// 发送内容
String msg = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
// 读取响应
System.out.println("收到服务端响应:");
ByteBuffer requestBuffer = ByteBuffer.allocate(1024);

while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
if (requestBuffer.position() > 0) break;
}
requestBuffer.flip();
byte[] content = new byte[requestBuffer.limit()];
requestBuffer.get(content);
System.out.println(new String(content));
scanner.close();
socketChannel.close();
}

}

e3c61a0d183ba180204d10f5d8803b2f.png

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;

/**
* 直接基于非阻塞的写法,一个线程处理轮询所有请求
*/
public class NIOServer1 {
/**
* 已经建立连接的集合
*/
private static ArrayList channels = new ArrayList<>();public static void main(String[] args) throws Exception {// 创建网络服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口
System.out.println("启动成功");while (true) {
SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新tcp连接通道// tcp请求 读取/响应if (socketChannel != null) {
System.out.println("收到新连接 : " + socketChannel.getRemoteAddress());
socketChannel.configureBlocking(false); // 默认是阻塞的,一定要设置为非阻塞
channels.add(socketChannel);
} else {// 没有新连接的情况下,就去处理现有连接的数据,处理完的就删除掉
Iterator iterator = channels.iterator();while (iterator.hasNext()) {
SocketChannel ch = iterator.next();try {
ByteBuffer requestBuffer = ByteBuffer.allocate(1024);if (ch.read(requestBuffer) == 0) {// 等于0,代表这个通道没有数据需要处理,那就待会再处理continue;
}while (ch.isOpen() && ch.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;
}if(requestBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理
requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];
requestBuffer.get(content);
System.out.println(new String(content));
System.out.println("收到数据,来自:" + ch.getRemoteAddress());// 响应结果 200
String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";
ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {
ch.write(buffer);
}
iterator.remove();
} catch (IOException e) {
e.printStackTrace();
iterator.remove();
}
}
}
}// 用到了非阻塞的API, 再设计上,和BIO可以有很大的不同// 问题: 轮询通道的方式,低效,浪费CPU
}
}

7c8772865516d219f7cdf6c2a9d006d2.png

(四)Select选择器

  • ① 介绍

Selector 是一个Java NIO 组件,可以检查一个或多个NIO通道,并确定哪些通道已准备好进行读取或写入,实现单个线程可以管理多个通道,从而管理或多个网络连接。

  • ② selector 监听多个 channel的不同事件

  1. Connect 连接(SelectionKey.OP_CONNECT)

  2. Accept 准备就绪(OP_ACCEPT)

  3. Read 读取(OP_READ)

  4. Write 写入(OP_WRITE)

  • ③ selector 选择器

一个线程处理多个通道的核心概念理解:事件驱动机制。
非堵塞的网络通道下,开发者通过Selector注册对于通道感兴趣的事件类型,线程通过监听事件来触发响应的代码执行(最底层hi操作系统的多路复用机制)

  • ④ 源码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 结合Selector实现的非阻塞服务端(放弃对channel的轮询,借助消息通知机制)
*/
public class NIOServerV2 {

public static void main(String[] args) throws Exception {
// 1. 创建网络服务端ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式

// 2. 构建一个Selector选择器,并且将channel注册上去
Selector selector = Selector.open();
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, serverSocketChannel);// 将serverSocketChannel注册到selector
selectionKey.interestOps(SelectionKey.OP_ACCEPT); // 对serverSocketChannel上面的accept事件感兴趣(serverSocketChannel只能支持accept操作)

// 3. 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));

System.out.println("启动成功");

while (true) {
// 不再轮询通道,改用下面轮询事件的方式.select方法有阻塞效果,直到有事件通知才会有返回
selector.select();
// 获取事件
Set selectionKeys = selector.selectedKeys();// 遍历查询结果e
Iterator iter = selectionKeys.iterator();while (iter.hasNext()) {// 被封装的查询结果
SelectionKey key = iter.next();
iter.remove();// 关注 Read 和 Accept两个事件if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.attachment();// 将拿到的客户端连接通道,注册到selector上面
SocketChannel clientSocketChannel = server.accept(); // mainReactor 轮询accept
clientSocketChannel.configureBlocking(false);
clientSocketChannel.register(selector, SelectionKey.OP_READ, clientSocketChannel);
System.out.println("收到新连接 : " + clientSocketChannel.getRemoteAddress());
}if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.attachment();try {
ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;
}if(requestBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理
requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];
requestBuffer.get(content);
System.out.println(new String(content));
System.out.println("收到数据,来自:" + socketChannel.getRemoteAddress());// TODO 业务操作 数据库 接口调用等等// 响应结果 200
String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";
ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
} catch (IOException e) {// e.printStackTrace();
key.cancel(); // 取消事件订阅
}
}
}
selector.selectNow();
}// 问题: 此处一个selector监听所有事件,一个线程处理所有请求事件. 会成为瓶颈! 要有多线程的运用
}
}

a03eb9b75c41b3f22c0b03f5b6d765c6.png

  • ⑤ NIO 和 BIO 的区别

93f82cf5817c324b8a15bf4ffc2db76b.png

  • ⑥ NIO Reactor的方式

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
* NIO selector 多路复用reactor线程模型
*/
public class NIOServerV3 {
/** 处理业务操作的线程 */
private static ExecutorService workPool = Executors.newCachedThreadPool();

/**
* 封装了selector.select()等事件轮询的代码
*/
abstract class ReactorThread extends Thread {

Selector selector;
LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>();/**
* Selector监听到有事件后,调用这个方法
*/public abstract void handler(SelectableChannel channel) throws Exception;private ReactorThread() throws IOException {
selector = Selector.open();
}volatile boolean running = false;@Overridepublic void run() {// 轮询Selector事件while (running) {try {// 执行队列中的任务
Runnable task;while ((task = taskQueue.poll()) != null) {
task.run();
}
selector.select(1000);// 获取查询结果
Set selected = selector.selectedKeys();// 遍历查询结果
Iterator iter = selected.iterator();while (iter.hasNext()) {// 被封装的查询结果
SelectionKey key = iter.next();
iter.remove();int readyOps = key.readyOps();// 关注 Read 和 Accept两个事件if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {try {
SelectableChannel channel = (SelectableChannel) key.attachment();
channel.configureBlocking(false);
handler(channel);if (!channel.isOpen()) {
key.cancel(); // 如果关闭了,就取消这个KEY的订阅
}
} catch (Exception ex) {
key.cancel(); // 如果有异常,就取消这个KEY的订阅
}
}
}
selector.selectNow();
} catch (IOException e) {
e.printStackTrace();
}
}
}private SelectionKey register(SelectableChannel channel) throws Exception {// 为什么register要以任务提交的形式,让reactor线程去处理?// 因为线程在执行channel注册到selector的过程中,会和调用selector.select()方法的线程争用同一把锁// 而select()方法实在eventLoop中通过while循环调用的,争抢的可能性很高,为了让register能更快的执行,就放到同一个线程来处理
FutureTask futureTask = new FutureTask<>(() -> channel.register(selector, 0, channel));
taskQueue.add(futureTask);return futureTask.get();
}private void doStart() {if (!running) {
running = true;
start();
}
}
}private ServerSocketChannel serverSocketChannel;// 1、创建多个线程 - accept处理reactor线程 (accept线程)private ReactorThread[] mainReactorThreads = new ReactorThread[1];// 2、创建多个线程 - io处理reactor线程 (I/O线程)private ReactorThread[] subReactorThreads = new ReactorThread[8];/**
* 初始化线程组
*/private void newGroup() throws IOException {// 创建IO线程,负责处理客户端连接以后socketChannel的IO读写for (int i = 0; i < subReactorThreads.length; i++) {
subReactorThreads[i] = new ReactorThread() {@Overridepublic void handler(SelectableChannel channel) throws IOException {// work线程只负责处理IO处理,不处理accept事件
SocketChannel ch = (SocketChannel) channel;
ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (ch.isOpen() && ch.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;
}if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];
requestBuffer.get(content);
System.out.println(new String(content));
System.out.println(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());// TODO 业务操作 数据库、接口...
workPool.submit(() -> {
});// 响应结果 200
String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";
ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {
ch.write(buffer);
}
}
};
}// 创建mainReactor线程, 只负责处理serverSocketChannelfor (int i = 0; i < mainReactorThreads.length; i++) {
mainReactorThreads[i] = new ReactorThread() {
AtomicInteger incr = new AtomicInteger(0);@Overridepublic void handler(SelectableChannel channel) throws Exception {// 只做请求分发,不做具体的数据读取
ServerSocketChannel ch = (ServerSocketChannel) channel;
SocketChannel socketChannel = ch.accept();
socketChannel.configureBlocking(false);// 收到连接建立的通知之后,分发给I/O线程继续去读取数据int index = incr.getAndIncrement() % subReactorThreads.length;
ReactorThread workEventLoop = subReactorThreads[index];
workEventLoop.doStart();
SelectionKey selectionKey = workEventLoop.register(socketChannel);
selectionKey.interestOps(SelectionKey.OP_READ);
System.out.println(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());
}
};
}
}/**
* 初始化channel,并且绑定一个eventLoop线程
*
* @throws IOException IO异常
*/private void initAndRegister() throws Exception {// 1、 创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);// 2、 将serverSocketChannel注册到selectorint index = new Random().nextInt(mainReactorThreads.length);
mainReactorThreads[index].doStart();
SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
}/**
* 绑定端口
*
* @throws IOException IO异常
*/private void bind() throws IOException {// 1、 正式绑定端口,对外服务
serverSocketChannel.bind(new InetSocketAddress(8080));
System.out.println("启动完成,端口8080");
}public static void main(String[] args) throws Exception {
NIOServerV3 nioServerV3 = new NIOServerV3();
nioServerV3.newGroup(); // 1、 创建main和sub两组线程
nioServerV3.initAndRegister(); // 2、 创建serverSocketChannel,注册到mainReactor线程上的selector上
nioServerV3.bind(); // 3、 为serverSocketChannel绑定端口
}
}

PS:NIO为开发者提供了功能丰富及强大的IO处理API,但是在应用开发的过程中,直接使用JDK提供的API,比较繁琐,而且要想将性能进行提升,光有NIO还是不够的,还需要将多线程技术与之结合起来。因为网络编程本身的复杂性,以及JDK API开发的使用难度较高,所以开源社区中,涌出来很多的JDK NIO进行封装了,增强后的网络编程框架,例如:Netty、Mina等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/561166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何查看服务器文件进程,如何查看服务器上的所有进程

如何查看服务器上的所有进程 内容精选换一换华为云SSL证书管理服务帮助中心&#xff0c;为用户提供产品简介、用户指南、常见问题等技术文档&#xff0c;帮助您快速上手使用云证书管理服务。分析辅助软件是一款支持部署到多台服务器目标环境上&#xff0c;实现对整个业务集群的…

python minimize_Python数学规划案例一

Python数学规划案例一问题、模型、数据、算法、结果&#xff0c;统一地表述&#xff0c;是习惯也是效率。我的公众号数学规划模型表述习惯采用五个部分&#xff1a;Set, Data, Variable, Objective, Constraints&#xff1b;每个Notation&#xff0c;采用一个主字符&#xff0c…

java map转string_【库学科技】32道常见的Java基础面试题

内容来源于图灵 侵删。什么是 Java 虚拟机(JVM)&#xff1f;为什么 Java 被称作是“平台无关的编程语言”&#xff1f;Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。Java 被设计成允许应用程序可以运行在任意的平…

cout输出字符串_leetcode C++题解系列-042 字符串相乘

题目给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。示例 1:输入: num1 "2", num2 "3"输出: "6"示例 2:输入: num1 "123", num2 "456&quo…

python序列类型举例说明_Python(第八课,序列类型)

引言&#xff1a; 我们之前学过整数&#xff0c;浮点数&#xff0c;字符串&#xff0c;今天带来的更具有包容性。 今天带来的是高级数据类型&#xff0c;包括列表&#xff0c;元组&#xff0c;集合和字典。根据他们特性不同&#xff0c;可以分为序列类型&#xff0c;集合类型&a…

上传附件_留学落户|上传附件预审时一定一定要注意的问题!

点击上方蓝色字体&#xff0c;关注启铭君。www.minqifudao.com启铭君相信大家都知道&#xff0c;从2019年留学落户“一网通办”新系统上线&#xff0c;可以在网上进行材料申报&#xff0c;“让数据多走路&#xff0c;让群众少跑腿”。在新系统中填报资料&#xff0c;怎样才能做…

python斐波那契数列30_python的30个骚操作

1、冒泡排序2、计算x的n次方的方法3、计算a*a b*b c*c ……4、计算阶乘 n!5、列出当前目录下的所有文件和目录名6、把一个list中所有的字符串变成小写&#xff1a;7、输出某个路径下的所有文件和文件夹的路径8、输出某个路径及其子目录下的所有文件路径9、输出某个路径及其子…

python中值滤波介绍_Python 实现中值滤波、均值滤波的方法

红包&#xff1a;Lena椒盐噪声图片&#xff1a;# -*- coding: utf-8 -*- """ Created on Sat Oct 14 22:16:47 2017 author: Don """ from tkinter import * from skimage import io import numpy as np imio.imread(lena_sp.jpg, as_greyTrue) …

竖向图片插入_Word小技巧:让你的图片文字排版更有创意

想在头条中发表文章或者写论文&#xff0c;插入的图片太单调&#xff1f;今天小编就简单跟大家分享几个小技巧&#xff0c;图片搭配文字让你的版面更有可读性。第一种&#xff1a;最简单的横向文字排版具体做法&#xff1a;在word中插入图片后&#xff0c;插入一个文本框后输入…

11有没有压力感应_特殊感应器赋予机械手多维触感

最新研发的机械触感装备&#xff0c;已经不仅具有可伸展的韧性&#xff0c;还具有感知压力、形变和拉力的功能&#xff0c;将为软体机器人、虚拟现实(VR)和现实增强(AR)等设备带来革命性的巨变。美国康乃尔大学(Cornell University)工程学院副教授谢泼德(Rob Shepherd)说&#…

如何用手机打开dcm格式图片_如何防止自己的图片被盗用?这 4 招教你优雅加水印...

出门旅游一趟&#xff0c;好不容易拍到一组相当满意的图片。想要把这些图片上传到社交平台&#xff0c;如何才能让大家一看就知道这是自己拍的作品&#xff0c;同时又防止盗图呢&#xff1f;答案是在图片上加上自己的水印&#xff0c;这次有用功将和大家分享下&#xff0c;如何…

python 堆_面试再问你什么是堆和栈,你就把这篇文章甩给他

栈&#xff1a;管程序如何运行的&#xff0c;程序如何执行&#xff0c;如何处理数据。(局部变量其实也是存在栈中的&#xff0c;引用数据类型在栈中存的是地址引用)(栈的空间就不需要那么大了)堆&#xff1a;管数据存储的。(引用数据类型的存放,所以堆的空间是比较大的)生活理解…

3d 仪表盘_新一代标致2008官图发布 配备3D全息仪表盘

【太平洋汽车网 新车频道】标致发布了新一代2008的官图&#xff0c;新车基于PSA集团的CMP平台打造&#xff0c;采用了标致最新一代的家族式设计语言&#xff0c;造型风格极具辨识度。据悉&#xff0c;新一代2008将提供汽油、柴油和纯电动三种动力版本供消费者选择&#xff0c;其…

python人脸识别程序如何嵌入到app_只用Python就能写安卓,简单几步实现人脸识别的App...

最近闲来无事&#xff0c;研究研究在安卓上跑Python。 想起以前玩过的kivy技术&#xff0c;kivy[1]是一个跨平台的UI框架。当然对我们最有用的是&#xff0c;kivy可以把python代码打包成安卓App。但是由于安卓打包的工具链很长&#xff0c;包括android sdk打包java代码、ndk编译…

object htmldivelement什么意思_深入探究 Function amp; Object 鸡蛋问题

&#xff08;给前端树加星标&#xff0c;提升前端技能&#xff09;转自&#xff1a;高级前端进阶引言上篇文章用图解的方式向大家介绍了原型链及其继承方案&#xff0c;在介绍原型链继承的过程中讲解原型链运作机制以及属性遮蔽等知识&#xff0c;今天这篇文章就来深入探究下 F…

如何拉取k8s镜像_K8s 从懵圈到熟练 – 镜像拉取这件小事

导读&#xff1a;相比 K8s 集群的其他功能&#xff0c;私有镜像的自动拉取&#xff0c;看起来可能是比较简单的。而镜像拉取失败&#xff0c;大多数情况下都和权限有关。所以&#xff0c;在处理相关问题的时候&#xff0c;我们往往会轻松的说&#xff1a;这问题很简单&#xff…

hadoop没有datanode_Hadoop运行在Kubernetes平台实践

Hadoop与Kubernetes就好像江湖里的两大绝世高手&#xff0c;一个是成名已久的长者&#xff0c;至今仍然名声远扬&#xff0c;一个则是初出茅庐的青涩少年&#xff0c;骨骼惊奇&#xff0c;不走寻常路&#xff0c;一出手便惊诧了整个武林。Hadoop与Kubernetes之间有很深的渊源&a…

git 修改commit_Git从8到13 深入了解Git特性

上一章简单的介绍了一些常用的Git命令&#xff0c;这一章主要是深入了解一下Git的稍微高级的一些知识和指令。8.首先我们先来通过查看Git目录里面的文件来深入了解一下Git版本控制的构造。 查看HEAD可以知道当前所在的分支。在config文件里面存储着Git里面的一些配置信息&#…

eclipse maven打包jar 部分jsp无法访问_Maven系列教材 (九)- 在Eclipse中创建maven风格的java web项目...

Maven系列教材 &#xff08;九&#xff09;- 在Eclipse中创建maven风格的java web项目步骤1:删除j2ee目录步骤2:新建Maven 项目步骤3:这个界面点下一步步骤4: 这个界面使用webapp&#xff0c;点下一步 步骤5:这一步填写如图所示的信息步骤6:此时得到的maven web 项目的问题步骤…

final关键字_深入分析Java中的final关键字

Java中被final修饰的变量与普通变量有何区别&#xff1f;被final修饰的变量不可更改、被final修饰的方法不可重写是怎样做到的&#xff1f;带着疑问我们一点点拨开云雾。一、final的内存定义及规则对于final关键字&#xff0c;编译器、处理器从读写两个角度限制了其使用规则&am…