集合类
一、什么是集合以及使用的好处?Java有哪些常见的集合类?
集合概念
-
集合就是一个放数据的容器,准确的说是放数据对象引用的容器
-
集合类存放的都是对象的引用,而不是对象的本身
-
集合类型主要有3种:set(集)、list(列表)和map(映射)。
集合类的特点有三个:
1、集合类这种框架是高性能的。对基本类集(动态数组,链接表,树和散列表)的实现是高效率的。一般人很少去改动这些已经很成熟并且高效的APl;2、集合类允许不同类型的集合以相同的方式和高度互操作方式工作;
3、集合类容易扩展和修改,可以很容易地稍加改造就能满足自己的数据结构需求。
集合类带来的好处:
(1)降低编程难度:在编程中会经常需要链表、向量等集合类,如果自己动手写代码实现这些类,需要花费较多的时间和精力。调用Java中提供的这些接口和类,可以很容易的处理数据。
(2)提升程序的运行速度和质量:Java提供的集合类具有较高的质量,运行时速度也较快。使用这些集合类提供的数据结构,程序员可以从“重复造轮子”中解脱出来,将精力专注于提升程序的质量和性能。
(3)无需再学习新的APl:借助泛型,只要了解了这些类的使用方法,就可以将它们应用到很多数据类型中。如果知道了LinkedList的使用方法,也会知道LinkedList怎么用,则无需为每一种数据类型学习不同的API。
(4)增加代码重用性:也是借助泛型,就算对集合类中的元素类型进行了修改,集合类相关的代码也几乎不用修改。
常用的集合类:Map接口和Collection接口是所有集合框架的父接口。
- Collection接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
参考文章:JAVA集合的介绍及使用_java中集合的用法-CSDN博客
二、数组和集合的区别,集合是否可以存储NULL
数组和集合的区别:
1.数组是大小固定的,一旦创建无法扩容;集合大小不固定,
2.数组的存放的类型只能是一种,集合存放的类型可以不是一种(不加泛型时添加的类型是Object);
3.数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查(不懂),都是最快的.
ArrayList就是基于数组创建的容器类.
参考文章:数组和集合区别_集合和数组区别-CSDN博客
集合是否可以存储NULL :
List中全部可以存null
ArrayList 可以存null
LinkedList 可以存null
Queue队列中
只有LinkedList 可以存null Deque是Queue的子接口
Map中
HashMap 允许存null
LinkedHashMap 允许存null
TreeMap 不允许存nullHashtable不允许存null
Set
HashSet、LinkedSet可以存null
TreeSet 不允许存null
三、 ArrayList 与 LinkedList 的区别?ArrayList 和 Vector 的区别
ArrayList 与 LinkedList 的区别:
1.底层数据结构不同:ArrayList 的底层是基于数组来实现的,而 LinkedList 底层是基于链表实现的;(可以理解为 ArrayList 与 LinkedList 的区别实际上就是数组和链表的区别)
2.适用场景不同:ArrayList 更适合于使用随机查找较多的场景,LinkedList 更适合于使用随机插入较多的场景;
3.查询、插入、删除的时间复杂度不同:由于 ArrayList 是基于索引的数据接口,它的底层是基于数组的,可以随机读取,所以其查询的时间复杂度为O(1),而插入和删除的时间复杂度为O(n);LinkedList 是以元素列表的形式存储数据的,每一个元素和它的前一个元素和后一个元素连接在一起,它的底层是基于链表的,所以其查询的时间复杂度为O(n),插入和删除的时间复杂度为O(1);
4.ArrayList 实现了 List 接口,LinkedList 也实现了 List 接口,除此之外,LinkedList 还实现了 Deque 接口,所以 LinkedList 还可以当作队列来用;
5.LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指向前一个元素,一个指向后一个元素。
详细参考文章: ArrayList 与 LinkedList_arraylist和linkedlist-CSDN博客
ArrayList 和 Vector 的区别:
ArrayList 与 Vector 的区别主要包括两个方面:
1、同步性:Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全的问题,所以效率会高一些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
2、数据增长:ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个人超过了容量时,就需要增加 ArrayList 和 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要去的一定的平衡。Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。
四、ArrayList添加元素的流程或扩容机制
1)ArrayList 中维护了一个 Object 类型的数组,elementData。
2)当每次创建 ArrayList 对象的时候,如果使用的是无参构造器,则初始的 elementData 的容量为 0,第一次添加的时候则扩容 elementData 为 10,如果需要再次扩容,则扩容为原来的 1.5 倍
3)如果使用的是指定大小的构造器,则初始的 elementData 的容量就是指定的大小,如果需要扩容,也是直接扩容为 elementData 的 1.5 倍。
详细参考: 六千字详解!一篇看懂 ArrayList 的扩容机制(完整源码解析)_arraylist 的扩容机制了解吗?-CSDN博客
五、lterator和Listlterator的区别
Iterator(迭代器)和 ListIterator 的区别:
(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能
(2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
(3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
(4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。
文章参考:集合的遍历:Iterator 和ListIterator的详解_list<cscan*>::iterator iterscan;-CSDN博客
六、什么是RandomAccess接口
RandomAccess是一个标记接口,实现该接口表示支持快速访问。这是一个空接口,没有任何方法。
参考文章:【Java】ArrayList 为啥要实现 RandomAccess 接口_randomaccess interface-CSDN博客
七、Fail-Fast 和 Fail-Safe
fail-fast和fail-safe的区别:
fail-safe允许在遍历的过程中对容器中的数据进行修改,而fail-fast则不允许。
详细参考文章:
fail-fast(快速失败)机制和fail-safe(安全失败)机制的介绍和区别-CSDN博客
fail-fast和fail-safe详解_fail-fast & fail-safe-CSDN博客
八、ArrayList中的elementData为何被transient修饰
ArrayList 是 Java 集合框架中的一个重要类,用于存储动态大小的对象数组。在 ArrayList 的实现中,elementData 是一个用于存储实际元素的数组。而 transient 关键字在 Java 中被用于声明一个字段,该字段不应被序列化。
参考文章: arraylist属性中的elementdata关键字被transient修饰了,是为什么?_arraylist 中 elementdata 为何被 transient 修饰-CSDN博客
Map部分
九、HashMap底层数据结构
HashMap以键值对的方式进行存储,键不能重复,值可以重复但一个键只能对应一个值。底层采用数组+链表+红黑树(JDK1.8及之后)的数据结构。
- 数组:底层实际是存储一个节点Node的地址;
- 链表:引入链表是为了解决哈希冲突问题;
- 红黑树:引入红黑树是为了解决当链表长度大于8且数组长度大于64时查询慢的问题。
详细参考文章: 深入理解HashMap底层结构_hashmap底层数据结构-CSDN博客
十、链表和红黑树之间的转换
1、链表长度超过 8,数组大小小于 64
- 如果链表长度超过了长度阈值 8。
- 如果数组大小等于16,小于最小树化容量64。
在这种情况下,即使链表长度超过了8,由于总容量小于64,链表不会转为红黑树,而是会进行扩容操作。
2、链表长度没有超过 8,数组大小大于 64
- 如果链表长度等于 5,没有超过长度阈值 8。
- 如果数组大小等于80,大于最小树化容量 64。
链表会在其长度达到 8 时才会转换为红黑树。因此,如果链表长度等于 5,那么它不会转为红黑树,无论数组(即HashMap的桶数组)大小是多少。
详细参考文章:【HashMap】链表和红黑树互相转换的几种情况和数组的扩容机制_hashmap红黑树转链表条件-CSDN博客
十一、HashMap底层链表转红黑树的阈值为什么是8?红黑树转链表为什么是6?
参考文章:jdk1.8中HashMap底层链表转红黑树的阈值为什么是8?红黑树转链表为什么是6?_为啥在1.8里hashmap由链表转红黑树的阈值是8-CSDN博客
十二、为何使用红黑树而非二叉树或平衡树
参考文章:腾讯面试题:有了二叉查找树、平衡树为啥还需要红黑树?-CSDN博客
十三、HashMap的put()方法执行流程
HashMap中的put方法执行过程大体如下:
1、判断键值对数组table[i]是否为空(null)或者length=0,是的话就执行resize()方法进行扩容。
2、不是就根据键值key计算hash值得到插入的数组索引i。
3、判断table[i]==null,如果是true,直接新建节点进行添加,如果是false,判断table[i]的首个元素是否和key一样,一样就直接覆盖。
4、判断table[i]是否为treenode,即判断是否是红黑树,如果是红黑树,直接在树中插入键值对。
5、如果不是treenode,开始遍历链表,判断链表长度是否大于8,如果大于8就转成红黑树,在树中执行插入操作,如果不是大于8,就在链表中执行插入;在遍历过程中判断key是否存在,存在就直接覆盖对应的value值。
6、插入成功后,就需要判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过了,执行resize方法进行扩容。
参考文章: 浅析HashMap的put()方法执行流程_hashmap的put方法流程-CSDN博客
十四、hash值如何计算,常见的hash函数
参考文章:几种常见的哈希函数(散列函数)构造方法_构造散列(hash)函数的方法-CSDN博客
【数据结构】哈希(Hash)-CSDN博客
HashMap初始大小为什么是16 加载因子什么是0.75 扩容倍数为什么是2_hashmap为什么默认16 0.75-CSDN博客
十五、HashMap是否线程安全以及如何解决
HashMap线程不安全
参考文章:为什么HashMap线程不安全?以及实现HashMap线程安全的解决方案_hashmap线程安全吗 什么解决方案-CSDN博客
如何设计一个HashMap:Java性能优化(三):Java基础-HashMap的设计与优化_hashmap优化方法-CSDN博客