【知识点】Java集合

 

Java集合

白话说,Java集合就是一个篮子体系,主要阐述如何装东西。

1. 篮子是什么样子的?(篮子要有什么功能,比如简单的存/取,其实就是要有什么接口定义)
2. 篮子都有哪几种?(接口实例,如常用的ArrayList、LinkedList、HashSet、HashMap、ConcurrentHashMap)
3. 使用哪种篮子好?(查询、增删修改的效率,其实关注点就是实现的数据结构和算法)

专业说,引用别人专业总结,如下图所示,学习Java集合框架主要关注 接口、实现、以及算法。

集合框架是一个用来代表和操纵集合的统一架构:
1. 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
2. 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
3. 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

1. 集合框架结构图

如图所示,接口主要包括Collection、List、Set、Map接口等。其实java集合根据单值和键值对又分为两种,单值集合类List和Set,以及KV键值对Map。

2.常用集合分类

Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序

Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap

3. List集合

3.1 ArrayList

ArrayList特点:
1. 底层基于数组table[],查找快(索引下标)
2. 非线程安全
3. 元素能够重复,有序
4. 数组可动态扩容,初始容量10,扩容1.5倍
5. ArrayList无参构造初始时并没有分配容量10,而是一个空数组,在add方法添加时,首先判断当前容量是否足够,不够则进行扩容,第一次时才赋值容量10,扩容后将元素放到末尾(若使用索引插入则插入到指定位置)

ArrayList继承:
1. Serializable:序列化和反序列化(标记性接口)
2. Cloneable:被克隆对象必须实现Cloneable接口,重写clone方法(标记性接口)
3. RandomAccess:表明ArrayList通过for循环比iterator迭代器快(标记性接口),LinkedList则相反(没有实现该接口)
4. AbstractList:AarrayList的骨架实现
Clone相关:
1. Clone其实调用的是Object类的clone方法(操作系统底层native方法),Clone出一个新的对象(对象地址不同)
2. 基本数据类型可以达到完全复制,引用类型数据不可以
2.1 浅拷贝:拷贝一个对象时,若对象有引用类型的属性,则clone后,两个对象的引用类型属性指向的是同一个引用对象
2.2 深拷贝:上述的引用类型属性继承clonenable接口实现clone方法,并在对当前对象拷贝时,先拷贝一份引用类型对象,显示赋值给当前对象,这样拷贝之后互不影响。
集合和数组:
1. 数组大小确定,集合基于可调动大小的数组实现,可动态扩容
2. 数组可以是基本类型,也可以是引用类型,集合只能是应用类型
3. 数组只能存储同一类型数据,集合可以存储各种数据类型,一般用泛型进行限制

3.2 LinkedList

LinkedList特点:
1. 底层基于双向链表实现,
2. 查询慢,增删快、有序
3. 非线程安全

双向链表:维护着一个first和last的Node节点

ArrayList与LinkedList:
1. ArrayList相比比较浪费空间资源
2. 在指定索引位置增加删除元素时,ArrayList效率较低(后面的元素都要进行移动)
3. ArrayList扩容元素后,原来的数组会被垃圾回收
4. LinkedList增删快速,不浪费空间(有多少元素分配多少元素)
LinkedList如何实现队列、堆栈???

4. Set集合

4.1 HashSet

HashSet特点:
1. HashSet无序、元素不能重复
2. 线程不安全
3. 可以存储null元素
4. 底层基于HashMap来实现(value是一个常量对象 new Object()),key必须重写equals和hashCode方法

思考:
HashSet底层基于HashMap,值存放在key中,因HashMap在key重复时,会覆盖value值,故可以认为HashSet元素不能重复其实就是若重复则被覆盖,还是只有一个相同的元素

4.2 LinkedHashSet

LinkedHashSet特点:
1. LinkedHashSet继承与HashSet,又通过构造函数重载,开辟一个LinkedHashMap来实现,故其底层原理基于LinkedHashMap。
2. LinkedHashMap维护一个head和tail,类型LinkedHashMap,记录头和尾。
3. LinkedHashSet的add方法是在HashSet中实现的,其实就是HashMap的实现(LinkedHashMap中并没有实现add方法)。hashMap插入元素之后,维护一个双向链表来保证顺序。

5. Map集合

5.1 HashMap

