C++之set和map的运用

目录

序列式容器和关联式容器

熟识set

在STL中的底层结构:

set的构造和迭代器

set的增删查

multiset和set的差异

练习题:

熟识map

map类的介绍

pair类型介绍

map的构造

map的增删查

map的数据修改

测试样例:

multimap和map的差异

练习


序列式容器和关联式容器

前⾯我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧密的关联关系,⽐如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构,两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。


本章节讲解的map和set底层是红⿊树,红⿊树是⼀颗平衡⼆叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。

熟识set

在STL中的底层结构:

set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模版参数。

set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参数。 ⼀般情况下,我们都不需要传后两个模版参数。
 set底层是⽤红⿊树实现,增删查效率是 O(logN) ,迭代器遍历是⾛的搜索树的中序,所以是有序的。

set的构造和迭代器

⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都不⽀持迭代器修改数据,修改
关键字数据,破坏了底层搜索树的结构。set迭代器是一个双向迭代器(支持的操作:*(解引用)、++(递增)、--(递减)、==!=(比较))。

set的构造我们关注以下⼏个接⼝即可。

// empty (1) ⽆参默认构造
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷⻉构造
set (const set& x);
// initializer list (5) initializer 列表构造
set (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator
-> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

测试用例: 

void test_set()
{//无参默认构造set<int> s1;//迭代器区间构造vector<int> v1;for (int i = 0; i < 10; i++){v1.push_back(i);}set<int> s2(v1.begin(), v1.end());for (auto& e : s2){cout << e<<' ';}cout << endl;//拷贝构造set<int> s3(s2);for (auto& e : s3){cout << e <<' ';}cout << endl;//列表构造set<int> s4({ 4,5,6,7,8 });for (auto& e : s4){cout << e << ' ';}cout << endl;
}

set的增删查

set的增删查关注以下⼏个接⼝即可:

Member types
key_type
-> The first template parameter (T)
value_type -> The first template parameter (T)
// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回大于等于val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回小于val位置的迭代器
iterator upper_bound (const value_type& val) const;

测试用例:

void test_set()
{	int a[] = { 3,1,4,6,7,9,2,5,9,5,1 };set<int> s1;for(auto& e:a){//单个数据插入s1.insert(e);}for (auto& e : s1){cout << e << ' ';}cout << endl;set<int>s2;//列表插入s2.insert({ 3,1,4,6,7,9,2,5,9,5,1 });for (auto& e : s1){cout << e << ' ';}cout << endl;vector<int> v1;for (int i = 0; i < 10; i++){v1.push_back(i);}set<int>s3;//迭代器区间插入//s3.insert(v1.begin(), v1.end());s3.insert(s1.begin(), s1.end());for (auto& e : s3){cout << e << ' ';}cout << endl;auto it = s1.lower_bound(2);auto it1 = s1.upper_bound(5);//删除大于等于2小于等于5的区间s1.erase(it, it1);for (auto& e : s1){cout << e << ' ';}cout << endl;//find//用算法库寻找O(N)auto pos1=find(s1.begin(), s1.end(), 9);//用set的成员函数寻找O(logN)auto pos2=s1.find(9);//利用count快速寻找int x;//set不冗余,所以即使有相同的,在s1中存在的个数都为1cin >>x ;if (s1.count(x)){cout << "在" << endl;}else{cout << "不在" << endl;}
}

multiset和set的差异

multiset和set的使⽤基本完全类似,主要区别点在于multiset⽀持值冗余,那么
insert/find/count/erase都围绕着⽀持值冗余有所差异,具体参看下⾯的样例代码理解。

#include<iostream>
#include<set>
using namespace std;
int main()
{
// 相⽐set不同的是,multiset是排序,但是不去重
multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
// 相⽐set不同的是,count会返回x的实际个数
cout << s.count(x) << endl;
// 相⽐set不同的是,erase给值时会删除所有的x
s.erase(x);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}

练习题:

349. 两个数组的交集

思路:先让两个数组用set排序,再去遍历两个对象中的数据,如果第一个对象中的数比第二个对象中的数据要大,那么这时候我们就要让第二个对象向后走;同理,如果第一个对象中的数比第二个对象中的数据要小,那么这时候我们就要让第一个对象向后走;如果相同我们就尾插进vector对象中,同时记得set对象同时都要向后走。

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {set<int>s1(nums1.begin(),nums1.end());set<int> s2(nums2.begin(),nums2.end());vector<int> v1;auto it1=s1.begin();auto it2=s2.begin();while(it1!=s1.end()&&it2!=s2.end()){if(*it1<*it2){it1++;}else if(*it1>*it2){it2++;}else{v1.push_back(*it1);it1++;it2++;}}return v1;}
};

142. 环形链表 II

当时在学习数据结构的时候我们也遇到过这道题,当时我们运用了快慢指针,即快指针走三步,而慢指针走一步。

但今天我们学习了set,就可以轻松解决这道题。在上面学习count成员函数的时候,我们可以直接用count来查找val是否存在。

思路:定义一个存放节点指针的对象,对链表进行循环的过程中用count成员函数检查set中是否存在节点,如果没有就插入进对象中,如果有就返回该节点(进入环的节点)。如果遍历完了链表还没有找到,那么就是单链表,不存在环,返回nullptr。

class Solution {
public:ListNode *detectCycle(ListNode *head) {set<ListNode*>s1;ListNode*cur=head;while(cur){if(s1.count(cur)){return cur;}s1.insert(cur);cur=cur->next;}return nullptr;}
};

熟识map

map类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实现,增删查改效率是 O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。

pair类型介绍

map底层的红⿊树节点中的数据,使⽤pair<Key, T>存储键值对数据。 

typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}

