首页 > 其他分享 >集合(3)

集合(3)

时间:2023-07-29 18:02:38浏览次数:25  
标签:hash key int 数组 集合 null HashMap

HashSet类

HashSet实现了Set接口

HashSet本质上是HashMap

public HashSet() {
        map = new HashMap<>();
    }

存储的元素不重复、无序其取决于hash确定索引的结果

底层说明

HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)

示例:

集合(3)_HashSet

说明:

1)先获取传入元素的哈希值(hashCode方法)

2)对哈希值进行运算,得出索引,找到数组该索引的位置

3)判断是否有元素,无元素直接存放,有元素,遍历链表用equals方法进行判断是否有相等元素

4)相等,则不添加,add方法返回false,

不相等,则添加到链表的尾部,add方法返回true

5)在java8中,当一条链表的元素个数超过8时,就会进行树化(红黑树)

6)可以对hash()和equals()进行重写,实现用户自定义去重

应用

//定义Person类,有三个属性 名字,年龄,工资
//当名字和年龄的值相同时,则认为时相同员工,不能添加到HashSet集合中
public class Test1 {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        set.add(new Person("A",1,1.0));
        set.add(new Person("A",2,2.0));
        set.add(new Person("A",3,3.0));
        set.add(new Person("A",4,4.0));
        set.add(new Person("A",1,5.0));
        System.out.println(set);
    }
}
class Person{
    String name;
    int age;
    double wage;

    public Person(String name, int age, double wage) {
        this.name = name;
        this.age = age;
        this.wage = wage;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age  
            && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", wage=" + wage +
                '}';
    }
}

底层分析源码

1)HashSet类的本质其实是HashMap类,其中的属性如下

---- jdk 17 ---- HashMap中的属性 ----

static final int DEFAULT_INITIAL_CAPACITY = 1<<4;
//默认的初始容量16

static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的最大容量

static final float DEFAULT_LOAD_FACTOR = 0.75f;
//构造函数中未指定时使用的负载系数,当数据量达到容量的0.75倍时,进行扩容

static final int TREEIFY_THRESHOLD = 8;
//当链表的长度超过8时,会进行树化,变为红黑树

static final int UNTREEIFY_THRESHOLD = 6;
//代表红黑树的节点小于等于6时,会重新变为链表

static final int MIN_TREEIFY_CAPACITY = 64;
//当发生哈希冲突且哈希表的容量小于64时,会选择扩容,而不是选择拉链法
//哈希冲突:
//	不同的key计算出了相同的数组索引
//拉链法:
//	解决哈希冲突的方式,在链表末尾加入冲突的元素

transient Set<Map.Entry<K,V>> entrySet;
//存放所有的键值对

final float loadFactor;
//加载因子

transient int modCount;
//每次改变结构时,都会自增,是一种错误检测机制。
//在迭代集合的时候,若结构发生改变,会发生fail-fast,然后抛出异常。

private static final long serialVersionUID = 362498820763181265L;
//用于比较版本

transient int size;
//数组中数据量的大小

transient Node<K,V>[] table; 
//存放所有元素节点的数组

int threshold;
//数组扩容的阈值

---- jdk 17 ---- HashSet中的属性 ----
private static final Object PRESENT = new Object();
//PRESENT新对象

private transient HashMap<E,Object> map;
//HashMap对象

static final long serialVersionUID = -5024744406713321676L;
//用于比较版本

2)HashMap的构造器

---- jdk 17 ---- HashMap中的构造器 ----

public HashSet() {
        map = new HashMap<>();
    }

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f)+1, 16));
        addAll(c);
    }

public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

3)HashMap的方法,这里从add方法进入,看看底层是如何实现的

---- jdk 17 ---- HashMap中的方法,add方法为入口 ----
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    	//put方法
    }

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    	//1)hash(key)
    	//2)putVal(hash(key), key, value, false, true)
    }

4)hash(key)方法,根据元素值的哈希值,计算出相应的数组下标

 static final int hash(Object key) { // 根据哈希值,计算数组索引返回
        int h;
        return (key == null) ? 
       		0 : (h = key.hashCode()) ^ (h >>> 16);
    }

5)putVal(hash(key),key,value,false,true)方法,加入值

final V putVal(int hash, K key, V value, 
               boolean onlyIfAbsent,boolean evict) {
       	Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    //判断当前的数组是否为空,为空进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
 //resize()方法 ---- 扩容机制
    
    	//根据hash数组索引,判断是否数组索引hash上有无元素存在
    	//若没有,则搞一个新节点直接添加到该数组索引位置上
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //若已经存在元素了
            Node<K,V> e; K k;
            
            if (p.hash == hash && ((k = p.key) == key 
                               || (key != null && key.equals(k))))
            //如果当前位置上的元素和传入元素的hash值和key值相等
            //将p赋值给e
            	e = p;
            else if (p instanceof TreeNode)
                //如果当前是红黑树结构,则加入到红黑树中
            	e = ((TreeNode<K,V>)p).putTreeVal(this, tab, 
                                              hash, key, value);
            else {
                //此位置存在元素,且是普通链表结构
                //将该元素添加链表尾部
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        //头结点的下一个结点为null,插入新结点
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            //若插入后,链表长度超过8,变为红黑树
                            // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果在链表中找到了相同的key,退出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || 
                         (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //出现了哈希冲突的现象,e被新值替代,节点位置不变
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //e被代替,并返回该值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                	
                //空实现,其步骤在LinkedHashMap中实现
                //根据访问先后顺序对元素排序
                afterNodeAccess(e);
                return oldValue;
            }
        }
    	//错误检测机制
        ++modCount;
        if (++size > threshold)
            //数组元素超过阈值,会进行扩容
            resize();
    	//与afterNodeAccess()方法一致,空实现
        afterNodeInsertion(evict);
        return null;
    }

