Java:Map和Set

news2024/12/29 10:23:53

一、搜索树

在真正学习Map和Set之前,我们有必要先来了解一种树型结构:二叉搜索树!

1、概念

  二叉搜索树又被称为【二叉排序树】,顾名思义,这是一颗排好序的树!它或者是一颗空树,或者是具有以下性质的二叉树:

1、若它的左子树不为空,则左子树上的所有节点的值都小于根节点的值

2、若它的右子树不为空,则右子树上的所有结点的值都大于根节点的值

3、它的左右子树也分别为二叉搜索树 

观察此图,可以发现,这这棵树的【中序遍历】序列是一个有序序列!

2、 查找关键字方法:

【基本思想】

  根据二叉搜索树的性质,我们可以知道,对于每一颗树,根节点的左子树元素都【小于】根节点元素,根节点的右子树元素都【大于】根节点元素。

  那么,在查找关键字的时候,不必每次都遍历整棵二叉树,首先,【先比较】根节点元素和关键字的大小:

1、根节点元素【大于】关键字:找左子树

2、根节点元素【小于】关键字:找右子树

3、根节点元素【等于】关键字:返回根节点

 【代码实现】

public TreeNode search(int key){
            if(root==null){
                return null;
            }
            TreeNode cur=root;
            while (cur!=null){
                if(cur.val>key){
                    //根节点元素 比 关键字 大,找左子树
                    cur=cur.left;
                }else if(cur.val<key){
                    //根节点元素 比 关键字 小,找右子树
                    cur=cur.right;
                }else{
                    //根节点元素 等于 关键字
                    return cur;
                }
            }
            //没有找到
           return null;
    }    
        
    


3、插入元素方法:

【基本思想】

  在实现这个方法之前,有一点需要我们知道:搜索二叉树不能拥有相同的数据!因此,当插入的数据在二叉树已经拥有的情况下,无法进行插入!

  插入原理:

  1、创建一个新节点,新节点的值即插入元素的值

  2、先判断整棵树的的根节点是否为空,如果为空,直接将新节点赋值给root

  3、创建cur和parent,cur找到空节点!parent定位cur的父亲节点!

  4、根据父亲节点元素大小和插入元素的大小,判断新节点插入到父亲节点的左子叶或者右子叶

【动画演示】

【代码实现】 


        //插入数据方法
        public void insert(int val) {
            TreeNode node = new TreeNode(val);
            //当根节点为空
            if (root == null) {
                root = node;
                return;
            }

            //根节点不为空
            TreeNode cur = root;
            TreeNode parent = null;
            while (cur != null) {
                parent = cur;
                if (cur.val > val) {
                    cur = cur.left;
                } else if (cur.val < val) {
                    cur = cur.right;
                } else {
                    //遇到相同的数据,不能插入!
                    return;
                }
            }

            //此时cur==null,插入数据
            if (parent.val > val) {
                //根节点元素大于val,插入左节点
                parent.left = node;
            } else {
                //根节点元素小于val,插入右节点
                parent.right = node;
            }
        }


4、删除关键字方法:

【基本思想】

    在删除【关键字】之前,首先我们需要找到该关键字,创建变量cur定位关键字所在的节点!创建parent定位cur的父亲节点!

    在cur找到关键字之后,删除关键字主要划分为三种情况:

1、cur.left==null:

 【情况1】:cur为根节点root,此时只需要将根节点root替换为cur.left即可!

【情况2】:cur不是根节点root,cur是parent(父亲节点)的左子叶节点 ,此时只需要将parent的左子树替换为cur的右子树即可!

【情况3】:cur不是根节点root,cur是parent(父亲节点)的右子叶节点,此时只需要parent的右子树替换为cur的右子树即可

2、cur.right==null

【情况1】:cur是根节点root,此时只需要将根节点root替换为cur.left即可

【情况2】:cur不是根节点root,cur是parent(父亲节点)的左子叶节点,此时只需要将parent的左子树替换为cur的左子树即可

【情况3】:cur不是根节点root,cur是parent(父亲节点)的右子叶节点,此时只需要将parent的右子树替换为cur的左子树即可

