集合框架


接口Collection有两个派生接口,一个List,一个Set,Queue继承自Collection,通常用LinkedList实现

接口List的实现类有ArrayList(基于动态数组),LinkedList(基于双向链表),Vector(线程安全的)

栈Stack继承自Vector,基本的push和pop方法,peek得到栈顶元素,empty判断堆栈是否为空,search检测一个元素在堆栈中的位置

接口Set的实现类有HashSet,LinkedHashSet,TreeSet 特点:无序,元素不重复

接口Map有一个派生接口ConcurrentMap,两个实现类,HashTable,HashMap

1、HashMap
  1. hash算法:

    static final int hash(Object key){
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    在hash表中查找下标时使用的是:(length - 1)& hash = hash % length, 其中length是hash表的容量

    将hashCode值无符号右移16位,也就是 int 类型的一半,刚好将该二进制数对半切开,并使用按位异或,则尽量打乱了hashCode的低16位,从而降低hash冲突,当table的length较小时,使得高16位也参与了运算。

  2. 自动将容量控制为 2 的 n 次幂

    static final int tableSizefor(int cap){
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n+1;
    }
    

    其中MAXIMUM_CAPACITY = 2^30, 之所以不是2^31,是因为二进制中数字中的最高位是符号位

    int n = cap - 1; 之所以要先减一,是为了保证传入的参数 cap 本就为 2 的 n 次幂的情况下,不会变为原来的2倍。该方法会在实例化的时候调用。

    jdk1.7中使用数组+链表实现,使用Entry来代表每个节点

    jdk1.8中使用数组+链表/红黑树实现,使用Node来代表节点,含有hash,key,value,next属性,当转换为红黑树时,用TreeNode

  3. 与hashtable的比较

    1. hashtable不要求底层数组的容量一定是2的 n 次幂,初始容量是11;hashmap则有要求,初始容量为16
    2. 扩容时hashtable变为原来的2倍+1(oldCap*2+1);hashmap变为原来的2倍(newCap = oldCap<<1)
    3. 确定下标时,hashtable用的是取模运算;hashmap用的是&操作,效率高
    4. hashtable直接取的key.hashCode,而hashmap重新计算了key的hash值,能更有效的避免hash冲突
    5. hashtable中的key-value都不允许为null;hashmap可以,但是只能有一个null的key,value可以有多个
    6. hashtable是线程安全的,是因为都是synchronized的方法;hashmap是非线程安全的
2、ConcurrentHashMap
  1. jdk1.7中的实现原理:

    采用数组 + segment + 分段锁实现

    segment是类似于hashmap的结构,每一个分段都类似于一个hashmap,分段锁继承自ReentrantLock

    ConcurrentHashMap定位一个元素需要进行两次hash操作,第一次定位到segment,第二次定位到元素所在的链表的头部

    优点:对元素所在的segment加锁,从而提高了并发能力

    缺点:hash过程比普通的hashmap要长

  2. jdk1.8中的实现原理:

    参考了hashmap,采用数组 + 链表/红黑树

    内部采用CAS + Synchronized来保证线程安全,1.8中彻底放弃了segment,而采用Node来存储节点。其中val 和 next 使用volatile来修饰,保证并发的可见性。

    而且1.8中的锁粒度更小,锁每个组;1.7中锁的是segment

    CAS:是一种乐观锁的实现方式,包含三个操作数 内存位置(V) 预期原值(A) 和 新值(B)

    若 V == A,则将 V 更新为 B,否则通过无限循环来获取数据

    CAS缺点:

    • cpu 开销大,在并发量比较高的情况下,如果许多线程循环往复尝试更新某一个变量,却又一直更新不成功,循环往复,,会给cpu带来很大的压力
    • 不能保证代码块的原子性,只能保证一个变量的原子操作
3、ArrayList
  1. 默认容量为10;其实在初始化的时候,若不指定容量大小,其实创建的是一个空的对象数组。真正的指定默认容量为10,是在进行add操作时,先进行容量判断。若初始为空数组,则将容量设置为默认容量10。然后再去判断是否需要进行扩容。

    public boolean add(E e) {
    	ensureCapacityInternal(size + 1);  // Increments modCount!!
    	elementData[size++] = e;
    	return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
    	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    	}
    	ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
    	modCount++;
    	if (minCapacity - elementData.length > 0)
    		grow(minCapacity);
    }
    private void grow(int minCapacity) {
    	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);
    	elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //由于在进行进行扩容时,需要进行元素的复制,比较消耗性能,所以在创建时应该指定合适的大小
    
  2. 与LinkedList比较

    1. ArrayList是基于动态数组实现;LinkedList基于双向链表实现
    2. 随机访问元素时,ArrayList的效率高于LinkeList,LinkedList是线性存储,查找需要遍历
    3. 对数据进行增删操作时,LinkedList效率高于ArrayList,ArrayList是数组,增删操作需要进行数据的移动
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值