IO模型与NIO基础--NIO网络传输选择器--字符编码

news2025/2/22 3:07:51

放进NIO体系进行网络编程的工作流程:

在这里插入图片描述

Selector的创建

通过调用Selector.open()方法创建一个Selector,如下:
Selector selector = Selector.open();

向Selector注册通道

通过Channel.register()方法来实现,
注意:Channel和Selector一起使用时,Channel必须处于非阻塞模式下。
channel.configureBlocking(false); //设置通道为非阻塞模式
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

register()方法的第二个参数:是一个“兴趣(interest)集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。
可以监听四种不同类型的事件:

  1. Connect 链接就绪,某个channel成功连接到另一个服务器称为“连接就绪”。
  2. Accept 接收就绪,一个server socket channel准备好接收新进入的连接称为“接收就绪”。
  3. Read 读就绪,一个有数据可读的通道可以说是“读就绪”。
  4. Write 写就绪,等待写数据的通道可以说是“写就绪”。
    这四种事件用SelectionKey的四个常量来表示:
    1.SelectionKey.OP_CONNECT
    2.SelectionKey.OP_ACCEPT
    3.SelectionKey.OP_READ
    4.SelectionKey.OP_WRITE
    如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey说明
      当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。
这个对象包含了一些有用的属性:
1.interest集合
2.ready集合
3.Channel
4.Selector
5.附加的对象(可选)
interest集合
interest集合是你所选择的感兴趣的事件集合。
可以通过SelectionKey读写interest集合,像这样:

SelectionKey selectionKey=channel.register(selector, SelectionKey.OP_xxxx);
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

      可以看到,用“和”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。
ready集合
      ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。
可以这样访问ready集合:
int readySet = selectionKey.readyOps();
      可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

另外
从SelectionKey访问Channel和Selector很简单。如下:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();  

附加的对象
      可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。
例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

选择器的select()
一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。
这些方法返回你所感兴趣的事件(如连接、接受、读或写)同时这些事件已经准备就绪的那些通道。
换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
下面是select()方法:
1.select()阻塞到至少有一个通道在你注册的事件上就绪了。
2.select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数),一般用这个,不能无限阻塞。
3.selectNow()不会阻塞,不管什么通道就绪都立刻返回,此方法执行非阻塞的选择操作。
如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

select()方法有返回值,返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。
如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,
如果另一个通道就绪了,它会再次返回1。
如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,
但在每次select()方法调用之间,只有一个通道就绪了。
选择器的selectedKeys()
      一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法获取到:

Set selectedKeys = selector.selectedKeys();

   当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey对象。
这个对象代表了注册到该Selector的通道。
可以通过SelectionKey的selectedKeySet()方法访问这些对象。

可以遍历这个已选择的键集合来访问就绪的通道。如下:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // 一个连接被ServerSocketChannel接受
    } else if (key.isConnectable()) {
        // 与远程服务器建立了连接
    } else if (key.isReadable()) {
        // 一个channel做好了读准备
    } else if (key.isWritable()) {
        // 一个channel做好了写准备
    }
    keyIterator.remove();
}

      这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。
注意每次迭代末尾的keyIterator.remove()一定要调用,Selector不会自己从已选择键集中移除SelectionKey实例。
必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。若不移除,下次还是读取原来的数据,这是要命的。
      SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。
选择器的wakeUp()
      某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。
只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。
阻塞在select()方法上的线程会立马返回。
如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,上一个调用select()方法的阻塞线程会立即醒来(wake up)。

选择器的close()
      用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。
通道本身并不会关闭。

客户端与服务端简单交互实例

下面的程序涉及到一些网络编程的知识:

  1. 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接
  2. 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接
  3. 服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信

服务器:

/*
 * nio网络编程实例,这是服务端
 * 1.建立ServerSocketChannel通道,设置非阻塞 ,并绑定一个地址,客户链接到这个地址,等待客户端的链接
 * 2.获取选择器的实例,注册通道到里面去,设置感兴趣的事件,ServerSocketChannel这种通道只有一个事件--链接事件
 * 3.准备缓冲区:两个,一个读,一个写
 * 4.实现与客户端交互,首先一个死循环,不停的获取各种客户端链接请求通道,轮询作用似的
 *   a.选择器的select(1000)方法,会获取各通道对象个数(通道感兴趣事件的)1000是表示阻塞一秒,如果一秒之内没结果,重新轮询
     b.有通道就绪,则拿到通道对象键值集合Set,遍历集合,找到接入链接的通道,让其接入,返回一个能读写的通道,不是上面的接入通道
     c.读写通道也要注册到选择器里面,先读事件,同一个通道的多次注册,如是不同的事件改变,只是注删了一个通道
 */
public class ServerDemo {
	public static void main(String[] args) {
		try {
		    
			ServerSocketChannel serverSocketChannel= ServerSocketChannel.open();
			serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8000));
			serverSocketChannel.configureBlocking(false);
			
			Selector selector=Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  //接入事件就绪
			
			ByteBuffer readBuffer= ByteBuffer.allocate(1024);
			ByteBuffer writeBuffer=ByteBuffer.allocate(1024);
			writeBuffer.put("this is server".getBytes());  //写缓冲区中写入这个字符串
			writeBuffer.flip();             //翻转非直接缓冲区,非直接缓冲区,postion指针不能自动指向开缓冲区开头处
			
			while(true) {
				int nReady=selector.select(1000);  //捕获所有通道
				if(nReady==0)  continue;    //一个通道都没有,一秒后继续查询
				Set<SelectionKey> keys =selector.selectedKeys();
				Iterator<SelectionKey> iterator = keys.iterator();
				while(iterator.hasNext()) {
					SelectionKey key=iterator.next();
					iterator.remove();  //手动移除,让下次轮询通道是全新的,而不是这次的
					if(key.isAcceptable()) {  //如果是准备链接的通道,就让客户端链接, 当然了,这个程序简单只有一个通道
						SocketChannel channel=serverSocketChannel.accept();
						channel.configureBlocking(false);
						channel.register(selector, SelectionKey.OP_READ);  //不用担心,下次读客户发送的信息,因为有死循在那不的轮询
					}else if(key.isReadable()) {  //如果通道对象key是有可读事件,就读到缓冲区中
						SocketChannel channel = (SocketChannel) key.channel();
						readBuffer.clear();
						channel.read(readBuffer);
						readBuffer.flip();
						/*while(readBuffer.hasRemaining()) {  //判断缓冲区还没有数据,有的话,从缓冲区读数据,打印到控制台
				    		System.out.print("读到的数据是:"+(char)readBuffer.get());  //读得是字节码,中文会乱码,一次读一个字节
				    	}*/
						System.out.println("服务器端收到的数据,直接打印:"+ new String(readBuffer.array()));
					    key.interestOps(SelectionKey.OP_WRITE);
					}else if(key.isWritable()){
						SocketChannel channel =(SocketChannel)key.channel();
						writeBuffer.rewind();
						channel.write(writeBuffer);
						key.interestOps(SelectionKey.OP_READ);  //以便下次再读
						
					}
				}
			}
			
	     }catch (Exception e) {
			e.printStackTrace();
		}
	}

}

客户端

/*
 * nio网络编程实例,这是客户端
 * 不用实例化,链接通道(类似洒店的迎宾)
 * 1.通道
 * 2.缓冲区,两个,一个读,一个写
 * 3.先向服务器写,再读数据
 * 
 */
public class ClientDemo {
	public static void main(String[] args) {
		try {
			SocketChannel channel = SocketChannel.open();
			channel.connect(new InetSocketAddress("127.0.0.1",8000));  //链接到服务器
			
			ByteBuffer readBuffer= ByteBuffer.allocate(1024);
			ByteBuffer writeBuffer=ByteBuffer.allocate(1024);
			writeBuffer.put("this is client".getBytes());  //写缓冲区中写入这个字符串
			writeBuffer.flip(); 
			
			while(true) {
				writeBuffer.rewind();    //重置缓冲区,写入
				channel.write(writeBuffer);
				
				readBuffer.clear();
				channel.read(readBuffer);
				readBuffer.flip();
				System.out.println("客户端收到的数据:"+new String(readBuffer.array()));
				
				
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
	}

}

字符集和Charset

基础认识

计算机中储存的信息都是用二进制数表示的;
      而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。
      通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为"编码";
      反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。
      在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。

      字符集(Charset):多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,字符和二进制数字的对应规则不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、GBK字符集、UTF-8字符集等。
      字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),
与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。
而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。
      元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
常用字符集和字符编码