3、cur.left==null  &&  cur.right==null

  如果cur的左右节点都不为空,那么我们就不能直接删除该节点,如果直接删除,后面的节点很难有序连接起来!因此,我们使用【替换法】 

  以此为例子,此时要删除cur这个节点,它的值为60,那么,我们可以先在cur的右子树中寻找该树的【最小值】节点,同时删除该【最小值】节点!将这个【最小值】赋值给【cur.val】,这样,并不会影响整棵树为二叉搜索树的性质!

     

  那么该如何找到右子树的最小值节点?这里我们创建变量target来定位该最小值节点,使用targetP来定位target的父亲节点!

  首先,我们知道,对于二叉搜索树,其最小值节点一定在该树的最左边! 因此,我们只需要使target不断往树的左边遍历,直至找到某一个节点,该节点不存在在左子叶,此时该节点即为我们需要的最小值节点!

找到该节点后,替换时因为需要删除最小值节点,因此划分为两种情况:

【情况1】:target为targetP(父亲节点)的左子叶节点,此时删除最小值节点,只需要将targetP的左子树替换为target的右子树即可

【情况2】:target为targetP(父亲节点)的右子叶节点,此时删除最小值节点只需要将targetP的右子树替换为target的右子树即可


      //删除数据方法:
        public void remove(int key){
        TreeNode cur=root;
        TreeNode parent=null;
        //寻找要删除的节点,使用cur定位要删除的节点,parent定位cur的父亲节点
        while(cur!=null){
            if(cur.val>key){
                parent=cur;
                cur=cur.left;
            } else if (cur.val<key) {
                parent=cur;
                cur=cur.right;
            }else{
                //找到删除的节点
                removeNode( cur,parent);
                return;
            }
        }
        }

        //删除指定元素方法:
        public void removeNode(TreeNode cur,TreeNode parent){
            if(cur.left==null){

                //1、cur.left==null
                if(cur==root){ //cur为根节点
                    root=cur.right;
                } else if(cur==parent.left){ //cur是左子叶节点
                    parent.left=cur.right;
                    }else{//cur是右子叶节点
                        parent.right=cur.right;
                    }

            } else if (cur.right==null) {

                //2、cur.right==null
                if(cur==root){//cur为根节点
                    root=cur.left;
                } else if(cur==parent.left){//cur为左子叶节点
                        parent.left=cur.left;
                    } else {//cur为右子叶节点
                        parent.right=cur.left;
                    }

            }else{

                //3、cur的左右子叶都为空
                TreeNode target=cur.right;
                TreeNode targetP=cur;
                //使用target定位比cur右子树中最小的元素
                while(target.left!=null){
                    targetP=target;
                    target=target.left;
                }
                cur.val=target.val;
                if(target==targetP.left){//target为左子叶节点
                    targetP.left=target.right;
                }else{//target为右子叶节点
                    targetP.right=target.right;
                }

            }


        }



二、Map和Set

  Map和Set是一种专门用来进行搜索的【数据结构】!拥有查找数据的功能,同时也可以进行插入数据和删除数据操作!

1、两种模型

  一般,我们把搜索的数据称之为【关键字】(Key),和【关键字】对于的称为【值】(Value),二者合一称为Key-Value键值对,由此衍生出来两种模型:

1、Key-Value模型:

  这里举一个例子,Key-Value就好像是《水浒传》里面【人物名字】与【江湖外号】的关系,宋江的外号是及时雨,宋江对应Key,及时雨对应Value。Value可以存储关键字Key的信息。

  又比如,在一篇英文文章中,可以将文章中出现过的每一个单词称之为Key,而Value值可以用来记录每一个Key单词出现了几次。

2、纯Key模型:

  而纯Key模型的意思也仅仅是Key-Value模型没有了Value而已!其他都是相同的!

【此时看不懂也没有关系,我觉得还是实际了解Map和Set里面的方法会更加好理解!】



 三、Map的使用

  Map其实是一个【接口】,【TreeMap类】实现了这个【接口】,并且,TreeMap类底层就是我们前面所学的【二叉搜索树】!这也就是前面我们为什么要学习二叉搜索树的原因!  

  另外,该接口存储的是Key-Value结构的【键值对】,并且Key一定是【唯一】的,不能重复!

【Map常用方法】

1、V put(K key,V  value)&&V get(Object key)

【功能】

put(K key,V value):设置key对应的value

V get(Object key):返回key对应的value

【实例1】

给key设置过value的情况:

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        //给key设置value
        map.put("宋江","及时雨");
        map.put("孙悟空","齐天大圣");

        //使用val来接收get方法的返回值
        String val=map.get("宋江");
        System.out.println(val);
        String  val2=map.get("孙悟空");
        System.out.println(val2);
    }
}

 程序运行:

