隐藏页面特效

Java集合

img

概述Java集合框架的基础接口

Collection 集合的顶级接口,对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,元素可重复
│—————-├ LinkedList 接口实现类,底层数据结构为双链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类,可变数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 接口实现类 底层是hash table数据结构,使用hash表(数组)存储元素,不可重复
│————————└ LinkedHashSet 底层数据结构为哈希表与双链表 元素不可重复
└ —————-TreeSet 底层实现的数据结构为红黑树,源码中直接使用的TreeMap实现操作,特点:元素排好序
└ —————-SortedSet 接口,直接继承自Set

Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 , 底层是哈希表(hash table), 不同步, 线程不安全
│—————–├ LinkedHashMap 双向链表和哈希表实现,不允许重复key
├ ——–TreeMap 底层红黑树对所有的key进行排序,不允许重复key
└———ConcurrentHashMap 线程安全

简述List和Set

首先List 和Set都是接口,继承自Collection接口。

List接口的常见实现类有:ArrayList、LinkedList、Vactor,这三者也都继承了 AbstractList抽象类,LinkedList多继承了一次AbstractSequentialList;
其中,ArrayList实现类的内部实现是动态数组形式,意味着可以根据数组下标随机访问元素,读取效率更高;相对的,写入的效率低,插入和删除的效率相对低;
而LinkedList底层数据结构是双链表(自JDK1.7以后由双向循环链表改为双向链表),由于是链表,所以没有长度限制,添加或删除元素时只需要改变指针指向就可以了(连接新结点或断开某个结点);相对的,访问结点是,链表上需要挨个结点访问,因此LinkedList读取效率相对较低,但插入和删除效率相对较高;(但是实际业务上ArrayList写入效率并不比LinkedList低多少,能用ArrayList尽量用ArrayList;讲个笑话,LinkedList作者自己曾经说过,Josh Bloch:"Does anyone actually use LinkedList?I wrote it,and I never use it。" LinkedList作者自己造轮子,自己不用,好厨子从来不吃自己做的饭)
Vector和ArrayList的功能类似,ArrayList是后面替代Vector的,底层都是使用数组实现的,但是Vector的方法是同步的,线程安全,默认初始容量为10,扩容默认翻倍,可指定扩容的大小;ArrayList如果没有设置容量的话默认初始容量为10,扩容大小为初始容量的50%。

ArrayList 几个重要属性

1. `private static final long serialVersionUID = 8683452581122892189L;` :序列化编号,序列化和反序列化时比较版本的 2. `DEFAULT_CAPACITY = 10`:默认arrayList的初始容量 10 3. `private static final Object[] EMPTY_ELEMENTDATA = {};` :用于空实例的共享空数组实例,在ArrayList的有参构造函数(构造具有指定初始容量的空列表。)中,当参数initialCapacity (列表的初始容量)为0时,会使用 EMPTY_ELEMENTDATA 给elementData赋值。 4. `private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};` 和EMPTY_ELEMENTDATA 一样,数组在第一次添加元素时会判断elementData是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,假如等于则会初始化容量为DEFAULT_CAPACITY,也就是10;数组无参构造函数在初始化时会使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA给elementData赋值。**无参的构造方法,在jdk1.6的环境下,初始容量是10;在jdk1.7的环境下,初始容量是10;在jdk1.8的环境下,初始容量是0。** - 为什么要区分EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA? - 针对初始化时的扩容判断 当为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时说明是无参构造函数创建的,则可以直接扩容为DEFAULT_CAPACITY也就是10为初始容量 5. `transient Object[] elementData;` :注意,elementData是transient修饰的,不是private ;被transient修饰的变量不参与序列化和反序列化; 6. `private int size;` :数组列表大小(包含元素数量) 7. `public ArrayList(int initialCapacity) {...}` :有参构造函数,构造具有指定初始容量的空列表 8. `public ArrayList() {}` : 无参构造函数,创建一个初始容量为0的空数组(无参的构造方法,在jdk1.6的环境下,初始容量是10;在jdk1.7的环境下,初始容量是10;在jdk1.8的环境下,初始容量是01.8的注释上依然写着初始容量是10,但很显然是错的) 9. `public ArrayList(Collection<? extends E> c) {}` 构造一个含有指定Collection集合元素的数组列表,如果放的是Map类型,需要转成Collection 10. `private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;` :数组最大容量 Integer最大值为0x7fffffff=2^31-1;

ArrayList扩容

ArrayList扩容和初始容量是有关的,在jdk1.6、1.7时,ArrayList默认初始容量为10,但是在jdk1.8时,默认初始容量为0(无参构造方法或有参构造指定容量为0时)
我们知道,ArrayList有三种构造方法,一个无参构造,一个含有指定泛型Collection类型集合元素的有参构造(添加Map类型的需要强转)以及一个指定初始容量的有参构造

在无参构造方法中,可以看到:

给elementData 赋值一个常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA(彩蛋:注释 Constructs an empty list with an initial capacity of ten. 1.8的注释中依然是构造一个初始容量为10的集合,这点是错的)


可以看出 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 其实是一个Object类型常量空对象

指定初始容量的有参构造中,给elementData 赋值的也是一个常量空对象:EMPTY_ELEMENTDATA

if判断数组中的元素个数,为零则直接看else if部分:给elementData 赋值一个常量 EMPTY_ELEMENTDATA

添加集合的有参构造中也是如此:

当 传入的初始容量initialCapacity > 0为真时,创建一个大小为initialCapacity的空数组,并将引用赋给elementData;

当 传入的初始容量initialCapacity = 0为真时,将空数组EMPTY_ELEMENTDATA赋给elementData;

当 传入的初始容量initialCapacity < 0为真时,直接抛出IllegalArgumentException异常。

可以看出,无论是有参还是无参构造,虽然给elementData 赋值的是两个常量,但都是空的数组对象,也就是说在初始化的时候,ArrayList默认初始容量是为0的
至于为什么要用两个常量来给elementData 赋值,是因为后面扩容时,可以根据这个标记来判断,该数组是通过无参构造方法初始化的,还是有参构造初始化的,会影响到第一次扩容容量大小;如果是无参构造,在添加第一个元素是,给数组进行第一次扩容,容量为10

第一次添加元素时:

看看ensureCapacityInternal方法:

计算容量:可以从源码上看到,当第一次调用add(E e)方法的时候,判读是不是无参构造函数创建的对象,如果是,将DEFAULT_CAPACITY即10作为ArrayList的容量,此时minCapacity = 1

另外,ArrayList扩容时,如果是无参构造,那么第一次扩容大小为10,第二次扩容大小为0.5倍当前容量,即10+5=15;第三次扩容时,扩容大小是15+7=22;
为什么是+7而不是+7.5?
因为在源码中,以位移运算来计算增加容量的:int newCapacity = oldCapacity + (oldCapacity >> 1);
即 newCapacity 是 当前list容量oldCapacity+ 当前容量右移之后计算的结果,如果当前容量为15,二进制为1111,右移一位后即0111(2)=7(10),所以第三次扩容后,容量大小为15+7=22;

扩容计算方法:


__EOF__

本文作者Destiny-2015
本文链接https://www.cnblogs.com/destiny-2015/p/17189343.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   destiny-2015  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示