数据结构(三):集合、字典、哈希表

news2024/11/27 14:27:58

数据结构(三)

  • 一、集合(Set)
    • 1.封装一个集合类
    • 2.集合常见的操作
      • (1)并集
      • (2)交集
      • (3)差集
      • (4)子集
  • 二、字典(Map)
  • 三、哈希表
    • 1.什么是哈希表?
    • 2.哈希化之后的位置冲突解决
      • (1)链地址法(拉链法)
      • (2)开放地址法
    • 3.优秀的哈希函数
      • (1)快速的计算
      • (2)均匀的分布
    • 4.封装一个哈希表
      • (1)基础结构的搭建
      • (2)哈希函数的设计
      • (3)put插入/修改的方法
      • (4)get获取某个key对应的value
      • (4)remove删除某个key对应的元组
    • 5.哈希表的扩容
      • (1)为什么需要扩容?
      • (2)扩容的思路
      • (3)扩容代码
      • (4)如何判断一个数为质数?(我面试被问过)
      • (5)实现扩容时,容量为质数
    • 6.完整的封装代码

一、集合(Set)

1.封装一个集合类

集合通常是由一组无序的、不能重复的元素构成。
不能重复意味着不能通过下标值进行访问,而且相同的对象在集合中只会存在一份。

在ES6中的Set类就是一个集合类,这里我们重新封装一个Set类,了解集合的底层实现。
JavaScript中的Object类中的key就是一个集合,可以使用它来封装集合类Set。

class Set {
    constructor() {
        //集合使用对象存储,因为元素不能重复(对象中属性不能重复)
        this.items = {};
    }

    //向集合添加一个新的项
    add(value) {
        //先判断有没有这个属性,有的话就不添加
        if(this.has(value)) return false;
        //使用[]的方式添加属性,这样属性名就和value一样
        this.items[value] = value;
        return true;
    }

    //检测集合(对象)中是否有这个属性
    has(value) {
        return this.items.hasOwnProperty(value);
    }

    //删除某个元素
    remove(value) {
        if(!this.has(value)) return false;
        delete this.items[value];
    }

    //移除集合中所有项
    clear() {
        this.items = {};
    }

    //返回集合的长度
    size() {
        return Object.keys(this.items).length;
    }

    //获取集合中所有的值
    getValues() {
        return Object.values(this.items);
    }
}

测试代码:

let set = new Set();
set.add('zzy');
set.add('努力的但丁');
set.add('DantinZhang');
console.log(set.items);
set.remove('zzy');
console.log(set.items);
set.clear();
console.log(set.items);

2.集合常见的操作

先准备好测试代码段:

//集合1
let set = new Set();
set.add('zzy');
set.add('努力的但丁');
set.add('DantinZhang');
console.log(set.getValues()); //['zzy', '努力的但丁', 'DantinZhang']

//集合2
let newSet = new Set();
newSet.add('元素1');
newSet.add('元素2');
newSet.add('努力的但丁');
console.log(newSet.getValues()); //['元素1', '元素2', '努力的但丁']

(1)并集

主要思路就是先创建一个新的集合,把集合1元素都放进去
然后遍历集合2,如果集合2中的元素没有在新集合中,就add进去。
当然啊,遍历集合2也可以直接将所有元素add进去,因为我们封装的add做了去重判断

//并集
 union(otherSet) {
     //集合对象1:this
     //集合对象2:otherSet
     //1.创建一个新的集合
     let unionSet = new Set();
     //2.先把集合对象1中的元素全部放到新集合中
     let values = this.getValues();
     values.forEach(el => {
         unionSet.add(el);
     })
     //3.遍历集合对象2,判断其中的元素在新集合中有没有
     values = otherSet.getValues();
     for(let i = 0; i < values.length; i++) {
         //这里其实不写判断也可以,因为add方法做了判断
         if(!unionSet.has(values[i])) {
             unionSet.add(values[i]);
         }
     }
     //4.返回合并之后的结果
     return unionSet;
 }

测试代码:

//并集
let union = set.union(newSet);
console.log(union.getValues()); //['zzy', '努力的但丁', 'DantinZhang', '元素1', '元素2']

(2)交集

主要思路就是遍历集合1,看看每个元素是否在集合2中存在,如果存在就添加到新的集合中,最后返回新的集合。

