今天我们就来简单的了解下java中的集合,有了解过得朋友都知道,也都用过,比如说做常用的List,还有Set、Map,而且像List和Set都是用于存储单列数据的集合,他们的父接口都是Conllection,List的特点是存储的值是有序且允许重复的,Set的特点是存储的元素是唯一、不可重复且无序的,而Map的特点则是用于存储双列数据的集合,存储的数据时无序的,键不可以重复,值可以重复。

图片

接下来我们就把它们拆出来详细的来聊一聊。

List接口(通常有些面试官会问这样一个比较基础的问题:List是接口还是类?大家觉得呢,有经验的小伙伴肯定一下子就能回答出来,但是有一些刚开始学习的小白同学可能会有一些犹豫哦,那这次我都会给大家说说滴,小白同学看完以后有人再问你这样的问题就不会再犹豫啦)

首先我们要明确:我们java中的集合分为单列集合和双列集合,单列集合的顶级接口是:Conllection,双列集合的顶级接口是:Map

图片

而List就是属于单列集合Conllection的子接口,List接口的实现类如下:(所以List是接口不是类哦,通过下面可以多了解了解List接口的实现类都有哪些):

图片

1.ArrayList(List接口的实现类):底层数据结构是数组,查询快,增删慢,因为众所周知ArrayList的底层数据结构是数组,向ArrayList中加入元素add的时候,有可能会导致List的扩容, 因为ArrayList底层是数组结构,但数组不支持动态扩容,所以ArrayList的扩容机制就是再创建一个新数组,把就数组数据迁移到新数组,然后再加入新元素,这样一个过程下来也就是造成ArrayList增加元素慢的核心原因了。

而ArrayList在删除元素时底层是将删除索引位置起到最后索引位置结束中所有的元素,一一向前复制一个索引位置,再将最后索引位置元素设置为null,简单来说就是,假设我现在要删除这个集合中0索引位置上的元素,我将0索引位置上面的元素删除之后,并不是将这个位置的元素直接设置为null,而是后面1索引位置到最后索引位置上的所有元素都要走一个一一向前复制一个索引位置过程,直到最后一个索引元素向前复制结束之后,将末尾位置上空缺的元素复制为null,所以也就是说在删除元素的时候会影响其他索引位置上的元素的位置,所以也会降低删除元素的效率。

图片

(ArrayList集合删除元素之后其他索引位置上的元素移动位置的变化过程)理解完增删慢的原因,其实查询快的原因大家就已经很好理解了,ArrayList在查询元素时底层是通过访问数组元素方式进行查询。数组(Array)是一种线性表(线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向)数据结构。

它用一组连续的内存空间,来存储一组具有相同类型的数据。并且声明一个数组时,会在内存中申请一块连续相邻的内存空间。当要通过索引访问数组元素时,可通过数组地址值和偏移量,直接计算出要查找元素的内存地址,所以数组支持通过索引直接访问元素,效率非常高。数组中的元素都是有下标的,所以根据下标就可以很快的找到你要查询的元素了(使用get()方法, 就可以直接取的是数组对应下标中的值)。

2.LinkedList(List接口的实现类):底层数据结构是链表,查询慢,增删快

图片

LinkedList之所以查询慢是因为,LinkedList底层是一个链表, 链表肯定没有下标的概念,所以他不能像ArrayList一样,使用get()方法, 就可以直接取的是数组对应下标中的值,它只能是对总数遍历,然后取循环下标的值,所以如果LinkedList链表size越大,那么会使for循环的次数增多导致遍历的时间也会越长,查询也就慢了(但是如果大家在编码过程中还是需要考虑使用LinkedList的话在查询的时候可以使用getFirst()、getLast() 方法减少for循环的次数来相对增加查询的速度)。

而LinkedList之所以增加元素时快主要是因为,LinkedList 使用add(E e)直接添加元素或者add(int index, E e)指定位置添加元素,这两种方法插入元素,相对与ArrayList效率是较高的,因为ArrayList增加元素的时候可能需要扩容和元素的拷贝,增加了开销,而LinkedList 是断开指定位置的链接把新节点添加进来即可,所以整个添加过程中,系统也只做了两件事情,添加一个新的节点,然后保存该节点和前节点的引用关系,简单来说就是新增加了一个链接而已,所以效率高。

删除元素的时候也是根据remove(Object o) 删除元素或者remove(int index)删除指定位置元素这两种方法进行元素的删除,所以简单来说删除效率高主要是因为只需要删除某个链接,再链接新的元素即可,不需要修改列表中剩余的元素。

3.Vector(List接口的实现类):底层数据结构是数组,正因为它的底层是数组所以他的特点跟ArrayList一样是查询快,增删慢的特点,这一点可以参考上面ArrayList的解释。Set接口也是单列集合Conllection的子接口,接下来我们就继续看看他的实现类有哪些吧!

图片

1.HashSet(Set接口的实现类):底层数据结构是哈希表,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,HashSet就是为了提高查询效率的,意思就是在查询某个值是否存在的时候,ArrayList需要遍历才能获取到某个值的位置,而HashSet可以通过HashCode快速进行定位,而且HashSet是根据哈希算法来进行对象的存取的,存取速度很快,当HashSet中元素的个数超出数组原本的大小,就会进行扩容,而哈希表其实主要依赖hashcode()和equals()这两个方法。简单来讲就是首先通过hashcode()来判断hashcode值是否相同,如果相同的话就会执行equals()来查看他们的返回值是否相同,如果返回true的话,就说明这两个元素重复,则不进行添加,如果返回是false的话,就直接添加到集合当中。如果hashcode值不相同就可以直接添加到集合当中,以上就是针对于HashSet的特点的简单介绍。

