集合(包含hashCode以及一些常见面试题)

本文详细介绍了Java集合框架中的各种数据结构,如List(ArrayList和LinkedList)、Set(HashSet)和Map(HashMap),重点讨论了它们的特点、实现方式、并发安全性和扩容策略。涉及面试常见问题,如HashMap的哈希冲突解决、容量设定和线程安全问题。
摘要由CSDN通过智能技术生成

目录

集合

集合的特点

List(接口)

ArrayList

Vector和ArrayList集合区别

LinkedList

ArrayList和LinkedList的区别

Set

HashSet

equals和hashCode

Map(接口)

HashMap

HashMap中put方法

Map接口下其他一些实现类

面试题

为什么HashMap的容量是2的倍数?

为什么HashMap链表转红黑树的阈值和红黑树转链表的阈值不都为8呢?

扩容在什么时候呢?为什么扩容因子是0.75?

HashMap 是线程安全的吗?多线程下会有什么问题?


集合

保存一组相同类型的元素时,我们会用到数组,但数组一旦被定义,便无法进行修改。

在日常开发中,我们更加需要的是可以动态扩展的一个容器,此时就有了集合

集合的特点

提供一种存储空间可变的存储模型,存储的数据容量可以发生改变

List(接口)

实现类 arraylist linkedlist 存入数据 都是保证有序性 并且可以重复

ArrayList

ArrayList<>()创建一个长度为10的底层数组,第一次添加元素时,真正创建数组

add()添加一个元素到集合中的过程 调用add()添加元素时,先检查底层数组是否还能放得下,如果可以,直接将元素添加到末尾, 如果不可以,会创建一个新的数组,将原来的数组内容复制过去

  size  --->记录实际装入数组的元素个数
       public boolean add(E e) {
      ensureCapacityInternal(size + 1);  // 检查元素能否放的进去
      elementData[size++] = e;
      return true;
  }
      放进去后容量大小 - 底层数组长度 >0
            if (minCapacity - elementData.length > 0)
                    grow(minCapacity); 扩容
                    
             private void grow(int minCapacity) {
      // overflow-conscious code
      int oldCapacity = elementData.length;
      int newCapacity = oldCapacity + (oldCapacity >> 1); 新数组容量为原来的1.5倍
      if (newCapacity - minCapacity < 0)
          newCapacity = minCapacity;
      if (newCapacity - MAX_ARRAY_SIZE > 0)
          newCapacity = hugeCapacity(minCapacity);
      // minCapacity is usually close to size, so this is a win:
       数组复制,创建一个新数组,将原来的数组内容复制到新数组中
      elementData = Arrays.copyOf(elementData, newCapacity);
  } 

Vector和ArrayList集合区别

相同点:

  • ArrayList和Vector默认初始化容量=10

  • 底层都是基于数组实现

  • List接口下的子类

不同点:

  • ArrayList 线程不安全 Vector 线程是安全的

  • ArrayList每次扩容是原来容量的1.5倍

  • Vector每次扩容是原来容量的2倍

  • Vector设置每次扩容的容量。

  • ArrayList 懒加载的形式初始化容量,Vector直接通过构造函数初始化 数组容量=10

LinkedList

底层是基于链表实现的,可以存放重复的元素,有序

add() 每添加一个元素创建一个Node对象
     E item;
     Node<E> next;
     Node<E> prev;
LinkedList<String> llist = new LinkedList();
                  llist.add("a");
                  llist.add("b");
                  llist.add("c");
                  llist.add("d");
                  llist.add("a");
                  //index<size/2从头节点开始向后找,否则从尾节点向前找
                  System.out.println(llist.get(3));
                  System.out.println(llist);

ArrayList和LinkedList的区别

  • ArrayList基于数组实现,LinkedList基于双向链表实现

  • ArrayList查找效率更高,LinkedList增删效率更高

Set

实现类HashSet(无序)、TreeSet(有序)

HashSet

  • 基于hashMap来实现的,是一个不允许有重复元素的集合

  • 允许有null值

  • 无序的,即不会记录插入的顺序

  • 没有Get方法,所以不能使用普通for循环遍历