打印出来key对应的value值

【实例2】

没有给key设置value的情况:

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        map.put("宋江","及时雨");
        map.put("孙悟空","齐天大圣");
        //使用val来接收get方法的返回值
        String val=map.get("宋江2");//没有给"宋江2"设置过value
        System.out.println(val);
    }
}

 程序运行:

get的返回值是null



2、V getOrDefault(Object key,V defaultValue)

【功能】

返回key对应的value,如果key不存在,返回默认值defaultValue 

【实例】

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        map.put("宋江","及时雨");
        //关键字存在,返回关键字的value
        String val=map.getOrDefault("宋江","hhhhh");
        System.out.println(val);
        //关键字不存在,返回传入的默认值
        String val2=map.getOrDefault("宋江1","hhhhhhh");
        System.out.println(val2);
    }
}

程序运行:



3、V remove(Object key) 

【功能】

删除key对应的映射关系

【实例】

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        map.put("宋江","及时雨");
        map.put("孙悟空","齐天大圣");
        String val=map.remove("孙悟空");//返回值是要删除的关键字的value
        System.out.println(val);

    }
}

 



4、Set<K>  keySet() 

【功能】

返回所有的key,存储在集合Set中。 

【实例】

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        map.put("宋江","及时雨");
        map.put("孙悟空","齐天大圣");
        //返回所有的key,存储在set中
        Set<String> set=map.keySet();
        System.out.println(set);

    }
}



5、Set< Map.Entry<K,V> > entrySet( )

【功能】

返回所有的Key-Value映射关系 ,存储在Set中

【实例】

public class Test {
    public static void main(String[] args) {
        Map<String,String> map=new TreeMap<>();
        map.put("宋江","及时雨");
        map.put("孙悟空","齐天大圣");
        //返回所有的key-Value,存储在set中
        Set<Map.Entry<String,String>> entries =map.entrySet();
        System.out.println(entries);

    }
}



【注意事项】 

 1、Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现该接口的类TreeMap和HashMap

2、Map中存放键值的Key是唯一的,而Value是可以重复的

3、Map存入的Key必须是可以比较的,如果Key是自定义类型的类,一定要提供比较方式!

4、Map中的Key不能修改,但是Key对应的Value可以修改,如果要修改Key,只能先删除,再重新插入!



四、Set的使用

【注意事项】

1、Set与Map不同的点在于:Set只存储了Key,并没有Value;

2、与Map相同的是,Set也不能存储重复的Key;

3、实现Set接口常见的类:TreeSet和HashSet

【Set常用方法】

方法:                                 功能:
boolean add(E e)                      添加元素,但是重复元素不会被添加
void clear()                          清空集合
boolean contains(Object o)            判断o是否在集合中
boolean remove(Object o)              删除集合中的o
int size()                            返回set中的元素个数
boolean isEmpty()                     检测set是否为空,为空返回true,不为空返回false


五、哈希表

【概念】

  在【顺序表】和【平衡树】中,元素与关键字与其存储的位置没有对应的关系,因此在查找一个元素的时候,必须要经过关键字的多次比较,搜索的效率取决于搜索过程中元素的比较次数。

  这里我们想要一种理想的搜索方法,可以不经过任何比较,一次直接就从表中得到要搜索的元素。就好比摆在我们面前有50个不一样的锁,再给我们一个钥匙,我们要想找到这个钥匙对应的锁,如果钥匙和锁之间没有标记,我们只能拿着钥匙一个一个尝试,直至找到匹配的锁才行,但是,如果每一把锁都有标记数字,这个钥匙也有标记数字,1号钥匙可以打开1号锁,2号钥匙可以打开2号锁……那么,我们只需要根据锁和钥匙之间的【映射关系】,直接找到对应的锁就行了!

  如果要实现以上理想的搜索方法,就要构造一种【存储结构】,通过某个【函数】(HashFunc/哈希函数)使元素的【存储位置】与它的【关键字】之间能够建立【一 一映射】的关系,那么在查找时,我们就可以很快找到元素!

  最后,这种结构就是我们要学习的【哈希表】


  当向该结构中:

【插入元素】:

  根据插入元素的关键字,以此函数【计算】出该元素的存储位置并按此位置进行存放!

【搜索元素】:

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