  1. ASCII字符集&编码
    ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语。
  2. GBXXXX字符集&编码
    计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。
    但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
    规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,
    前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,
    这样我们就可以组合出大约7000多个简体汉字了。
    上面的编码规则形成的字符集就是GB2312。
    GB2312或GB2312-80是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》
       由于GB 2312-80只收录6763个汉字,有不少汉字并未有收录在内,如部分在GB 2312-80推出以后才简化的汉字(如"啰"),部分人名用字(如中国前总理朱镕基的"镕"字),台湾及香港使用的繁体字,日语及朝鲜语汉字等。因此1995年发布了GBK编码,是在GB2312-80标准基础上的进行了扩展,共收录了21003个汉字
  3. BIG5字符集&编码
    Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准。
  4. Unicode
    像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB2312/GBK/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。
          为了解决这个问题,产生了——Unicode。Unicode编码系统为表达任意语言的任意字符而设计。
    它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。
    在计算机科学领域中,Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。
    Unicode是标准,是规则,
    UTF-32/ UTF-16/ UTF-8是三种字符编码方案就是Unicode的具体实现。
    UTF-8:电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码方案,也是用的最多的.

java的字符集

      Java默认使用Unicode字符集,但很多系统并不使用Unicode字符集,那么当从系统中读取数据到Java程序时,就可能出现乱码的问题。
      JDK1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转换关系,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset所支持字符集的方法,Charset类是不可变的。
      Charset类提供了一个静态方法availableCharset()来获取当前JDK所支持的所有字符集。返回的是一个SortedMap<String,Charset>
例:查看JDK支持的所有字符集

public class CharsetTest{
	public static void main(String[] args) {
		SortedMap<String,Charset> map = Charset.availableCharsets();
		for(String s:map.keySet()) {
			System.out.println(s);
		}
	}
}
-------------------------------------------------------------------------
Big5
Big5-HKSCS
CESU-8
EUC-JP
EUC-KR
GB18030
GB2312
GBK
....
UTF-8
...

创建字符集对象与获取相应的编码解码对象

      一旦知道字符集的别名后,就可以用Charset的forName()方法来创建对应的Charset对象,
forName方法的参数就是字符集的别名。
拿到Charset对象,就可以调用它的newEncoder和newDecoder方法,创建对应的编码器和解码器对象。
解码器对象CharsetDecoder里的decode()方法就可以将ByteBuffer转为CharBuffer,CharsetEncoder里的encode()方法作用相反,将CharBuffer转为ByteBuffer。

编码与解码缓冲区

接下来使用编码器将CharBuffer中的字符序列转换为字节序列ByteBuffer。
CharBuffer和ByteBuffer是java NIO中的IO操作类。
编码器,编码字符缓冲区后,转换为字节序列缓冲区
解码器,解码字节缓冲区后,转换为字符序列缓冲区。
编码

public class CharsetTest{
	public static void main(String[] args) throws CharacterCodingException {
		Charset charset = Charset.forName("GBK");
		CharsetEncoder encoder = charset.newEncoder();
		
		CharBuffer charBuffer = CharBuffer.allocate(20);
		charBuffer.put("熊少文");
		charBuffer.flip();
		ByteBuffer byteBuffer=encoder.encode(charBuffer);
		for(int i=0;i<byteBuffer.limit();i++) {
			System.out.println(byteBuffer.get(i)+"");
		}
		
	}
}
-------------------------------------------------------
-48
-36
-55
-39
-50
-60

解码

public class CharsetTest{
	public static void main(String[] args) throws Exception {
		Charset charset = Charset.forName("GBK");
		CharsetEncoder encoder = charset.newEncoder();
		
		CharBuffer charBuffer = CharBuffer.allocate(20);
		charBuffer.put("熊少文");
		charBuffer.flip();
		ByteBuffer byteBuffer=encoder.encode(charBuffer);
		for(int i=0;i<byteBuffer.limit();i++) {
			System.out.println(byteBuffer.get(i)+"");
		}
		
		
		CharsetDecoder decoder=charset.newDecoder();
		//byteBuffer.flip();  //解码器不用翻转,
		CharBuffer charBuffer2 = decoder.decode(byteBuffer);
		//charBuffer2.flip();   //也不用翻转
		
		for(int j=0;j<charBuffer2.limit();j++) {
			System.out.print(charBuffer2.get(j));
		}
	}
}
-----------------------------------------------------------------
-48
-36
-55
-39
-50
-60
熊少文

NIO2.0即AIO

