HashSet的常用方法及其源码展示

目录

代码展示

源码部分

add

1.E是什么

2.E的例子

3.E的好处

3.1类型安全

3.2代码复用

4.插入过程

4.1计算哈希值

4.2确定存储位置

4.3处理哈希冲突(如果有)

4.4整个处理的流程HashBuck代码展示

contains

1.含义

2.功能

containsKey

1.含义

2.功能

remove

1.含义

2.功能

Iterator

1.含义

2.功能

hasNext

1.含义

2.功能

next

1.含义

2.功能


代码展示

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        TestSet();
    }

        public static void TestSet() {
            // HashSet是基于哈希表实现的集合,它不保证元素的顺序,添加、删除、查找等操作的平均时间复杂度为常数时间
            Set<String> s = new HashSet<>();

            // add(key): 如果key不存在,则插入,返回true
            // 如果key存在,返回false
            boolean isIn = s.add("apple");
            s.add("orange");
            s.add("peach");
            s.add("banana");
            System.out.println(s.size());
            System.out.println(s);

            isIn = s.add("apple");
            // add(key): key如果是空,抛出空指针异常
            // s.add(null);
            // contains(key): 如果key存在,返回true,否则返回false
            System.out.println(s.contains("apple"));
            System.out.println(s.contains("watermelen"));

            // remove(key): key存在,删除成功返回true
            // key不存在,删除失败返回false
            // key为空,抛出空指针异常
            s.remove("apple");
            System.out.println(s);
            s.remove("watermelen");
            System.out.println(s);

            Iterator<String> it = s.iterator();
            while (it.hasNext()) {
                System.out.print(it.next() + " ");
            }
            System.out.println();
        }
    }

源码部分

add

1.E是什么

是一个泛型参数,它代表集合中元素的类型。

2.E的例子

当创建一个具体的集合实例时,例如Set<String> s = new TreeSet<>();,这里的String就是E所代表的实际类型。也就是说,E在这个例子中被确定为String类型,所以add方法中的e参数的类型就是String,它用于接收一个要添加到集合中的String类型的元素。

3.E的好处

3.1类型安全

通过使用泛型,编译器可以在编译时检查元素的类型是否符合集合的要求。例如,在Set<String> s中,如果试图添加一个非String类型的元素(如一个Integer),编译器会报错,从而避免了在运行时出现类型不匹配的错误。

3.2代码复用

泛型使得集合类(如Set接口及其实现类)可以处理多种不同类型的元素,而不需要为每种元素类型编写单独的集合类。例如,Set接口可以通过指定不同的E类型来创建可以容纳Integer类型元素的集合(Set<Integer>)、容纳Double类型元素的集合(Set<Double>)等,大大提高了代码的复用性。

4.插入过程
4.1计算哈希值
  • 当调用 s.add("apple") 等添加操作时,首先会根据元素(如 "apple")计算其哈希值。哈希值是通过哈希函数对元素进行计算得到的一个整数值。不同的元素通常会计算出不同的哈希值,但也有可能不同元素计算出相同哈希值(这种情况称为哈希冲突,后面会提到如何处理)。
  • 例如,对于 "apple" 这个字符串,哈希函数会根据其字符编码等信息计算出一个对应的哈希值,假设为 hashValue1

4.2确定存储位置

4.3处理哈希冲突(如果有)

扩容之后再次找到应该放在数组下标的位置

4.4整个处理的流程HashBuck代码展示
public class HashBuck {

    static class Node{

        //一个元素由3个部分组成
        //存储在哈希桶中的数据元素的一个值
        //通过 key % array.length 来实现简单的哈希计算
        public int key;

        //它是与特定 key 相关联并存储在哈希桶中的实际数据内容
        public int val;

        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    //定义了一个节点数组
    public Node[] array;

    //计数
    public int UsedSize;

    //设置哈希桶来接受相同的元素
    public HashBuck(){
        //它先开辟了一个能容纳 10 个 Node 类型节点的数组作为哈希桶
        //然后通过 put 方法将键值对插入到对应的哈希桶位置中
        array = new Node[10];
    }

    //桶中放置元素
    public void put(int key,int val){

        //找到这个元素在数组中的下标
        int index = key % array.length;
        //在找到数组中位置的地方设立一个节点指针
        Node cur = array[index];

        //头插法

        while (cur != null){
            //遍历这个数组中有没有和插入元素相同的数
            //如果有就更新这个相同的val
            //因为map不能设置相同的值
            if(cur.key == key){
                cur.val = val;
                return;
            }
            cur = cur.next;
        }

        //如果没有就将其进行创建节点
        //再头插法连接

        Node node = new Node(key, val);
        node.next = array[index];
        array[index] = node;
        UsedSize++;

        //尾插法
        /*
        //开创新节点
        Node node = new Node(key, val);

        //用这个标志去移动完成尾插法加入新的节点
        while (cur.next != null){
            cur = cur.next;
        }
        cur.next = node;
        */


        //元素加入桶中了之后要判断是否负载

        if(loadFactor() >= 0.75){

            //说明已经超载需要扩容
            resize();

        }

    }