//交集
intersection(otherSet) {
    //1.定义一个交集集合
    let intersectionSet = new Set();
    //2.拿集合1中的元素和集合2中元素相比较
    let values1 = this.getValues();
    values1.forEach(el => {
        //集合1有集合2也有的收起来
        if(otherSet.has(el)) {
            intersectionSet.add(el);
        }
    })
    //3.返回交集集合
    return intersectionSet;
}

测试代码:

//交集
let intersection = set.intersection(newSet);
console.log(intersection.getValues()); //['努力的但丁']

(3)差集

差集的思路和交集是相反的,遍历集合1,看看每个元素是否在集合2中存在,如果不存在就add到新集合里,最后返回新集合。

//差集,刚好和交集相反
difference(otherSet) {
    //1.定义一个差集集合
    let diffSet = new Set();
    //2.集合1中的元素去集合2中查找
    let values1 = this.getValues();
    values1.forEach(el => {
        //集合1有集合2没有的收起来
        if(!otherSet.has(el)) {
            diffSet.add(el);
        }
    })
    //3.返回结果
    return diffSet;
}

测试代码:

//差集
let difference = set.difference(newSet);
console.log(difference.getValues()); //['zzy', 'DantinZhang']

(4)子集

主要思路是遍历集合1,看每个元素是否在集合2中存在,如果存在就return,不存在就继续查找,如果所有元素都在集合2中存在,那么久返回true。

这里有一个大坑,就是forEach中写return是无效的,只能跳出当前循环继续下一轮循环,并不能跳出整个循环,更不能跳出整个函数。所以这里要用for循环比较好。

//子集的判断
isSubset(otherSet) {
    //判断集合1是否是集合2的子集
    let values = this.getValues();
    //forEach的坑:return无法终止循环,无法跳出函数
    // values.forEach(el => {
    //     if(!otherSet.has(el)) return false;
    // }) 
    for(let i = 0; i < values.length; i++) {
        if(!otherSet.has(values[i])) return false;
    }
    return true;
}

测试代码:

let set = new Set();
set.add('zzy');
set.add('努力的但丁');
set.add('DantinZhang');
console.log(set.getValues());
//子集的判断
let subset = new Set();
subset.add('zzy');
subset.add('努力的但丁');
subset.add('DantinZhang');
subset.add('ht');
console.log(subset.getValues());
let isSubset = set.isSubset(subset);
console.log(isSubset); //true

二、字典(Map)

数组、集合、字典是任何语言都有的数据结构。其中集合和字典比较像,集合可以理解为只有值,字典可以理解为键值对。

这里要注意,对象和ES6的Map数据结构是不一样的,对象的键可以是String可以是Symbol,而Map数据结构的键可以是任意数据类型。

对字典的封装简单写一下,和集合基本一样。

//字典和集合差不多,都可以基于对象封装
class Map {
    constructor() {
        this.items = {};
    }

    add(key, value) {
        if(this.has(key)) return false;
        this.items[key] = value;
    }

    has(key) {
        return this.items.hasOwnProperty(key);
    }

    remove(key) {
        if(!this.has(key)) return false;
        delete this.items[key];
    }

    get(key) {
        if(!this.has(key)) return undefined;
        return this.items[key]
    }

    keys() {
        return Object.keys(this.items);
    }

    size() {
        return this.keys.length;
    }

    clear() {
        this.items = {};
    }
}

三、哈希表

从这里开始就上难度了tmd,看看大佬的博客

1.什么是哈希表?

哈希表是基于数组实现的,但是相对于数组和链表来说,它的查找效率更高,因为它通常会对插入元素的下标值进行变换。这种变换称为哈希函数,通过哈希函数获取hashCode,并通过取余操作获取元素在数组中的下标。

哈希表的一些概念:
1、哈希化:将大数字转化成数组范围内下标的过程,称之为哈希化;
2、哈希函数:我们通常会将单词转化成大数字,把大数字进行哈希化的代码实现放在一个函数中,该函数就称为哈希函数;
3、哈希表:对最终数据插入的数组进行整个结构的封装,得到的就是哈希表。

