哈希表及其基础(java详解)

目录

一、哈希表基础 

二、哈希函数的设计

哈希函数的设计原则

三、java中的hashCode

基本数据类型的hashCode使用

自定义类型的hashCode使用 

需要注意

四、哈希冲突的处理 链地址法Seperate Chaining

五、实现属于我们自己的哈希表

六、哈希表的动态空间处理和复杂度分析

七、哈希表更复杂的动态空间处理方法

我们哈希表的bug

八、更多哈希冲突的处理方法

开放地址法

其他


本篇博客花费大量时间写的十分详细,为了保证思路理解的连贯性,希望各位耐心地读下去。

一、哈希表基础 


参考LeetCode第387号问题:给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

提示:你可以假定该字符串只包含小写字母。

这种方法的时间复杂度是O(n)。

int[26] freq就是一个哈希表,每一个字符都和一个索引相对应,index = ch - 'a',这是哈希表的优势——把字符转化成一个索引。直接用一个数组来进行O(1)的查找操作,把字符转化为索引的函数就称为哈希函数f(ch) = ch - 'a'。

我们很难保证每一个键通过哈希函数的转换会对应不同的索引,而两个键对应同一个索引就会产生哈希冲突,我们需要在哈希表上操作来解决哈希冲突,这个我们后面再讲。

哈希表完美的体现了用空间换时间的思想。比如我们18位数的身份证号的存储,如果可用空间足够大,我们可以用O(1)的时间实现。我们希望每一个键通过哈希函数得到的索引分布越均匀越好。

二、哈希函数的设计

学习哈希表我们最主要解决的两个问题:一个是哈希函数的设计,一个是如何解决哈希冲突。

接下来我们来看各种类型的数据怎么通过哈希函数转化为一个数组的索引。 

整型:

小范围的正整数直接使用。

小范围的负整数进行偏移。 -100 ~ 100 ——> 0 ~ 200

大整数:

比如18位的身份证号

方法:取模。  比如取后四位,等同于mod10000,不能取后六位,因为牵扯到人的出生日期,一个月最多有31天,所以整个数据不会超过32万,只利用32/100会造成分布不均匀。取后几位还要考虑有没有利用所有的信息,比如身份证号中还有人的出生省份、城市、月份等信息,如果不全面利用也会造成哈希冲突概率上升。一个简单的解决办法就是模一个素数。这个解决办法的证明是数论的范畴,感兴趣的朋友可以自己去了解一下。

由图片我们可以看出,模一个素数比模一个和数分布更均匀。

 这个网站给出了根据数据规模不同,取什么素数是合适的。

根据你的数据范围,最后一列给出了推荐的素数。当然这种东西我们不需要深究,了解一下就好了。

浮点型:

在计算机中都是32或者64位的二进制表示,只不过计算机解析成了浮点数,如果我们的键是浮点型的话,我们就可以使用这个浮点型所存储的空间当作整型来进行处理。也就是把浮点数所占用的32位或者64位的空间使用整数来进行解析,这片空间就同样可以表示一个整数,问题就转化成了一个大的整数来转换成索引的方式。

字符串:

转成整型处理,如果要区分字母大小写,那么26进制还不够,用户自己可以选择转成多少进制的大整型。

下面这两个式子是等价的,只是简化了一些。M就是一个素数。

为了防止整型溢出,我们可以把取模挪到括号里面。

//为了实现字符串的这个小逻辑,我们写个循环
int hash = 0;
for(int i = 0; i < s.length(); i++){hash = (hash * B + s.charAt(i)) % M}

复合类型/自定义类型:

也是转成整型类型,并且和字符串的处理方式差不多。

比如我们自定义一个日期类型——Date: year, month, day. 我们可以用字符串的方式来换成整型,如下式。

总而言之,我们都是转成整型来处理,但这并不是唯一的方法,感兴趣的朋友可以去了解更多。

哈希函数的设计原则

三、java中的hashCode

在java语言中,hashCode方法可以直接获得基本数据类型的哈希函数的值,如果我们要自定义一个类型,我们可以覆盖在Object类中就有的hashCode函数。

下面我们来看一下在java中hashCode的使用方法。

基本数据类型的hashCode使用

