java实践2之大话集合List、Map、Set

4 篇文章 0 订阅
1 篇文章 0 订阅

java实践2之大话集合List、Map、Set

集合也是java的基础,我们在开发中常用的。知道其中的一些实现原理,可以让我们在开发中更好的去使用它。尤其是在项目的性能要求比较高的情况下,理解他的数据结构,我们可以进行手写改造,避免问题。 下面跟大家分享我在工作中用到的一些集合和自己的一些理解。

一、List接口

先说一下ArrayList和LinkedList,为什么要说这两个实现类呢,因为基础面试经常会被问到的,还有它的数据结构非常重要。我们在工作过程中,实际大多数场景应该只用过ArrayList,至少我是这样的。其他例如LinkedList、其他的实现类,虽然没用过,但是了解一下,还是有必要的。但是链表这个数据结构是必须要理解的。

1、ArrayList

内部实现是一个数组 对象。
在这里插入图片描述

2、LinkedList:

内部是一个链表。而且还是双向链表。
在这里插入图片描述每个对象都包装为一层Node。每个Node都存在3个属性,一个是我们添加的对象、一个是指向上一个Node的指针、另一个是指向下一个node的指针。

3Arraylist VS LinkedList

理解了上面的结构。那么他们的区别很容易就知道了。
1、遍历:我们在使用的过程中 更多的时候只把数据库的数据查出来,放到List中,然后可能会遍历一下,处理一下对象中的某个值(例如DB性别字段存的是1、0,但前端需要男和女),这时候他们速度都差不多,都是必须得从头遍历到最后。有些细节不同,例如一个是数组对象,一个是链表的Node,那么在初始化时Node可能会比数组要多占一些空间,因为Node要比数组多了两个属性pre和next这些都是要占内存空间的。但是Arraylist是由于是可变的,空间不够它也会新建空间,然后再把原数据复制到新空间来进行扩容的,空间可能会出现冗余,所以我认为如果遍历的话他们没有区别。(实际可能arraylist要快一点,数组在内存空间是连续的,而链表不是)
ArrayList扩容
ArrayList al=new ArrayList(0);设置cap=0 al.add(new Object()); 添加一个新对象。开始是cap=0,然后变为10。多了一些冗余空间,防止下次再add时,每次都需要扩容的情况,所以我们在使用中尽量要去明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作,而造成浪费。
在这里插入图片描述2、查询:查询的话分情况,如果需要查找某个对象的话,ArrayList和LinkedList是一样的,都得遍历然后进行比较。ArrayList只有在知道索引下标的情况下速度才是快的,因为是数组,可以直接定位。而LinkedList则不可以,因为他没有定义数组和索引下标。
3、新增:理解了他们的数据结构,那么就理解了新增对象时他们也是一样,直接放到最后即可。如果空间不够Arraylist需要进行一下动态扩容。
4、删除
ArrayList:在删除(下标为2)对象3时,比较麻烦,除了删除操作,后面的数据也要向前移动1位。数组cap长度不变。

在这里插入图片描述
LinkedList:在删除对象2时,只需要更改它相邻的两个节点的指针指向即可,上一个改next指向,下一个改pre指向。
在这里插入图片描述5、修改:修改的话实际是分两个操作,先查询再修改,如果能直接索引定位那么Arraylist快,如果遍历的话那么他们是一样的。

4、Vector:

结构和使用方式跟ArrayList差不多,只是它用了synchronized。是线程安全的。
还有其他一些实现类,没用过,这里就不多说了。

二、Map接口

Map接口这个也会经常用到,用的最多的应该是HashMap,在多线程的时候也会用到ConcurrentHashMap,偶尔会用一下TreeMap和LinkedHashMap但是不多。为什么不先说Set呢,因为我觉着 list中的数组结构、链表结构和Map这几个更重要一些,理解了他们的数据结构后,再看Set就很容易了。

1、HashMap:

hashmap我认为在map接口中是最重要并且最常用的一个实现类,理解了它的结构 也是很重要的。hashmap内部实现 1.8之前是一个数组+链表。1.8是数组+链表/红黑树(会转换)。我是如何理解的呢?
例如:要插入o、q、s三条数据。(这里我们使用简单的hash算法,先取它们的asc码值和10取余来计算hash值,实际实现,我觉着太复杂了不好理解,我们理解他的作用和功能就可以了)。

数据hash值
o111%10=1
q113%10=3
s115%10=5

通过hash算法后,把上面算出来的hash值直接当作索引下标放入数组中,下标位置对应位置。

下标(上面hash算出来的值当作下标)数据
0null
1o
2null
3q
4null
5s

理解了上面的表,那么他的优缺点是显而易见的,hash实际使用的是数组结构,通过hash值来计算数组的下标,这样查询和插入、可以直接定位下标的话是非常快的。但代价也是有的,比如上面的表,我就3条数据,但是他实际创建了10个空间,如果我们的算法不好的话还可能会创建更大的空间,耗费资源。
这时有的朋友可能会问,不是数组+链表嘛,这也没链表啊。别着急,实际是这样的,可以思考下,这时候我再插入个字符’y’,他的asc码是121,和10取余后,这不就重复了吗?那么我们来看一下hashmap是怎么存储的。