【哈希函数】

  介绍【哈希函数】我还是举一个例子,这样比较好懂:

  现在,我们有一个数据集合{1,7,6,4,5,9}

  哈希函数设置为:hash(key)=key%capacity  (capacity为存储元素底层空间的总的大小)

1、【哈希冲突】

  按照这种计算方法,那么毫无疑问,会出现下面这种情况:

  假如现在该结构要添加关键字为【14】的元素,根据哈希函数计算,应该存入下标为4的数组中,可是原结构的该位置,已经存储了关键字为【4】的元素!这种情况就是【哈希冲突】!

  【哈希冲突】可以理解为关键字不同的元素根据哈希函数,计算出来相同的存储位置!

   首先,我们要明确一个点,冲突的发生是必然的,无法避免,但我们应该尽量【降低冲突率】! 

   讲到冲突率,让我们来认识一个与【冲突率】有关的新概念:载荷因子

   哈希表的【载荷因子】定义为:ɑ=填入表中的元素 /  哈希表的长度

  一般来说,载荷因子越大,冲突率越高!

  降低载荷因子的方法是:扩容!



2、【解决哈希冲突的方法】

  解决哈希冲突一般采用两种方法:【闭散列】和【开散列】 

【闭散列】

 闭散列也叫开放地址法,当发生哈希冲突时,如果哈希表【未被填满】,说明哈希表还有空位置,那么可以把key存放在冲突位置的【下一个】空位置中。

  找空位置这里提供两种方法:

1、线性探测:

例如,在以下哈希表中,我们还要插入元素{14,24,34}

  这三个数有共同的特点:根据哈希函数计算,应该存入下标4的位置,可是该位置已经有元素了!

那么,就要采用【线性探测】方法,找到存储位置的【下一个空位置】,存入元素!

 

2、 二次探测

  线性探测有一个缺点:有可能会让冲突的元素集中在一个地方,正如上面的例子!为解决该问题我们可以采用二次探测!

  二次探测找空位置的方法为:

新位置下标值=(存储位置下标值  +  i  的平方)% 哈希表长度

第一次计算时,i=1,第二次计算时,i=2,以此类推



 【开散列】(哈希桶)

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

  哈希桶可以理解为【数组+链表】的组合,哈希表存储的元素为链表节点!



 六、模拟实现【哈希桶】

首先,我们创建一个类:HashBuck类!在这个类当中,我们定义一个内部类:Node类!

public class HashBuck {

    //定义节点类(内部类)
    static class Node{
        //成员变量
        public int key;
        public int value;
        public Node next;

        //构造方法
        public Node(int key,int value ){
            this.key=key;
            this.value=value;
        }
    }


    //哈希表
    public Node[] array;
    public int usedSize;//记录哈希表存储元素个数
    public double loadFactor=0.75;//载荷因子默认为0.75
    //初始化哈希表
    public HashBuck(){
        //默认容量为10
        array=new Node[10];
    }
}


1、【添加元素方法】

【基本思想】

  给哈希表添加元素这个方法主要可以划分4个步骤:

1、根据关键字计算出存储位置的下标

     计算下标的哈希函数:HashFunc(key)=key%哈希表容量

2、遍历该存储位置所在的链表,判断其关键字是否已经存在,存在则更新其value即可,不能重复添加包含相同的关键字的元素

3、在该存储位置所在的链表找不到该关键字,则使用【头插法】添加该元素

4、计算【载荷因子】的大小,如果载荷因子大于默认值0.75,则该哈希表需要扩容

   扩容的时候,一般采用2倍扩容,扩容之后,由于其哈希函数:  HashFunc(key)=key%哈希表容量   与 【哈希表容量】有关,因此需要将表中的每一个元素重新放置!


【代码实现】

 //添加元素方法
    public void put(int key,int value){
        //1、先根据关键字计算出存储下标
        int index=key%array.length;
        Node cur=array[index];
        //2、看看该关键字是否已经存在,存在则更新其value即可
        while (cur!=null){
            if(cur.key==key){
                cur.value=value;
                return;
            }
            cur=cur.next;
        }
        //3、找不到该关键字,使用【头插法】添加该元素
        Node node=new Node(key,value);
        node.next=array[index];
        array[index]=node;
        usedSize++;
        //4、判断【载荷因子】是否大于默认值,大于则扩容
        if(loadFactorCount()>=loadFactor){
            reSize();
        }
    }

    //计算载荷因子方法
    private double loadFactorCount(){
        return usedSize*1.0/array.length;
    }

    //扩容方法
    private void reSize(){
        //2倍扩容
        Node[] newArray=new Node[2*array.length];
        //对哈希表中元素进行重新放置
        for(int i=0;i<array.length;i++){
            Node cur=array[i];
            while(cur!=null){
                Node curN=cur.next;
                //移动元素到新的位置
                int newIndex=cur.key%newArray.length;
                cur.next=newArray[newIndex];
                newArray[newIndex]=cur;

                //cur往下遍历
                cur=curN;
            }
        }
        //将【旧哈希表】的引用更新为【新哈希表】的引用
        array=newArray;
    }