HashMap特点:
1. kv键值对存储,键值均允许为null,键只能有一个是null的,但是值不限制。
2. jdk1.7底层数据结构是数组+链表,jdk1.8之后底层数据结构是数组+链表+红黑树。
3. HashMap线程不安全
4. HashMap初始化数组大小为16,扩容时为原来容量的2倍(扩容时须为2的次方大小,底层源码扩容时取大于当前所需容量最小的2的次方容量,可以使用构造函数初始化时指定容量大小)
HashMap须知:
1. 为什么扩容时大小必须为2的N次方?
底层源码中,HashMap存放元素时,先获取Key的hashcode值(hashcode为整数,占用4个字节,即******** ******** ******** ********),为使元素能较为均匀的分配到数组槽中,以容量16为例,容量减一即为00000000 00000000 00000000 00001111,这样与hashcode值做位与运算时,最后4位便能被分配到0-15的槽上。另外,为了分配更加分散,改进hashcode方法,将hashcode的高16位参于运算,即获取key的hashcode之后,将高16位与低16位先做亦或运算,再与数组容量减一做位与运算。(其实,如果有更好的hashcode方法,你随便如何扩容,只不过大牛已经如此改写了底层代码,数组扩容只能2的N次方)

2. jdk1.7和jdk1.8下HashMap的不同之处
2.1 jdk1.7中HashMap进行扩容后,链表上的记录被反转,即原先处于聊表头的数据,扩容后备放到了数组尾部
2.2 jdk1.7链表上新元素插入在头部,jdk1.8之后,尾插法(因要循环获取链表上的元素,既然要遍历一遍,那便插入在尾部,省的再进行头部查询)
2.3 jdk1.8下HashMap的链表元素超过8时,会转换为红黑树
2.4 jdk1.7下HashMap的扩容是在存放元素时,先判断当前集合元素+1是否超过阈值大小(且当前槽上非空),若超过阈值,则先进行扩容再存放元素,jdk1.8下先存放元素,然后再进行扩容
2.5 jdk1.7中元素定义为Etry,jdk1.8之后使用的是Node,其实属性定义一样

3. HashMap存放的元素(对象),要重写其equals和hashcode方法
HashMap存放的元素,key不能重复(key重复时会覆盖原来的value),因此要判断key是否相等,若key为自定义对象,因业务等原因要重写equals和hashcode方法,故HashMap中常常使用String类型对象作为key值,若使用自定义引用对象,尽量使用final修饰,final修饰后,引用对象的内存地址不再改变,这样在equals方法中便能判断该值相等,当然要有==判断相等的逻辑。

4. 为什么要引入红黑树?
为了提升查询性能,解决链表极端情况下全部遍历的情况,链表长度大于8时会转换为红黑树结构。

5.2 ConcurrentHashMap

ConcurrentHashMap特点:
1. 线程安全
如何保证线程安全:
1. 初始化容量时使用CAS无锁化机制保证线程安全
2. put元素时,放置到hash槽时(hash槽上元素为空时)也使用CAS无锁化机制保证线程安全
3. hash槽上元素非空时,使用synchronized代码块加锁(CAS每次都需要和内存中的数据进行比对,若集合插入频繁时,要一直进行比较,会与链表/红黑树上的元素进行对比,比对是否key一致,一致的话覆盖原来的元素,比对是否已经插入等),如何理解呢?若此处使用CAS,则比较次数会很多,CAS已经不太适合。
4. 对hash槽上的元素进行加锁(锁粒度降低,1.7中segment分段锁机制,1.8之后使用synchronize锁,因为其性能已经得到很大的提升和lock没多少差距了)。
5. 数组扩容时,若另外一个线程在对hash槽上的元素进行移动(hash槽元素有个标识字段,标识当前元素是否在扩容移动元素),则当前线程帮忙扩容(去分段领取任务)。

6. collection遍历

两种遍历方式:
方式一:将集合转换为数组(toAaary()),遍历数组
方式二:iterator迭代器遍历(hasNext(),next())


并发修改异常:
当迭代集合时,对集合进行修改,此时迭代结果就不再确定,系统检测到这种行为,会抛出并发修改异常。
方式一:使用for循环进行遍历
方式二:通过listIterator()返回一个ListIterator接口,该接口能够遍历list,并在迭代期间,可对list做出修改

【推荐】

传智LinkedList课程:
1. AaaryList的优缺点介绍
2. LikedList的优缺点介绍
3. LinkedList的接口介绍
其中,要了解到接口定义,抽象类模板定制(共性接口实现),子类多态的理解
其次:
1. 了解get()/set()/clear()/indexOf()/add()方法,LinkedList的索引并不像ArrayList那样,索引值意味着N次向下遍历查询
2. 了解父类LinkedList中size变量的使用,具体实现类在每次CRD时,更新size的大小。

【说明】有些地方未做验证,请保持怀疑态度

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值