综上所述,哈希表简单理解,就是把单词通过幂的连乘(可以唯一标识一个玩意儿,例如6543=6 * 103 + 5 * 102 + 4 * 10 + 3,还有cats = 3 * 273 + 1 * 272 + 20 * 27 + 17 =60337;)唯一标识出来,然后放入一个长度为n的数组中,放入时通过取余来判断放入的位置(比如数组长度是12,那么余数的范围是0-11,正好对应数组的12个下标)

2.哈希化之后的位置冲突解决

此时问题来了,取余后产生的下标,可能会产生冲突(虽然概率小,但还是会有)。那么如何解决冲突呢?

(1)链地址法(拉链法)

我们以长度为10的数组举例,什么是链地址法?

如下图所示,我们将每一个数字都对10进行取余操作,则余数的范围0~9作为数组的下标值。并且,数组每一个下标值对应的位置存储的不再是一个数字了,而是存储由经过取余操作后得到相同余数的数字组成的数组或链表。
在这里插入图片描述
这样可以根据下标值获取到整个数组或链表,之后继续在数组或链表中查找就可以了。而且,产生冲突的元素一般不会太多。

总结:链地址法解决冲突的办法是每个数组单元中存储的不再是单个数据,而是一条链条,这条链条常使用的数据结构为数组或链表,两种数据结构查找的效率相当(因为链条的元素一般不会太多)。

(2)开放地址法

开放地址法的主要工作方式是寻找空白的单元格来放置冲突的数据项。
在这里插入图片描述
根据探测空白单元格位置方式的不同,可分为三种方法:

线性探测、二次探测、再哈希法。这里太复杂了,先跳过,想了解点链接。

装填因子:当前哈希表中已经包含的数据项和整个哈希表长度的比值
装填因子 = 总数据项 / 哈希表长度
开放地址法的装填因子最大为1,因为只有空白的单元才能放入元素;
链地址法的装填因子可以大于1,因为只要愿意,拉链法可以无限延伸下去(到无穷);

3.优秀的哈希函数

哈希表的优势在于它的速度,所以哈希函数不能采用消耗性能较高的复杂算法。提高速度的一个方法是在哈希函数中尽量减少乘法和除法。

性能高的哈希函数应具备以下两个优点:
1、快速的计算:减少乘法;
2、均匀的分布:数组长度为质数;

(1)快速的计算

霍纳法则:在中国霍纳法则也叫做秦久韶算法,具体算法为:

在这里插入图片描述

求多项式的值时,首先计算最内层括号内一次多项式的值,然后由内向外逐层计算一次多项式的值。这种算法把求n次多项式f(x)的值就转化为求n个一次多项式的值。

变换之前:

乘法次数:n(n+1)/2次;
加法次数:n次;

变换之后:

乘法次数:n次;
加法次数:n次;

如果使用大O表示时间复杂度的话,直接从变换前的O(N²)降到了O(N)。

(2)均匀的分布

这个不太懂为什么,但是为了保证数据在哈希表中均匀分布,当我们需要使用常量的地方,尽量使用质数;比如:哈希表的长度、N次幂的底数等。

4.封装一个哈希表

本次封装,所有的冲突解决方案以:链地址法(拉链法)为主。
在这里插入图片描述

(1)基础结构的搭建

这里需要初始化三个属性,一个是用来存储元素的数组storage,一个是用来计算装载因子的变量:元素个数count,一个是数组长度limit
其中装载因子是要用于判断数组是否需要扩容,如果装载因子>0.75,那么就要扩容,装载因子<0.25就要缩容。

//封装哈希表(基于链地址法解决冲突情况)
class HashTable {
    constructor() {
        //1.定义一个数组来存储元素
        this.storage = [];
        //2.count:已经存储的总长度,用来计算装载因子
        //装载因子(已经存储的/数组总长)用来判断是否扩容
        this.count = 0;
        //3.数组的长度
        this.limit = 7;
    }
}

(2)哈希函数的设计