2、【获取value值的方法】

//获取value值方法
    public int get(int key){
        //计算出该下标的位置
        int index=key%array.length;
        Node cur=array[index];
        //遍历当前链表,看看是否存在该关键字
        while(cur!=null){
            if(cur.key==key){
                return  cur.value;
            }
            cur=cur.next;
        }
        //找不到该关键字
        return -1;
    }


七、泛型类【哈希桶】

1、【类对象获取哈希值】

  学到这里,我们发现目前存入的【关键字】是一个int类型的数据,可以通过哈希函数计算下标值,那么,如果存入的关键字是一个【类对象】,该如何计算其存储位置的下标呢?

  这里我举一个例子:

  首先,我们实例化一个Student类,该学生类有成员变量stuN表示学号!

  接下来,实例化两个学生类对象,将其存入哈希表中!在此之前,我们得想办法将对象转换为整型数据,这里可以使用hashCode方法,每一个对象都可以通过调用该方法,返回一个int类型的数据!这个数称为【哈希值】,我们可以将其视为该对象的【关键字】,计算出来该对象的存储位置下标!

public class Test {
    public static void main(String[] args) {
        Student student1=new Student("123");
        Student student2=new Student("123");
        //调用hashCode方法
        int hashcode1=student1.hashCode();
        int hashcode2=student2.hashCode();
        System.out.println(hashcode1);
        System.out.println(hashcode2);

    }
}

   我们认为,学号相同的学生可以被认为是同一个学生,但是具有相同学号的对象调用hashCode方法返回的哈希值 并不是相同的,因此,我们需要在该类中重写hashCode方法equals方法

   【重写方法】具体步骤:

  1、 这里可以使用快捷建【insert+insert】,弹出以下画面,选中【蓝色选项】

2、接下来一直按next,直到出现create,按下即可! 

重写方法后,重新运行代码,会发现student1和student2产生了相同的哈希值! 

  



2、【泛型类哈希桶代码】

//泛型类的【哈希桶】
class HashBuck1<K,V>{
  
    //定义节点类(内部类)
    static class Node<K,V>{
        public K key;
        public V val;
        public Node<K,V> next;

        public Node(K key,V val){
            this.key=key;
            this.val=val;
        }
    }
    
    
    //哈希表
    public Node<K,V>[] array;
    public int usedSize;
    public double loadFactor=0.75;
    
    //初始化哈希表
    public HashBuck1(){
      array=new Node[10];//默认数组容量是10
    }
    
    
    //添加元素方法:
    public void put(K key,V val){
        //1、获取对象的哈希值,根据哈希值计算存储地址的下标
        int hash=key.hashCode();
        int index=hash%array.length;
        Node<K,V> cur=array[index];

        //2、判断该对象是否已经存入哈希表中
        while(cur!=null){
            if(cur.key.equals(cur.key)){
                cur.val=val;
                return;
            }
            cur=cur.next;
        }

        //3、该列表不存在该对象,添加该对象
        Node<K,V> node=new Node(key,val);
        node.next=array[index];
        array[index]=node;
        usedSize++;

        //4、计算载荷因子,如果过大,扩容
        if(loadFactorCount()>=loadFactor){
            reSize();
        }
    }

    //计算载荷因子方法;
    private double loadFactorCount(){
        return usedSize*1.0/array.length;
    }


    //扩容方法:
    private void reSize(){
        Node<K,V>[] newArray=new Node[2*array.length];
        //对哈希表中元素进行重新放置
        for(int i=0;i<array.length;i++){
            Node<K,V> cur=array[i];
            while(cur!=null){
                Node<K,V> curN=cur.next;
                int hash=cur.key.hashCode();
                int newIndex=hash%newArray.length;
                cur.next=newArray[newIndex];
                newArray[newIndex]=cur;
                cur=curN;
            }
        }
        array=newArray;
    }

