1,集合可以分为两大类,Collection和Map
1,collectin下面又有两个子接口
2,list接口下面有三个常用的实现类
3、set接口下有三个常用的实现类
4、map接口下有常用的五个实现类
详细介绍
2、List接口详解
1、list接口
存储有序的,可重复的数据,常用的实现子类有三种,ArrayList、LinkedList、Vector
2、list三个子类的不同点
ArrrayList:作为list接口的主要实现类;是线程不安全的,效率高,底层使用Object[] elementData存储
LinkedList:对于频繁的插入,删除操作,使用此效率比ArrayList高;底层使用的是双向链表
Vector:作为List接口的古老实现类,是线程安全的,效率低,底层使用的是Object[] elementData存储
3,ArrayList的底层源码分析(基于jdk1.8)
(1)当我们去创建一个ArrayList的时候底层会先为我们创建一个空数组,也就是elementData[]={}
//ArrayList的空参构造器
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
(2)当我们第一次添加数据的时候,ArrayList会为我们创建一个容量为10的数组
public boolean add(E e) {
//检查容器的容量是否足够,如果不足够就会扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
//第一次添加的时候会执行if语句里面的内容,DEFAULT_CAPACITY =10
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);
}
//扩容的方法,如果容量不够就会扩容到原来的1.5倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容后的容量,是原来的1.5倍,前提是不是第一次添加数据的时候,因为第一次添加数据的时候,oldCapacity=0,扩容后还是0;
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);
}
(3)当我们添加多个数据的时候,如果当前容量不够,就会扩容,扩容到原来的1.5倍,扩容的过程和步骤2一样。
(4)总结
当我们创建一个ArrayList的时候,底层回我们创建一个容量为0的数组
当我们第一次添加的时候,ArrayLIst会为我们创建一个容量为10的数组
但我们添加的数据大于,当前数组容量的时候会进行扩容,扩容为原来的1.5倍,同时将原来数组的内容复制到新的数组中
4、LinkedList的底层源码分析
LikendList底层是双向链表,其实就是对双向链表的操作
5、Vector源码分析
vector现在基本不用了,所以在这里简单总结一下
(1)Vector的底层也是数组,在一开始创建的时候,会创建一个容器为10的数组
(2)当我们添加数据的时候容器不够的时候,会进行扩容,扩容到原来的2倍
3、Set接口详解
1、set接口常用的实现类
HashSet, LinkedHashSet ,TreeSet
2、HashSet
HashSet:作为set接口的主要实现类,是线程不安全的,可以存储null值,底层是HashMap
3、linkedHashSet
LinkedHashSet:作为HashSet的子类,遍历其内部数据的时候,可以按照添加的数据
4、TreeSet
TreeSet:可以按照添加对象的属性进行排序,底层是TreeMap
4、Map接口
1、map常用的子类
2、HashMap源码分析
* 三、HashMap的底层实现原理?以jdk7为例说明:
* HashMap map = new HashMap():
* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1):
* 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
* 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
* 的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
* 如果equals()返回false:此时key1-value1添加成功。----情况3
* 如果equals()返回true:使用value1替换value2。
*
* 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
* 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
*
* jdk8 相较于jdk7在底层实现方面的不同:
* 1. new HashMap():底层没有创建一个长度为16的数组
* 2. jdk 8底层的数组是:Node[],而非Entry[]
* 3. 首次调用put()方法时,底层创建长度为16的数组
* 4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
* 4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
*
* DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
* DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
* threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
* TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
* MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64