unordered_map 与 unordered_set 的模拟实现

unordered_map 与 unordred_set 的模拟实现与 map 与 set 的模拟实现差不多。map 与 set 的模拟实现中,底层的数据结构是红黑树。unordered_map 与 unordered_set 的底层数据结构是哈希表。因此,在模拟实现 unordered_map 与 unordred_set 之前你必须确保你已经熟练的掌握哈希表。如果你还对哈希表感到陌生,你可以参考我之前写过的这两篇文章:
哈希表开散列的实现
我们这里实现的 unordered_mapunordered_set 是以开散列实现的哈希表作为底层数据结构的哦!

修改存储的数据

在 unordered_map 与 unordred_set 的使用部分,我们知道:他俩存储的数据类型是不一样的,为了让哈希表能同时适配出 unordered_mapunordered_set 我们就需要对 HashNode 进行一定程度的修改。原理比较简单哈,因为他们俩的数据类型不一样,我们只需要将他俩存储的数据类型模板化即可。通过传入不同的数据类型,实例化出 HashNode 存储不同元素的哈希表。

  • 当这个模板参数传入 pair 那么是 unordered_map
  • 当这个模板参传入的不是一个 pair 那么就是一个 unordered_set
template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};

我们将原本两个模板参数改为了一个,传入什么类型,那么 HashNode 就存储这个类型。

key 的获取

对于 unnordered_set 来说,他的 key 值就是模板参数 T。但是对于 unordered_map 来说,他的 key 值却是:T.first。因为 unordered_map 存储的是一个 pair 嘛。
怎么解决呢?处理思路和模拟实现 mapset 是一样的。通过仿函数来实现。
因此在 HashTable 中又要增加一个模板参数,不妨叫做:KeyOfT。在这个仿函数中,我们会根据传入的数据类型获取他的 key 值。

  • 对于 unordered_map 来说仿函数会返回传入参数 first。因为它存储的是 pair 嘛。key 值就是他的 first。
  • 对于 unordered_set 来说,仿函数直接将传入参数本身返回。因为他存储的数据就是 key 值本身嘛!
//这是 unordered_set.h 的代码
#pragma once
#include"Hash.h"template<class K>
class unordered_set
{
public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:HashTable<K, K, SetKeyOfT> _ht;
};

在上面的代码中,我们定义了一个结构体 SetOfT 在这个结构体中,我们重载了圆括号运算符。然后将这个类型传入 HashTable 用来获取 unorered_set 的 key 值。你可能会问:为什么还要传入一个 K 值来实例化哈希表呢?不着急等会儿马上为你解答。

//这是 unordered_map.h 中的代码
#pragma once
#include "Hash.h"template<class K, class V>
class unordered_map
{
public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
private:HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};

同样的,我们定义了一个结构体:MapOfT 在这个结构体结构体中,重载了圆括号运算符,然后将这个结构体类型传入 HashTable 中用来获取 unordered_map 的 key 值。


我们修改了 HashNode 的存储数据, HashTable 的模板参数。那么 HashTable 中的代码也需要做相应的修改。
我们先来看 HashTable 的模板参数:
在这里插入图片描述

  • K:有了这个模板参数,HashTable 的函数参数的类型能更加好写:
    在这里插入图片描述
    在这里插入图片描述
    上面的两个成员函数中,我们都需要用到 key 的类型来定义函数的形参。如果 HashTable 的模板参数不加上 K,我们就很难获取 unordered_mapunoredred_set 的 key 值的类型。这便是为啥要在 unordered_map.hunordered_set 中实例化哈希表的时候传入 key 值的类型。是不是非常精妙。

  • T:存储的数据类型,unordered_map 就是 pair<K, V>unordered_set 就是:K。

  • KeyOfT:用该类型实例化出来对象,调用 operator(),仿函数嘛!获取 key 值。

  • HashFunc:这个模板参数可以处理字符串作为 key 值的情况。详细的逻辑在之前的文章:哈希表闭散列实现中讲过。


我们再来看 HashTable 中代码的修改:

  • insert 函数的参数不再是 const pair<K, V>& kv ,而是 const T& data
  • 所有的获取 key 值的地方,都要使用仿函数来获取。

普通迭代器