map的构造

map的⽀持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,map⽀持修改value数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构。//pair(key,value)

// empty (1) ⽆参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷⻉构造
map (const map& x);
// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator
-> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

map的增删查

map接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value.

Member types
key_type
-> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type
-> pair<const key_type,mapped_type>
// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;

map的数据修改

map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有⼀个⾮常重要的修改接⼝operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数据,所以他是⼀个多功能复合接⼝.
需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为
mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的T映射值叫做value。

Member types
key_type
-> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type
-> pair<const key_type,mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),
//如果找到了通过iterator可以修改key对应mapped_type值
iterator find (const key_type& k);
// ⽂档中对insert返回值的说明
// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现
operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
⼀个是insert返回值pair<iterator,bool>
pair<iterator,bool> insert (const value_type& val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的
迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能
pair<iterator, bool> ret = insert({ k, mapped_type() });
iterator it = ret.first;
return it->second;
}

测试样例:

void  map_test()
{map<string, string>dict;dict.insert(make_pair ("sort", "排序"));dict.insert({ "left","左边" });dict.insert(pair<string, string>("second", "第二"));pair<string, string>kv("right", "右边");dict.insert(kv);//插入失败,冗余了dict.insert({ "left","左边,剩余" });auto it = dict. begin();while (it != dict.end()){cout << it->first << "->" << it->second<<endl;it++;}cout << endl;for (auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;string str;while (cin >> str){auto ret = dict.find(str);if (ret != dict.end()){cout << "找到了" << endl;}elsecout << "没找到" << endl;}}
void map_test1()
{//测试迭代器和[]运算符的使用string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
"苹果", "⾹蕉", "苹果", "⾹蕉" };map<string, int> countMap;/*for (auto& e : arr){auto ret = countMap.find(e);if (ret != countMap.end()){ret->second++;}else{countMap.insert({ e,1 });}}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;*///[]的用法//1.插入map<string, string> dict;dict["left"];//2.修改dict["right"] = "右边";dict["right"] = "右边,向右";//3.插入加修改dict["sort"] = "排序";for (auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;//上面的countMap可以运用[]计算个数for (auto& e : arr){countMap[e]++;}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;
}
int main()
{//map_test();map_test1();return 0;
}

multimap和map的差异

multimap和map的使⽤基本完全类似,主要区别点在于multimap⽀持关键值key冗余,那么
insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这⾥跟set和multiset完全⼀样,⽐如find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[],因为⽀持key冗余,[]就只能⽀持插⼊了,不能⽀持修改。