HashSet中添加元素时是如何去除重复元素的?
            判断要求是既快又安全.
  System.out.println("abc".equals("abc")); equals判断安全但效率低.
           怎么提高比较速度 ?
               使用内容的hash值比较(哈希值是整数), 但是哈希值是不安全(有可能内容不同,哈希值相同)
         最终解决方案就是先用内容计算的哈希值比较,如果出现了相同的哈希值,那么再调用equals方法判断内容是否相同

equals和hashCode

hashCode属于Object父类中 Java虚拟机提供给每个对象生成一个hashCode值 整数类型 int类型

ps:

  • 如果equals方法比较两个对象相等,则hashCode值也一定相等

  • 但是两个对象的hashCode值相等不代表使用equals比较也相等

  • 如果两个对象的hashCode值相等,但是值不同,这就是我们所说的Hash冲突问题

String strA = "a";
Integer int97 = 97;// 整数类型
System.out.println(strA.hashCode());//97
System.out.println(int97.hashCode());//97

Map(接口)

键值对存储

键不能重复,值可以重复

实现类HashMap、TreeMap、Hashtable

HashMap

数组+链表+红黑树 结构实现 线程不安全,默认初始化容量为16

当向HashMap中添加元素时,先用key计算出一个哈希值,通过哈希算法,计算出此key在哈希表中的位置,如果这个位置上没有元素,则直接插入

当再次插入值时,如果计算出的位置上已经存在值了,那么就会向元素的下一位添加(链表)

当链表的长度大于8并且数组长度大于64时,链表就会转成红黑树

如果红黑树节点个数小于6,转为链表

当数组被使用了0.75时,哈希数组会进行扩容,扩容为原来的2倍

HashMap中put方法
put(K,V) 添加元素整个过程 以及底层存储结构
              public V put(K key, V value) {
                              计算哈希值
                return putVal(hash(key), key, value, false, true);
}
以下源码我们可以看出hashMap的key是可以存储null的

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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) 哈希数组为空或长度为0时,初始化数组 resize()
         n = (tab = resize()).length;  n=哈希表长度
     if ((p = tab[i = (n - 1) & hash]) == null) 数组长度-1 & hash == 哈希值%数组长度 计算元素位置
         tab[i] = newNode(hash, key, value, null);
     else {
     
     }

Map接口下其他一些实现类

HashMap        hash表+链表+红黑树 结构实现 线程不安全
TreeMap        底层是红黑树实现 并且是有序的
Hashtable      hash表+链表+红黑树 结构实现 线程安全 不能存储为null的键

面试题

HashMap 是怎么解决哈希冲突的?

①开放定址法:从发生冲突的那个位置开始,通过一定的次序从hash表里面找一个空闲的位置。之后将发生冲突的元素存入到这个空闲位置中。

②链式寻址法:也是现在HashMap所用的方法,当发生hash冲突时,会进行链表的转化。以原本数组中相同位置的元素为链表头结点。

③再 hash 法:发生冲突后,再次用另外一个hash函数对这个key进行hash,一直运算下去直到不再冲突。这种方式会增加计算时间,性能影响较大

为什么HashMap的容量是2的倍数?

  • 为了方便哈希取余 (数组长度-1)&哈希值

  • 在扩容时,利用扩容后的大小也是2的倍数,将已经产生hash碰撞的元素完美的转移到新的数组中去

为什么HashMap链表转红黑树的阈值和红黑树转链表的阈值不都为8呢?

因为如果两者都是8,假如发生碰撞,节点增减刚好在8附近,会发生链表和红黑树的不断转换,导致资源浪费

扩容在什么时候呢?为什么扩容因子是0.75?

临界值(threshold )= 默认容量(DEFAULT_INITIAL_CAPACITY) * 默认扩容因子(DEFAULT_LOAD_FACTOR)

如果我们的负载因子比较大,元素比较多,空位比较少的时候才会扩容,那么发生哈希冲突的概率就会增加,查找的时间成本也就会增加

如果我们的负载因子比较小,元素比较少,空位比较多就会发生扩容,发生哈希碰撞的概率减小了,查找时间成本降低,但是空间成本就会增加

HashMap 是线程安全的吗?多线程下会有什么问题?

HashMap不是线程安全的,可能会发生这些问题:

  • 多线程的 put 可能导致元素的丢失。多线程同时执行 put 操作,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。

  • put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出 threshold 而导致 rehash,线程 2 此时执行 get,有可能导致这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重开之Java程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值