不用说,哈希表的迭代器肯定不是原生指针。因为每一个哈希表节点数据并不是连续的物理空间。
我们就要思考如何封装哈希表的迭代器:

  • 首先,类中肯定包含 HashNode 的指针。
  • 其次,在对迭代器进行加加或者减减运算时,可能会跨越不同的哈西桶,这个时候就比较难办了!不妨先想想我们应该如何解决。
    在这里插入图片描述
    • 假设我们当前迭代器指向的 HashNode 的值为 44,那么当这个迭代器加加之后,我们就要跨越当前的哈希桶,找到下一个有效的 HashNode
    • 再假设我们当前迭代器指向的 HashNode 的值为 7,那么当这个迭代器减减之后,我们也要跨越当前哈希桶,向前找到一个有效的 HashNode
  • 刚才谈到,我们迭代器加加或者减减之后可能跨越哈希桶。那么是不是要在迭代器中封装当前的哈希桶在数组中的下标呢?仔细一想其实是不需要的,因为我们可以通过当前迭代器,拿到存储数据的 key 值,经过除留余数法获得其在数组中的下标。
    但是另外的一个问题就暴露出来了:跨越哈希桶的时候,迭代器中拿不到哈希表的 _table 哇,我们就无法向前或者向后寻找一个有效的 HashNode。因此,在迭代器中我们可以封装一个 HashTable 的指针,在跨越哈希桶的时候就能顺利地向前或者向后查找啦!(当然实现的方法有很多,这取决于你的想象力啦!比如:你可以将 _table 传过来)
template <class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
struct __HashIterator
{typedef HashNode<T> Node;typedef __HashIterator<K, T, KeyOfT, HashFunc> self;//构造函数__HashIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht){_node = node;_pht = pht;}HashTable<K, T, KeyOfT, HashFunc>* _pht;Node* _node;
};

operator++()

  • 如果当前迭代器的 _node->_next 不为 nullptr,说明当前节点的 _next 是一个有效的节点。我们直接修改 _node_node->_next 即可。
  • 如果当前迭代器的 _node->_next 为空,那么,我们就需要寻找下一个位置。
    • 首先我们需要计算当前迭代器的 _nodeHashTable 的哪个下标。
    • 然后从计算出来的下标的下一个位置开始,查找下一个不为空的哈希桶。
    • 如果找不到不为空的哈希桶,说明当前迭代器的节点已经是哈希表的最后一个有效元素了。我们可以令 _nodenullptr 充当我们 end 迭代器。
self operator++()
{if(_node->_next) //当前的哈希桶还有节点{_node = _node->_next;return *this;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();++hashi;while(hashi < _pht->_table.size()){if(_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;return *this;}
}

operator!=()

这个函数简单,直接用节点的指针比较就可以啦!

bool operator!=(const self& s)
{return _node != s._node;
}

operator*() 与 operator->()

这两个函数我们也写了很多遍了。分别返回数据域和数据域的指针就行。

T& operator*()
{return _node->_data;
}T* operator->()
{return &_node->_data;
}

begin() 和 end()

这两个函数是在 HashTable 的类中写,千万不敢写迷糊了。

  • begin():我们只需要遍历 _table 找到第一个不为空的哈希桶。返回这个哈希桶的第一个节点就行。如果找不到,就用 nullptr 作为迭代器构造函数的第一个参数返回。
  • end():直接用 nullptr 作为迭代器的第一个参数即可。
    __HashIterator 中,构造函数的第二个参数是哈希表的指针,begin 与 end 返回时第二个参数应该怎么传递呢?是不是就是 this 指针哇!这就是为什么我们将哈希表的指针作为迭代器成员的原因,因为简单嘛!如果是 _table 也行,总归要麻烦点。
iterator begin()
{for(size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if(cur) return iterator(cur, this);}return iterator(nullptr, this);
}iterator end()
{return iterator(nullptr, this);
}

你编译一下代码,发现编译不通过:原因就是在迭代器中你要用哈希表,在哈希表中你又要用迭代器。无论将哪一个类放在前面都行不通。因此需要加前置声明。

template <class K, class T, class KeyOfT, class HashFunc>
class HashTable;

unorered_mapunordered_set 添加 begin() 与 end()

这两个函数都比较简单哈!我们已经在 HashTable 中实现了 begin 与 end 函数。因此只需要在这两个容器中分别调用 begin 与 end 函数即可。

iterator begin()
{return _ht.begin();
}iterator end()
{return _ht.end();
}

上面的代码在 unordred_mapunordered_set 中都搞一份就行啦!