void multimap_test()
{multimap<string, string> dict;dict.insert({ "left","左边" });dict.insert({ "left","剩余" });for (auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;//dict["left"] = "激进分子";//报错,multimap中[]不能修改value值。
}
int main()
{multimap_test();return 0;
}

练习

138.随机链表的复制

在之前我们用C语言写这道题的时候,代码要敲很多,今天我们学习了map,就可以用map类轻松解决。

思路:我们建立一个map对象,pair中放两个节点的指针,当我们在遍历链表的时候,让新链表和原链表构成映射关系。(新链表和原链表有着一一对应的关系,可以通过原链表的节点就可以找到新链表的节点,且节点内容相同)

1.遍历链表。往新链表中插入节点,并让新链表的节点和原链表的节点构成映射关系。

2.处理random。在重新遍历数组,让新链表中的节点的random成员等于原链表中的节点的random。

class Solution {
public:Node* copyRandomList(Node* head) {//先复制单链表Node*cur=head;map<Node*,Node*>copyNode;Node*copyhead=nullptr;Node*copytail=nullptr;while(cur){if(copyhead==nullptr){copyhead=copytail=new Node(cur->val);}else{copytail->next=new Node(cur->val);copytail=copytail->next;}copyNode[cur]=copytail;//成映射关系cur=cur->next;}//复制cur=head;Node*pre=copyhead;while(cur){if(cur->random==nullptr){pre->random=nullptr;}else{pre->random=copyNode[cur->random];//不可pre->random=cur->random,
//指向的地址不一样,相当于pre节点的random指针指向了原链表,不是指向新链表。}cur=cur->next;pre=pre->next;}return copyhead;}
};

692. 前K个⾼频单词 - ⼒扣(LeetCode)
题目中让我们按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。在排序之前我们应该对数组中的字符串进行统计个数,然后才能按照它的要求排序。

该题有三种排序思路:

1.运用算法库中的sort函数进行排序。sort函数是运用快排的思想,是不稳定的,所以在仿函数的时候我们不仅要比较字典顺序还要比较出现频率。

2.运用stable_sort函数进行排序。在插入map对象的时候,map对象里的字符串都已经按照字典顺序进行排序了,而stable_sort函数具有稳定性,那么在仿函数中我们只需要比较出现的频率就可以了。

3.运用priority_queue容器适配器进行建大堆实现。在前面的学习中我们就了解到priority_queue是优先级队列,每个元素都会有一个优先级概念,会按照固定的顺序定义。 在C++ STL中,默认情况下,优先级队列(priority_queue)的首元素始终是最大的元素。

思路1:

class Solution {
public:
struct compare
{bool operator()(pair<string,int> v1,pair<string,int>v2){return (v1.second==v2.second&&v1.first<v2.first)||v1.second>v2.second;}
};vector<string> topKFrequent(vector<string>& words, int k) {map<string,int>mapsort;for(auto&e:words){mapsort[e]++;}vector<pair<string,int>>v(mapsort.begin(),mapsort.end());sort(v.begin(),v.end(),compare());vector<string>v1;for(int i=0;i<k;i++){v1.push_back(v[i].first);}return v1;}
};

思路2:

class Solution {
public:
struct compare
{bool operator()(pair<string,int> v1,pair<string,int>v2){return v1.second>v2.second;}
};vector<string> topKFrequent(vector<string>& words, int k) {map<string,int>mapsort;for(auto&e:words){mapsort[e]++;}vector<pair<string,int>>v(mapsort.begin(),mapsort.end());stable_sort(v.begin(),v.end(),compare());vector<string>v1;for(int i=0;i<k;i++){v1.push_back(v[i].first);}return v1;}
};

思路3:

class Solution {
public:
struct compare
{bool operator()(pair<string,int> v1,pair<string,int>v2){//要注意优先级队列底层是反的,⼤堆要实现⼩于⽐较,所以这⾥次数相等,想要字典//序⼩的在前⾯,要⽐较字典序⼤的为真return (v1.second==v2.second&&v1.first>v2.first)||v1.second<v2.second;}
};vector<string> topKFrequent(vector<string>& words, int k) {map<string,int>mapsort;for(auto&e:words){mapsort[e]++;}//创建一个优先队列 p,其元素类型是 pair<string, int>。//使用 vector<pair<string, int>> 作为底层容器。//使用自定义的比较函数 Compare 来确定元素的优先级顺序。//将 mapsort 中的所有键值对初始化到优先队列 p 中,并按照 Compare 的规则进行排序。priority_queue<pair<string,int>,vector<pair<string,int>>,compare>p(mapsort.begin(),mapsort.end());vector<string>v1;for(int i=0;i<k;i++){v1.push_back(p.top().first);p.pop();}return v1;}
};

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

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

相关文章

【Bluedroid】蓝牙 SDP(服务发现协议)模块代码解析与流程梳理

本文深入剖析Bluedroid蓝牙协议栈中 SDP&#xff08;服务发现协议&#xff09;服务记录的全生命周期管理流程&#xff0c;涵盖初始化、记录创建、服务搜索、记录删除等核心环节。通过解析代码逻辑与数据结构&#xff0c;揭示各模块间的协作机制&#xff0c;包括线程安全设计、回…

【实战项目】简易版的 QQ 音乐:一

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能自我实现简易版的 QQ 音乐。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a…

Linux_进程退出与进程等待

一、进程退出 ‌退出场景‌ ‌正常终止‌&#xff1a;代码执行完毕且结果符合预期&#xff08;退出码为 0&#xff09;。‌异常终止‌&#xff1a;运行结果错误&#xff08;退出码非 0&#xff09;或进程被信号强制终止。&#xff08;如 SIGINT 或 SIGSEGV&#xff09;。 ‌退…

GD32F407单片机开发入门(二十八)USB口介绍及CDC类虚拟串口通讯详解及源码

文章目录 一.概要二.USB2.0基本介绍及虚拟串口介绍三.GD32单片机USB模块框图四.GD32单片机USB设备模式五.GD32F407VET6 USB设备CDC类六.配置一个USB虚拟串口收发例程七.工程源代码下载八.小结 一.概要 GD32F407VET6USB虚拟串口是一种采用GD32F407VET6单片机&#xff0c;通过US…

MySQL 主从配置超详细教程

文章目录 前言一、安装 MySQL二、主服务器&#xff08;Master&#xff09;配置三、从服务器&#xff08;Slave&#xff09;配置四、测试主从复制五、注意事项 前言 MySQL 主从配置是一种实用的数据库架构&#xff0c;主服务器处理写入操作&#xff0c;从服务器负责只读操作&am…

Python爬虫实战:获取百度学术专题文献数据并分析,为读者课题研究做参考

一、引言 在信息爆炸的当下,学术研究需要大量相关资料支撑。百度学术作为重要学术资源平台,蕴含丰富学术文献。利用爬虫技术获取百度学术特定主题文章数据,能为学术研究提供全面、及时信息。本研究旨在用 Python 实现对百度学术 “主题爬虫” 相关文章的爬取,并对数据深入…

手撕基于AMQP协议的简易消息队列-6(服务端模块的编写)

在MQServer中编写服务端模块代码 在MQServer中编写makefile文件来编译服务端模块 .PHONY: server CFLAG -I../ThirdLib/lib/include LFLAG -L../ThirdLib/lib/lib -lgtest -lprotobuf -lsqlite3 -pthread -lmuduo_net -lmuduo_base -lz server:server.cpp ../MQCommon/messag…

linux tar命令详解。压缩格式对比

1.压缩格式对比 压缩格式命令选项文件扩展名压缩率速度无压缩-cvf.tar无最快gzip-czvf.tar.gz中等较快bzip2-cjvf.tar.bz2较高较慢xz-cJvf.tar.xz最高最慢 9. 更多参考 【Linux基础】文件压缩tar命令指南tar压缩方式对比

解锁跨平台开发的新时代——Compose Multiplatform

解锁跨平台开发的新时代——Compose Multiplatform 在当今移动和桌面应用程序开发领域,跨平台解决方案是开发者们梦寐以求的工具。而由JetBrains打造的Compose Multiplatform正是这样一款现代UI框架,它基于Kotlin技术,为开发者构建高性能且美观的用户界面提供了极大的便利和…

【算法学习】递归、搜索与回溯算法(二)

算法学习&#xff1a; https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言&#xff1a; 在&#xff08;一&#xff09;中我们挑了几个经典例题&#xff0c;已经对递归、搜索与回溯算法进行了初步讲解&#xff0c;今天我们来进一步讲解…

HTTP请求与缓存、页面渲染全流程

文章目录 前言**1. HTTP请求与缓存处理****缓存机制**• 强缓存&#xff08;Cache-Control / Expires&#xff09;• 协商缓存&#xff08;Last-Modified / ETag&#xff09; **2. 服务器响应与数据解析****3. HTML DOM 构建****4. CSSOM 构建****5. 渲染树&#xff08;Render …

限流算法学习笔记(一)Go Rate Limiter

文章目录 1. 背景与概述1.1 什么是速率限制1.2 Go Rate Limiter 的定义与价值 2. 核心思想与设计理念2.1 令牌桶算法的基本原理2.2 惰性评估设计2.3 多种处理策略的平衡2.4 简单易用的偶发控制 3. 架构设计与组件3.1 整体架构3.2 Limiter 组件3.3 Reservation 组件3.4 Limit 类…

n8n工作流自动化平台的实操:生成统计图的两种方式

1.成果展示 1.1n8n的工作流 牵涉节点&#xff1a;Postgres、Code、QuickChart、Edit Fields、HTTP Request 12.显示效果 2.实操过程 2.1节点说明 2.1.1Postgres节点&#xff1a; 注&#xff1a;将明细数据进行汇总。 2.1.2code节点&#xff1a; 注&#xff1a;将 查询的数…

JavaScript中数组和对象不同遍历方法的顺序规则

在JavaScript中&#xff0c;不同遍历方法的顺序规则和适用场景存在显著差异。以下是主要方法的遍历顺序总结&#xff1a; 一、数组遍历方法 for循环 • 严格按数组索引顺序遍历&#xff08;0 → length-1&#xff09; • 支持break和continue中断循环 • 性能最优&#xff0c;…

缓存(1):三级缓存

三级缓存是指什么 我们常说的三级缓存如下&#xff1a; CPU三级缓存Spring三级缓存应用架构&#xff08;JVM、分布式缓存、db&#xff09;三级缓存 CPU 基本概念 CPU 的访问速度每 18 个月就会翻 倍&#xff0c;相当于每年增⻓ 60% 左右&#xff0c;内存的速度当然也会不断…

Android setContentView()源码分析

文章目录 Android setContentView()源码分析前提setContentView() 源码分析总结 Android setContentView()源码分析 前提 Activity 的生命周期与 ActivityThread 相关&#xff0c;调用 startActivity() 时&#xff0c;会调用 ActivityThread#performLaunchActivity()&#xf…

uniapp自定义步骤条(可二开进行调试)

前言 有一个业务需求是需要一个步骤条&#xff0c;但是发现开源的都不太合适&#xff0c;所以就自己写了一个。 开始 test.vue <template><view class"authenticateRecordDetails_container"><!-- 进度 --><view class"authenticateSte…

22、近端策略优化算法(PPO)论文笔记

近端策略优化算法&#xff08;PPO&#xff09;论文笔记 一、研究背景与目标二、**方法****3.1 策略梯度基础****3.2 信任区域方法&#xff08;TRPO&#xff09;****3.3 剪切代理目标函数&#xff08;LCLIP&#xff09;****3.4 自适应KL惩罚系数****3.5 算法实现** 三、 L CLIP…

web 自动化之 Selenium 元素定位和浏览器操作

文章目录 一、元素定位的八大方法1、基于 id/name/class/tag_name 定位2、基于 a 标签元素的链接文本定位3、基于xpath定位4、css定位 二、浏览器操作1、信息获取2、 浏览器关闭3、 浏览器控制 一、元素定位的八大方法 web 自动化测试就是通过代码对网页进行测试&#xff0c;在…

前端面经 作用域和作用域链

含义&#xff1a;JS中变量生效的区域 分类&#xff1a;全局作用域 或者 局部作用域 局部作用域&#xff1a;函数作用域 和 块级作用域ES6 全局作用域:在代码中任何地方都生效 函数中定义函数中生效&#xff0c;函数结束失效 块级作用域 使用let或const 声明 作用域链:JS查…