    //获取value值的方法;
    public V get(K key){
        int hash=key.hashCode();
        int index=hash%array.length;
        Node<K,V> cur=array[index];
        while(cur!=null){
            if(cur.key.equals(key)){
                return cur.val;
            }
            cur=cur.next;
        }
        return null;
    }
}

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

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

相关文章

未来人类文明的可持续发展

未来人类文明若要实现永远延续,建立一个演化模型确实是一个有远见的想法。演化模型可以帮助我们预测和规划未来,从而更好地应对可能出现的挑战。以下是对这个想法的展开论述: 建立演化模型:首先,我们需要收集当前节点的人类文明数据,包括科技、经济、政治、文化、环境等方…

C语言【文件操作】(1)

文章目录 1.为什么使用文件2.文件是什么&#xff1f;2.1程序文件2.2数据文件 3.二进制文件和文本文件4.文件的打开和关闭4.1流和标准流流标准流 4.2文件指针4.3文件的打开和关闭 结语 1.为什么使用文件 很简单 长久的存储数据 如果没有文件&#xff0c;我们写程序所产生的数据…

【Docker】如何注册Hub账号并上传镜像到Hub仓库

一、创建Hub账户 浏览器访问&#xff1a;hub.docker.com 点击【Sign up】注册账号 输入【邮箱】【用户名】【密码】 ps&#xff1a;用户名要有字母数字&#xff1b;订阅不用勾选 点击【Sign up】注册即可 点击【Sign in】登录账号 输入【邮箱】【密码】 点击【Continue】登录 二…

OceanBase 轻量级数仓关键技术解读

码到三十五 &#xff1a; 个人主页 为了更好地聚合和治理跨域数据&#xff0c;帮助企业用较低的成本快速聚合分析&#xff0c;快速决策&#xff0c;不断的让企业积累的数据产生价值&#xff0c;从全域海量数据抓取&#xff0c;高性能流批处理&#xff0c;元数据血缘治理等等方面…

spring高级篇(八)

本篇对Spring MVC 的执行流程做一个简单总结 MVC执行流程总结 当浏览器发送一个请求&#xff0c;例如http://localhost:8080/hello&#xff0c;请求到达服务器后&#xff0c;一般会进行如下操作&#xff1a; 1、首先会经过DispatcherServlet&#xff0c;默认映射路径为 /&…

WAF防火墙可以给您解决什么问题?哪些情况下使用WAF最适合?

一、什么是WAF&#xff1f; Web应用防护系统&#xff08;也称为&#xff1a;网站应用级入侵防御系统。英文&#xff1a;Web Application Firewall&#xff0c;简称&#xff1a;WAF&#xff09;。利用国际上公认的一种说法&#xff1a;Web应用防火墙是通过执行一系列针对HTTP/H…

Vulnstack(一)

0x00 Preface 网上有很多关于 Vulnstack&#xff08;一&#xff09; 的优质文章&#xff0c;本篇文章仅用于记录笔者自身的学习过程。因能力有限&#xff0c;过程中多多少少存在不完善的地方或是未解决的问题&#xff0c;日后有机会会补充上。 内网渗透基础总结&#xff1a;手…

《从Paxos到Zookeeper》——第四、七章:基本概念及原理

目录 第四章 Zookeeper与Paxos 4.1 Zk是什么 4.1.1 Zk特性 4.1.2 Zk基本概念 4.1.2.1 集群角色(Follower, Leader, Observer) 4.1.2.2 数据模型 4.1.2.3 ZNode(数据节点) 4.1.2.4 Session(会话) 4.1.2.5 ACL&#xff08;Access Control Lists&#xff09; 4.1.2.6 Watcher(事件…

网安笔记(纯兴趣,随缘更新)

对于千锋教育的网安课程的笔记 (一)虚拟机环境搭建 01虚拟机概述 传统运行模式:一台计算机同时只能运行一个操作系统 虚拟机运行架构: 1.寄生架构 &#xff08;实验环境、测试环境&#xff09; • 虚拟机作为应用软件安装在操作系统上 • 可以在此应用软件上安装多个操作系统…

AI终端设备的自动化分级