通过前面部分的分析,我们的哈希函数要分为两步:
1、把字符串转换成较大的hashCode(这里幂的底数选择37
2、hashCode和哈希表的长度取余,返回位置

//设计哈希函数
//1>将字符串转换成比较大的数字:hashCode
//2>把hashCode压缩到数组范围(size)内
hashFun(str, size) {
    //1.定义hashCode变量
    let hashCode = 0;
    //2.霍纳算法O(N),计算hashCode的值
    // cats => 获取Unicode编码
    for (let i = 0; i < str.length; i++) {
        //这里的理论比较不好理解,代码先记住吧。
        hashCode = 37 * hashCode + str.charCodeAt(i);
    }
    //3.取余数
    let index = hashCode % size;
    //4.返回位置索引
    return index;
}

(3)put插入/修改的方法

主要思路:
1、先通过哈希函数计算出key要插入的位置index
2、定义一个bucket来存储该位置的值(初始没有值是undefined)
3、判断插入的位置是否有bucket,如果没有就创建一个空数组
4、如果有了bucket,就遍历它,对比每个key,看是否已经添加,如果已经添加那么就要修改当前key对应的value值,修改完return出去
5、如果遍历完没有return,说明需要添加,直接push添加就行了。
在这里插入图片描述
函数代码:

//插入/修改方法
put(key, value) {
    //1.获取位置
    let index = this.hashFun(key, this.limit);
    let bucket = this.storage[index];
    console.log('当前桶:', index, bucket);
    //2.判断插入的位置是否已经创建数组(桶)
    if (bucket == undefined) {
        bucket = []; //创建桶然后放到索引位置
        this.storage[index] = bucket;
    }
    //3.如果已经有了桶,那么就遍历看是否已经添加
    for (let i = 0; i < bucket.length; i++) {
        let tuple = bucket[i];
        if (tuple[0] == key) {
            //3.1如果添加了,就修改value
            tuple[1] = value;
            return true;
        }
    }
    //4.循环结束如果没有返回,那么就要新增
    bucket.push([key, value]);
    //5.装载因子增加
    this.count++; 
    return true;
}

测试代码:

let hashTable = new HashTable();
hashTable.put('name', 'zzy'); //5位置
hashTable.put('age', 18); //2位置
console.log(hashTable.storage); 
结果:[null, null, [['age',18]], null, null, [['name','zzy']], null]

(4)get获取某个key对应的value

主要思路:
1、获取该key对应的位置index
2、获取该位置对应的桶bucket
3、如果bucket里是空的,说明肯定没有该元素,返回null
4、bucket不为空,就遍历它,依次对比key,如果有就返回
5、没有说明没有插入过,那么就返回null

//获取某个key对应的value
get(key) {
    //1.先找到位置
    let index = this.hashFun(key, this.limit);
    //2.找到该位置的桶
    let bucket = this.storage[index];
    //3.判断桶是否为空,为空说明肯定没有key
    if(bucket == undefined) return null;
    //4.如果不为空,那么就遍历桶
    for(let i = 0; i < bucket.length; i++) {
        let tuple = bucket[i]; //存储每一轮的元组
        //5.如果找到key,就返回它对应的value
        if(tuple[0] == key) {
            return tuple[1];
        }
    }
    //6.如果遍历完没有return,说明没找到
    return null;
}

测试代码:

 let hashTable = new HashTable();
 hashTable.put('name', 'zzy');
 hashTable.put('age', 18);
 console.log(hashTable.storage);
 console.log(hashTable.get('age')); //18
 console.log(hashTable.get('www')); //null

(4)remove删除某个key对应的元组

主要思路和前边差不多:
1、获取该key对应的位置index
2、获取该位置对应的桶bucket
3、如果bucket里是空的,说明不用删除了,返回null
4、bucket不为空,就遍历它,依次对比key,如果有就删除并返回value(count-1)
5、没有说明没有插入过,那么就返回null

//删除某个key对应的元组
remove(key) {
    //1.先找到位置
    let index = this.hashFun(key, this.limit);
    //2.找到该位置对应的桶
    let bucket = this.storage[index];
    //3.如果桶是空的,那么直接返回true
    if(bucket == undefined) return null;
    //4.如果桶不为空,就遍历寻找key所对应的元组
    for(let i = 0; i < bucket.length; i++) {
        let tuple = bucket[i];
        if(tuple[0] == key) {
            bucket.splice(i,1);
            this.count --; //别忘了插入个数-1
            return tuple[1];
        }
    }
    //5.如果走到这里,说明没有这个元素
    return null;
}

测试代码:

let hashTable = new HashTable();
hashTable.put('name', 'zzy');//位置5
hashTable.put('age', 18); //位置2
console.log(hashTable.storage);//结果:[null, null, [['age',18]], null, null, [['name','zzy']], null]
//测试获取元素
console.log(hashTable.get('age')); //18
console.log(hashTable.get('www')); //null
//测试删除
console.log(hashTable.remove('age'));//18
console.log(hashTable.storage);//[null,[],null,null,null,[['name','zzy']],null]

5.哈希表的扩容

(1)为什么需要扩容?

前面我们在哈希表中使用的是长度为7的数组,由于使用的是链地址法,装填因子(loadFactor)可以大于1,所以这个哈希表可以无限制地插入新数据。

但是,随着数据量的增多,storage中每一个index对应的bucket数组(链表)就会越来越长,这就会造成哈希表效率的降低。

(2)扩容的思路

loadFactor > 0.75时就扩容,loadFactor < 0.25时就缩容,扩容可以直接扩大两倍(扩容成质数后面再说),扩容之后所有数据都要同步修改,那么扩容的思路是什么呢?

1、首先,定义一个变量oldStorage把扩容前的旧数组存储起来
2、把当前数组变成一个容量更大的数组(重置属性)
3、把旧数组oldStorage中的每个bucket中的每个元素都取出来依次添加到新数组中

在这里插入图片描述

(3)扩容代码

//哈希表的扩容,传入新的数组长度
resize(newLimit) {
    //1.定义一个变量存储旧数组
    let oldStorage = this.storage;
    //2.初始化数组长度
    this.storage = [];
    this.count = 0;
    this.limit = newLimit;
    //3.把旧数组中存放的东西依次拿出来添加到新数组
    for(let i = 0; i < oldStorage.length; i++) {
        let bucket = oldStorage[i];
        //如果当前桶里没东西,就继续下一轮循环
        if(bucket == undefined) continue;
        //如果桶里有东西,就遍历它
        for(let i = 0; i < bucket.length; i++) {
            //调用put方法依次把元组放进新的数组中
            //注意put方法接收两个参数(key,value)
            let tuple = bucket[i];
            this.put(tuple[0],tuple[1]);
        }
    }
    //4.直到所有的桶里的元素都迁移完毕,那么扩容就欧了
}

上面这个方法应该什么时候调用呢?
在我们插入元素且count改变后,应该对count进行一个判断,如果装载因子loadFactor > 0.75就进行扩容(这里先两倍)

//判断是否需要扩容操作
count++;
if(this.count > this.limit * 0.75){
  this.resize(this.limit * 2)
}

在我们删除元素且count改变后,进行判断,如果装载因子loadFactor< 0.25就缩容。(这里先两倍)。

this.count --; //别忘了插入个数-1
if(this.limit > 7 && this.count < this.limit * 0.25) {
    this.resize(Math.floor(this.limit / 2));
}

(4)如何判断一个数为质数?(我面试被问过)

所谓质数,就是只能被1和它本身整除的数,那么有了这个思路,我们可以这样去判断一个数是否为质数:

//判断一个数是否为质数
function isPrime(num) {
    if(num <= 1) return false;
    for (let i = 2; i < num; i++) {
        if (num % i == 0) return false;
    }
    return true;
}

但是实际上还有更高效的判断方法,想象一下,一个数字如果要找两个因数,那么这两个因数一定是一个a在平方根的左侧,另一个b在平方根的右侧,如果我们把循环条件设置为开平方根的数字,那么如果左侧的a中没有可以整除的结果,右侧的b就不用再找了。

//判断一个数是否为质数更高效的方法
function isPrimeNb(num) {
    if(num <= 1) return false;
    let sqrtNum = Math.sqrt(num);
    for (let i = 2; i < sqrtNum; i++) {
        if (num % i == 0) return false;
    }
    return true;
}

(5)实现扩容时,容量为质数

先把上一步写的方法加进去:

//判断一个数是否为质数
isPrime(num) {
    if(num <= 1) return false;
    let sqrtNum = Math.sqrt(num);
    for (let i = 2; i < sqrtNum; i++) {
        if (num % i == 0) return false;
    }
    return true;
}

把当前数变成质数:

扩容/缩容之后,通过循环调用isPrime判断得到的容量是否为质数,不是则+1,直到是为止。比如原长度:7,2倍扩容后长度为14,14不是质数,14 + 1 = 15不是质数,15 + 1 = 16不是质数,16 + 1 = 17是质数,停止循环,由此得到质数17。

//把当前数变成质数
getPrime(num) {
    while(!isPrime(num)) {
        num++;
    }
    return num;
}

然后在插入方法中:

//6.判断是否需要扩容
if(this.count > this.limit * 0.75) {
    let newLimit = this.getPrime(this.limit * 2);
    this.resize(newLimit);
}

在删除方法中:

if(this.limit > 7 && this.count < this.limit * 0.25) {
    let newLimit = Math.floor(this.limit / 2);
    let newPrimeLimit = this.getPrime(newLimit);
    this.resize(newPrimeLimit);
}

测试代码:

 let hashTable = new HashTable();
 hashTable.put('name', 'zzy');
 hashTable.put('age', 18); 
 hashTable.put('class1','Tom')
 hashTable.put('class2','Mary')
 hashTable.put('class3','Gogo')
 hashTable.put('class4','Tony')
 hashTable.put('class5','5')
 hashTable.put('class6','6')
 hashTable.put('class7','7')
 hashTable.put('class8','8')
 console.log(hashTable.limit); //17

6.完整的封装代码

请见github:https://github.com/DantinZhang/js-data-structures-algorithms

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

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

相关文章

Powershell Install SQL Server 2022

前言 SQL Server 2022 (16.x) 在早期版本的基础上构建,旨在将 SQL Server 发展成一个平台,以提供开发语言、数据类型、本地或云环境以及操作系统选项。 SQL Server Management Studio (SSMS) 是一种集成环境,用于管理从 SQL Server 到 Azure SQL 数据库的任何 SQL 基础结构…

nginx如何用html显示多个图片并加入播放链接

需求背景通过nginx来做个点播服务&#xff0c;ffmpeg截取视频中的某一帧作为视频的封面&#xff0c;前端页面展示这个封面&#xff0c;&#xff0c;并链接到对应的视频播放链接&#xff0c;加载播放器进行播放简单介绍一下ffmpeg截取视频中的某一帧的方式截取视频的第一帧&…

HashedWheelTimer

序言这种算法是一种轮询算法的优化升级,能够以只有一个Timer的情况下处理大量的定时任务.Begin结合HashedWheelTimer的思想根据自然时间1分钟为例,来做大批量的定时任务触发首先定一个长度为60的数组,数组中存放的是Set集合,集合里面是任务详情.当有定时任务刚来的时候判断是否…

死锁检测组件 -- 使用hook检测死锁

目录 hook hook是什么 dlsym()函数 hook的实现步骤 加入hook的demo C/CLinux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 hook hook可以把系统或第三方库提供的函数&#xff0c;替换成我们写的同名函数。会调用我们实现的函数。 hook是什么 hook提供了两…

07-Java异常分类以及处理机制

1.异常概念 Java标准库内建了一些通用的异常&#xff0c;这些类以Throwable为顶层父类。Throwable又派生出Error类和Exception类。 1.错误&#xff1a;是程序无法处理的错误&#xff0c;表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关&#xff0c;而表示…

企业电子招采系统源码——信息数智化招采系统

​ 信息数智化招采系统 服务框架&#xff1a;Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构&#xff1a;VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术&#xff1a;Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monitor、…

Centos7.9安装GitLab

1、下载 Index of /gitlab-ce/yum/el7/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 下载最新版本gitlab-ce-15.4.2-ce.0.el7.x86_64 2、安装基础依赖并启动 #安装依赖 yum install -y curl policycoreutils-python openssh-server postfix #配置开机启动 sys…

APP测试面试题汇总基础+进阶

目录 一、基础篇 1、请介绍一下&#xff0c;APP测试流程&#xff1f; 2、APP测试需要提前准备哪些测试资源&#xff1f; 3、APP测试和Web测试的区别&#xff1f; 1.系统结构方面 2.性能方面 3.兼容性方面 4、相对于 Wed 项目&#xff0c;APP有专项测试 5、Android手机和…

七种方式实现高并发秒杀

新建skill模块 pom依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId>…

@Intercepts为基础实现数据完整性保护

本文以Intercepts为基础&#xff0c;通过拦截器的方式拦截数据库操作包括query、insert、update、delete操作对数据的完整性保护。Intercepts是mybatis中的一个常用拦截器注解&#xff0c;表明当前对象是一个拦截器&#xff0c;当前类通过implements Interceptor实现Intercepto…

cuda性能分析工具

NVIDIA nvprof / nvvpNSight系列Nsight Systems本地使用远程使用结果分析Nsight Compute本地使用远程使用结果分析NVIDIA nvprof / nvvp 由2008年起开始支持的性能分析器&#xff0c;交互性好&#xff0c;利于使用记录运行日志时使用命令nvprof可视化显示日志时使用命令nvvp&a…

SpringCloud+Nacos+Gateway

SpringCloudNacosGatewaySpringBoot整合GatewayNacos一. 环境准备1. 版本环境2. 服务环境二. 实战1.创建用户服务2.创建订单服务3.创建网关服务4.测试三. 避坑指南问题1--503问题问题2--网关服务启动报错SpringBoot整合GatewayNacos 本篇文章只演示通过gateway网关服务访问其他…

【论文速递】MMM2020 - 电子科技大学提出一种新颖的局部变换模块提升小样本分割泛化性能

【论文速递】MMM2020 - 电子科技大学提出一种新颖的局部变换模块提升小样本分割泛化性能 【论文原文】&#xff1a;A New Local Transformation Module for Few-shot Segmentation 【作者信息】&#xff1a;Yuwei Yang, Fanman Meng, Hongliang Li, Qingbo Wu,Xiaolong Xu an…

SpringMVC(2)

一)接受到JSON格式的数据:使用RequestBody来进行接收 ResponseBody表示的是返回一个非页面的数据 RequestBody表示的是后端要接受JSON格式的数据 一)接收单个格式的JSON格式的数据&#xff0c;我们使用一个对象来进行接收 1)我们之前接受GET请求中的queryString中的参数的时候&…