    /*
    * 面试题目:
    * 扩容需要注意什么?
    *  答:把所有元素重新哈希
    * */

    private void resize() {
        //假设是2倍扩容,不能直接即将元素还放在原来下标
        //eg.  12%10=2  12%20=12
        Node[] tmpArr = new Node[array.length*2];

        //遍历原来数组,将所有元素《重新哈希》到新的数组当中
        //如果没有元素则会显示null
        for (int i = 0; i < array.length; i++) {

            Node cur = array[i];
            while (cur != null){
                //不能直接即将元素还放在原来下标
                int newIndex = cur.key % tmpArr.length;

                //记录当前节点的下一个节点
                //因为如果cur后面有多个数据,想都先插入再跳过这个数组下标位置
                Node curNext = cur.next;

                //再头插入
                cur.next = tmpArr[newIndex];
                tmpArr[newIndex] = cur;
                cur = curNext;
            }

        }


        //最终将扩容好覆盖到原来数组
        array = tmpArr;

    }

    private double loadFactor(){
        return UsedSize*1.0 / array.length;
    }


    public int get(int key){
        //怎么取到这个下标?
        //先看这个在什么位置
        int index = key % array.length;

        Node cur = array[index];

        while (cur != null){
            if(cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }

}

contains

1.含义

检查集合中是否包含特定的元素。它是java.util.Collection接口定义的方法之一

2.功能

检查一个集合是否包含特定的元素

containsKey

1.含义

检查映射(如HashMapTreeMap等)中是否包含指定键(key)的方法

2.功能

containsKey方法来判断元素是否存在。先计算元素的哈希值,然后根据哈希值找到元素可能存储的位置(索引),再检查该位置及其关联的链表(如果有哈希冲突)中是否存在与给定元素相等的元素。

remove

1.含义

用于从集合中删除元素的方法

2.功能

从集合中移除一个指定的元素

Iterator

1.含义

iterator 是一个方法,用于获取与特定集合(如 ListSet 等)相关联的迭代器对象。在这里,s 是一个 TreeSet 集合(前面代码定义为 Set<String> s = new TreeSet<>();),s.iterator() 就是调用 TreeSet 集合的 iterator 方法来获取一个能够遍历该 TreeSet 集合元素的迭代器。

2.功能

它提供了一种统一且通用的方式来访问集合中的元素,而无需关心集合内部的具体存储结构和实现细节。不同类型的集合(如 ArrayListHashSetTreeSet 等)都可以通过各的 iterator 方法提供适合自身的迭代器,以便按照特定的顺序(例如,TreeSet 按照元素的排序顺序)遍历集合中的元素。

hasNext

1.含义

hasNext 是迭代器接口(Iterator)中定义的一个方法。当通过 s.iterator() 获取到迭代器对象并赋值给 it(即 Iterator<String> it = s.iterator();)后,it.hasNext() 就是调用这个迭代器对象上的 hasNext 方法。它用于判断在当前迭代器位置之后是否还有其他元素可供遍历。

2.功能

在遍历集合元素的过程中,每次循环都会先调用 hasNext 来检查是否还有下一个元素。如果 hasNext 返回 true,则表示迭代器后面还有未被遍历到的元素,此时可以继续执行后续操作(如调用 next 方法获取下一个元素);如果 hasNext 返回 false,则说明已经遍历完集合中的所有元素,循环应该结束。

next

1.含义

next 同样是迭代器接口(Iterator)中定义的一个方法。当 it.hasNext() 返回 true 时,it.next() 就是调用这个迭代器对象上的 next 方法。它用于获取当前迭代器位置之后的下一个元素,并将迭代器的指针向前移动一位,以便下次循环时能够获取到下一个新的元素。

2.功能

在遍历集合的代码逻辑中,通常是在 hasNext 返回 true 的情况下调用 next 方法来逐个获取集合中的元素。例如,在你给出的代码中,通过 System.out.print(it.next() + " "); 将获取到的下一个元素输出到控制台,并在元素后面添加一个空格,从而实现逐个元素的输出展示,直到 hasNext 返回 false,完成整个集合元素的遍历输出。

综上所述,iterator 用于获取集合的迭代器对象,hasNext 用于判断是否还有后续元素可供遍历,next 用于获取下一个元素并移动迭代器指针,它们共同协作实现了对集合元素的有序、完整的遍历操作。


当遇到方法时,去了解源码会更加理解这个方法的运作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值