代码写到这里我们就可以使用范围 for 遍历我们自己实现的 unordred_mapunordered_set 了。但是我们运行代码之后又报了一个错误。说是:_table 不可访问。在迭代器中我们封转了哈希表的指针。但是因为 _table 是哈希表中的私有成员,外部当然不能访问啦!
在这里插入图片描述
这里就有两种比较靠谱的解决方式:

  • 写一个函数,返回 HashTable 的 _table 成员。
  • 用友元,谁是谁的友元呢?当然是 __HashIteratorHashTable 的友元啦!
template <class K, class T, class KeyOfT, class HashFunc>
friend struct __HashIterator;

上面的代码在 visual studio 中运行是没有问题的,但是在 vscode 中就不能运行。报错的提示是:嵌套作用域不能使用相同的模板参数。如果你遇到了这样的报错提示,那么随便改一下模板参数的名称即可!

const 迭代器

首先我们要明确普通迭代器与 const 迭代器的区别,在之前封转 list,map,set 我们就已经知道,其实就是 begin 与 end 的返回值类型不一样嘛!处理方式都是一样的:将不一样的地方参数化即可!因此我们又要向 __HashIterator 中加入模板参数啦!

template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct __HashIterator
  • Ref:表示返回引用,如果是普通迭代器传入的 Ref 就是 T&,返回的也就是 T& 啦!如果是 const 迭代器,传入的 Ref 就是 const T&,返回的自然也是 const T& 啦!
  • Ptr:表示返回指针。如果是普通迭代器传入的 Ptr就是 T*,返回的也就是 T* 啦!如果是 const 迭代器,传入的 Ptr就是 const T*,返回的自然也是 const T* 啦!

因此:在 HashTable 中就要传入不同的参数类型,来定义出普通迭代器和 const 迭代器。

typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
typedef __HashIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;

实现了 cosnt_iterator 之后呢?我们就需要为 HashTable 添加 const 迭代器的版本,也是非常简单呢!添加完成了之后,我们再为 unordred_mapunordered_set 添加 cosnt 迭代器。其中 unordered_set 的 key 值不允许修改,因此 unordered_set 的 iterator 与 const_iterator都是 const_iterator。
map 解决 key 不能被修改的方法就是:在 pair 中为 K 加上 const。
但是这么修改之后报错:
在这里插入图片描述

invalid conversion from ‘const HashTable<int, int, unordered_set::SetKeyOfT, DefaultHashFunc >’ to ‘HashTable<int, int, unordered_set::SetKeyOfT, DefaultHashFunc >’ [-fpermissive]

这是什么原因呢?begin() const 中的 const 修饰的是 this 指向的内容。因此,这里的 this 的完整类型为:const HashTable<K, T, KeyOfT, HashFunc> * 但是呢?迭代器的构造函数的第二个参数是:HashTable<K, T, KeyOfT, HashFunc>* pht
非 const 是无法转化为 const 类型的,因此会报错,解决办法就是在构造函数的参数加上 cosnt 就行。并且将 __HashIterator 的哈希表指针的成员改为 const。因为迭代器中不会通过哈希表的指针修改哈希表的内容。因此这么写完全没有问题呢!

修改 Find 返回值

在库函数中 find 的返回值是 iterator。因此我们也需要修改。修改的方法很简单,只需在返回的地方处理一下就可以啦!
在返回的地方调用构造函数就可以啦!

iterator Find(const K &key)
{HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node *cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);
}

修改 insert 函数的返回值

首先我们得知道库函数中 Insert 的返回值:pair<iterator, bool>

  • 在判断哈希表中是否已经存在这个元素的时候,需要用 iterator 接收 Find 函数的返回值。通过这个返回值与 end 做比较,如果不等于 end 说明哈希表中已经存在这个元素了,我们返回这个迭代器,和 false 构造出来的 pair 即可。
  • 如果是新插入,就用新插入的节点构造一个迭代器对象和 true 一起打包成 pair 返回即可。
    在这里插入图片描述
    在这里插入图片描述
    好的,我们现在已经修改完成了 HashTable 中的 Insert 函数。之后呢,我们顺利的修改了 unordered_mapunordered_set 中的 insert 函数,但是修改之后编译,发现又出问题了!
    在这里插入图片描述
    如上图,在 unordered_set 中,insert 的返回值虽然看上去是:pair<iterator, bool> 但是因为 unordered_set 的 iterator 与 const_iterator 都是 const_iterator 。因此,unordered_set 的 insert 函数返回值的实际类型是:pair<const_iterator, bool> 自然会出现类型不兼容,无法转换的问题。
    解决办法在 mapset 的模拟实现部分已经讲解过了。
    我们可以为 __HashIterator 加上一个非常像构造函数的函数:
typedef __HashIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;
__HashIterator(const Iterator& it):_node(it._node),_pht(it._pht)
{}

Iterator 是用 T*,T&定义的,而不是 Ref 和 Ptr,因此当这个迭代器是普通迭代器的时候,就是拷贝构造函数。当这个迭代器是 const 迭代器的时候,就是一个类型转换。非常巧妙。

unordered_map 的 opertor[]

原理比较简单,就是调用 insert 函数。无论插入成功还是失败,都将 insert 返回值对应的 iterator 的数据中的 second 返回即可。

V& operator[](const K& key)
{pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;
}

完整代码

Hash.h

#pragma once
#include <vector>template <class K>
struct DefaultHashFunc
{size_t operator()(const K &key){return (size_t)key;}
};// 12:00
template <>
struct DefaultHashFunc<string>
{size_t operator()(const string &str){// BKDRsize_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};template <class T>
struct HashNode
{T _data;HashNode<T> *_next;HashNode(const T &data): _data(data), _next(nullptr){}
};template <class K, class T, class KeyOfT, class HashFunc>
class HashTable;template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct __HashIterator
{typedef HashNode<T> Node;typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> self;typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> Iterator;//构造函数__HashIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht){_node = node;_pht = pht;}__HashIterator(const Iterator& it):_node(it._node),_pht(it._pht){}self operator++(){if(_node->_next) //当前的哈希桶还有节点{_node = _node->_next;return *this;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();++hashi;while(hashi < _pht->_table.size()){if(_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;return *this;}}bool operator!=(const self& s){return _node != s._node;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}const HashTable<K, T, KeyOfT, HashFunc>* _pht;Node* _node;
};template <class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:typedef HashNode<T> Node;typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;typedef __HashIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;private:template <class U, class Q, class W, class E, class Y, class I>friend struct __HashIterator;public:iterator begin(){for(size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if(cur) return iterator(cur, this);}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{for(size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if(cur) return const_iterator(cur, this);}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}HashTable(){_table.resize(10, nullptr);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node *cur = _table[i];while (cur){Node *next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}pair<iterator, bool> Insert(const T &data){KeyOfT kot;iterator it = Find(kot(data));if(it != end()){return make_pair(it, false);}HashFunc hf;// 负载因子到1就扩容if (_n == _table.size()){// 16:03继续size_t newSize = _table.size() * 2;vector<Node *> newTable;newTable.resize(newSize, nullptr);// 遍历旧表,顺手牵羊,把节点牵下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node *cur = _table[i];while (cur){Node *next = cur->_next;// 头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();// 头插Node *newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}iterator Find(const K &key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node *cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}bool Erase(const K &key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node *prev = nullptr;Node *cur = _table[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", i);Node *cur = _table[i];while (cur){cout << cur->_kv.first << ":" << cur->_kv.second << "->";cur = cur->_next;}printf("NULL\n");}cout << endl;}private:vector<Node *> _table; // 指针数组size_t _n = 0;		   // 存储了多少个有效数据
};

unodered_map.h

#pragma once#include "Hash.h"template<class K, class V>
class unordered_map
{
public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}private:HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};

unordered_set.h

#pragma once
#include"Hash.h"template<class K>
class unordered_set
{
public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename HashTable<K, K, SetKeyOfT>::const_iterator iterator;typedef typename HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const K& key){pair<typename HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}iterator begin() const{return _ht.begin();}iterator end() const{return _ht.end();}private:HashTable<K, K, SetKeyOfT> _ht;
};

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

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

相关文章

开源语音大语言模型来了!阿里基于Qwen-Chat提出Qwen-Audio!

论文链接&#xff1a;https://arxiv.org/pdf/2311.07919.pdf 开源代码&#xff1a;https://github.com/QwenLM/Qwen-Audio 引言 大型语言模型&#xff08;LLMs&#xff09;由于其良好的知识保留能力、复杂的推理和解决问题能力&#xff0c;在通用人工智能&#xff08;AGI&am…

基于51单片机超声波测距汽车避障系统

**单片机设计介绍&#xff0c; 基于51单片机超声波测距汽车避障系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的超声波测距汽车避障系统是一种用于帮助汽车避免碰撞和发生事故的设备&#xff0c;以下是一个基本…

Nginx安装与配置、使用Nginx负载均衡及动静分离、后台服务部署、环境准备、系统拓扑图

目录 1. 系统拓扑图 2. 环境准备 3. 服务器安装 3.1 mysql&#xff0c;tomcat 3.2 Nginx的安装 4. 部署 4.1 后台服务部署 4.2 Nginx配置负载均衡及静态资源部署 1. 系统拓扑图 说明&#xff1a; 用户请求达到Nginx若请求资源为静态资源&#xff0c;则将请求转发至静态…

轻量级web开发框架:Flask本地部署及实现公网访问界面

轻量级web开发框架&#xff1a;Flask本地部署及实现公网访问界面 文章目录 轻量级web开发框架&#xff1a;Flask本地部署及实现公网访问界面前言1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 前言 本篇文章讲解如何…

指针运算详解

1.引入 指针的基本运算有三种&#xff0c;分别是&#xff1a; • 指针- 整数 • 指针-指针 • 指针的关系运算 2.指针- 整数 因为数组在内存中是连续存放的&#xff0c;只要知道第⼀个元素的地址&#xff0c;顺藤摸⽠就能找到后⾯的所有元素。 int arr[10] {1,2,3,4,5,…

Node.js入门指南(三)

目录 Node.js 模块化 介绍 模块暴露数据 导入模块 导入模块的基本流程 CommonJS 规范 包管理工具 介绍 npm cnpm yarn nvm的使用 我们上一篇文章介绍了Node.js中的http模块&#xff0c;这篇文章主要介绍Node.js的模块化&#xff0c;包管理工具以及nvm的使用。 Node…

JVM 内存分析工具 MAT及实践

线程分析工具 MAT 官网下载地址&#xff1a;http://www.eclipse.org/mat/downloads.php mat百度网盘链接&#xff1a;&#xff08;速度更快&#xff09; 链接&#xff1a;https://pan.baidu.com/s/1tMp8MQIXuPtg9zBgruO0Ug?pwdjqtv 提取码&#xff1a;jqtv jdk17 百度网盘链接…

ElasticSearch查询语法及深度分页问题

一、ES高级查询Query DSL ES中提供了一种强大的检索数据方式,这种检索方式称之为Query DSL&#xff08;Domain Specified Language 领域专用语言&#xff09; , Query DSL是利用Rest API传递JSON格式的请求体(RequestBody)数据与ES进行交互&#xff0c;这种方式的丰富查询语法…

Unity 讯飞 之 讯飞星火大模型的简单封装和使用(补充讯飞大模型识图功能)

Unity 讯飞 之 讯飞星火大模型的简单封装和使用&#xff08;补充讯飞大模型识图功能&#xff09; 目录 Unity 讯飞 之 讯飞星火大模型的简单封装和使用&#xff08;补充讯飞大模型识图功能&#xff09; 一、简单介绍 二、实现原理 三、注意事项 四、效果预览 五、案例简单…

php通过curl方式发送接受xml数据

目录 1、php通过curl方式发送xml数据 2、php通过file_get_contents接受curl方式发送xml数据 1、php通过curl方式发送xml数据 <?php function sendXmlData($url, $xmlData) {$ch curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFE…

Redis高并发缓存架构

前言&#xff1a; 针对缓存我们并不陌生&#xff0c;而今天所讲的是使用redis作为缓存工具进行缓存数据。redis缓存是将数据保存在内存中的&#xff0c;而内存的珍贵性是不可否认的。所以在缓存之前&#xff0c;我们需要明确缓存的对象&#xff0c;是否有必要缓存&#xff0c;怎…

MTK联发科MT6762/MT6763/MT6765安卓核心板参数规格比较

MT6762安卓核心板 MTK6762安卓核心板是一款工业级高性能、可运行 android9.0 操作系统的 4G智能模块。 CPU&#xff1a;4xCortex-A53 up to 2.0Ghz/4xCortex-A53 up to 1.5GhzGraphics&#xff1a;IMG GE8320 Up to 650MhzProcess&#xff1a;12nmMemory&#xff1a;1xLP3 9…