摘要&#xff1a; AI智体被定义为感知环境、做出决策和采取行动的人工实体。 受SAE&#xff08;汽车工程师学会&#xff09;自动驾驶6个级别的启发&#xff0c;AI智体也根据效用和强度进行分类&#xff0c;分为以下几个级别&#xff1a; L0——无AI&#xff0c;有工具&#xf…

Mac上的数字足迹助手,myTracks一键管理!

myTracks for Mac是一款在macOS系统上运行的强大且易于使用的GPS跟踪软件应用程序。它专为户外探险家、运动爱好者和旅行者设计&#xff0c;可以帮助用户轻松记录和管理GPS轨迹、航点和地理标记照片。 首先&#xff0c;myTracks具有出色的GPS轨迹记录功能。它能够从各种设备&a…

Linux课程机房虚拟机

Linux课程机房虚拟机 机房虚拟机&#xff08;默认不能联网的&#xff09;&#xff1a; 百度网盘&#xff1a;https://pan.baidu.com/s/1WqSvqB3Y7b_D4690CDBlJA?pwdaugc 123网盘&#xff1a;https://www.123pan.com/s/tQ0UVv-LiolA.html提取码:F4xm ‍ 联网使用说明&…

AI智能体|使用扣子Coze创建AI绘画助手

大家好&#xff0c;我是无界生长。 昨天我们分享了《AI智能体&#xff5c;使用扣子Coze创建AI绘画工作流》&#xff0c;今天分享下如何使用Coze&#xff08;扣子&#xff09;创建AI绘画助手&#xff0c;调用之前创建的绘画工作流。学会了的话&#xff0c;欢迎分享转发&#xff…

Qt模型视图代理之QTableView应用的简单介绍

往期回顾 Qt绘图与图形视图之绘制带三角形箭头的窗口的简单介绍-CSDN博客 Qt绘图与图形视图之Graphics View坐标系的简单介绍-CSDN博客 Qt模型视图代理之MVD(模型-视图-代理)概念的简单介绍-CSDN博客 Qt模型视图代理之QTableView应用的简单介绍 一、最终效果 二、设计思路 这里…

《LTC与铁三角∶从线索到回款-人民邮电》关于铁三角不错的论述

《LTC与铁三角∶从线索到回款-人民邮电》一书中&#xff0c;关于铁三角不错的论述&#xff0c;收藏之&#xff1a;客户责任人的角色定义及核心价值 AR 的核心价值定位主要体现在三个方面&#xff1a;客户关系、 客户满意度、竞争对手 “ 压制 ” 。 维护客户关系&#x…

汽车热辐射、热传导、热对流模拟加速老化太阳光模拟器系统

汽车整车结构复杂&#xff0c;材料种类繁多&#xff0c;在使用过程中会面临各种严酷气候环境的考验&#xff0c;不可避免会出现零部件材料老化、腐蚀等不良现象&#xff0c;从而影响汽车的外观、功能&#xff0c;甚至产生安全隐患。因此&#xff0c;分析汽车零部件材料老化腐蚀…

基于机器学习的网络流量识别分类

1.cicflowmeter的目录框架&#xff1a; 各部分具体代码 FlowMgr类&#xff1a; package cic.cs.unb.ca.flow;import cic.cs.unb.ca.Sys; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.time.LocalDate;public class FlowMgr {protected static final…

每日一博 - 闲聊架构设计中的多级缓存设计

文章目录 方法论概述客户端缓存应用层缓存服务层缓存缓存设计的注意事项总结 思维导图戳这里 方法论概述 从客户端到服务层&#xff0c;缓存的应用广泛而重要。通过合理的缓存设计&#xff0c;能够有效地提高系统的性能并降低延迟。 客户端缓存 在客户端层面&#xff0c;浏览…

代码学习录打卡Day13

1 滑动窗口最大值 使用单调队列&#xff0c;需要一个队列&#xff0c;这个队列呢&#xff0c;放进去窗口里的元素&#xff0c;然后随着窗口的移动&#xff0c;队列也一进一出&#xff0c;每次移动之后&#xff0c;队列告诉我们里面的最大值是什么。 class MyQueue { public:vo…

JavaEE 多线程详细讲解(1)

1.线程是什么 &#xff08;shift F6&#xff09;改类名 1.1.并发编程是什么 &#xff08;1&#xff09;当前的CPU&#xff0c;都是多核心CPU &#xff08;2&#xff09;需要一些特定的编程技巧&#xff0c;把要完成的仍无&#xff0c;拆解成多个部分&#xff0c;并且分别让…