unordered-------Hash

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——哈希表
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:哈希是一种映射的思想,哈希表即使利用这种思想,在查找上进行很少的比较次数就能够将元素找到,非常的高效,在一定程度上,效率比红黑树还要强,因此在C++11中,STL又提供了4个unordered系列的关联式容器,他们的底层就是哈希。

目录

一.unordered系列关联式容器

1. unordered_map

1.1 unordered_map的构造

1.2unordered_map的容量

 1.3unordered_map的迭代器

1.4 unordered_map的元素访问

1.5unordered_map的查询 

1.6unordered_map的修改操作

 1.2unordered_set

二.哈希

1.哈希概念

2. 哈希冲突

3.哈希冲突解决

三.实现闭散列除留余数法+线性探测

1.整体结构

2.插入

3.查询

4.删除

四.开散列

1.开散列实现

2.插入

3.查询

4.删除

5.析构函数

五.完整代码即测试


 

一.unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 log{_{2}}^{N},即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好
的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset学生可查看文档介绍。

1. unordered_map

reference-------unordered_map

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是前向迭代器。

1.1 unordered_map的构造

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

1.2unordered_map的容量

 

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

 1.3unordered_map的迭代器

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器

1.4 unordered_map的元素访问

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。 

1.5unordered_map的查询 

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

1.6unordered_map的修改操作

函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素

 1.2unordered_set

reference-------unordered_set

二.哈希

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

1.哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log{_{2}}^{N}),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  • 插入元素

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

  • 搜索元素

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
取元素比较,若关键码相等,则搜索成功。

 该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)。

例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

 用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?发生位置冲突。

2. 哈希冲突

对于两个数据元素的关键字k1和k2,有k1 != k2,但有:Hash(k1) ==
Hash(k2),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

哈希碰撞的产生一部分原因是,哈希函数设计的不够合理。

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

 常见哈希函数:

1.直接定址法--(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀。
缺点:需要事先知道关键字的分布情况。
使用场景:适合查找比较小且连续的情况。

2. 除留余数法--(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。


3. 平方取中法--(了解)
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况。


4. 折叠法--(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况。


5. 随机数法--(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。通常应用于关键字长度不等时采用此法。

6. 数学分析法--(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如:

 假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况。

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

3.哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列。

闭散列:

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
那如何寻找下一个空位置
呢?

1. 线性探测

比如下面中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  •  插入

通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

  • 删除

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影
响。因此线性探测采用标记的伪删除法来删除一个元素。

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State{EMPTY, EXIST, DELETE};

三.实现闭散列除留余数法+线性探测

1.整体结构

	//状态  enum State{	EXIST,EMPTY,DELETE	};//数据结点template<class K, class V>struct HashDate {HashDate(){}HashDate(pair<K,V> kv):_kv(kv){}pair<K, V> _kv;State state = EMPTY;};//开放定址法哈希表template<class K, class V>class HashTable{typedef HashDate<K, V> Date;public://插入bool insert(pair<K, V> kv){}//删除bool erase(const K& key){}//查询pair<int,bool> find(const K& key){}private:vector<Date> _sh;    //数据存储size_t _n = 0;       //存储数据的个数};
}

2.插入

插入的步骤:

  1. 计算插入位置
  2. 线性探测
        bool insert(pair<K, V> kv){//开放定值法,线性探测解决冲突//1.计算插入位置int hashi = kv.first % _sh.size();int i = 1; int hashn = hashi;//r如果插入的位置已经有数据了,进行线性探测while (_sh[hashn].state == EXIST){hashn = hashi + i;hashn %= _sh.size();i++;}//探测空位置,插入数据_sh[hashn] = Date(kv);//修改状态_sh[hashn].state = EXIST;_n++;return true;}

注意:在计算插入位置时,我们楚留余数法,使用的是表的size,而不是capacity,是因为我们真正合法插入的位置,是size控制的。

扩容:

哈希表什么情况下进行扩容?如何扩容?

这里引入一个新的概念:负载因子

 我们约定当负载因子达到0.7时就扩容,扩容的步骤:

    bool insert(pair<K, V> kv){//如果插入的值已经存在if (find(kv.first).second){return false;}//扩容//负载因子为0.7if (_sh.size() == 0 || _n * 10 / _sh.size() == 7){//1.确定新的容量int newsize = _sh.size() == 0 ? 10 : _sh.size() * 2;//每一次扩容都要重新插入数据//2.创建新的哈希表HashTable<K, V> newHash;//3.将原表数据插入进去,复用已经实现的逻辑newHash._sh.resize(newsize);for (auto e : _sh){newHash.insert(e._kv);}//最后将新表与旧表交换即可_sh.swap(newHash._sh);}//开放定值法,线性探测解决冲突int hashi = kv.first % _sh.size();int i = 1; int hashn = hashi;while (_sh[hashn].state == EXIST){hashn = hashi + i;hashn %= _sh.size();i++;}_sh[hashn] = Date(kv);_sh[hashn].state = EXIST;_n++;return true;}

3.查询

查询步骤:

  1. 判空
  2. 探测查找
pair<K,bool> find(const K& key){//如果表中是空的,返回坐标,和插入falseif (_sh.empty()){return pair<K, bool>(-1, false);}//计算位置size_t hashi = key % _sh.size();int i = 1; int hashn = hashi;//线性探测直到遇到空while (_sh[hashn].state!=EMPTY){if (_sh[hashn].state == EXIST && _sh[hashn]._kv.first == key){//如果探测到,存在且键值相等,就返回该键值和turereturn pair<K, bool>(hashn,true);}hashn = hashi + i;hashn %= _sh.size();i++;if (hashn == hashi){//已经探测一圈回到了原点,即没找到return pair<K, bool>(-1, false);}}//遇到空没找到return pair<K, bool>(-1, false);}

4.删除

	    //删除bool erase(const K& key){//先查询,存在就删除,不存在直接返回falsepair<int, bool> retfind = find(key);if (!retfind.second){return false;}//删除后修改状态_sh[retfind.first].state = DELETE;return true;}

线性探测优点:实现非常简单。
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。
如何缓解呢?

四.开散列

开散列概念:

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

1.开散列实现

 开散列的结点和结构:

    template<class K, class V>struct HashDate{HashDate(pair<K, V> kv):_kv(kv),_next(nullptr){}pair<K, V> _kv;   //键值对HashDate* _next;  //下一个结点的指针};template<class K,class V>class Hashbucket{typedef HashDate<K, V> Date;typedef pair<K, V> KV;public:bool insert(KV kv){//...}Date* find(const K& key){//....}bool erase(const K& key){//...}~Hashbucket(){//....}private:vector<Date*> _table;  //数据存储size_t _n = 0;         //数据存储个数};
}

2.插入

因为_table的表中存储都是每个结点的指针,每一个桶实际上就是一个链表,所以对哈希的插入实际就是对链表的头插。

bool insert(KV kv)
{//插入//计算桶的位置size_t hashi = kv.first % _table.size();//创建结点Date* newNode = new Date(kv);//新节点的next指向头节点newNode->_next = _table[hashi];//新插入的结点变成新的头_table[hashi] = newNode;_n++;
}

扩容:

约定当数据个数达到桶的个数时,进行扩容:

扩容步骤:

bool insert(KV kv){//如果待插入的数据已经存在if ( find(kv.first)){return false;}//扩容if (_n == _table.size()){//计算新的桶数size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;//创建一个新的表vector<Date*> newtable;newtable.resize(newsize);//将就表中的结点拿过来,注意此处直接拿旧的结点插入新表,//而不是拿到数据后创建新的结点,减少不必要的消耗for (auto& cur : _table){while (cur){size_t hashi = cur->_kv.first % newtable.size();Date* next = cur->_next;cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}}//新旧表交换_table.swap(newtable);}//插入size_t hashi = kv.first % _table.size();Date* newNode = new Date(kv);newNode->_next = _table[hashi];_table[hashi] = newNode;_n++;}

3.查询

查询步骤:

  1. 找到桶
  2. 遍历桶中数据

找到了返回结点指针,没找到返回nullptr

		Date* find(const K& key){//如果表是空的,即就是没有一个数据if (_table.empty()){return nullptr;}//1.计算桶的位置size_t hashi = key % _table.size();Date* cur = _table[hashi];//2.在桶里面遍历查询while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}

4.删除

删除归根结底还是链表的删除:

删除的步骤:

  1. 判断在不在表中
  2. 计算出桶的位置
  3. 找打待删除结点,并记录其前一个结点
  4. 改变指向,释放结点

 

    bool erase(const K& key){//1.查找在不在表中if (!find(key)){return false;}//2.计算桶的位置size_t hashi = key % _table.size();Date* cur = _table[hashi];//记录前驱结点Date* prov = nullptr;//3.找到待删除的结点while (cur){if (cur->_kv.first == key){if (prov == nullptr){_table[hashi] = cur->_next;	}else{prov->_next = cur->_next;}//4.删除改变指向,释放结点delete cur;return true;}else{prov = cur;cur = cur->_next;}}}

5.析构函数

遍历表中每一个桶,并对每一个桶进行释放,对桶的销毁,就是对链表的销毁。

		~Hashbucket(){for (auto& cur : _table){while (cur){Date* next = cur->_next;delete cur;cur = next;}cur = nullptr;}}

五.完整代码即测试

#pragma once
#include<vector>
#include<iostream>
using namespace std;namespace HashOpenAdress
{enum State{EXIST,EMPTY,DELETE};template<class K, class V>struct HashDate{HashDate(){}HashDate(pair<K,V> kv):_kv(kv){}pair<K, V> _kv;State state = EMPTY;};template<class K, class V>class HashTable{typedef HashDate<K, V> Date;public:bool insert(pair<K, V> kv){//如果插入的值已经存在if (find(kv.first).second){return false;}//扩容//负载因子为0.7if (_sh.size() == 0 || _n * 10 / _sh.size() == 7){//确定新的容量int newsize = _sh.size() == 0 ? 10 : _sh.size() * 2;//每一次扩容都要重新插入数据HashTable<K, V> newHash;newHash._sh.resize(newsize);for (auto e : _sh){newHash.insert(e._kv);}_sh.swap(newHash._sh);}//开放定值法,线性探测解决冲突int hashi = kv.first % _sh.size();int i = 1; int hashn = hashi;while (_sh[hashn].state == EXIST){hashn = hashi + i;hashn %= _sh.size();i++;}_sh[hashn] = Date(kv);_sh[hashn].state = EXIST;_n++;return true;}bool erase(const K& key){pair<int, bool> retfind = find(key);if (!retfind.second){return false;}_sh[retfind.first].state = DELETE;return true;}pair<int,bool> find(const K& key){if (_sh.empty()){return pair<int, bool>(-1, false);}size_t hashi = key % _sh.size();int i = 1; int hashn = hashi;while (_sh[hashn].state!=EMPTY){if (_sh[hashn].state == EXIST && _sh[hashn]._kv.first == key){return pair<int,bool>(hashn,true);}hashn = hashi + i;hashn %= _sh.size();i++;if (hashn == hashi){return pair<int, bool>(-1, false);}}return pair<int, bool>(-1, false);}private:vector<Date> _sh; //数据存储size_t _n = 0;       //存储数据的个数};
}namespace HashBucket
{template<class K, class V>struct HashDate{HashDate(pair<K, V> kv):_kv(kv),_next(nullptr){}pair<K, V> _kv;HashDate* _next;};template<class K,class V>class Hashbucket{typedef HashDate<K, V> Date;typedef pair<K, V> KV;public:bool insert(KV kv){//如果待插入的数据已经存在if ( find(kv.first)){return false;}//扩容if (_n == _table.size()){//计算新的桶数size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;//创建一个新的表vector<Date*> newtable;newtable.resize(newsize);//将就表中的结点拿过来,注意此处直接拿旧的结点插入新表,//而不是拿到数据后创建新的结点,减少不必要的消耗for (auto& cur : _table){while (cur){size_t hashi = cur->_kv.first % newtable.size();Date* next = cur->_next;cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}}//新旧表交换_table.swap(newtable);}//插入size_t hashi = kv.first % _table.size();Date* newNode = new Date(kv);newNode->_next = _table[hashi];_table[hashi] = newNode;_n++;}Date* find(const K& key){//如果表是空的,即就是没有一个数据if (_table.empty()){return nullptr;}//1.计算桶的位置size_t hashi = key % _table.size();Date* cur = _table[hashi];//2.在桶里面遍历查询while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool erase(const K& key){//1.查找在不在表中if (!find(key)){return false;}//2.计算桶的位置size_t hashi = key % _table.size();Date* cur = _table[hashi];//记录前驱结点Date* prov = nullptr;//3.找到待删除的结点while (cur){if (cur->_kv.first == key){if (prov == nullptr){_table[hashi] = cur->_next;	}else{prov->_next = cur->_next;}//4.删除改变指向,释放结点delete cur;return true;}else{prov = cur;cur = cur->_next;}}}~Hashbucket(){for (auto& cur : _table){while (cur){Date* next = cur->_next;delete cur;cur = next;}cur = nullptr;}}private:vector<Date*> _table;size_t _n = 0;};
}

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

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

相关文章

前端基础1——HTML标记语言

文章目录 一、基本了解二、HTML常用标签2.1 文本格式化标签2.2 列表标签2.3 超链接标签2.4 图片标签2.5 表格标签2.6 表单标签2.6.1 提交表单2.6.2 下拉表单2.6.3 按钮标签 2.7 布局标签 一、基本了解 网页组成&#xff08;index.html页面&#xff09;&#xff1a; HTML标记语言…

Verilog开源项目——百兆以太网交换机(一)架构设计与Feature定义

Verilog开源项目——百兆以太网交换机&#xff08;一&#xff09;架构设计与Feature定义 &#x1f508;声明&#xff1a;未经作者允许&#xff0c;禁止转载 &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f511;全新原创以太网交换机项目&#xff0c;Blog内容将聚…

23.8.11.用apifox端口号与java接口链接的时候少了个/导致连接不成功。

用apifox端口号与java接口链接的时候少了个/导致连接不成功。 原因分析&#xff0c;因为拼接的位置少了个/ 如图所示

【Java转Go】快速上手学习笔记(六)之网络编程篇一

目录 TCP一个简单案例server.go 服务端client.go 客户端 HTTPserver.go 服务端client.go 客户端 RPC一个很简单的示例server.go 服务端client.go 客户端 WebSocketserver.go 服务端client.go 客户端 完整代码server.go 服务端client.go 客户端 go往期文章笔记&#xff1a; 【J…

(笔记四)利用opencv识别标记视频中的目标

预操作&#xff1a; 通过cv2将视频的某一帧图片转为HSV模式&#xff0c;并通过鼠标获取对应区域目标的HSV值&#xff0c;用于后续的目标识别阈值区间的选取 img cv.imread(r"D:\data\123.png") img cv.cvtColor(img, cv.COLOR_BGR2HSV) plt.figure(1), plt.imshow…

开始MySQL之路——MySQL 事务(详解分析)

MySQL 事务概述 MySQL 事务主要用于处理操作量大&#xff0c;复杂度高的数据。比如说&#xff0c;在人员管理系统中&#xff0c;你删除一个人员&#xff0c;你即需要删除人员的基本资料&#xff0c;也要删除和该人员相关的信息&#xff0c;如信箱&#xff0c;文章等等&#xf…

打造互动体验:品牌 DTC 如何转变其私域战略

越来越多的品牌公司选择采用DTC 模式与消费者进行互动&#xff0c;而非仅仅销售产品。通过与消费者建立紧密联系&#xff0c;DTC模式不仅可以提供更具成本效益的规模扩张方式&#xff0c;还能够控制品牌体验、获取宝贵的第一方数据并提升盈利能力。然而DTC模式的经济模型比许多…

Docker创建Consul并添加权限控制

一、部署Consul 1、拉取镜像&#xff1a; docker pull consul:<consul-version> 2、运行 docker run --name consul1 -p 8300:8300/tcp -p 8301:8301/tcp -p 8301:8301/udp -p 8302:8302/tcp -p 8302:8302/udp -p 8500:8500 -p 8600:8600/tcp -p 8600:8600/udp -v /h…

数据结构——栈

栈 栈的理解 咱们先不管栈的数据结构什么&#xff0c;先了解栈是什么&#xff0c;栈就像一个桶一样&#xff0c;你先放进去的东西&#xff0c;被后放进的的东西压着&#xff0c;那么就需要把后放进行的东西拿出才能拿出来先放进去的东西&#xff0c;如图1&#xff0c;就像图1中…

Android Studio调试出现错误时,无法定位错误信息解决办法

做项目时运行项目会出现问题&#xff0c;但是找不到具体位置&#xff0c;如下图所示&#xff1a;感觉是不是很懵逼~&#xff0c;Log也没有显示是哪里的问题 解决方案&#xff0c;在右侧导航栏中选择Gradle——app——build&#xff0c;然后点击运行 运行结果如下&#xff0c;很…

C#: Json序列化和反序列化,集合为什么多出来一些元素?

如下面的例子&#xff0c;很容易看出问题&#xff1a; 如果类本身的无参构造函数&#xff0c; 就添加了一些元素&#xff0c;序列化&#xff0c;再反序列化&#xff0c;会导致元素增加。 如果要避免&#xff0c;必须添加&#xff1a; new JsonSerializerSettings() { Object…

SQL语法与DDL语句的使用

文章目录 前言一、SQL通用语法二、DDL语句1、DDL功能介绍2、DDL语句对数据库操作&#xff08;1&#xff09;查询所有数据库&#xff08;2&#xff09;查询当前数据库&#xff08;3&#xff09;创建数据库&#xff08;4&#xff09;删除数据库&#xff08;5&#xff09;切换数据…

17 django框架(中)视图|模板

文章目录 框架介绍模型类视图视图的功能页面重定向 视图函数的使用url匹配过程错误视图补充 捕获url参数类型介绍 普通登录案例&#xff08;前情准备&#xff09;HttpReqeust 对象HttpResponse 对象QueryDict 对象&#xff08;即GET POST &#xff09;总结 ajaxajax的登录样例 …

C# task多线程创建,暂停,继续,结束使用

1、多线程任务创建 private void button1_Click(object sender, EventArgs e) //创建线程{CancellationToken cancellationToken tokensource.Token;Task.Run(() > //模拟耗时任务{for (int i 0; i < 100; i){if (cancellationToken.IsCancellationRequested){return;…

开发卡牌gamefi游戏需要多少钱?

卡牌游戏作为一种受欢迎的游戏形式&#xff0c;吸引了众多开发者的关注。然而&#xff0c;开发一款成功的卡牌游戏需要全面考虑多个方面的因素&#xff0c;其中之一就是资金投入。本文将从专业性和投入回报的角度&#xff0c;探讨开发一款卡牌游戏所需的资金投入。 一、专业性的…

Ansible项目实战管理/了解项目环境/项目管理

一&#xff0c;项目环境 1.项目基础 项目过程 调研阶段 设计阶段 开发阶段 测试阶段 运营阶段 2.项目环境 个人开发环境 公司开发环境 项目测试环境 项目预发布环境 灰度环境&#xff1a;本身是生产环境&#xff0c;安装项目规划&#xff0c;最终所有的生产环境都发…

最新Nmap入门技术

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 Nmap详解 Nmap&#xff08;Network Mapper&#xff0c;网络映射器&#xff09;是一款开放源代码的网络探测和安全审核工具。它被设计用来快速扫…

Linux操作系统--linux概述

1.Linux概述 Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff08;OS&#xff09;。简单的说就是一种操作系统。在日常中常见的操作系统有一下三种: 2.linux起源和背景 (1).linux的诞生 linux操作系统是由李纳斯托瓦兹&#xf…

小研究 - J2EE 应用服务器的软件老化测试研究

软件老化现象是影响软件可靠性的重要因素&#xff0c;长期运行的软件系统存在软件老化现象&#xff0c;这将影响整个业务系统的正常运行&#xff0c;给企事业单位带来无可估量的经济损失。软件老化出现的主要原因是操作系统资源消耗殆尽&#xff0c;导致应用系统的性能下降甚至…

读书笔记——《万物有灵》

前言 上一本书是《走出荒野》&#xff0c;太平洋步道女王提到了这本书《万物有灵》&#xff0c;她同样是看一点撕一点的阅读。我想&#xff0c;在她穿越山河森林&#xff0c;听见鸟鸣溪流的旅行过程中&#xff0c;是不是看这本描写动物有如何聪明的书——《万物有灵》&#xf…