2.LinkedHashSet(Set接口的实现类):底层数据结构是链表+哈希表,由链表保证元素的有序,由哈希表保证元素的唯一,LinkedHashSet是一个基于LinkedHashMap实现的有序去重集合列表,LinkedHashSet中的元素没有重复;LinkedHashSet中的元素有顺序,维护了添加顺序;LInkedHashSet可以存储null值;这个容器不知道大家在平时的工作用的多吗,反正我基本上没有用过,所以就是就是我个人针对于他的特点做的简单介绍。

3.TreeSet(Set接口的实现类):底层数据结构是红黑树,特点是元素唯一,且有序,由自然排序和比较器排序来保证有序的特点,根据返回值是否是0来判断元素是否唯一。TreeSet是有序的Set集合,因此支持add、remove、get等方法,TreeSet不支持快速随机遍历,只能通过迭代器进行遍历,如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口,或者实现一个比较器,在类上implements Comparable,重写compareTo()方法,在方法内定义比较算法, 根据大小关系, 返回正数负数或零,在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储。

图片

简单写了一个排序大家可以粗鲁看看

图片

这是排序的一个运行结果图,遍历两次是因为使用了两种不同的遍历方式接下来就该说说双列集合Map了,大家可以看看他的实现类有哪些。

图片

1.HashMap(Map接口的实现类):底层数据结构是数组+链表,基于哈希表的 Map 接口的实现。并允许使用 null 值和 null 键,是以 key-value 存储形式存在,每一个键值对也叫做一个Entry,根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,键key为null的记录至多只允许出现一条,值value为null的记录可以有多条,HashMap 的实现不是同步的,这意味着它不是线程安全的,HashMap是由数组+链表+红黑树(JDK1.8后增加了红黑树部分,链表长度超过阈值(8)时会将链表转换为红黑树)实现的。对于HashMap经常会有面试官问HashSet和HashMap的区别?其实针对于这个问题,大家通过上述HashSet的解释也能总结出来了,

相同点:

1.都是采用的Hash散列算法分配的数据,

2.都是线程不安全的,

3.数据查询是无序的;

不同点:

1.继承的父类不同

2.set的单键,而hashmap是可以存键值对

3.hashmap是可以存不同的value但是hashset不支持相同value数据。

以上就是针对于HashMap特点的简单介绍,如果大家想了解关于源码的解释可以自行学习了解,以上只是做一个简单的入门了解。2.LinkedHashMap(Map接口的实现类):LinkedHashMap继承了HashMap类,默认情况下会使用entryset来获取的集合顺序与节点的插入顺序一致的。

除去hashmap的逻辑之外,我们都知道,LinkedHashMap中还维护了一个双向链表,当新建待插入的Entry时,我们要知道LinkedHashMap中的Entry类继承了HashMap中的Node类,而entry中新加了两个指针属性分别是before、after,它们分别指向之前和之后插入的节点。

同时LinkedHashMap中多了3个参数,head、tail,accessOrder,双向链表默认是按照插入的顺序来进行排列的,最先插入的节点(也就是指最老的节点)为head,最新插入的节点为tail。accessOrder参数表示双向列表的排列顺序是按照节点的插入顺序还是访问顺序,默认是false即插入顺序,true代表的是访问顺序。当排列模式是按访问顺序时,如果调用了get或put方法且key存在时,会调用afterNodeAccess方法,将最近被访问的节点移至双向队列的队尾。

其实最重要的一点我们要清楚LinkedHashMap通过特有底层双向链表的支持,使得LinkedHashMap可以保存元素之间的顺序,例如插入顺序或者访问顺序,而HashMap因为没有双向链表的支持,所以就不能保持这种顺序,所以它的访问就是随机的了,和HashMap一样,还是通过数组存储元素的。

图片

3.TreeMap(Map接口的实现类):它是一个有序的key-value集合,主要通过红黑树来实现,它最大的特点是遍历时是有顺序的,根据key的排序规则来。TreeMap继承AbstractMap(由于TreeMap继承于AbstractMap,所以它是一个Map,即一个key-value集合),实现NavigableMap、Cloneable、Serializable三个接口。

其中AbstractMap表明TreeMap为一个Map即支持key-value的集合, NavigableMap则意味着它支持一系列的导航方法(比如返回有序的key集合),具备针对给定搜索目标返回最接近匹配项的导航方法 。而且由于它是基于红黑树,所以它包含几个重要的成员变量:root(root 是红黑数的根节点)、size(是红黑数中节点的个数)、comparator (比较器,红黑数排序时,根据Entry中的key进行排序)。当然在面试过程中可能会有面试官问你这样的问题,例如:TreeMap和HashMap的区别是什么?根据这个问题,可以给大家简单总结一下。

相同点:

两者均是线程不安全的;

增删改查操作的;

两者插入节点时,key重复后都会覆盖旧值。

不同点:

底层数据结构不同。HashMap是基于数组+链表+红黑树的数据结构,TreeMap是基于红黑树的数据结构;

HashMap是无序的,TreeMap是有序的;HashMap允许存储null,TreeMap的键不允许存储null,但是值可以为null;

TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比较器。

好啦以上就是给大家简单总结的java集合,希望对大家有点帮助哦!主要的几张图中都给大家反应出来了具体java集合都有哪些,大家可以作为参考继续学习哦!


编程世界
7 声望0 粉丝

和大家一起分享java、web前端、Python、大数据等方面技术知识,探讨IT编程技术相关问题!