6)resize()扩容方法机制,对数组进行扩容

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
    	//数组
 
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
    	//数组容量
    
        int oldThr = threshold;
    	//数组扩容的阈值
    
    	//新数组的容量和阈值
        int newCap, newThr = 0;
    
        if (oldCap > 0) {
            //旧数组的容量不为0
            if (oldCap >= MAXIMUM_CAPACITY) {
                //容量达到了最大值
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY 
                     && oldCap >= DEFAULT_INITIAL_CAPACITY)
                //新数组的容量和阈值为原来数组的两倍
                
                newThr = oldThr << 1; 
            // double threshold
        }
        else if (oldThr > 0) 
            // initial capacity was placed in threshold
            newCap = oldThr;
        else {           
            
  // zero initial threshold signifies using defaults
  newCap = DEFAULT_INITIAL_CAPACITY;
  newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  //在这里,oldCap和oldThr都是<=0的,数组的容量和阈值取默认值,16和12
        }
        if (newThr == 0) {
          	//newThr为0时,newCao为16,加载因子loadFactor为0.75
            float ft = (float)newCap * loadFactor;//ft计算可得12
            newThr = (newCap < MAXIMUM_CAPACITY 
                      && ft < (float)MAXIMUM_CAPACITY 
                      ?(int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
  //表示数组下次需要扩容的阈值
        @SuppressWarnings({"rawtypes","unchecked"})
  			//在这里数组才真正的被创建出来
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
  
   			//如果原来的数组不为null,将原来的数组拷贝到新数组中
        if (oldTab != null) {
         
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, 
                                                 j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                      
                    }//else {  preserve order
                }//if((e = oldTab[j]) != null) 
            }//for
        }//if
        return newTab;
    }







标签:hash,key,int,数组,集合,null,HashMap
From: https://blog.51cto.com/u_16188762/6894069

相关文章

  • Java之不可变集合
    Java之不可变集合什么是不可变集合?不可变集合就是不可被修改的集合。集合的数据项在创建的时候提供,并且在整个生命周期中都不可被改变。否则报错为什么要创建不可变集合?如果当某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的选择。或者当集合对象被......
  • Linux&Docker命令集合
    linux查看服务安装目录serverdir#查看服务安装目录whereis[服务名]whereismysql#通过进程号查找目录ps-aux|grep[服务名]ps-aux|grepmysql#查看进程目录ll/proc/[进程号]/cwdlinux查看某个进程所在目录 docker查看文件列表查看docker里面的文件在哪里......
  • 集合(1)
    前言为什么使用集合?1)可以动态保存任意多个对象,使用较为方便2)提供了一系列方便的操作对象的方法3)使用集合添加、删除新元素的代码简洁明了Collection接口集合框架体系1)集合主要是两组(单列集合、双列集合)2)Collection接口有两个重要子接口List和Set,实现子类都是单列集合3)Map接口的实现......
  • YAML+PyYAML笔记 3 | YAML集合、结构、标量、标记使用
    (3|YAML集合、结构、标量、标记使用)1集合YAML支持三种集合类型:列表,映射和集。1.1列表列表是一种序列结构,它使用连字符“-”表示;如下三个元素的列表,元素之间用“-”:fruit:-apple-rubber-pear使用Pyyaml解析:#解析withopen("config_jihe.yaml")asf:......
  • java 调用方法返回集合
    Java调用方法返回集合的实现步骤对于刚入行的小白来说,Java调用方法返回集合可能会有一些困惑。在本文中,我将向你介绍如何实现Java调用方法返回集合的步骤,并提供相应的代码示例。让我们开始吧!步骤概览下面是实现Java调用方法返回集合的步骤概览。我们将在后续的部分中详细解释每......
  • List集合去重
    需求场景:接口返回的数据是一个List集合需要将这个集合中的数据进行一个过滤,保证没有重复数据.优点:使用迭代器进行去除多余数据不会,简单高效,不会发生过滤不全,数组越界等问题.实例代码:publicvoidlistTest(){List<String>list=newArrayList<>(Arrays.as......
  • 面试-集合
    hashMap扩容大于当前容量0.75,扩容成2倍。创建一个新空数组,重新hash初始容量16链表大于8转换为红黑树,小于6退化为链表indexindex=HashCode(Key)&(Length-1)index的结果等同于HashCode后⼏位的值。只要输⼊的HashCode本身分布均匀,Hash算法的结果就是均匀的hashTable在很......
  • Redis的有序集合Zset为啥用跳表不用二叉树
    跳表和红黑树查找的时间复杂度都是logN,插入删除也是logN。范围查找貌似也都是O(k+logn),其中n是树中节点的数量,k是满足范围条件的节点数量。但是实现起来跳表要简单很多。1.zset有个很核心的操作叫范围查找,我们要查找某个范围区间的元素。跳表可以做到logN时间复杂度内的快......
  • 集合框架
    集合框架集合的概念概念:对象的容器,实现对对象常用的操作,类似数组功能和数组的区别:数组长度固定,集合长度不固定数组可以存储基本类型和引用类型,集合只能存储引用类型Collection接口Collection父接口特点:代表一组任意类型的对象,无序.无下标.不能重复方法:boole......
  • redis 取出指定集合
    Redis取出指定集合Redis是一种高性能的键值存储数据库,它支持多种数据类型,包括字符串、哈希、列表、集合和有序集合。在Redis中,集合是一种无序且唯一的数据结构,它可以存储多个元素。本文将介绍如何在Redis中取出指定集合的元素,并提供相关代码示例。Redis集合Redis集合是一个无序......