一、Collection:
1.Collection集合中不能直接存储基本数据类型,也不能存储Java对象,只能存储Java对象的内存地址(引用)
2.通过查看源码,可知Collection中含有的方法:
int size(); 返回collection集合的大小,包含的元素数量
boolean isEmpty(); 判断集合是否为空
boolean contains(Object o); 判断集合是否包含某元素
boolean add(E e); 添加元素
boolean remove(Object o); 删除集合中的某个元素
void clear(); 清除集合内的元素
Object[] toArray(); 调用这个方法把集合转换成数组
Iterator<E> iterator();集合的迭代器
代码演示
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add(1);
collection.add(2);
collection.add(3);
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
Iterable接口
在查看源码时我们注意到Collection继承了一个iterable接口,接下来我们简单认识下该接口:
iterable被称为内部迭代器,常用作容器类的接口,以支持遍历操作,一个类如果实现了iterable接口,就意味着该类本身支持遍历,并可以通过for-each这种循环语法来直接遍历。
iterable中有三个api:
//返回T元素类型的迭代器
Iterator<T> iterator();
// 对Iterable的每个元素执行给定操作(函数式接口),直到处理完所有元素或操作引发异常。
// 常用的场景是list.stream.foreach(),该方法支持多线程并行操作
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 将普通迭代器转换为可分割迭代器,用于流式操作
default Spliterator<T> spliterator(){}
二、List:
list接口定义: public interface List<E> extends Collection<E>
List
:有序集合,其中元素可以重复,并且每个元素都有一个明确的位置(索引)。List
的实现包括:
ArrayList
:基于数组的实现,提供了随机访问的能力。LinkedList
:基于链表的实现,适合频繁的插入和删除操作。Vector
:线程安全的ArrayList
实现。Stack
:基于Vector
的后进先出(LIFO)数据结构。
1.ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}基本概念
ArrayList
是一个实现了List
接口的类,它使用动态数组来存储元素。这意味着ArrayList
可以动态地改变其大小以适应添加或移除的元素。ArrayList
可以存储任何类型的对象,并允许有重复的元素。
内部实现
ArrayList
内部维护了一个Object[]
类型的数组,用于存储元素。当向ArrayList
添加元素时,如果当前数组的容量不足,ArrayList
会自动创建一个新的更大的数组,并将原有数组的内容复制到新数组中。默认情况下,每次扩容数组的容量会增加原来的50%。
接口实现
ArrayList
实现了以下接口:
List
:定义了一系列对有序集合的操作。RandomAccess
:表示这个列表支持快速的随机访问,意味着可以通过索引在常数时间内访问元素。Cloneable
:表明ArrayList
可以被克隆。Serializable
:允许ArrayList
被序列化和反序列化,方便在网络上传输或持久化存储。
常用方法
add(E e)
:在列表末尾添加一个元素。add(int index, E element)
:在指定位置插入一个元素。remove(Object o)
或remove(int index)
:删除指定元素或索引处的元素。get(int index)
:返回指定索引处的元素。set(int index, E element)
:替换指定索引处的元素。size()
:返回列表中的元素个数。isEmpty()
:检查列表是否为空。clear()
:清空列表中的所有元素。contains(Object o)
:检查列表中是否包含某个元素。indexOf(Object o)
:返回列表中第一次出现指定元素的索引,如果不存在则返回-1。lastIndexOf(Object o)
:返回列表中最后一次出现指定元素的索引。
线程安全性
ArrayList
本身是非线程安全的,如果多个线程并发地访问和修改ArrayList
,可能会导致数据不一致的问题。如果需要在多线程环境中使用ArrayList
,可以考虑使用Collections.synchronizedList
方法来创建一个线程安全的ArrayList
,或者使用线程安全的集合类型如Vector
或CopyOnWriteArrayList
2.LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}基本概念
LinkedList
是一个实现了List
和Deque
(双端队列)接口的类,这意味着它不仅可以作为列表使用,还可以作为栈、队列或双端队列使用。LinkedList
允许元素的重复,并且在链表中插入和删除元素的性能非常好。
内部实现
LinkedList
的内部由一系列节点组成,每个节点都包含一个元素以及对前一个节点和后一个节点的引用。这种双向链表结构使得在链表的任何位置插入或删除元素都非常高效,因为只需要更新前后节点的引用即可,时间复杂度为O(1)。
常用方法
LinkedList
继承了List
接口的所有方法,并添加了一些特定于链表和双端队列的方法:
addFirst(E e)
,addLast(E e)
:分别在链表头部或尾部添加元素。getFirst()
,getLast()
:获取链表头部或尾部的元素。removeFirst()
,removeLast()
:移除并返回链表头部或尾部的元素。peekFirst()
,peekLast()
:查看但不移除链表头部或尾部的元素。offerFirst(E e)
,offerLast(E e)
:在链表头部或尾部添加元素,如果添加失败(例如,队列已满),则返回false
。pollFirst()
,pollLast()
:移除并返回链表头部或尾部的元素,如果链表为空,则返回null
。
3.Vector
在Java中,
Vector
类是java.util
包下的一个实现List
接口的线程安全容器。与ArrayList
相比,Vector
在设计上提供了更多的线程安全保证,这使得它在多线程环境下更加可靠,但同时也可能影响性能。基本特性
- 线程安全:
Vector
的所有公共方法都是同步的,这意味着当多个线程尝试同时访问或修改同一个Vector
实例时,Vector
会自动处理线程同步,防止数据不一致。- 动态增长:类似于
ArrayList
,Vector
使用一个数组作为底层数据结构,并且当添加的元素超过当前数组容量时,它会自动扩展容量。- 初始容量和增量:
Vector
有一个初始容量,默认为10。当需要更多空间时,Vector
会按一定的增量(默认为当前容量的50%)来增加其容量。- 元素类型:
Vector
可以存储任何类型的对象,并且允许存储null
值。
使用场景
由于
Vector
的同步性质,它适合于多线程环境,尤其是在需要频繁读取而不频繁写入的情况下。然而,由于同步带来的开销,对于单线程环境或者对性能要求较高的场景,通常建议使用非线程安全但效率更高的ArrayList
。
性能考量
Vector
的同步机制可能导致性能瓶颈,特别是在高并发的写操作中。如果多线程环境下的性能是一个关键因素,可以考虑使用ConcurrentModificationException
安全的替代品,如CopyOnWriteArrayList
,或者使用Collections.synchronizedList(new ArrayList<...>())
来显式地控制同步策略。总的来说,
Vector
是一个功能强大但使用需要谨慎的容器,特别是在对性能敏感的应用中。如果线程安全不是必要条件,那么ArrayList
通常是一个更优的选择。
4.Stack
在Java中,
Stack
类是一个遵循后进先出(LIFO, Last-In-First-Out)原则的特殊类型容器,通常用于处理需要这种顺序数据访问的任务,比如表达式求值、括号匹配、函数调用堆栈等场景。Stack
类位于java.util
包中,并且它继承自Vector
类,因此它同样具有线程同步的特性。虽然
Stack
类在Java中可用,但现代的Java编程实践通常推荐使用Deque
接口的实现,如ArrayDeque
或LinkedBlockingDeque
,因为它们提供了更丰富的功能集并且性能通常更好。Stack
类的一些设计被认为过时,例如push
和pop
方法的命名在Java集合框架中并不常见。
特性
- 线程安全:由于继承自
Vector
,Stack
的所有公共操作都是线程同步的,这意味着可以在多线程环境中安全地使用它,无需额外的同步机制。- LIFO原则:
Stack
确保最后一个进入的元素将是第一个出来的,这是通过限制只能在列表的一端(顶部)进行添加和移除操作实现的。
常用方法
Stack
类提供了几个核心方法来操作栈顶元素:
push(Object item)
:将一个元素压入栈顶。pop()
:从栈顶移除并返回一个元素。如果栈为空,则抛出EmptyStackException
异常。peek()
:查看栈顶元素但不移除它。如果栈为空,则抛出EmptyStackException
异常。search(Object obj)
:返回一个对象在栈中的位置,即从栈顶开始的深度,如果对象不在栈中,则返回负数。empty()
:测试栈是否为空。
三、Set
特点
- 无重复性:
Set
中的每一个元素都是唯一的,不会有重复的实例。- 无序性:除了
LinkedHashSet
之外,Set
中的元素是没有固定顺序的。HashSet
和TreeSet
中的元素不会按照插入顺序或其他预定义顺序存储。
实现类
Set
接口有多种实现,每种都有其特定的特性和用途:
HashSet
:基于哈希表的Set
实现,提供了平均情况下的常数时间复杂度(O(1))操作,如添加、删除和查找。HashSet
不保证元素的顺序,而且它是非同步的,意味着在多线程环境中使用时需要额外的同步机制。
LinkedHashSet
:继承自HashSet
,但保持了元素的插入顺序。它通过链接列表维持了元素的顺序,同时提供了HashSet
的高性能。这对于需要保留插入顺序的场景非常有用。
TreeSet
:基于红黑树的Set
实现,提供了排序功能。TreeSet
会按照元素的自然顺序或由提供的Comparator
确定的顺序对元素进行排序。TreeSet
的添加、删除和查找操作的时间复杂度为O(log n),因为它必须维护树的平衡。
1.HashSet
主要特点
- 无重复性:
HashSet
中的元素必须是唯一的。如果尝试添加一个与集合中已有元素相等(通过equals()
方法比较)的新元素,HashSet
将不会添加这个新元素,并且add()
方法将返回false
。- 无序性:元素的存储和遍历顺序并不固定,也不一定与插入顺序相同,这是因为
HashSet
内部使用哈希算法来存储元素,这使得元素的存储位置依赖于它们的哈希码。- 允许一个null元素:
HashSet
允许一个null元素的存在。但是,如果尝试添加多个null元素,只有第一个会被存储,后续的null元素添加将被忽略,因为所有null元素都被视为相等。- 线程不安全:
HashSet
是线程不安全的,如果多个线程同时访问和修改HashSet
,可能会导致数据不一致性。如果需要线程安全的行为,可以使用Collections.synchronizedSet
或ConcurrentHashMap
的newKeySet
方法。
内部实现
HashSet
的内部实现是基于HashMap
的,其中元素作为键(key)存储,而所有的值都设置为PRESENT
,这是一个HashSet
类内部定义的静态对象引用。这样做的目的是为了利用HashMap
的高效哈希算法来实现HashSet
的快速查找、添加和删除操作。
2.TreeSet
主要特性
排序:
TreeSet
会按照元素的自然排序或由提供的Comparator
确定的顺序对元素进行排序。这意味着每次遍历TreeSet
时,元素都会以相同的顺序出现。唯一性:如同所有的
Set
实现,TreeSet
不允许重复元素。如果试图添加一个与集合中已有元素相等(通过equals()
方法比较)的元素,那么添加操作将会失败。可导航性:
TreeSet
实现了NavigableSet
接口,提供了如first()
,last()
,lower()
,higher()
,floor()
,ceiling()
等方法,这些方法允许在集合中进行导航和范围查询。效率:
TreeSet
的添加、删除和查找操作的时间复杂度通常为O(log n),这是因为红黑树的高度始终保持在log n的级别,其中n是树中节点的数量。
import java.util.Comparator;
import java.util.TreeSet;
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + ", " + age;
}
}
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<Person> treeSet = new TreeSet<>();
// 添加元素
treeSet.add(new Person("Alice", 30));
treeSet.add(new Person("Bob", 25));
treeSet.add(new Person("Charlie", 35));
// 输出元素,将按照年龄排序
System.out.println(treeSet);
}
}
3.LinkedHashSet
主要特性
有序性:
LinkedHashSet
保证元素的迭代顺序与插入顺序相同。这一点对于那些需要保存元素添加顺序的应用场景非常有用。唯一性:同其他
Set
实现一样,LinkedHashSet
不允许重复元素。如果尝试添加一个与集合中已有元素相等(通过equals()
方法比较)的元素,LinkedHashSet
将不会添加这个元素。性能:
LinkedHashSet
的性能特征与HashSet
类似,在平均情况下,添加、删除和查找操作的时间复杂度为O(1)。然而,由于维护了链表,它的开销略高于HashSet
,特别是当频繁执行迭代操作时。线程不安全:与
HashSet
一样,LinkedHashSet
不是线程安全的。在多线程环境中使用时,需要采取额外的同步措施。
内部实现
LinkedHashSet
内部使用了HashMap
来存储元素,其中每个元素都是HashMap
的键。为了保持元素的插入顺序,LinkedHashSet
使用了一个双向链表,这个链表与HashMap
中的键关联,从而保证了元素的顺序。
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
// 添加元素
set.add("one");
set.add("two");
set.add("three");
// 尝试添加重复元素
boolean added = set.add("one"); // false,因为"one"已经存在
System.out.println("Element 'one' added again: " + added);
// 遍历元素,元素将以添加的顺序输出
for (String element : set) {
System.out.println(element);
}
}
}
四、Queue
1.Deque
Deque
是Double-Ended Queue(双端队列)的缩写,它是Java集合框架中的一个接口,定义在java.util
包下。Deque
接口继承了Queue
接口,但提供了额外的方法来支持在队列的两端进行插入和删除操作。这意味着Deque
可以作为队列使用,也可以作为堆栈或者双端队列使用。主要方法
Deque
接口提供了以下主要方法,这些方法允许在队列的头部和尾部进行元素的插入、移除和检查:
addFirst(E e)
: 将元素添加到队列的头部。addLast(E e)
: 将元素添加到队列的尾部。offerFirst(E e)
: 类似于addFirst
,但如果是失败的操作,则返回false而不是抛出异常。offerLast(E e)
: 类似于addLast
,但如果是失败的操作,则返回false而不是抛出异常。removeFirst()
: 移除并返回队列头部的元素。removeLast()
: 移除并返回队列尾部的元素。pollFirst()
: 类似于removeFirst
,但如果队列为空,则返回null。pollLast()
: 类似于removeLast
,但如果队列为空,则返回null。getFirst()
: 返回队列头部的元素,但不移除它。getLast()
: 返回队列尾部的元素,但不移除它。peekFirst()
: 类似于getFirst
,但如果队列为空,则返回null。peekLast()
: 类似于getLast
,但如果队列为空,则返回null。push(E e)
: 将元素压入堆栈顶部(相当于addFirst
)。pop()
: 移除并返回堆栈顶部的元素(相当于removeFirst
)。
实现类
有几个类实现了
Deque
接口:
ArrayDeque
: 使用数组实现的Deque
,提供了基于数组的快速随机访问。LinkedList
: 虽然主要是一个双向链表,但它也实现了Deque
接口,可以作为一个双端队列使用。LinkedBlockingDeque
: 一个线程安全的Deque
实现,可以用于多线程环境。
2.PriorityQueue
主要特性
元素排序:
PriorityQueue
中的元素根据它们的自然排序(如果元素实现了Comparable
接口)或者由构造时提供的Comparator
进行排序。这意味着每次从队列中移除元素时,都会移除并返回具有最高优先级的元素(默认是最小的元素)。无界队列:理论上,
PriorityQueue
可以无限增长,直到耗尽可用内存。但是,你可以通过构造函数指定一个初始容量,这将影响内部数组的大小。不允许null元素:
PriorityQueue
不允许插入null
元素,这样做会导致抛出NullPointerException
。线程不安全性:
PriorityQueue
不是线程安全的。如果多个线程同时访问一个PriorityQueue
实例,并且至少有一个线程修改了队列,则必须外部同步。否则,结果是不确定的。提供头部元素:
PriorityQueue
提供了peek()
方法来查看队列头部的元素,而不会从队列中移除它。如果队列为空,peek()
将返回null
。
主要方法
除了
Queue
接口中定义的方法外,PriorityQueue
还提供了以下方法:
add(E e)
: 向队列中添加一个元素,如果队列已满,则抛出IllegalArgumentException
。offer(E e)
: 类似于add
,但不会抛出异常。如果无法立即添加元素,则返回false
。element()
: 返回队列头部的元素,如果队列为空,则抛出NoSuchElementException
。remove()
: 移除并返回队列头部的元素,如果队列为空,则抛出NoSuchElementException
。poll()
: 类似于remove
,但如果是空队列,则返回null
。
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个基于自然排序的PriorityQueue
PriorityQueue<Integer> pq = new PriorityQueue<>();
// 添加元素
pq.add(10);
pq.add(1);
pq.add(5);
// 查看队列头部元素
Integer topElement = pq.peek();
System.out.println("Top element: " + topElement); // 输出:1
// 移除队列头部元素
Integer removedElement = pq.poll();
System.out.println("Removed element: " + removedElement); // 输出:1
// 使用Comparator创建一个基于逆序排序的PriorityQueue
PriorityQueue<Integer> reversePQ = new PriorityQueue<>((a, b) -> b - a);
reversePQ.add(10);
reversePQ.add(1);
reversePQ.add(5);
// 查看队列头部元素
Integer reverseTopElement = reversePQ.peek();
System.out.println("Reverse top element: " + reverseTopElement); // 输出:10
}
}
五、Map
Map接口的主要方法
Map
接口提供了一些核心方法:
put(K key, V value)
:将指定的键值对添加到此映射中,如果键已存在,则替换旧值。get(Object key)
:返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。remove(Object key)
:移除指定键的映射关系(如果存在)。containsKey(Object key)
:如果此映射包含对于指定的键,则返回 true。containsValue(Object value)
:如果此映射包含指定的值,则返回 true。isEmpty()
:如果此映射不包含任何键值对,则返回 true。size()
:返回此映射中的键值对数量。clear()
:移除此映射中的所有映射关系。keySet()
:返回此映射中的键集视图。values()
:返回此映射中的值集视图。entrySet()
:返回此映射中包含的映射关系的集合视图。
Map的不同实现
Java中
Map
接口有多种实现,每种实现都有其特点和应用场景:
HashMap
:提供了基于哈希表的实现,提供了非常快的存取速度,但不保证映射关系的顺序。HashMap
允许键和值为null
。
LinkedHashMap
:继承自HashMap
,但保持了元素的插入顺序。LinkedHashMap
使用双向链表来维护元素的顺序,这使得它成为实现LRU缓存的理想选择。
TreeMap
:提供了基于红黑树的实现,可以按照键的自然顺序或者由Comparator
确定的顺序对键进行排序。TreeMap
不允许null
键,但允许null
值。
Hashtable
:类似于HashMap
,但是线程安全的,也就是说,多个线程可以共享一个Hashtable
实例而无需额外的同步措施。不过,Hashtable
不允许键或值为null
。
IdentityHashMap
:使用对象的身份而不是其equals()
方法来确定键的相等性。这在某些特定的场景下可能很有用,例如当需要基于对象引用而非对象内容来区分键时。
ConcurrentHashMap
:线程安全的Map
实现,提供了比Hashtable
更高的并发性能。它使用了分段锁的技术,允许并行的读写操作。
1.HashMap
主要特性
非线程安全:
HashMap
不是线程安全的,这意味着在多线程环境中使用时,如果没有适当的同步控制,可能会遇到数据不一致的问题。如果需要线程安全的Map
,可以考虑使用ConcurrentHashMap
。动态扩容:
HashMap
的容量初始默认为16,并且当键值对的数量超过容量乘以加载因子(默认为0.75)时,HashMap
会自动扩容,通常是当前容量的两倍。键值唯一性:在一个
HashMap
中,键是唯一的,而值可以重复。如果尝试插入一个已经存在的键,新的值会覆盖旧的值。存储和检索性能:由于
HashMap
是基于哈希表实现的,所以它在平均情况下提供了非常快的性能,尤其是当没有哈希冲突时。然而,如果多个键的哈希值相同(哈希冲突),性能会下降到O(n),其中n是冲突的键值对的数量。初始化容量和加载因子:在创建
HashMap
时,可以通过构造函数指定初始容量和加载因子。初始化容量是指哈希表的大小,加载因子用于确定何时需要重新哈希。
内部实现
HashMap
的内部实现是通过数组加链表(JDK 8之前)或数组加链表/红黑树(JDK 8及之后)的方式。在JDK 8中,当链表长度达到一定阈值时(默认为8),链表会转换成红黑树以提高查找性能。
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 插入键值对
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
// 获取值
Integer value = map.get("two");
System.out.println(value); // 输出:2
// 检查键是否存在
boolean containsKey = map.containsKey("four");
System.out.println(containsKey); // 输出:false
// 更新值
map.put("two", 22);
System.out.println(map.get("two")); // 输出:22
// 遍历Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 删除键值对
map.remove("one");
}
}
2.TreeMap
主要特性
排序:
TreeMap
中的元素会根据键的自然排序(即键实现Comparable
接口)或者通过构造函数传入的Comparator
进行排序。这意味着当你遍历TreeMap
时,元素总是按照排序的顺序出现。线程不安全性:
TreeMap
本身不是线程安全的。如果多个线程同时访问TreeMap
,并且至少一个线程修改了TreeMap
,则必须外部同步。否则,TreeMap
的迭代器可能会抛出ConcurrentModificationException
。性能:
TreeMap
的添加、删除和查找操作的时间复杂度通常为O(log n),其中n是树中节点的数量。这比基于哈希表的Map
实现慢,但提供了排序和有序操作的能力。导航方法:
TreeMap
实现了NavigableMap
接口,提供了高级的导航方法,如firstEntry()
,lastEntry()
,lowerEntry()
,higherEntry()
,floorEntry()
, 和ceilingEntry()
,这些方法允许你获取集合中的第一个、最后一个、小于或大于给定键的最近的键值对。子映射:
TreeMap
还提供了创建子映射的方法,如subMap()
,headMap()
, 和tailMap()
,这些方法允许你创建键在指定范围内的视图。
内部实现
TreeMap
使用红黑树作为其底层数据结构。红黑树是一种自平衡的二叉搜索树,它在插入和删除操作后能够自动调整树的结构,以保持良好的平衡状态,从而确保了树的高度始终在log n的级别,保证了操作的效率。
import java.util.*;
public class TreeMapExample {
public static void main(String[] args) {
// 创建一个自然排序的TreeMap
Map<Integer, String> map = new TreeMap<>();
// 添加元素
map.put(1, "One");
map.put(3, "Three");
map.put(2, "Two");
// 输出元素,将按照键的自然排序输出
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 创建一个使用自定义比较器的TreeMap
Comparator<String> reverseOrder = Collections.reverseOrder();
Map<String, Integer> reverseSortedMap = new TreeMap<>(reverseOrder);
// 添加元素
reverseSortedMap.put("a", 1);
reverseSortedMap.put("c", 3);
reverseSortedMap.put("b", 2);
// 输出元素,将按照键的逆序排序输出
for (Map.Entry<String, Integer> entry : reverseSortedMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
3.LinkedHashMap
主要特性
插入顺序或访问顺序:
LinkedHashMap
有两种模式,一是保持元素的插入顺序,二是维护访问顺序。在访问顺序模式下,最近访问的元素会移动到链表的末尾,这使得LinkedHashMap
非常适合实现LRU(Least Recently Used)缓存策略。性能:
LinkedHashMap
的性能与HashMap
相似,对于添加、删除和查找操作的时间复杂度通常为O(1),但由于它维护了一个额外的双向链表,所以在大量迭代操作时可能会比HashMap
稍微慢一些。允许null键和null值:
LinkedHashMap
允许一个null
键和任意数量的null
值,这一点与HashMap
相同。线程不安全性:
LinkedHashMap
不是线程安全的,这意味着在多线程环境下使用时,需要进行适当的同步处理。
内部实现
LinkedHashMap
的内部结构实际上是一个HashMap
加上一个双向链表。双向链表中的每个节点都指向HashMap
中的一个条目,这样就可以在保持HashMap
高效查找的同时,维护元素的顺序信息。
4.HashTable
主要特性
线程安全性:
Hashtable
的所有关键操作(如put
,get
,remove
等)都是同步的,这确保了在多线程环境中的数据一致性。然而,这种同步也会带来性能上的开销,因为在高并发情况下,多个线程可能需要等待获得锁才能访问Hashtable
。不允许null键或null值:与
HashMap
不同,Hashtable
不允许任何键或值为null
。如果尝试插入null
键或null
值,Hashtable
将抛出NullPointerException
。陈旧性:
Hashtable
是在Java早期版本中设计的,自那以后,更现代、更高效的线程安全Map
实现如ConcurrentHashMap
已被引入,它们提供了更好的性能和更丰富的功能。枚举支持:
Hashtable
提供了一个elements()
方法,返回一个Enumeration
,这在Java早期版本中很常见,但在现代Java代码中,更倾向于使用Iterator
或增强型for
循环。
5.ConcurrentHashMap
主要特性
线程安全性:
ConcurrentHashMap
是线程安全的,意味着多个线程可以同时读写ConcurrentHashMap
而不会引发数据不一致问题。这是通过使用锁分段技术实现的,即ConcurrentHashMap
将整个哈希表分为多个段(segment),每个段有自己的锁,这样多个线程可以同时在不同的段上进行操作,从而提高了并发性能。非阻塞性:
ConcurrentHashMap
的设计采用了非阻塞算法,这减少了线程之间的竞争,增加了整体的吞吐量。例如,读取操作通常不需要锁定,而写操作只锁定涉及到的段。允许null值:与
Hashtable
不同,ConcurrentHashMap
允许值为null
,但键仍然不能为null
。可配置的并发级别:
ConcurrentHashMap
的构造函数允许指定并发级别的参数,这决定了内部分割的数量,从而影响到性能和内存消耗。较高的并发级别意味着更多的分割,可以提高并发性能,但也可能导致更多的内存消耗。原子操作:
ConcurrentHashMap
提供了一些原子操作,如putIfAbsent
,computeIfAbsent
,merge
等,这些方法可以在不显式同步的情况下更新或计算值。
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 添加键值对
concurrentHashMap.put("one", 1);
concurrentHashMap.put("two", 2);
concurrentHashMap.put("three", 3);
// 原子地添加键值对,如果键不存在则添加,否则返回现有值
Integer value = concurrentHashMap.putIfAbsent("two", 22);
System.out.println(value); // 输出:2,因为"two"已经存在
// 计算或获取值
Integer computedValue = concurrentHashMap.computeIfAbsent("four", k -> 4);
System.out.println(computedValue); // 输出:4
// 遍历ConcurrentHashMap
concurrentHashMap.forEach((k, v) -> System.out.println(k + ": " + v));
}
}