集合

一、Arraylist类

1.1 默认初始容量

默认初始容量是10

private static final int DEFAULT_CAPACITY = 10;

1.2 数据序列化

用transient关键字修饰,意味着不使用默认的序列化方法,他使用writeObject方法来序列化,使用readObject来反序列化。

transient Object[] elementData;

1.3 构造方法

  • 带参数构造方法
   public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

如果参数大于0,则直接新建对应长度的Object数组返回。
如果参数等于0.则指向空数组的指针
参数小于0,抛出异常

  • 参数是Collection接口的实例
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

将Collection实例转换为数组,记录下实例的长度
长度小于0.指向空数组
长度大于0,elementData 数组指向该数组

1.4 添加

  • 判断集合是不是已经满了
  • 集合满的话判断集合的容量是不是小默认容量的一半,是的话,集合的长度增加12,否则增加当前集合长度的一半
  • 将旧的集合数据放到新的集合中

1.5 删除(index的删除)

  • 判断index是否大于集合的长度,大于,则抛出数组越界异常
  • 小于,则将index后面的元素向前复制,返回删除的元素

二、HashMap

参考:一文读懂HashMap

2.1 避免哈希冲突

HashMap使用链表法避免哈希冲突(相同hash值),当链表长度大于TREEIFY_THRESHOLD(默认为8)时,将链表转换为红黑树,当然小于UNTREEIFY_THRESHOLD(默认为6)时,又会转回链表以达到性能均衡。

2.2 HashMap结果图

在这里插入图片描述

2.3 默认容量

16,而且hashmap的容量只能是2的幂数

2.4 hash值的计算

  • 首先调用hashcode方法计算出key对应的hash值,记为hash1
  • 将hash1右移16位记为hash2,与hash1进行异或记为hash3
  • 用hash3与n(集合的长度减1)进行与运算,相当于做了取余运算,只不过这种运算更加高效和节省计算机资源

2.4.1 equals方法

未重写的形式:和==一样的功能,判断的是比较对象的地址是否相同。

 public boolean equals(Object obj) {
        return (this == obj);
    }

2.4.2 string类重写equals方法

  • 地址相同返回true
  • 地址不同转换为string类,逐个比较字符
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

2.4.3 为什么重写equals方法需要重写hashcode方法

equals方法重写后,比较的是对象的值。比如:stu stu1 = new stu(1,“张三”),stu stu2 = new stu(1,“张三”),使用重写后的equals方法,stu1.equals(stu2)返回true,使用未重写的hashcode,未重写的hashcode是根据对象的地址通过hash算法算来的,可见,使用未重写的hashcode比较,返回false,违反了equals方法相等,hashcode也必须相等的规定,就会产生,同样的key,返回的hashcode值不同,就会出错。

2.5 put元素的过程

  • 判断table是否为空,空的话先构造table
  • 判断key是否为空,是的话放在table为0的位置
  • 计算出key的hash值
  • 根据hash值找到元素要放的桶的位置,遍历桶中的链表,有相同的key值,就将对应的value替换,并返回oldvalu
  • 如果没有相同的key值,就进行新增节点的的操作
  • 新增节点需要判断新增节点后(当前节点不存在)节点的数量是否等于阈值和当前节点是否存在
  • 大于的话就进行扩容

注意:

  • 桶中链表的长度大于8,转为红黑树,小于6,转换为链表
  • 如果桶满了(容量*加载因子(0.5)),就resize,2倍

2.6 get元素的过程

  • 判断key是不是为null,是的话去0号位置的桶去找key为null的value然后返回
  • key不为null,根据key计算出对应的hash值
  • 找到hash值对应的通桶,根据key的值找到对应的value值

2.7 主要参数

1)桶(capacity)容量,即数组长度:DEFAULT_INITIAL_CAPACITY=1<<4;默认值为16

即在不提供有参构造的时候,声明的hashmap的桶容量;

2)MAXIMUM_CAPACITY = 1 << 30;

极限容量,表示hashmap能承受的最大桶容量为2的30次方,超过这个容量将不再扩容,让hash碰撞起来吧!

3)static final float DEFAULT_LOAD_FACTOR = 0.75f;

负载因子(loadfactor,默认0.75),负载因子有个奇特的效果,表示当当前容量大于(size/)时,将进行hashmap的扩容,扩容一般为扩容为原来的两倍。

