Java基础强化——集合框架

集合框架常用的数据结构

ArrayList动态扩容机制

ArrayList三种初始化方式:

//默认的构造器,将会以默认的大小来初始化内部的数组
public ArrayList();

//用一个Collection对象来构造,并将该集合的元素添加到ArrayList
public ArrayList(Collection<? extends E> c);

// 用指定的大小来初始化内部的数组
public ArrayList(int initialCapacity);

扩容条件:根据传入的最小需要容量minCapacity来和数组的容量长度对比,若minCapactity大于或等于数组容量,则需要进行扩容。jdk7中采用>>位运算,右移动一位, 容量相当于扩大了1.5倍。(1+右移一位0.5)

在JDK1.7中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量(默认10)。每次按照1.5倍(位运算)的比率通过copeOf的方式扩容。(newCapacity = oldCapacity + (oldCapacity >> 1))

ArrayList源码中的构造方法:

	/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

另外需要区分容量和大小的关系,通常情况下容量要大于等于大小(Capacity >= size)

import java.lang.reflect.Field;
import java.util.ArrayList;

public class ArrayListTest {

    public static void main(String[] args) {
        //创建初始容量长度为20的数组
        ArrayList<String> list = new ArrayList<>(20);

        /** 利用反射机制,获取ArrayList的容量*/
        int capacity = 0;
        Class c = list.getClass();
        Field f;
        try {
            f = c.getDeclaredField("elementData");
            f.setAccessible(true);

            Object[] o = (Object[]) f.get(list);
            capacity = o.length;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        System.out.println("Capacity: " + capacity); 

        /** ArrayList 的大小*/
        int size = list.size();
        System.out.println("Size: " + size);
    }
}

//Output:
//Capacity: 20
//Size: 0

HashMap及其扩容机制

HashMap存储数据采用的是散列表结构(数组+链表的结构),在JDK8中HashMap的底层数据结构已经变为数组+链表+红黑树的结构,这主要原因是为了减少hash冲突带来的影响。

HashMap的基本原理是散列表+拉链法,就是在往HashMap中put元素时,会先根据key的hash值得到这个元素在数组中的位置(即下标),然后把这个元素放到对应的位置中。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。

实际情况下,我们希望HashMap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素是我们想要元素,而不用再去遍历链表。最容易的做法就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是“模”运算的消耗是比较大的,在Java中选择了另外一种更快速,消耗更小的方式:首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)

static int indexFor(int h, int length) {
	return h & (length-1);
}  

HashMap中get()方法的执行过程是:首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。所以hashcodeequals方法是找到对应元素的两个关键方法,通过改写key对象的equalshashcode方法,我们就可以将任意的业务对象作为map的key。在判断两个对象是否真的相等时,必须保证它们的hashcode相同,且保证调用 equals()方法返回true。

HashMap扩容

在JDK7中,HashMap数据结构是数组+链表的方式,HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了,所以HasnMap内部有扩容机制来进行处理。

HashMap内部有:变量size,记录HashMap的底层数组中已用槽的数量;变量threshold,它是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子);变量DEFAULT_LOAD_FACTOR = 0.75f,默认加载因子为0.75。

HashMap扩容的条件是:当size大于threshold时,对HashMap进行扩容。

//举个例子假如现在有三个元素(3,5,7)要放入map里面,table的的容量是2,如下
[0]=null  
[1]=3->5->7  

//现在将table的大小扩容成4,分布如下:
[0]=null  
[1]=5->7  
[2]=null  
[3]=3  

在JDK8里面,HashMap的底层数据结构已经变为数组+链表+红黑树的结构了,因为在hash冲突严重的情况下,链表的查询效率是O(n),所以JDK8做了优化对于单个链表的个数大于8的链表,会直接转为红黑树结构算是以空间换时间,这样以来查询的效率就变为O(logN)。

简单总结就是,JDK7里面是先判断table的存储元素的数量是否超过当前的threshold=table.length*loadFactor(默认0.75),如果超过就先扩容,在JDK8里面是先插入数据,插入之后在判断下一次++size的大小是否会超过当前的阈值,如果超过就扩容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值