      JDK7在java.nio这个工具包里加入了很多新的元素
我们把这些改变叫NIO2.0(即AIO,Asynchronous IO:异步非阻塞IO)和NIO相比,同样有Channel和Buffer, 但没有Selector。

Path雷同于File路径

      Java Path接口是Java NIO 2.0更新的一部分,同Java NIO一起已经包括在Java6和Java7中。Java Path接口是在Java7中添加到java.nio的。
      Path接口位于java.nio.file包中,所以Path接口的完全限定名称为java.nio.file.Path。
      Java Path实例表示文件系统中的路径。一个路径可以指向一个文件或一个目录。路径可以是绝对路径,也可以是相对路径。
      在许多方面,java.nio.file.Path接口类似于java.io.File类,但是有一些细微的差别。不过,在许多情况下,您可以使用Path接口来替换File类的使用。
创建一个Path寮例
为了使用java.nio.file.Path实例必须创建一个Path实例。
您可以使用Paths类(java.nio.file.Paths)中的静态方法来创建路径实例,名为Paths.get()。

public class PathTest {
	public static void main(String[] args) {
		//您可以使用Paths类(java.nio.file.Paths)中的静态方法来创建路径实例,名为Paths.get()。
		Path path=Paths.get("c:\\xiong\\aaa.txt");//相对路径写法与File类一样
		Path path2=Paths.get("c:\\xiong","aaa.txt");
		System.out.println(path+"  "+path2);
		
		Path path3 = Paths.get(".","aaa.txt");    //当前目录下
		Path path4= Paths.get("..","aaa.txt");    //上一级目录下
		System.out.println(path3+"   "+path4);
		
	}

}
------------------------------------------------------------
c:\xiong\aaa.txt  c:\xiong\aaa.txt
.\aaa.txt   ..\aaa.txt

      在Unix系统(Linux、MacOS、FreeBSD等)上,上面的绝对路径可能如下:

Path path = Paths.get("/home/jakobjenkov/myfile.txt");

Java NIO Files

Java NIO Files类(java.nio.file.Files)提供了几种操作文件系统中的文件的方法。
java.nio.file.Files类与java.nio.file.Path实例一起工作。

Files.exists()
Files.exists()方法检查给定的Path在文件系统中是否存在。
可以创建在文件系统中不存在的Path实例。

public class FilesTest {
	public static void main(String[] args) {
		Path path=Paths.get("c:\\xiong\\aaa.txt");
		boolean exist = Files.exists(path, new LinkOption[] {LinkOption.NOFOLLOW_LINKS});
		System.out.println(exist);
	}

}
------------------------------------------------------------------
true

Files.createDirectory()
      Files.createDirectory()方法,用于根据Path实例创建一个新目录,下面是一个Files.createDirectory()例子:

public class FilesTest {
	public static void main(String[] args) {

		Path path=Paths.get("c:\\bbb\\");
		if(!Files.exists(path, new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) {
			try {
				Files.createDirectory(path);
			}catch(IOException e) {
				e.printStackTrace();
			}
		}
		
	}

}

第一行创建表示要创建的目录的Path实例。
在try-catch块中,用路径作为参数调用Files.createDirectory()方法。
如果创建目录成功,将返回一个Path实例,该实例指向新创建的路径。

如果该目录已经存在,则是抛出一个java.nio.file.FileAlreadyExistsException。
如果出现其他错误,可能会抛出IOException。
例如,如果想要的新目录的父目录不存在,则可能会抛出IOException。

Files.copy()
      Files.copy()方法从一个路径拷贝一个文件到另外一个目录:这个方法在File中没有。当需要复制文件时,用这个是十分方便的。

