集合、数组都是对多个数据进行存储操作的结构,简称 Java 容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
数组在存储多个数据方面的特点:
数组在存储多个数据方面的缺点:
单列集合,用来存储一个一个的对象
① List 接口:元素有序,可重复的集合---”动态”数组
面试:ArrayList 和数组的区别,调用 add 方法会调用哪些其他方法
ArrayList 是 Java 集合框架中的一个类,它是基于数组实现的动态数组。与数组相比,ArrayList 具有更灵活的长度、更方便的插入和删除操作等优点。
数组是 Java 语言中的一种数据类型,它具有固定长度,一旦被创建就不能改变长度。相比之下,ArrayList 可以根据需要动态增长或缩小。
调用 add 方法会调用以下方法:
- 检查是否需要扩容,如果需要,则调用 grow 方法进行扩容。
- 调用 rangeCheckForAdd 方法检查是否超出了数组的容量范围。
- 将要添加的元素添加到数组的末尾。
因为ArrayList是基于数组实现的,所以它的底层实现还涉及到一些其他方法,比如移动元素、删除元素等。但是,调用 add 方法时主要涉及到上述三个方法。
如,ArrayList(主要的实现类)、LinkedList(对于频繁的插入、删除操作)、Vector(古老的实现类、线程安全的)
② Set 接口:元素无序、不可重复的集合 ---类似高中的“集合”
如,HashSet、LinkedHashSet、TreeSet
③ Collection 接口继承树:
双列集合,用来存储一对(key - value)一对的数据。--类似于高中的“函数” y = f(x) (x1,y1) (x2,y2)。
如:HashMap、LinkedHashMap、TreeMap、Hashtable(子类:Properties)。
Map继承树:
① hashmap 和 hashtable的区别?
HashMap 和 Hashtable 都是 Java 集合框架中的哈希表实现类,它们之间的主要区别如下:
综上所述,HashMap 通常比 Hashtable 更适合在单线程环境下使用,而 Hashtable 则适合在多线程环境下使用。
② hashMap 源码。
③ 设计一个线程安全的 HashMap。
设计一个线程安全的 HashMap 可以有以下几种方案:
综上所述,使用 ConcurrentHashMap 是一个比较简单、高效的线程安全的 HashMap 的实现方式。如果需要更细粒度的控制,可以考虑使用其他方式,根据实际情况进行选择。
④ HashMap 在大量哈希冲突该怎么处理?
如果 HashMap 在存储大量数据时发生了哈希冲突,即不同的 key 映射到了同一个桶(bucket)中,这时候就需要使用链表或者红黑树来解决这个冲突。
在 JDK 1.8 及之前的版本中,HashMap 使用的是拉链法解决哈希冲突。具体来说,当发生哈希冲突时,HashMap 会将冲突的元素以链表的形式存储在同一个桶中,同时在链表头部存储最新的元素。这样,在查询元素时,HashMap 会先根据 key 计算出它应该被放到哪个桶中,然后在这个桶中遍历链表,查找对应的元素。当然,如果链表过长,这样的查找效率就会变得很低。
从 JDK 1.8 开始,HashMap 在桶中的链表长度达到一定阈值(默认为 8)时,会将链表转化为红黑树,以提高查询效率。红黑树是一种自平衡的二叉搜索树,它的查询效率比链表更高,但是在插入和删除元素时会比链表慢一些。
此外,如果 HashMap 存储的数据量太大,会导致每个桶中存储的元素过多,从而降低查询效率。这时候可以考虑调整 HashMap 的容量,以减少哈希冲突。具体来说,可以通过调用 HashMap 的 rehash()
方法重新计算每个元素的哈希值,并重新分配桶的位置,以达到更好的散列效果。
⑤ hashMap 底层结构;
HashMap 的底层数据结构是一个数组(Array)和链表(LinkedList)或红黑树(Red-Black Tree)的组合。具体来说,HashMap 将键值对存储在一个 Entry 数组中,每个 Entry 对象包含 key、value 和一个指向下一个 Entry 的指针。当多个键发生哈希冲突时,它们会被存储在同一个数组位置,形成一个链表。当链表长度超过一定阈值(默认为 8)时,链表会自动转化为红黑树,以提高查询效率。 HashMap 的数组长度为 2 的幂次方,这样可以保证在计算数组位置时,只需要进行位运算(&)而不需要进行模运算(%),提高计算效率。此外,HashMap 还具有自动扩容和重新分布的功能,当数组中元素的数量超过了容量的 0.75 倍时,HashMap 会自动扩容,将数组长度翻倍,并将元素重新分布到新的数组位置上。这样可以避免哈希冲突,提高查询效率。
综上所述,HashMap 的底层结构是一个数组和链表或红黑树的组合,通过哈希算法将元素存储在数组中,通过链表或红黑树解决哈希冲突,实现高效的键值对存储和查询。
⑥ ConcurrentHashMap 线程安全原理。
ConcurrentHashMap 是 JDK 提供的一个线程安全的哈希表实现,它是通过分段锁(Segment)来保证线程安全的,而不是像传统的 Hashtable 或者 synchronizedMap 一样直接使用一个锁来保护整个哈希表。
具体来说,ConcurrentHashMap 内部维护了一个 Segment 数组,每个 Segment 就是一个小的哈希表,它们之间是互相独立的,也就是说每个 Segment 中的操作都是线程安全的。每次对 ConcurrentHashMap 的操作都会先根据 key 计算出它应该被放到哪个 Segment 中,然后再对这个 Segment 加锁进行操作。这样就避免了对整个哈希表加锁,提高了并发性能。
另外,ConcurrentHashMap 还使用了一种叫做 CAS(Compare And Swap)的机制来保证数据的一致性。CAS 是一种无锁算法,它的基本思想是先读取内存中的值,然后在一个原子操作中比较这个值和期望的值是否相等,如果相等就更新为新的值,否则不做操作。这样可以避免使用锁,提高并发性能。
综上所述,ConcurrentHashMap 通过分段锁和 CAS 机制来保证线程安全,提高了并发性能。
⑦ HashMap 初始大小,红黑树何时退化成链表;
HashMap 的初始大小是 16,它是一个比较合理的值,因为它是 2 的幂次方,便于进行位运算,同时也不会浪费太多内存空间。在实际使用中,如果需要存储更多的数据,可以通过指定负载因子(load factor)来增加 HashMap 的容量,避免哈希冲突。
红黑树在什么情况下会退化成链表,这取决于 HashMap 中对应的桶中存储的元素个数以及红黑树的结构。具体来说,当一个桶中存储的元素个数小于等于 8 时,HashMap 会使用链表来存储这些元素。当桶中存储的元素个数大于 8 时,HashMap 会将链表转化为红黑树,以提高查询效率。当然,在某些情况下,红黑树可能会退化成链表,主要有两种情况:
Collection 接口是List、Set 和Queue接ロ的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如: Set和List)实现 。
在Java5之前,Java集合会丢失容器中所有对象的数据类型,把所有对象都当成Object类型处理;从Java5増加了泛型以后,Java集合可以记住容器中对象的数据类型
① 添加
add(Object obj)
addAll(Collection coll)
② 获取有效元素的个数
int size()
③ 清空集合
void clear()
④ 是否是空集合
boolean isEmpty()
⑤ 是否包含某个元素
boolean contains(Object obj) // 是通过元素的equals方法来判断是否 是同一个对象
boolean containsAll(Collection c) // 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()。
例:
⑥ 删除
boolean remove(Object obj) // 通过元素的equals方法判断是否是 要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll) // 取当前集合的差集
⑦ 取两个集合的交集
boolean retainAll(Collection c) // 把交集的结果存在当前集合中,不影响c
⑧ 集合是否相等
boolean equals(Object obj)
⑨ 转成对象数组
Object[] toArray()
⑩ 获取集合对象的哈希值
hashCode()
⑪ 遍历
iterator() // 返回迭代器对象,用于集合遍历
Iterator 对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
所有实现了Collection接口的集合类都有一个iterator()方法, 用以返回一个实现了Iterator接口的对象。
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建lterator对象,则必须有一个被迭代的集合
例:
① 内部的方法:hasNext() 和 next()
② 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
③ 内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用 remove()。
如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
Enumeration
Enumeration接口是Iterator迭代器的“古老版本”
Java 5 提供了foreach循环迭代访问Collection
例子:
Collection 子接口:
List 集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
List 除了从 Collection 集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合
Java 中数组用来存储数据的局限性:(长度、类型不可变)
面试题:ArrayList、LinkedList、Vector三者的异同?
a)同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
b)不同
ArrayList、LinkedList、Vector 都是 Java 中的集合类,它们有以下异同点:
- ArrayList 和 Vector 都是基于数组实现的,而 LinkedList 是基于链表实现的。
- ArrayList 和 Vector 的区别在于线程安全性:ArrayList 不是线程安全的,而 Vector 是线程安全的。这是因为 Vector 在每个方法上都进行了同步处理,而 ArrayList 没有。LinkedList 也不是线程安全的。
- ArrayList 和 Vector 的区别在于扩容机制:ArrayList 每次扩容时将当前数组长度增加一半,而 Vector 每次扩容时将当前数组长度翻倍。LinkedList 不需要扩容,因为它是基于链表实现的。
- ArrayList 和 Vector 的随机访问效率比较高,因为可以根据索引直接访问元素,而 LinkedList 的随机访问效率比较低,因为需要从头开始遍历链表查找元素。
- LinkedList 的插入和删除效率比 ArrayList 和 Vector 高,因为只需要修改指针,不需要移动元素。而 ArrayList 和 Vector 的插入和删除效率比较低,因为需要移动元素。
- LinkedList 比 ArrayList 和 Vector 的空间效率高,因为不需要预先分配数组大小,可以根据实际需要动态分配空间。 综上所述,ArrayList、LinkedList、Vector 各有特点,应根据实际需求选择合适的集合类。如果需要高效的随机访问和遍历,可以选择 ArrayList 或 Vector;如果需要高效的插入和删除操作,可以选择 LinkedList。同时,如果需要线程安全的集合操作,可以选择 Vector。
① ArrayList
ArrayList是List接口的典型实现类。本质上,ArrayList是对象引用的一个变长数组。
ArrayList 的 JDK1.8 之前与之后的实现区别?
ArrayList 在 JDK 1.8 之前和之后的实现区别主要体现在如何处理扩容时的数组复制。
在 JDK 1.8 之前,当 ArrayList 的元素数量达到容量时,需要进行扩容。扩容时,会创建一个新的数组,并将旧数组中的元素复制到新数组中。在 JDK 1.6 和 1.7 中,ArrayList 扩容时的复制方式是使用 System.arraycopy() 方法,这种方式效率较高。但是,在 JDK 1.8 中,ArrayList 扩容时的复制方式发生了改变。JDK 1.8 中的实现采用了一种新的复制方法,称为「分裂式迁移」(Splitting),它将原数组分为两个部分,分别复制到新数组的两个部分中。这种方式相对于 System.arraycopy() 方法,可以更好地利用 CPU 缓存,提高效率。
除了扩容时的数组复制方式发生了改变,JDK 1.8 还对 ArrayList 进行了性能优化,提高了其在遍历和删除元素时的效率。具体来说,JDK 1.8 在 ArrayList 中新增了一个成员变量 modCount,用于记录 ArrayList 的结构修改次数。在遍历和删除元素时,会先检查 modCount 是否发生变化,如果发生变化,则抛出 ConcurrentModificationException 异常,防止出现线程安全问题。同时,在删除元素时,JDK 1.8 新增了一个 removeIf() 方法,可以根据指定条件删除元素,提高了删除元素的效率。
综上所述,JDK 1.8 对 ArrayList 进行了一些性能优化和改进,提高了其在遍历和删除元素时的效率,并采用了一种新的扩容方式,提高了数组复制的效率。
ArrayList 源码分析:
HashMap初始化容量为16,扩容为2倍。 HashTable初始化为11,扩容为2n+1。
② LinkedList
LinkedList:双向链表,内部没有声明数组,而是定义了 Node 类型的 first 和 last, 用于记录首末元素。同时,定义内部类 Node,作为 LinkedList 中保存数据的基本结构。Node 除了保存数据,还定义了两个变量:
对于频繁的插入或删除元素的操作,建议使用 LinkedList 类,效率较高。
新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
源码分析:
③ Vector
Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
在各种list中, 最好把ArrayList作为缺省选择。当插入、删除频繁时,使用 LinkedList; Vector 总是比 ArrayList 慢,所以尽量避免使用。
新增方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
Vector的源码分析:
jdk7 和 jdk8 中通过 Vector() 构造器创建对象时,底层都创建了长度为 10 的数组。在扩容方面,默认扩容为原来的数组长度的 2 倍。
void add(int index, Object ele): //在index位置插入ele元素
boolean addAll(int index, Collection eles): //从index位置开始将eles中的所有元素添加进来
Object get(int index): //获取指定index位置的元素
int indexOf(Object obj): //返回obj在集合中首次出现的位置
int lastIndexOf(Object obj): //返回obj在当前集合中末次出现的位置
Object remove(int index): //移除指定index位置的元素,并返回此元素
Object set(int index, Object ele): //设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex): //返回从fromIndex到toIndex位置的子集合
List 遍历:
一个面试题:
Set 接口是 Collection 的子接口,set 接口没有提供额外的方法。Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
Set 判断两个对象是否相同不是使用 == 运算符,而是根据equals方法。
set接口实现类的对比:
set无序性与不可重复性的理解:
① HashSet
HashSet 是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet具有以下特点:
HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
对于存放在 Set 容器中的对象,对应的类一定要重写 equals() 和 hashCode(Object obj) 方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
HashSet中元素的添加过程:
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在 数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布, 该散列函数设计的越好)
要求:
以Eclipse/IDEA为例,在自定义类中可以调用工具自动重写equals和hashCode。 问题:为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?
选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的 “冲突”就越少,查找起来效率也会提高。(减少冲突)
并且31只占用5bits,相乘造成数据溢出的概率较小。
31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效 率)
31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结 果只能被素数本身和被乘数还有1来整除!(减少冲突)
LinkedHashSet
LinkedHashSet是HashSet的子类。LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
LinkedHashSet不允许集合元素重复。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
LinkedHashSet的使用:LinkedHashSet 作为 HashSet 的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
② TreeSet
TreeSet是SortedSet接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet 底层使用红黑树结构存储数据 。
特点:有序,查询速度比List快。
http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html
向TreeSet中添加的数据,要求是相同类的对象。
TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
a)自然排序(实现Comparable接口)
自然排序: TreeSet 会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列。
如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口。
实现Comparable的类必须实现compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小。
Comparable 的典型实现:
向TreeSet中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
当需要把一个对象放入TreeSet中,重写该对象对应的equal() 方法时,应保证该方法与compareTo(Objectobj)方法有一致的结果: 如果两个对象通过equals()方法比较返回true,则通过compareTo(Object obj)方法比较应返回0
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals()。
例:
b)定制排序(Comparator)
TreeSet的自然排序是根据集合元素的大小,进行元素升序排列。如果需要定制排序,比如降序排列,可通过Comparator接口的帮助。需要重写compare(To1,T o2)方法。
利用int compare(T o1,T o2)方法, 比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2; 如果返回0,表示相等;返回负整数,表示o1小于o2。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。使用定制排序判断两个元素相等的标准是:通过Comparator比较两 个元素返回了0。
定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals()。
例:
面试题:
List 额外提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。
Iterator和ListIterator主要区别:
HashMap 是 Map 接口使用频率最高的实现类。允许有 null 键和 null 值,与 HashSet 一样,不保证映射的顺序。一个 key-value 构成一个 entry。
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等;判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true。
HashMap的存储结构:
① JDK1.8之前
HashMap 的内部存储结构其实是数组和链表的结合。当实例化一个 HashMap 时, 系统会创建一个长度为 Capacity 的 Entry 数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个 bucket 都有自己的索引,系统可以根据索引快速的查找 bucket 中的元素。
每个 bucket 中存储一个元素,即一个 Entry 对象,但每一个 Entry 对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个 Entry 链。 而且新添加的元素作为链表的 head。
添加元素的过程:
向 HashMap 中添加 entry1(key,value),需要首先计算 entry1 中 key 的哈希值(根据 key 所在类的 hashCode() 计算得到),此哈希值经过处理以后,得到在底层 Entry[] 数组中要存储的位置 i。如果位置 i 上没有元素,则 entry1 直接添加成功。
如果位置 i 上已经存在 entry2(或还有链表存在的 entry3,entry4),则需要通过循环的方法,依次比较 entry1 中 key 和其他的 entry。如果彼此 hash 值不同,则直接添加成功。
如果 hash 值相同,继续比较二者是否 equals。如果返回值为 true,则使用 entry1 的 value 去替换 equals 为 true的 entry 的 value。
如果遍历一遍以后,发现所有的 equals 返回都为 false,则 entry1 仍可添加成功。entry1 指向原有的 entry 元素。
HashMap的扩容:
当 HashMap 中的元素越来越多的时候,hash 冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对 HashMap 的数组进行扩容,而在 HashMap 数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是 resize。
那么HashMap什么时候进行扩容呢?
当 HashMap 中的元素个数超过数组大小(数组总大小 length,不是数组中个数 size) * loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的默认值(DEFAULT_LOAD_FACTOR) 为 0.75,这是一个折中的取值。也就是说,默认情况 下,数组大小 (DEFAULT_INITIAL_CAPACITY) 为 16,那么当 HashMap 中元素个数超过 16 * 0.75 = 12(这个值就是代码中的 threshold 值,也叫做临界值)的时候,就把数组的大小扩展为 2*16 = 32,即扩大一倍,然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap 中元素的个数, 那么预设元素的个数能够有效的提高 HashMap 的性能。
在 JDK8 中,HashMap 的内部存储结构其实是数组+链表+树的结合。当实例化一个 HashMap 时,会初始化 initialCapacity 和 loadFactor,在 put 第一对映射关系时,系统会创建一个长度为 initialCapacity 的 Node 数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个 bucket 都有自己的索引,系统可以根据索引快速的查找 bucket 中的元素。
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
JDK8 的 HashMap 什么时候树形化呢?
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
在 JDK8 的 HashMap 中,当数组的某一个索引位置上的元素以链表形式存在的数据个数 >8 即为9,且当前数组的容量 >= 64(否则优先扩容)时,此时此索引位置上的所数据由链表改为使用红黑树存储。
关于映射关系的key是否可以修改?answer:不要修改
映射关系存储到 HashMap 中会存储 key 的 hash 值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的 hash 值了,因此如果已经 put 到 Map 中的映射关系,再修改 key 的属性,而这个属性又参与 hashcode 值的计算,那么会导致匹配不上。
② JDK1.8之后
JDK1.8相较于之前的变化
HashMap的底层实现原理?以jdk7为例说明:
HashMap 中重要的常量:
面试题
谈谈你对 HashMap 中 put/get 方法的认识?
HashMap 的扩容机制?默认大小是多少?
什么是负载因子( 或填充比)?负载因子值的大小,对HashMap有什么影响?
负载因子(Load Factor)是 HashMap 中的一个参数,它表示哈希表中元素的数量与容量的比值。具体来说,负载因子 = 元素数量 / 容量。例如,如果一个 HashMap 容量为 16,其中有 10 个元素,那么负载因子就是 10 / 16 = 0.625。
负载因子的作用是衡量 HashMap 中元素的密集程度。当负载因子超过一定阈值(一般为 0.75),意味着哈希表中的元素数量占用了大部分容量,此时就需要对哈希表进行扩容,以避免哈希冲突的发生,提高效率。因此,负载因子越大,哈希表扩容的次数越多,性能就会下降。相反,如果负载因子过小,哈希表的容量就会浪费,也会影响哈希表的性能。
在 Java 的 HashMap 中,负载因子是一个可调参数,可以通过构造方法或 setLoadFactor() 方法设置。默认的负载因子为 0.75,这是一个比较经验性的值,可以满足大多数场景的需求。如果需要更高的性能,可以将负载因子设置为更小的值,以减少扩容的次数;如果需要更少的内存占用,可以将负载因子设置为更大的值,以增加哈希表的容量。但需要注意的是,负载因子不宜设置过大或过小,否则都会影响哈希表的性能。
什么是吞吐临界值(或阈值、threshold)?
LinkedHashMap
LinkedHashMap 是 HashMap 的子类。与 LinkedHashSet 类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 key-Value 对的插入顺序一致
LinkedHashMap 的底层实现原理(了解):
TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeMap的Key的排序:
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException。
定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。此时不需要Map的Key实现Comparable接口。
TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回 0。
若使用自定义类作为TreeMap的key,所属类需要重写equals()和hashCode()方法,且equals()方法返回true时,compareTo()方法返回0。
TreeMap的两种添加方式:
Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
与HashMap不同,Hashtable不允许使用null作为key和value
与HashMap一样,Hashtable也不能保证其中key-Value对的顺序
Hashtable判断两个key相等、两个value相等的标准,与HashMap一致
Properties:
Object put(Object key,Object value) // 将指定key-value添加到(或修改)当前map对象中
void putAll(Map m) // 将m中的所有key-value对存放到当前map中
Object remove(Object key) // 移除指定key的key-value对,并返回value
void clear() // 清空当前map中的所有数据
Object get(Object key) // 获取指定key对应的value
boolean containsKey(Object key) // 是否包含指定的key
boolean containsValue(Object value) // 是否包含指定的value
int size() // 返回map中key-value对的个数
boolean isEmpty() // 判断当前map是否为空
boolean equals(Object obj) // 判断当前map和参数对象obj是否相等
Set keySet() // 返回所有key构成的Set集合
Collection values() // 返回所有value构成的Collection集合
Set entrySet() // 返回所有key-value对构成的Set集合
Collections:操作Collection、Map的工具类。
4.1 静态方法
排序操作:
reverse(List) // 反转 List 中元素的顺序
shuffle(List) // 对 List 集合元素进行随机排序
sort(List) // 根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator) // 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int) // 将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换:
Object max(Collection) // 根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator) // 根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection, Comparator)
int frequency(Collection, Object) // 返回指定集合中指定元素的出现次数
void copy(List dest,List src) // 将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal) // 使用新值替换 List 对象的所有旧值
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
面试题:Collection 和 Collections的区别?
Collection是Java集合框架中的一个接口,它是所有集合类型的根接口,定义了集合对象的基本操作,如添加、删除、遍历、判断元素是否存在等。
Collections是Java集合框架中的一个工具类,它提供了一系列静态方法,用于对集合对象进行排序、查找、替换、反转等操作。
简单来说,Collection是一个接口,描述了集合的基本操作,而Collections是一个工具类,提供了对集合进行操作的静态方法。