Java集合 -- ArrayList 的扩容机制HashMap 和 Hashtable的区别HashMap 和 HashSet区别HashSet如何检查重复

Posted CodeJiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合 -- ArrayList 的扩容机制HashMap 和 Hashtable的区别HashMap 和 HashSet区别HashSet如何检查重复相关的知识,希望对你有一定的参考价值。

1. ArrayList 的扩容机制

  • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;
  • 存放元素的数组:transient Object[] elementData;
  • 实际元素个数:private int size;

创建对象时调用的无参构造函数:

//这是一个空的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = ;
public ArrayList() 
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

  • 这段源码说明当你没有向集合中添加任何元素时,集合容量为0。那么默认的10个容量怎么来的呢?

这就得看看add方法的源码了:

add(E e)

public boolean add(E e) 
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;

  • 假设你new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);

该方法源码如下:

ensureCapacityInternal(int minCapacity)

private void ensureCapacityInternal(int minCapacity) 
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

  • 该方法中的参数minCapacity传入的值为size+1也就是 1,接着我们再进入到calculateCapacity(elementData, minCapacity)里面:

calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) 
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    
    return minCapacity;

  • 上文说过,elementData就是存放元素的数组,当前容量为0,if条件成立,返回默认容量DEFAULT_CAPACITY也就是10。这个值作为参数又传入ensureExplicitCapacity(int minCapacity)方法中,进入该方法查看源码:

ensureExplicitCapacity(int minCapacity)

private void ensureExplicitCapacity(int minCapacity) 
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);

  • 我们先不要管modCount这个变量。
  • 因为elementData数组长度为0,所以if条件成立,调用grow方法,重要的部分来了,我们再次进入到grow方法的源码中:

grow(int minCapacity)

private void grow(int minCapacity) 
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    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);

  • 这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;又声明了一个newCapacity变量其值为oldCapacity+一个增量,可以发现这个增量是和原数组长度有关的量,当然在这里也为0。第一个if条件满足,newCapacity的值为10(这就是默认的容量,不理解的话再看看前面)。第二个if条件不成立,也可以不用注意,因为MAX_ARRAY_SIZE的定义如下:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 这个值太大了以至于第二个if条件没有了解的必要。

  • 最后一句话就是为elementData数组赋予了新的长度,Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。

  • 这时候再回到add的方法中,接着就向下执行elementData[size++] = e;到这里为止关于ArrayList就讲解得差不多了,当数组长度为10的时候你们可以试着过一下源码,查一下每次的增量是多少(答案是每次扩容为原来的1.5倍)。


2. HashMap 和 Hashtable的区别

  1. 线程是否安全HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  2. 效率:因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
  3. 对Null key 和Null value的支持HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTableput 进的键值只要有一个 null,直接抛出 NullPointerException
  4. 初始容量大小和每次扩充容量大小的不同
    • 创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
    • 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。

HashMap 中带有初始容量的构造函数

下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。


3. HashMap 和 HashSet区别

如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。HashSet 的源码非常非常少,因为除了 clone()writeObject()readObject()HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。

  • 点进去HashSet的构造方法,我们发现HashSet就是用HashMap实现的:

  • 点进去HashSet的put方法,发现就是用的map的put方法(用map的key来保存数据):


4. HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

hashCode()与equals()的相关规定:

  1. 如果两个对象相等,则hashcode⼀定也是相同的
  2. 两个对象相等,对两个equals⽅法返回true。
  3. 两个对象有相同的hashcode值,它们也不⼀定是相等的。
  4. 综上,equals方法被覆盖过,则hashCode⽅法也必须被覆盖(equals返回true,两个对象就一定相等。而两个对象相等,则hashcode⼀定也是相同的)。
  5. hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写hashCode(),则该class的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。

==与equals的区别:

  • ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同。
  • ==是指对内存地址进行比较 equals()是对字符串的内容进行比较。
  • ==指引用是否相同 equals()指的是值是否相同。


以上是关于Java集合 -- ArrayList 的扩容机制HashMap 和 Hashtable的区别HashMap 和 HashSet区别HashSet如何检查重复的主要内容,如果未能解决你的问题,请参考以下文章

Java集合 -- ArrayList 的扩容机制HashMap 和 Hashtable的区别HashMap 和 HashSet区别HashSet如何检查重复

7-java安全基础——ArrayList,Vector源码分析

7-java安全基础——ArrayList,Vector源码分析

7-java安全基础——ArrayList,Vector源码分析

Java 常用集合类的底层扩容机制

Java 常用集合类的底层扩容机制