4)int threshold;阈值

阈值算法为capacity*loadfactory,大致当map中entry数量大于此阈值时进行扩容(1.8)

5)transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;(默认为空{})

核心的数据结构,即所谓的数组+链表的部分。

2.8 节点的类型

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        }

2.9 扩容和碰撞

扩容的思路:

  • 判断是否到达最大的容量
  • 达到最大容量,设置新的容量为原容量的2倍
  • 将原来table里面的值放到新的table里面
  • 扩容完之后需要重新根据key值计算在table上的hash位置

2.10 先插入?先扩容?

JDK1.8:先插入后扩容
JDK1.7:先扩容后插入

三、HashTable

3.1 基础参数

  • 默认加载因子:0.75
  • 初始容量:11
  • 扩容:2倍加1
  • 容量:hashtable的容量可以为任意值
  • 继承实现:继承自Dictioary类,实现map接口

3.2 重要的成员变量

table, count, threshold, loadFactor, modCount。

3.3 hash值的计算

  • 直接使用了hashcode计算出来的值,然后进行一次与运算,将负的哈希值转换为正的哈希值,再对数组的长度进行模运算。
 index = (hash & 0x7FFFFFFF) % tab.length;

3.4 hashmap和hashtable的区别

参考:HashTable和HashMap的区别详解

3.5 hashtable的遍历

  • 枚举遍历
public class list {
    public static void main(String[] args) {
        Hashtable<Object, Object> map = new Hashtable<>();
        map.put("1","12");
        map.put("4","22");
        map.put("3","32");
        map.put("5","42");
        Enumeration<Object> elements = map.elements();
        while (elements.hasMoreElements()){
            System.out.print(elements.nextElement() + " ");
        }
    }
}
  • entrySet遍历
public class list {
    public static void main(String[] args) {
        Hashtable<Object, Object> map = new Hashtable<>();
        map.put("1","12");
        map.put("4","22");
        map.put("3","32");
        map.put("5","42");
        Iterator<Map.Entry<Object, Object>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Object, Object> next = iterator.next();
            System.out.println((String)next.getValue() + " " + next.getKey() + " ");
        }
    }
}

  • keyset遍历
public class list {
    public static void main(String[] args) {
        Hashtable<Object, Object> map = new Hashtable<>();
        map.put("1","12");
        map.put("4","22");
        map.put("3","32");
        map.put("5","42");
        Iterator<Object> iterator = map.keySet().iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(map.get(next) + " " + next);
        }
    }
}

四、ConcurrentHashMap(JDK1.6和JDK1.7)

4.1 参考

Concurrenthashmap总结

4.2 put方法 JDK1.6和JDK1.7的区别

在获得segment锁之前,会通过trylock方法尝试获得锁,在尝试获得锁的过程中,会遍历对应的位置的链表,如果找不到相同的key值,则会提前创建好一个节点,为后续的put操作做准备,trylock多次,仍然无法获得锁,就会请求lock去获得锁。事先遍历的好处是,将已经遍历的结果进行缓存,等到真正put的时候就不需要缓存了。插入节点时,使用的是头结点插入的方法,再将链表插入到对应的数组位置中。

4.3 rehash

对之前的rehash进行了优化,在扩容之后,选择一个节点,这个节点之后的所有节点的索引都不改变,这个节点之前的索引变为Index + capacity。

4.4 remove

和put方法一样,在尝试获得锁时会进行遍历,已提高缓存命中率。

4.5 get和containskey

使用的是Unsafe的getObjectVolatile方法提供的原子读语义来获取对应的段上的链表,未使用锁,在读数据的过程中,其他线程可能对数据已经做了改变,返回的可能是过时的数据,如果要求强一致性,那么必须使用Collections.synchronizedMap()方法。

4.6 特性

  • 不允许key和value为null
  • 使用段锁来实现高并发

4.7 结构图

在这里插入图片描述

4.9 JDK1.8 实现

cas方式实现

4.10 扩容

2倍的容量进行扩容,只是对一个段进行扩容

4.11 size和containsKey方法

可能需要锁住整张表,按顺序上锁,再按顺序解锁,不然会产生死锁。

4.12 参考

HashMap、Hashtable、ConcurrentHashMap的原理与区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值