大家好!我是【程序员写荣】,希望接下来可以通过书写博客,使自己的程序员生涯欣欣向荣。这博客通过面试题的形式将各个知识点进行汇总,是对自己学习的一点点总结及记录。
文章目录
- 什么是集合?
- 集合有什么特点?
- 使用集合的好处?
- 集合和数组的区别?
- 常用的集合类用那些?
- 哪些集合类是线程安全的?
- Java 集合的快速失败机制“fail-fast”?
- 怎么确保一个集合不能被修改?
- Collection 与 Collections 的区别?
- Collection 父接口?
- List 子接口?
- ArrayList 实现类的特点?
- LinkedList 实现类的特点?
- Vector 实现类的特点?
- ArrayList 和 LinkList 的区别?
- ArrayList 和 Vector 的区别?
- RandomAccess 接口?
- 如何实现数组和 List 之间的转换?
- 多线程场景下如何使用 ArrayList?
- 为什么 ArrayList 的 elementData 加上 transient 修饰?
- AarrayList 的扩容机制?
- 什么是 Java 优先级队列(Priority Queue)?
- Set 子接口?
- HashSet 实现类的特点?
- TreeSet 实现类的特点?
- LinkedHashSet 实现类的特点?
- Comparable 和 Comparator 的区别?
- 无序性和不可重复性的含义是什么?
- HashSet 的实现原理?
- HashSet 如何保证数据不可重复的?
- hashCode() 和 equals() 的相关规定有哪些?
- == 与 equals 的区别?
- HashSet 与 HashMap 的区别?
- Map 父接口?
- HashMap 实现类的特点?
- HashTable 实现类的特点?
- TreeMap 实现类的特点?
- LinkedHashMap 实现类的特点?
- HashMap 的实现原理?
- HashMap 在 JDK 1.7和 JDK 1.8中有哪些不同?
- HashMap 的 put 方法具体流程?
- HashMap 的扩容过程?
- 什么是哈希?
- 什么是哈希冲突?
- HashMap 怎么解决哈希冲突的?
- 能否使用任何类作为 Map 的 key?
- 为什么 HashMap 中的 String、Integer 这样的包装类适合作为 key?
- 如果使用 Object 作为 HashMap 的 key,应该怎么办?
- HashMap 为什么不直接使用 hashCode() 处理后的哈希值直接作为 table 的下标?
- HashMap 的长度为什么是2的幂次方?
- HashMap 多线程操作导致死循环问题?
- HashMap JDK1.8为什么从头插法改为尾插法?
- HashMap 为什么实现两次扰动?
- HashMap 与 HashTable 有什么区别?
- 如何决定使用 HashMap 还是 TreeMap?
- ConcurrentHashMap 底层实现原理?
- HashMap 和 ConcurrentHashMap 的区别?
- ConcurrentHashMap 和 HashTable 的区别?
- 说说什么是红黑树?
- 迭代器 Iterator 是什么?
- Iterator 的特点?
- Iterator 怎么使用?
- Iterator 和 ListIterator 的区别?
什么是集合?
集合是 Java 提供的一种可以存储多个数据的容器。
集合有什么特点?
- 存储引用类型数据。
- 长度可变。
使用集合的好处?
- 容器的容量自增长。
- 提供了高性能的数据结构和算法,使编码更轻松,提高了程序执行速度和质量。
- 允许不同 API 之间的互操作,API 之间可以来回传递集合。
- 可以方便的扩展或改写集合,提高代码复用性和操作性。
- 通过使用 JDK 自带的集合类,可以降低代码维护和学习新 API 成本。
集合和数组的区别?
- 数组的长度是固定的。集合的长度是可变的。
- 数组可以存储基本类型数据,也可以存储引用类型数据。集合只能存储引用类型数据。
- 数组存储的元素的数据类型必须相同。集合存储的元素的数据类型可以不同。
常用的集合类用那些?
Collection 接口和 Map 接口是所有集合框架的父接口:
- Collection接口的子接口包括:List 接口和 Set 接口。
- List 接口的实现类主要有:AarrayList、LinkedList、Vector 和 Stack等。
- Set 接口的实现类主要有:HashSet、TreeSet 和 LinkedHashSet 等。
- Map 接口的实现类主要有:HashMap、TreeMap、HashTable、ConcurrentHashMap 等。
哪些集合类是线程安全的?
- Vector:比 ArrayList 多了个同步化机制。
- HashTable:比 HashMap 多了个线程安全。
- ConcurrentHashMap:是一种高效且线程安全的集合。
- Statck:堆栈类,先进后出。
Java 集合的快速失败机制“fail-fast”?
fail-fast 机制是 Java 集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 机制。例如:当某一个线程A通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程A访问集合时,就会抛 ConcurrentModificationException 异常,产生 fail-fast 事件。当然,不仅是多个线程,单个线程也会出现 fail-fast 机制,包括 ArrayList、HashMap 无论在单线程和多线程状态下,都会出现 fail-fast 机制,即上面提到的异常。
出现原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决方法:
- 在遍历的过程中,所有涉及到改变 modCount 值的地方全部加上 synchronized。
- 使用 CopyOnWwriteArrayList 来替换 ArrayList。
怎么确保一个集合不能被修改?
可以使用 Collections.unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java.lang.UnsupportedOperationException 异常。
Collection 与 Collections 的区别?
Collection 是 Java 集合框架中的基本接口,如List接口也是继承于它
Collections 是 Java 集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。
Collection 父接口?
- 特点
代表一组任意类型的对象,无序、无下标、不能重复。
- 常用方法
boolean add(Object obj) //添加一个对象。
boolean addAll(Collection c) //讲一个集合中的所有对象添加到此集合中。
void clear() //清空此集合中的所有对象。
boolean contains(Object o) //检查此集合中是否包含o对象。
boolean equals(Object o) //比较此集合是否与指定对象相等。
boolean isEmpty() //判断此集合是否为空。
boolean remove(Object o) //在此集合中移除o对象。
int size() //返回此集合中的元素个数。
Object[] toArray() //姜此集合转换成数组。
List 子接口?
- 特点
有序、有下标、元素可以重复。
- 常用方法
void add(int index,Object o) //在index位置插入对象o。
boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
Object get(int index) //返回集合中指定位置的元素。
List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
ArrayList 实现类的特点?
底层数据结构:Object 数组。查询快,增删慢。线程不安全。运行效率快。默认长度10,加载因子1,扩容增量1.5倍。
LinkedList 实现类的特点?
底层数据结构:双向链表。查询慢,增删快。线程不安全。
Vector 实现类的特点?
底层数据结构:Object 数组。查询慢,增删慢。线程安全。运行效率慢。默认长度10,加载因子1,扩容增量1倍。
ArrayList 和 LinkList 的区别?
- 底层数据结构:ArrayList 底层使用的是 Object 数组,LinkList 底层使用的是双向链表。
- 是否支持快速随机访问:快速随机访问就是通过元素的序号快速获取元素对象。ArrayList 支持,LinkList 不支持。
- 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。不指定位置的插入和删除时间复杂度为 O(1),指定位置的插入和删除时间复杂度为 O(n-i)。LinkList 采用链表存储,不指定位置的插入和删除不受元素位置的影响时间复杂度为 O(1),指定位置的插入和删除因为需要先移动到指定位置再插入,所以时间复杂度为 O(n)。
- 是否保证线程安全:ArrayList 和 LinkList 都是不同步的,所以不保证线程安全。
- 内存空间占用:ArrayList 的空间占用主要体现在 list 列表的结尾会预留一定的容量空间,而 LinkList 的空间占用主要体现在它的每一个元素都需要存放直接前驱、直接后继和数据。
ArrayList 和 Vector 的区别?
底层数据结构:底层使用的都是 Object 数组。
是否支持快速随机访问:都支持。
是否保证线程安全:Vector使用同步代码块实现线程同步所以是线程安全的,而 ArrayList 是非线程安全的。
性能:ArrayList 性能优于 Vector。
扩容:ArrayList 扩容增加1.5倍,而 Vector 扩容增加1倍。
RandomAccess 接口?
接口中什么都没有定义,只作为标识实现这个接口的类具有随机访问功能。
如何实现数组和 List 之间的转换?
List ==》数组:使用集合的 toArray(T[] array) ,无参方法返回值为 Object[] 类,强制转换类型可能会抛出 ClassCastException 异常。
数组 ==》List:使用 Aarrays.asList(),返回内部类 ArrayList,就不能使用修改集合相关的方法了,会抛 UnsupportedOperationException 异常。
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,可以通过 Collections.synchronizedList() 方法将其转换为线程安全的容器后在使用。
为什么 ArrayList 的 elementData 加上 transient 修饰?
ArrayList 实现了 Serializable 接口,支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 方法实现。每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度又减小了序列化后的文件大小。
AarrayList 的扩容机制?
以无参构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素时,才真正分配容量,即向数组中添加第一个元素时,数组容量扩为10。
ArrayList 每次扩容之后容量都会变为原来的1.5倍左右(oldCapacity 为偶数就是1.5倍,否则是1.5倍左右(奇数会丢掉小数))。
什么是 Java 优先级队列(Priority Queue)?
优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序
- 优先队列中元素默认排列顺序是升序排列
- 但对于自己定义的类来说,需要自己定义比较器
方法:
peek()//返回队首元素
poll()//返回队首元素,队首元素出队列
add()//添加元素
size()//返回队列元素个数
isEmpty()//判断队列是否为空,为空返回true,不空返回false
特点:
- 1.基于优先级堆
- 2.不允许null值
- 3.线程不安全
- 4.出入队时间复杂度O(log(n))
- 5.调用remove()返回堆内最小值
Set 子接口?
- 特点
无序、无下标、元素不可重复。
- 方法
全部继承自 Collection 接口。
HashSet 实现类的特点?
底层数据结构:HashMap 的键。不保证迭代和插入集合中元素的顺序一致。线程不安全。默认长度16,加载因子0.75,扩容增量2倍。
TreeSet 实现类的特点?
底层数据结构:红黑树。实现了 SortedSet 接口,对集合元素自动排序。基于排序顺序实现不重复。线程不安全。
LinkedHashSet 实现类的特点?
底层数据结构:链表。保证迭代和插入集合中元素的顺序一致。线程不安全。
Comparable 和 Comparator 的区别?
comparable 接口实际上是出自 java.lang 包,它有一个 compareTo(Object obj) 方法用来排序;comparator 接口实际上是出自 java.util 包,它有一个compare(Object obj1,Object obj2) 方法用来排序。
Comparable:它可以让一个对象自身具备比较功能。哪个对象需要具备比较功能,这个对象所属的类就需要实现Comparable接口,实现其中的compareTo方法。
Comparator:它是单独的比较器,可以把这个接口的实现类对象单独传递给TreeSet集合,那么这时集合中的元素就会按照当前指定的这个比较器进行比较。开发者如果需要使用比较器的时候,需要定义类实现Comparator接口,同时实现其中的compare方法。
无序性和不可重复性的含义是什么?
- 什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
- 什么是不可重复性?不可重复性是指添加的元素按照 equals() 判断时 ,返回 false,需要同时重写 equals() 方法和 HashCode() 方法。
HashSet 的实现原理?
HashSet 是基于HashMap 实现的,HashSet 的值存放于 HashMap 的 key 上,HashMap 的 value 统一为 PRESENT。相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
HashSet 如何保证数据不可重复的?
向 HashSet 中 add() 元素时,判断元素是否存在的依据,不仅要比较 hash 值,同时还要结合 equals 方法比较。向 HashSet 中 add() 方法会使用 HashMap 的 put() 方法。
HashMap 的 key 是唯一的,HashSet 添加进去的值就是作为 HashMap 的 key,并且在 HashMap 中如果 key value相同时,会用新的 value 覆盖掉旧的 value。所以不会重复。
hashCode() 和 equals() 的相关规定有哪些?
- 如果两个对象相等,则 hashcode 一定也是相同的。
- 两个对象相等,对两个 equals 方法返回 true。
- 两个对象有相同的 hashcode值,它们也不一定是相等的。
- 综上,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
- hashCode() 的默认行为是对腿上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
== 与 equals 的区别?
- ==是判断两个变量或实例是不是指向同一内存空间,equals 是判断两个变量或实例所指向的内存空间的值是不是相同。
- ==是指对内存地址进行比较,equals 是对字符串的内容进行比较
- ==是指引用是否相同,equals 指的是值是否相同
HashSet 与 HashMap 的区别?
不同 | HashSet | HashMap |
---|---|---|
实现接口 | Set接口 | Map接口 |
存储内容 | 存储对象 | 存储键值对 |
添加元素方式 | add() | put() |
计算 hashcode 方式 | 使用成员对象计算 hashcode。两个对象 hashcode 可能相同,所以 equals 方法判断对象的相等性。 | 使用键计算 hashcode |
效率 | 慢 | 快 |
Map 父接口?
- 特点
- 用于存储任意键值对(key-value)。
- 键:无序、无下标、不允许重复。
- 值:无序、无下标、允许重复。
- 方法
V put(K key,V value) //将对象存入到集合中,关联键值。key重复则覆盖原值。
Object get(Object key) //根据键获取相应的值。
Set //返回所有的key
Collection values() //返回包含所有值的Collection集合。
Set<Map.Entry<K,V>> //键值匹配的set集合
HashMap 实现类的特点?
底层数据结构:JDK 1.8是数组、链表和红黑树。允许用 null 作为key 或是 value。线程不安全,运行效率快。默认长度16,加载因子0.75,扩容增量2倍。
HashTable 实现类的特点?
底层数据结构:数组和链表。不允许 null 作为 key 或是 value。线程安全,运行效率慢。
TreeMap 实现类的特点?
底层数据结构:二叉树。实现了 SortedMap 接口,可以对 key 自动排序。线程不安全。
LinkedHashMap 实现类的特点?
底层数据结构:数组、链表、红黑树和链表。允许 null 作为 key 或是 value。有序。线程不安全。
HashMap 的实现原理?
HashMap 是基于哈希表的 Map 接口的非同步实现,是以键值对存储数据的容器,key 和 value 都允许 null 的存在,底层是数组、链表和红黑树,是非线程安全的。
HashMap 是基于 Hash 算法实现:
当我们往 HashMap 中 put 元素时,利用 key 的 hashCode 重新 hash 计算出当前对象的元素在数组中的下标。
存储时,如果出现 hash 值相同的 key,此时有两种情况:如果 key 相同,则覆盖原始值。如果 key 不同,则放入链表中。
存储时,当链表中的数据超过8个以后,链表会转化为红黑树,从原来的 O(n) 到 O(logn)。
获取时,直接找到 hash 值对应的下标,在进一步判断 key 是否相同,从而找到对应值。
HashMap 在 JDK 1.7和 JDK 1.8中有哪些不同?
不同 | JDK 1.7 | JDK 1.8 |
---|---|---|
数据结构 | 数组和链表(链表散列) | 数组、链表和红黑树 |
初始化方式 | 单独函数:inflateTable() | 直接集成到了扩容函数resize()中 |
hash值计算方式 | 扰动处理=9次扰动=4次位运算+5次异或运算 | 扰动处理=2次扰动+1次异或运算 |
存储数据的规则 | 无冲突时,存放数组;冲突时,存放链表 | 无冲突时,存放数组;冲突&链表长度<8,存放链表;冲突&链表长度>8,树化并存放红黑树 |
插入数据方式 | 头插法(先将原位置的数据移到后1位,在插入数据到该位置) | 尾插法(直接插入到链表尾部或红黑树) |
扩容后存储位置的计算方式 | 全部按照原来方法进行计算(即 hashcode ->> 扰动函数 ->> (h&length-1)) | 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量) |
HashMap 的 put 方法具体流程?
当我们 put 的时候,首先计算 key 的 hash 值,这里调用了 hash 方法, hash方法实际是让 key.hashCode() 与 key.hashCode()>>>16 进行异或操作,高 16bit 补0,一个数和0异或不变,所以hash函数大概的作用就是:高 16bit 不变,低 16bit 和高 16bit 做了一个异或,目的是减少碰撞。按照函数注释,因为bucket数组大小是2的幂,计算下标 index = (table.length - 1) & hash,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高 16bit 和低 16bit 异或来简单处理减少碰撞,而且 JDK8 中用了复杂度 O(logn) 的树结构来提升碰撞下的性能。
①.判断键值对数组 table[i] 是否为空或为 null,否则执行 resize() 进行扩容;
②.根据键值 key 计算 hash 值得到插入的数组索引 i,如果 table[i]==null,直接新建节点添加,转向⑥,如果 table[i] 不为空,转向③;
③.判断 table[i] 的首个元素是否和key一样,如果相同直接覆盖 value,否则转向④,这里的相同指的是 hashCode 以及 equals;
④.判断 table[i] 是否为 treeNode,即 table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历 table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现 key 已经存在直接覆盖 value 即可;
⑥.插入成功后,判断实际存在的键值对数量 size 是否超多了最大容量 threshold,如果超过,进行扩容。
HashMap 的扩容过程?
- 第一步把数组长度变为原来的两倍,
- 第二步把旧数组的元素重新计算hash插入到新数组中。
- jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
什么是哈希?
Hash,一般翻译为“散列”,就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值。
特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同;但是,根据同一散列函数计算出的散列值如果相同,那么输入值不一定相同。
什么是哈希冲突?
当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,称为哈希碰撞。
HashMap 怎么解决哈希冲突的?
- 通过链地址法解决哈希冲突。因为数组寻址容易,插入删除困难,链表寻址困难,单插入删除容易。将数组和链表结合在一起,发挥两者的优势。
- 使用两次扰动函数(hash 函数)来降低哈希冲突的概率,使得数据分布更均匀。
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快。
能否使用任何类作为 Map 的 key?
可以,但是需要注意以下几点:
- 如果类重写了 equals 方法,也应该重写 hashCode 方法。
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
- 如果一个类没有 equals,不应该在 hashCode 使用它。
- 自定义 key 最好为不可变的,缓存起来,拥有更好的性能。
为什么 HashMap 中的 String、Integer 这样的包装类适合作为 key?
包装类的特性能保证 hash 值的不可更改性(都是 final 类型)和计算准确性(已重写 equals 和 hashCode 方法),能够有效减少 hash 碰撞的几率。
如果使用 Object 作为 HashMap 的 key,应该怎么办?
重写 hashCode 方法和 equals 方法。
HashMap 为什么不直接使用 hashCode() 处理后的哈希值直接作为 table 的下标?
hashCode() 方法返回的是 int 整数类型,其范围为 -(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而 HashMap 的容量范围是在16(初始化默认值)~2 ^ 30,HashMap 通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过 hashCode() 计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
解决方法:
- HashMap 自己实现了自己的 hash() 方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
- 在保证数组长度为2的幂次方的时候,使用 hash() 运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;
HashMap 的长度为什么是2的幂次方?
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。采用%取余的操作来实现。
重点:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率。
HashMap 多线程操作导致死循环问题?
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize() 方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。
当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B
HashMap JDK1.8为什么从头插法改为尾插法?
- JDK1.7 中 HashMap 默认采用数组+单链表方式存储元素,当元素出现哈希冲突时,会存储到该位置的单链表中。这和 JDK1.8 不同,除了数组和单链表外,当单链表中元素个数超过8个时,会进而转化为红黑树存储,巧妙地将遍历元素时时间复杂度从 O(n) 降低到了 O(logn))。
- HashMap 在 JDK1.7 中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题。而在 JDK1.8 中采用尾插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了
- JDK1.7 扩容时需要重新计算哈希值和索引位置,JDK1.8 并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。
HashMap 为什么实现两次扰动?
这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
HashMap 与 HashTable 有什么区别?
线程安全:HashMap 是线程不安全的,HashTable内部方法都经过 synchronized修饰是线程安全的。
效率:因为线程安全问题,所以 HashMap 的效率高于 HashTable。
对 key 和 value 的 null 支持:HashMap 支持一个键仅为 null,可以有一个或多个键对应的值为 null。HashTable 不支持 null 作为 key 使用。
底层数据结构:JDK1.8 以后HashMap 的底层为数组、链表和红黑树。HashTable的底层是数组和链表。
初始容量大小和每次扩容量大小:创建时如果不指定容量初始值,HashMap 默认的初始值为16,每次扩容为原来的2倍,HashTable 默认的初始值为11,每次扩容为原来的2n+1。创建时如果指定容量初始值,HashMap 会扩容为2的幂次方大小,HashTable 直接使用给定的大小。
单线程使用 HashMap,多线程使用 ConcurrentHashMap。
如何决定使用 HashMap 还是 TreeMap?
对于在 Map 中插入、删除和定位元素这类操作,HashMap 是最好的选择。然而,假如你需要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。基于你的 collection 的大小,也许向 HashMap 中添加元素会更快,将 map 换为 TreeMap 进行有序 key 的遍历。
ConcurrentHashMap 底层实现原理?
JDK1.7:
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
底层数据结构:Segment(锁 ReentrantLock) + HashEntry(键值对)
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
JDK1.8:
底层数据结构:数组、链表和红黑树
Node + CAS + Synchronized,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
HashMap 和 ConcurrentHashMap 的区别?
- ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用 lock 锁进行保护,相对于 HashTable 的 synchronized 锁的粒度更精细了一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的。(JDK1.8之后 ConcurrentHashMap 启用了一种全新的方式实现,利用 CAS 算法。)
- HashMap 的键值对允许有 null,但是 ConCurrentHashMap 都不允许。
ConcurrentHashMap 和 HashTable 的区别?
底层数据结构:ConcurrentHashMap JDK1.7 的底层采用分段的数组和链表,JDK1.8 的底层采用数组、链表和红黑树。HashTable 的底层采用数组和链表。
实现线程安全的方式:① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个 Segment,比 Hashtable 效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
说说什么是红黑树?
- 每个节点不是黑色就是红色。
- 根节点是黑色。
- 如果一个节点是红色,那么它的俩个子节点就是黑色
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
最短路径:全是黑节点;最长路径:黑白相间。-----》所以当最长路径黑色节点数目同最短路径黑色节点相同时,最长路径恰好是最短路径的俩倍。
颜色表示:因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们将链接的颜色保存在表示结点的Node数据类型的布尔变量color中(若指向它的链接是红色的,那么该变量为true,黑色则为false)。当我们提到一个结点颜色时,我们指的是指向该结点的链接的颜色。
迭代器 Iterator 是什么?
Iterator 对象称为迭代器,迭代器可以对集合进行遍历。定义:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。
Iterator 的特点?
只能单向遍历。更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
Iterator 怎么使用?
- 使用集合中的方法 iterator() 获取迭代器的实现类对象,使用 Iterator 接口接收(多态)
- 使用 Iterator 接口中的方法 hasNext 判断还有没有下一个元素
- 使用 Iterator 接口中的方法 next 取出集合中的下一个元素
Iterator 和 ListIterator 的区别?
- Iterator 可以遍历所有集合,ListIterator 只能遍历 List 及其子类。
- Iterator 只能单向遍历,ListIterator 可以双向遍历(previous() 和 hasPrevious()方法)。
- Iterator 只能遍历和删除元素,ListIterator 有更多的方法,例如:添加、修改和获取前或后的索引位置。
非常感谢您的阅读!如果文章有书写错误或不清楚的地方,希望您评论指出,我将第一时间改正。如果您喜欢这篇文章的话,请你点赞、评论和收藏,如果您还能点击关注,那就是对我最大的鼓励!