读懂下文,安装数据库不再求人

想要数据存储必须要有数据库为支撑。在项目运行的时候也是要提前安装好并导入表结构和数据。通俗点来说&#xff0c;学会了万事不求人。 这里就整理了一份关于Windows和Linux系统下安装Mysql的操作命令。 Windows下安装MySQL 1、设置环境变量 设置环境变量是为了让你在任何…

小程序接口封装、异步加载、Promise

目录 1、页面准备 2、在app.js中处理当前环境以便切换api的环境、公共变量 3、定义post、get请求方法 request.js 4、api.js 接口列表调用index.js的post、get请求 5、index.js 需要返回数据的页面 api.js 、index.js 示例 异步实现 async、await 1、页面准备 目录结构…

JAVA 常用类型之String结构

String在java中我们是用来操作字符串的&#xff0c;但它的底层结构确是一个char[]数组&#xff0c;通过数组的方式将每个字符进行保存。 使用时&#xff1a;String str"ABCD"&#xff0c;内部存value确是&#xff1a;value[A,B,C,D]; 如下图&#xff1a; 参考String源…

七大排序算法的多语言代码实现

文章目录 前言 一、排序算法 1.原理简述 2.分类与复杂度 二、实例代码 1.冒泡排序 C Python Java Golang Rust Dephi 2.选择排序 C Python Java Golang Rust Dephi 3.插入排序 C Python Java Golang Rust Dephi 4.希尔排序 ​编辑 C Python Java Gola…

Linux网络技术学习(五)—— 网络设备初始化(I)

文章目录什么时候进行的设备初始化&#xff1f;设备注册和初始化NIC&#xff08;网卡 Network Interface Card&#xff09;初始化的基本目标设备与内核之间的交互硬件中断中断类型传送节流方式为了改善效率中断共享IRQ处理函数映射的组织irqaction结构体存储方式什么时候进行的…

android fwk模块之Sensor架构

本文基于Android 12源码整理&#xff0c;包含如下内容&#xff1a; 通信架构应用层实现使用方式SensorManager抽象接口具体实现fwk层的实现native中的SensorManager的初始化流程native中的消息队列初始化与数据读取sensorservice实现HAL层的实现通信架构 应用层实现 涉及代码&…