//基本数据类型的hashCode使用
public class Main {public static void main(String[]args){int a = 42;//因为a不是一个对象,所以我们转化成包装类System.out.println(((Integer)a).hashCode());int b = -42;System.out.println(((Integer)b).hashCode());double c = 3.1415926;System.out.println(((Double)c).hashCode());String d = "fyj";System.out.println(d.hashCode());}
}

自定义类型的hashCode使用 

//自定义类型
public class Student {int grade;//年级int cls;//班String firstName;//名String lastName;//姓Student(int grade, int cls, String firstName, String lastName){this.grade = grade;this.cls = cls;this.firstName = firstName;this.lastName = lastName;}@Overridepublic int hashCode(){//把四部分看成四个数字,把整体看成这四部分组成的B这个进制的数int B = 31;int hash = 0;hash = hash * B + grade;hash = hash * B + cls;//firstName是字符串,我们可以调用它的hashCode转化为一个整数hash = hash * B + firstName.hashCode();hash = hash * B + lastName.hashCode();//如果我们不希望区分大小写,可以改成下面//传入后全换成小写//hash = hash * B + firstName.toLowerCase().hashCode();//hash = hash * B + lastName.toLowerCase().hashCode();//传入后全换成大写//hash = hash * B + firstName.toUpperCase().hashCode();//hash = hash * B + lastName.toUpperCase().hashCode();return hash;}public static void main(String[]args) {Student student = new Student(2,2, "o","f");System.out.println(student.hashCode());}
}

有了初步了解后,我们就可以尝试着使用java中给我们提供的有关哈希表的数据结构,下面是java哈希表的基本使用方法。

//导入底层基于哈希表的集合HashSet
import java.util.HashSet;
import java.util.HashMap;
public class Main {public static void main(String[]args){Student student = new Student(2,2, "o","f");System.out.println(student.hashCode());HashSet<Student> set = new HashSet<>();set.add(student);HashMap<Student, Integer> scores = new HashMap<>();scores.put(student, 100);}
}

需要注意

如果我们自己覆盖的那个HashCode方法注释掉,我们再次运行依然可以成功。因为对于java来说,每一个Object类默认有一个HashCode的实现,它是根据我们创建的每一个Obect的地址相应的转化成一个整型。如果我们在注释掉自己覆盖的HashCode情况下再创建一个student2,让student2和student的内容一样,运行出来的它们两个的HashCode值是不一样的。因为两个对象的地址不同,转化为的整型就不同。如果用我们自己覆盖的HashCode方法,两个HashCode值就一样了。

我们自己覆盖的HashCode方法只是帮助获取对象的HashCode值,在产生哈希冲突的时候,我们依旧需要比较这两个类是不是相等的。除了覆盖HashCode,我们还需要equals来判断两个对象是否相等。

@Override//我们需要传入的是Object类型,而不是Student类型public boolean equals(Object o){//是否是同一个引用if(this == o){return true;}if(o == null){return false;}//获取对象的运行时类if(getClass() != o.getClass()){return false;}Student another = (Student)o;return this.grade == another.grade &&this. cls == another.cls &&this.firstName == another.firstName &&this.lastName == another.lastName;}

这时候出现哈希冲突时,就算对应同一个值也可以区分出两个对象。接下来我们将会真正实现一个哈希表。

四、哈希冲突的处理 链地址法Seperate Chaining

其实哈希表本质就是一个数组,我们先假设有这么一个数组,这时候来了一个键是k1的元素,我们只需要用hashcode(k1) % M,在java中hashcode相应的结果可能是负值,我们还要想办法把负号给抹去。因为在Java中,哈希码是一个int类型的值,可以是正数或负数。但是,哈希表中的索引值必须是非负整数,因此如果哈希码是负数,就需要将其转换为非负整数。

在看源码或者别人写的代码中我们很常见到下面这种写法:

通过整型和31个1按位与把最高位的负号抹去。因为计算机中二进制表示的最高位是符号位,1是负0是正,0和1按位与是0,1和1按位与还是0,最后都是正号。

假如k1计算完后索引为4,那么就存在数组索引为4的地方,这时候如果来了两个索引都为2的k2、k3怎么办?那我们就以链表的形式都存在索引为2的地方,这就是链地址法。

对于每个位置,我们说是存一个链表,其实它的本质就是存一个查找表, 查找表的实现方式还可以用树结构实现,比如可以用平衡树,这时候每个位置存的就不是一个链表,而是一个TreeMap的查找表,这时候我们的HashMap本质就是一个TreeMap的数组。

除了映射的角度,我们还可以从集合的角度,那时候HashSet 就是一个TreeSet数组。不管怎样,哈希表的本质就是一个查找表,底层的数据结构只要是适合做查找表的就可以。

五、实现属于我们自己的哈希表

//哈希表中的k不用像二分搜索树一样具有可比较性
//因此不用继承Comparable接口
//同样HashTable的k必须要实现hashCode这个方法
//不过在Java中所有的类型都是Object的子类,而Object默认实现了hashCode方法
//所以具体在代码上我们对这个k也不需要有特殊的要求,如果有必要可以自己去覆盖自己的hashCode方法//底层是一个红黑树
import java.util.TreeMap;public class HashTable<K, V> {private TreeMap<K, V>[] hashtable;private int size;//哈希表具体有多少个元素private int M; //哈希表有多少个位置/哈希表的大小public HashTable(int M){this.M = M;size = 0;hashtable = new TreeMap[M];//初始化数组for(int i = 0 ; i < M ; i ++)hashtable[i] = new TreeMap<>();//实例化}public HashTable(){this(97);//默认传97的固定的值}private int hash(K key){//把key转化成一个整型后通过按位与保证为非负数return (key.hashCode() & 0x7fffffff) % M;}public int getSize(){//哈希表中一共有多少元素return size;}public void add(K key, V value){//增TreeMap<K, V> map = hashtable[hash(key)];//基于多次重复计算的hash(key)小优化if(map.containsKey(key))//如果已存在key,那就理解成把key的值修改为valuemap.put(key, value);else{map.put(key, value);//真正的添加数据size ++;}}public V remove(K key){//删V ret = null;//ret是return value的意思TreeMap<K, V> map = hashtable[hash(key)];//找到key对应的的哈希函数的值if(map.containsKey(key)){ret = map.remove(key);size --;}return ret;}public void set(K key, V value){//改TreeMap<K, V> map = hashtable[hash(key)];if(!map.containsKey(key))throw new IllegalArgumentException(key + " doesn't exist!");map.put(key, value);}public boolean contains(K key){//是否存在return hashtable[hash(key)].containsKey(key);}public V get(K key){//查询某一个键对应的值是多少return hashtable[hash(key)].get(key);}
}

六、哈希表的动态空间处理和复杂度分析

前面我们说过,我们创建的哈希表有M个地址,如果放入N个元素,那么平均来看每个地址会有N/M个元素会产生哈希冲突。 

如果每个地址是链表,那么时间复杂度O(N/M)。

如果每个地址是平衡树,那么时间复杂度O(log(N/M))。

以上是平均来看的,而不是最坏的情况,最坏的情况是所有元素都挤在一个地址,那时候就分别是O(N)和O(logN)了。我们前面也说过哈希表的设计原则,我们的哈希表要尽可能地分布均匀。所以在哈希表设计恰当时是不会出现最坏情况的,我们还是要看平均情况下。

我们之前说哈希表的优势在于能让时间复杂度成为O(1)级别,现在我们的时间复杂度为什么都和N有关呢?原因就在于这个M是固定的,它是一个常数,当N趋于无穷大的时候,N/M总体也是趋于无穷的。 我们的哈希表就是一个数组,我们整体数据结构如果基于一个静态数组是不合理的,空间总是固定的,所以我们的数组应该是动态内存分配的。当平均每个地址承载的元素多过一定程度时我们就扩容,反之就缩容。

下面是我们引入动态扩容缩容的哈希表代码:

import java.util.Map;
import java.util.TreeMap;public class HashTable<K, V> {//设定平均每个地址哈希冲突的上界/下界private static final int upperTol = 10;private static final int lowerTol = 2;//初始容量private static final int initCapacity = 7;private TreeMap<K, V>[] hashtable;private int size;private int M;public HashTable(int M){this.M = M;size = 0;hashtable = new TreeMap[M];for(int i = 0 ; i < M ; i ++)hashtable[i] = new TreeMap<>();}public HashTable(){this(initCapacity);}private int hash(K key){return (key.hashCode() & 0x7fffffff) % M;}public int getSize(){return size;}public void add(K key, V value){TreeMap<K, V> map = hashtable[hash(key)];if(map.containsKey(key))map.put(key, value);else{map.put(key, value);size ++;//扩容if(size >= upperTol * M) //等价于size/M >= upperTolresize(2 * M);}}public V remove(K key){V ret = null;TreeMap<K, V> map = hashtable[hash(key)];if(map.containsKey(key)){ret = map.remove(key);size --;//缩容if(size < lowerTol * M && M / 2 >= initCapacity)//M不要小于初始的容积量resize(M / 2);}return ret;}public void set(K key, V value){TreeMap<K, V> map = hashtable[hash(key)];if(!map.containsKey(key))throw new IllegalArgumentException(key + " doesn't exist!");map.put(key, value);}public boolean contains(K key){return hashtable[hash(key)].containsKey(key);}public V get(K key){return hashtable[hash(key)].get(key);}//自动扩容实现private void resize(int newM){TreeMap<K, V>[] newHashTable = new TreeMap[newM];for(int i = 0 ; i < newM ; i ++)newHashTable[i] = new TreeMap<>();int oldM = M;this.M = newM;for(int i = 0 ; i < oldM ; i ++){TreeMap<K, V> map = hashtable[i];for(K key: map.keySet())newHashTable[hash(key)].put(key, map.get(key));}this.hashtable = newHashTable;}
}

七、哈希表更复杂的动态空间处理方法

对于哈希表来说,元素从N增加到upperTol * N,地址空间增倍,均摊后的平均复杂度是O(1),每个操作在O(lowerTol) ~ O(upperTol)。

可能有细心的朋友会发现,当我们扩容M时,2 * M 不再是一个素数了,这样可能会导致哈希表的分布不均匀。怎么解决呢?其实很简单,我们可以根据前面提到的素数表来进行扩容。

比如,当我们初始的M是53,我们可以直接扩到下一个M推荐值97,而不是再用那种M * 2 的方式扩容了。观察表我们可以发现,最后一列这些推荐的相邻的素数值也在尽量地保持一个二倍左右的关系,这使扩容也更加合理。

下面是加入了素数表的哈希表代码:

import java.util.TreeMap;public class HashTable<K extends Comparable<K>, V> {//素数表private final int[] capacity= {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};private static final int upperTol = 10;private static final int lowerTol = 2;private int capacityIndex = 0;//指向capacity数组的索引为0所在位置的数字private TreeMap<K, V>[] hashtable;private int size;private int M;public HashTable(){this.M = capacity[capacityIndex];size = 0;hashtable = new TreeMap[M];for(int i = 0 ; i < M ; i ++)hashtable[i] = new TreeMap<>();}private int hash(K key){return (key.hashCode() & 0x7fffffff) % M;}public int getSize(){return size;}public void add(K key, V value){TreeMap<K, V> map = hashtable[hash(key)];if(map.containsKey(key))map.put(key, value);else{map.put(key, value);size ++;if(size >= upperTol * M && capacityIndex + 1 < capacity.length){//防止数组越界capacityIndex ++;resize(capacity[capacityIndex]);}}}public V remove(K key){V ret = null;TreeMap<K, V> map = hashtable[hash(key)];if(map.containsKey(key)){ret = map.remove(key);size --;if(size < lowerTol * M && capacityIndex - 1 >= 0){capacityIndex --;resize(capacity[capacityIndex]);}}return ret;}public void set(K key, V value){TreeMap<K, V> map = hashtable[hash(key)];if(!map.containsKey(key))throw new IllegalArgumentException(key + " doesn't exist!");map.put(key, value);}public boolean contains(K key){return hashtable[hash(key)].containsKey(key);}public V get(K key){return hashtable[hash(key)].get(key);}private void resize(int newM){TreeMap<K, V>[] newHashTable = new TreeMap[newM];for(int i = 0 ; i < newM ; i ++)newHashTable[i] = new TreeMap<>();int oldM = M;this.M = newM;for(int i = 0 ; i < oldM ; i ++){TreeMap<K, V> map = hashtable[i];for(K key: map.keySet())newHashTable[hash(key)].put(key, map.get(key));}this.hashtable = newHashTable;}
}

我们哈希表的均摊复杂度为O(1),同时我们牺牲了顺序性。我们的算法难以避免的都是有得必有失的,有一种高级的数据结构是集合和映射,现在我们要有意识的把它分为基于平衡树的有序集合和有序映射以及基于哈希表的无序集合和无序映射。希望感兴趣的朋友可以尝试自己封装属于自己的无序集合和无序映射。

我们哈希表的bug

我们前面代码中提到我们的HashTable中的K是不需要具有可比较性的,这是哈希表的一个优点,同时我们也失去了哈希表的顺序性。但是我们的实现的TreeMap的k是需要继承Comparable具有可比较性的,这时K产生了矛盾,这是一个bug。

如果我们的每个地址对应一个链表 ,那么这个矛盾是不存在的,毕竟链表不需要有可比较性。我们前面提到的哈希冲突达到一定程度会转成红黑树,其实这种转换也是有条件的,前提就是实现了可比较性,不然就仍然保持链表,这样就保证了我们的代码是有逻辑的。

八、更多哈希冲突的处理方法

开放地址法

定义:对于我们哈希表中的每一个地址,所有的哈希值的元素都有可能进来。 每一个地址不再是存储链表了,而是直接存储元素的值,如果有两个元素产生了哈希冲突,那么第二个元素就放在第一个元素往后找下一个空的地方,如下图所示。

这种方法在某些情况下效率是很低的,比如如果一块很大的连续空间已经被占满了,这时又来了一个元素需要放在索引为1的位置,这时候它就需要从头开始一直往下找空的位置。我们对此的解决方法是不再每次只走一个位置了,而是使用平方探测法,遇到哈希冲突时依次+1 +4 +9 +16来寻找空的地址。我们也可以用二次哈希法,遇到哈希冲突时我们用另一个哈希函数来计算它要存放的地址。

对于开放地址法,我们也可以设计扩容操作,只要扩容的负载率的值选的合适,那么时间复杂度也可以是O(1)级别的。

其他

除了链地址法和开放地址法这两个有名的方法,还有再哈希法Rehashing、Coalesced Hashing,感兴趣的朋友可以自己去了解。 

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

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

相关文章

10、外观模式(Facade Pattern,不常用)

外观模式&#xff08;Facade Pattern&#xff09;也叫作门面模式&#xff0c;通过一个门面&#xff08;Facade&#xff09;向客户端提供一个访问系统的统一接口&#xff0c;客户端无须关心和知晓系统内部各子模块&#xff08;系统&#xff09;之间的复杂关系&#xff0c;其主要…

vscode插件离线下载

离线下载插件地址&#xff1a;https://marketplace.visualstudio.com/VSCode

SQL server 基线安全加固操作

目录 账号管理、认证授权 ELK-Mssql-01-01-01 ELK-Mssql-01-01-02 ELK-Mssql-01-01-03 ​​​​​​​ ELK-Mssql-01-01-04 ​​​​​​​ ELK-Mssql-01-01-05 ​​​​​​​ELK-Mssql-01-01-06 日志配置 ELK-Mssql-02-01-01 通信协议 ELK-Mssql-03-01-01 ​​​​​…

【i.MX6ULL】linux驱动bh1750模块

I2C-BH1750 1、设备地址 引脚说明 VCC5VGNDGNDSCLPB6SDAPB7ADDRVCC/GND bh1750设备的地址由引脚 ADDR 来决定 ADDR接GND 当ADDR引脚接地时设备bh1750的地址为&#xff1a;0x23(7bit) ADDR接VCC 当ADDR引脚接地时设备bh1750的地址为&#xff1a;0x5c(7bit) 2、工作模式 …

基于深度学习CRNN的水表读数识别系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着科技的不断发展&#xff0c;深度学习技术在各个领域都取得了显著的成果。其中&#xff0c;基于深度学习的图像识别技术在计算机视觉领域具有重要的应用价值。…

Linux 多线程(C语言) 备查

基础 1&#xff09;线程在运行态和就绪态不停的切换。 2&#xff09;每个线程都有自己的栈区和寄存器 1&#xff09;进程是资源分配的最小单位&#xff0c;线程是操作系统调度执行的最小单位 2&#xff09;线程的上下文切换的速度比进程快得多 3&#xff09;从应用程序A中启用应…

制作一个RISC-V的操作系统四-嵌入式开发介绍

文章目录 什么是嵌入式开发交叉编译查看一些GCC文件夹 调试器GDB相关语法命令 模拟器QEMUQEMU的安装和使用项目构造工具MakeMakeFile的构成make的运行 练习4-1练习4-2练习4-3 什么是嵌入式开发 程序跑到开发板上&#xff0c;或者说运行到硬件上 交叉编译 简单理解交叉编译来说…

API自动化测试:如何构建高效的测试流程

一、引言 在当前的软件开发环境中&#xff0c;API&#xff08;Application Programming Interface&#xff09;扮演了极为重要的角色&#xff0c;连接着应用的各个部分。对API进行自动化测试能够提高测试效率&#xff0c;降低错误&#xff0c;确保软件产品的质量。本文将通过实…

LeetCode 每日一题 Day 5【Hard】

2646. 最小化旅行的价格总和 现有一棵无向、无根的树&#xff0c;树中有 n 个节点&#xff0c;按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示树中节点 ai 和 b~i ~之间存在一条边。 每个节点都关…

OpenResty入门与实践:下载安装、环境变量、常用命令及案例解析

文章目录 一、Openresty下载安装二、设置环境变量三、常用命令四、入门案例五、实践案例1、lua-nginx-module1&#xff09;入门案例2&#xff09;获取Nginx uri中的单一变量3&#xff09;获取Nginx uri中的所有变量 2、Nginx缓存1&#xff09;Nginx全局共享内存缓存2&#xff0…

使用 MITRE ATTCK® 框架缓解网络安全威胁

什么是MITRE ATT&CK框架 MITRE Adversarial Tactics&#xff0c; Techniques&#xff0c; and Common Knowledge&#xff08;ATT&CK&#xff09;是一个威胁建模框架&#xff0c;用于对攻击者用来入侵企业、云和工业控制系统&#xff08;ICS&#xff09;并发起网络攻击…

《PFL》论文阅读笔记

一、概要 随着联邦学习的发展&#xff0c;简单的聚合算法已经不在有效。但复杂的聚合算法使得联邦学习训练时间出现新的瓶颈。本文提出了并行联邦学习&#xff08;parallel federated learning&#xff0c;PFL&#xff09;&#xff0c;通过调换中心节点聚合和广播的顺序。本文…

OpenHarmony亮相MTSC 2023 | 质量效率共进,赋能应用生态发展

11月25日&#xff0c;MTSC 2023第十二届中国互联网测试开发大会在深圳登喜路国际大酒店圆满举行。大会以“软件质量保障体系和测试研发技术交流”为主要目的&#xff0c;旨在为行业搭建一个深入探讨和交流的桥梁和平台。OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&a…

Spring Boot与Mybatis基础配置(手动写增删改查)

一、 配置 1.新建项目 1.项目基础配置 解释&#xff1a;记得把这个改成start.aliyun.com要不没有java8也就是jdk1.8 2.项目依赖配置 2.配置maven 配置前&#xff1a; 配置后&#xff1a; 3.创建子项目并配置父子项目pom.xml 配置父pom.xml 声明当前项目不是要打成jar包的…

反序列化漏洞详解(二)

目录 pop链前置知识&#xff0c;魔术方法触发规则 pop构造链解释&#xff08;开始烧脑了&#xff09; 字符串逃逸基础 字符减少 字符串逃逸基础 字符增加 实例获取flag 字符串增多逃逸 字符串减少逃逸 延续反序列化漏洞(一)的内容 pop链前置知识&#xff0c;魔术方法触…

学习UnitTest框架,轻松打造无懈可击的代码!

一、什么是UnitTest&#xff1f; 1、介绍 unittest是Python自带的一个单元测试框架&#xff0c;它可以做单元测试&#xff0c;也能用于编写和运行重复的测试工作。 它给自动化测试用例开发和执行提供了丰富的断言方法&#xff0c;判断测试用例是否通过&#xff0c;并最终生成…

纯js实现录屏并保存视频到本地的尝试

前言&#xff1a;先了解下&#xff1a;navigator.mediaDevices&#xff0c;mediaDevices 是 Navigator 只读属性&#xff0c;返回一个 MediaDevices 对象&#xff0c;该对象可提供对相机和麦克风等媒体输入设备的连接访问&#xff0c;也包括屏幕共享。 const media navigator…

python爬虫-某公开数据网站实例小记

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 第一步&#xff1a;分析页面和请求方式 此网站没有技巧的加密&#xff0c;仅是需要携带cookie和请求…

万界星空科技灯具行业MES介绍

中国是LED照明产品最大的生产制造国&#xff0c;如今&#xff0c;我国初步形成了包括LED外延片的生产、LED芯片的制备、LED芯片的封装以及LED产品应用在内的较为完超为产业链&#xff0c;随着LED照明市场渗诱率的快速警升&#xff0c;LED下游应用市场将会越来越广阔。这也将推动…

智能优化算法应用:基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.寄生捕食算法4.实验参数设定5.算法结果6.参考…