插入 数据’y’  hash值:121%10=1  再插入 数据’ e’ hash值:101%10=1

hashmap 数组+链表 结构

下标(上面hash算出来的值当作下标)数据
0null
1对象(o next)->对象(y next)-> 对象(e next)->null
2null
3对象(q next)->null
4null
5对象(s next)->null

hashmap 数组+树的结构:
在这里插入图片描述从上面的图中我们可以看到,当插入数据时,它是先计算hash值然后根据,下标直接放到数组下表对应的对象中。当遇到冲突后,他会放到链表/树中。

理解了hash的作用和数据处理方式后,我们就很容易看出来他的优缺点了。
它的增删改查时,先计算hash值,然后可以直接定位到数组下标位置,这样是非常快的.当冲突很多时,1.8之前的版本会遍历链表去做插入/查询。由于遍历会比较慢,最差情况可能要遍历所有,在1.8版本的时候,链表和树可以转换。
那么它的特点,通过上面的理解,我们也可以很容易看到,不需要遍历所有了,因为使用了树结构,可以使用二分查找来提高处理速度了。但是树的代价是复杂变得复杂,而且需要更多的存储空间。所以1.8之后hashmap根据情况对树和链表做了切换。
其余的hashmap会用到的参数,加载因子和初始大小cap,我觉着大家知道他们作用是什么就行,有需要查一下具体的数值,加载因子和cap是扩容时用到的。

2、HashTable:

它和hashMap的区别是因为它是安全的,synchronized修饰,这个没用过。

3、ConcurrentHashMap:

这个也很重要在多线程中经常会用到,我觉着它是由于hashmap线程不安全, hashTable锁整块效率慢,而诞生的优化版安全的hashMap。它其实很好理解,它其实就是对数据进行了分块,操作时可以分块去synchronized锁定。例如:数组(1~10)+链表/红黑树 –>拆分为 :数组A(1-5)+链表/红黑树 和 数组B(6-10)+链表/红黑树。这样拆分后,如果要插入两条数据, hash后,需要分别到数组A和数组B,那么他俩可以同时插入。以前是锁整个,他们会排队,会慢一些。现在不需要锁整个了,操作哪一块,就锁哪一块,不需要缩整块。其实分块这种思路我们在很多时候都有运用。比如多线程写文件时,如果写一个文件,那么它们线程之间会有竞争。但是我们每个线程同时自己写自己的,最后再进行合并,这样效率就会高很多。

三、Set接口

关于set接口,我用到的不多,偶尔会用到的实现类HashSet、TreeSet、LinkedHashSet。它的特点是数据不可重复。其实理解了上面 数组、链表、树(红黑树),那么Set也很容易就理解了。

1、HashSet

hashset它是无序的,底层实际是用了hashMap。通过 hashCode和 equals保证数据不重复;所以我们如果使用hashset来保证对象不重复时,还要重写hashCode和equals方法。

2、TreeSet:

TreeSet:它的底层实现是一棵树(红黑树),所以它的数据增删改查时可能要比Hash慢一点点。每次操作都得排序、对树使用二分查找来进行维护。不像Hash结构可以直接定位。注意使用TreeSet时要重写compare。否则它无法判定对象大小,无法排序。

3、LinkedHashSet:

linkedHashSet:我认为它是对hashset的升级,在hashMap的基础上增加了链表。来维护对象的进入顺序。

总结:

List特点:元素有放入顺序,元素可重复 ;arrayList是数组定位快,会动态扩容,linkedlist是链表删除快。
Map特点:键值对存放。hashmap,线程不安全,内部是数组+链表/红黑树。 hashmap2.0 (concurrentHashmap),线程安全。
Set特点:元素无放入顺序,元素不可重复。HashSet:底层实现是hashMap,TreeSet:可排序,LinkedHashMap,可以保证元素放入顺序。

写在最后

其实我觉着记住那些特点不是最重要的。重要的是我们要理解他们的数据结构,数组、链表、hash大概的原理,我们可以自己尝试实现一下,用数组实现集合,用链表实现集合,用数组+链表实现 k-v存储,优化k-v存储,加入树算法。理解之后,那么他们的特点是很容易就能推断出来的。
我们自己实现一下就会发现。无论是stringbuilder、arrayList或者hashMap等等,在我们创建时,必须得去申请它的大小,这个是固定的,如果不够就需要重新申请,这个就是动态扩容。开发中我们最好能去预估他的cap大小,加一些冗余,让他可以不要频繁的申请空间,影响性能。

好了这篇分享完了,纯手打,希望对大家会有帮助,文章中有哪些错误,欢迎大家多多和我交流、批评和指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值