   Path spath = Paths.get("c:\\xiong\\aaa.txt");
   Path dpath = Paths.get("c:\\xiong\\aaa.txt");
   try{
       Files.copy(spath,dpath);
       //Files.copy(spath,dpath,StandardCopyOption.REPLACE_EXISTING);  //覆盖复制
    }catch(IOException e){
       e.printStackTrace();
     }
     

Files.move()
Java NIO Files还包含一个函数,用于将文件从一个路径移动到另一个路径。
移动文件与重命名相同,但是移动文件既可以移动到不同的目录,也可以在相同的操作中更改它的名称。
java.io.File类也可以使用它的renameTo()方法来完成这个操作,但是现在已经在java.nio.file.Files中有了文件移动功能。

Path sourcePath = Paths.get("data/logging-copy.properties");
Path destinationPath = Paths.get("data/subdir/logging-moved.properties");
try {
    Files.move(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    //移动文件失败
    e.printStackTrace();
}

Files.delete()
Files.delete()方法可以删除一个文件或者目录。

Path path = Paths.get("data/subdir/logging-moved.properties");
try {
    Files.delete(path);
} catch (IOException e) {
    // 删除文件失败
    e.printStackTrace();
}

首先,创建指向要删除的文件的Path。然后调用Files.delete()方法。
如果Files.delete()由于某种原因不能删除文件(例如,文件或目录不存在),会抛出一个IOException。

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

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

相关文章

【亚马逊开发者账号02】终审问题SA+review_Pre-review+Doc.xlsx

1.终审问题 你好感谢您在此过程中的回复和协作。所有想要构建具有受限 SP-API 角色的公开可用应用程序的开发人员都必须与我们的解决方案架构师团队一起完成架构审核。 这将需要详细说明应用程序的数据流、个人身份信息 &#xff08;PII&#xff09; 的数据保护控制&#xff0…

c++标准io与线程,互斥锁

封装一个 File 类&#xff0c; 用有私有成员 File* fp 实现以下功能 File f "文件名" 要求打开该文件 f.write(string str) 要求将str数据写入文件中 string str f.read(int size) 从文件中读取最多size个字节&#xff0c; 并将读取到的数据返回 析构函数 #…

在高流量下保持WordPress网站的稳定和高效运行

随着流量的不断增加&#xff0c;网站的稳定和高效运行变得越来越重要&#xff0c;特别是使用WordPress搭建的网站。流量过高时&#xff0c;网站加载可能会变慢&#xff0c;甚至崩溃&#xff0c;直接影响用户体验和网站正常运营。因此&#xff0c;我们需要采取一些有效的措施&am…

Cython学习笔记1:利用Cython加速Python运行速度

Cython学习笔记1&#xff1a;利用Cython加速Python运行速度 CythonCython 的核心特点&#xff1a;利用Cython加速Python运行速度1. Cython加速Python运行速度原理2. 不使用Cython3. 使用Cython加速&#xff08;1&#xff09;使用pip安装 cython 和 setuptools 库&#xff08;2&…

web的分离不分离:前后端分离与不分离全面分析

让我们一起走向未来 &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[1504566…

记录一个ES分词器不生效的解决过程

问题背景 商城项目,其中商品查询检索使用的是ES, 但存在某些商品查询不到的问题 例如:某商品名包含AA_BBB这样的关键词,但是搜索"AA"不能查询到该商品,但是将商品名修改为AA BBB后就能查询到了. 怀疑是分词的问题,但看代码,在创建ES索引时在对应字段上也定义了分词器…

高性能内存对象缓存Memcached详细实验操作

目录 前提准备&#xff1a; cache1&#xff0c;2&#xff1a; 客户端cache-api&#xff08;一定得是LAMP环境&#xff09; memcache实现主主复制以及高可用(基于以上完成) cache1,2: memcachekeepalived(基于以上完成) cache1,2: 前提准备&#xff1a; 1. 准备三台cent…

css之display:grid布局改块级元素布局

1.问题&#xff1a; div是块级元素&#xff0c;一个div元素占一行&#xff0c;但是&#xff0c;今天测试样式时&#xff0c;总是会有两个div并占一行&#xff0c;很困惑&#xff0c;结果发现是app这个样式 在main.css里 #app样式布局在main.ts里被应用 2.原因以及样式分析 im…

推荐一个github star45k+进阶的java项目及知识的网站

mall是github上star 45k的一个java项目 mall项目是一套电商系统&#xff0c;包括前台商城系统及后台管理系统&#xff0c;基于SpringBootMyBatis实现&#xff0c;采用Docker容器化部署。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心…

第2章 深入理解Thread构造函数

Thread的构造函数。 2.1 线程的命名 在构造一个Thread时可以为其命名。 2.1.1 线程的默认命名 下面构造函数中&#xff0c;并没有为线程命名。 Thread() Thread(Runnable target) Thread(ThreadGroup group, Runnable target)打开源码会看到 public Thread(Runnable targe…

node 使用 Redis 缓存

缓存是什么&#xff1f; 高并发下&#xff0c;一个项目最先出问题的&#xff0c;并不是程序本身&#xff0c;而是数据库最先承受不住。 在数据库上我们可以做很多优化&#xff0c;例如优化 SQL 语句&#xff0c;优化索引&#xff0c;如果数据量大了&#xff0c;还可以分库、分表…

PMBOK第7版整体架构全面详解

1. 引言 7月1日对于项目管理从业者和研究者而言&#xff0c;是个非凡意义的一个时间&#xff0c;这一天&#xff0c;翘首以待的《 项 目管理知识体系指南 》&#xff08;PMBOK&#xff09;第七版终于发布了。 总体而言&#xff0c;PMBOK第七版集百家之所长&#xff0c;成一…

【Scrapy】Scrapy教程6——提取数据

前一小节我们拿到了页面的数据,那页面中那么多内容,我们想要其中的部分内容,该如何获取呢?这就需要对我们下载到的数据进行解析,提取出来想要的数据,这节就讲讲如何提取数据。 引入 我们编辑保存下来的shouye.html文件看下,发现这是什么鬼,全是如下图的代码。 没错…

golang panic信息捕获

背景 我们的日志接入阿里云sls平台&#xff0c;但是&#xff0c;日志是以json的格式存储在阿里云sls平台上&#xff0c;程序中产生的error,info等日志都可以实现以json的格式打印。但是&#xff0c;golang程序中产生的panic信息本身不是以json的格式输出&#xff0c;这就导致p…

一周学会Flask3 Python Web开发-http响应状态码

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Flask程序中&#xff0c;客户端发出的请求触发相应的视图函数&#xff0c;获取返回值会作为响应的主体&#xff0c;最后生成…

goland无法debug项目

1、其实个原因是因为正在使用的Delve调试器版本太旧&#xff0c;无法兼容当前的Go语言版本1.2。Delve是Go语言的一个调试工具&#xff0c;用于提供源码级别的调试功能。Go语言每隔一段时间会发布新版本&#xff0c;而相应的调试器Delve也可能会更新以提供新的特性或修复已知问题…

Python VsCode DeepSeek接入

Python VsCode DeepSeek接入 创建API key 首先进入DeepSeek官网&#xff0c;https://www.deepseek.com/ 点击左侧“API Keys”&#xff0c;创建API key&#xff0c;输出名称为“AI” 点击“创建"&#xff0c;将API key保存&#xff0c;复制在其它地方。 在VsCode中下载…

Ubuntu22.04.6如何固定ip地址

Ubuntu22.04.6如何固定ip地址 主要参见这篇博客 ubuntu 桌面版如何设置固定IP地址_ubuntu桌面版如何修改ip-CSDN博客 1.先查看一下当前的IP是多少

腿足机器人之十- SLAM地图如何用于运动控制

腿足机器人之十- SLAM地图如何用于运动控制 腿足机器人SLAM地图的表示与处理全局路径规划&#xff1a;地形感知的路径搜索基于A*的三维路径规划基于RRT*的可行步态序列生成 局部运动规划&#xff1a;实时步态调整与避障动态窗口法的腿足适配模型预测控制&#xff08;MPC&#x…

毕业项目推荐:基于yolov8/yolov5/yolo11的果蔬检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…