Java集合学习笔记

一、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,或者使用线程安全的集合类型如VectorCopyOnWriteArrayList

2.LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
基本概念

LinkedList是一个实现了ListDeque(双端队列)接口的类,这意味着它不仅可以作为列表使用,还可以作为栈、队列或双端队列使用。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会自动处理线程同步,防止数据不一致。
  • 动态增长:类似于ArrayListVector使用一个数组作为底层数据结构,并且当添加的元素超过当前数组容量时,它会自动扩展容量。
  • 初始容量和增量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接口的实现,如ArrayDequeLinkedBlockingDeque,因为它们提供了更丰富的功能集并且性能通常更好。Stack类的一些设计被认为过时,例如pushpop方法的命名在Java集合框架中并不常见。

特性
  • 线程安全:由于继承自VectorStack的所有公共操作都是线程同步的,这意味着可以在多线程环境中安全地使用它,无需额外的同步机制。
  • LIFO原则Stack确保最后一个进入的元素将是第一个出来的,这是通过限制只能在列表的一端(顶部)进行添加和移除操作实现的。
常用方法

Stack类提供了几个核心方法来操作栈顶元素:

  • push(Object item):将一个元素压入栈顶。
  • pop():从栈顶移除并返回一个元素。如果栈为空,则抛出EmptyStackException异常。
  • peek():查看栈顶元素但不移除它。如果栈为空,则抛出EmptyStackException异常。
  • search(Object obj):返回一个对象在栈中的位置,即从栈顶开始的深度,如果对象不在栈中,则返回负数。
  • empty():测试栈是否为空。

三、Set

特点
  • 无重复性Set中的每一个元素都是唯一的,不会有重复的实例。
  • 无序性:除了LinkedHashSet之外,Set中的元素是没有固定顺序的。HashSetTreeSet中的元素不会按照插入顺序或其他预定义顺序存储。
实现类

Set接口有多种实现,每种都有其特定的特性和用途:

  1. HashSet:基于哈希表的Set实现,提供了平均情况下的常数时间复杂度(O(1))操作,如添加、删除和查找。HashSet不保证元素的顺序,而且它是非同步的,意味着在多线程环境中使用时需要额外的同步机制。

  2. LinkedHashSet:继承自HashSet,但保持了元素的插入顺序。它通过链接列表维持了元素的顺序,同时提供了HashSet的高性能。这对于需要保留插入顺序的场景非常有用。

  3. TreeSet:基于红黑树的Set实现,提供了排序功能。TreeSet会按照元素的自然顺序或由提供的Comparator确定的顺序对元素进行排序。TreeSet的添加、删除和查找操作的时间复杂度为O(log n),因为它必须维护树的平衡。

1.HashSet

主要特点
  1. 无重复性HashSet中的元素必须是唯一的。如果尝试添加一个与集合中已有元素相等(通过equals()方法比较)的新元素,HashSet将不会添加这个新元素,并且add()方法将返回false
  2. 无序性:元素的存储和遍历顺序并不固定,也不一定与插入顺序相同,这是因为HashSet内部使用哈希算法来存储元素,这使得元素的存储位置依赖于它们的哈希码。
  3. 允许一个null元素HashSet允许一个null元素的存在。但是,如果尝试添加多个null元素,只有第一个会被存储,后续的null元素添加将被忽略,因为所有null元素都被视为相等。
  4. 线程不安全HashSet是线程不安全的,如果多个线程同时访问和修改HashSet,可能会导致数据不一致性。如果需要线程安全的行为,可以使用Collections.synchronizedSetConcurrentHashMapnewKeySet方法。
内部实现

HashSet的内部实现是基于HashMap的,其中元素作为键(key)存储,而所有的值都设置为PRESENT,这是一个HashSet类内部定义的静态对象引用。这样做的目的是为了利用HashMap的高效哈希算法来实现HashSet的快速查找、添加和删除操作。

2.TreeSet

主要特性
  1. 排序TreeSet会按照元素的自然排序或由提供的Comparator确定的顺序对元素进行排序。这意味着每次遍历TreeSet时,元素都会以相同的顺序出现。

  2. 唯一性:如同所有的Set实现,TreeSet不允许重复元素。如果试图添加一个与集合中已有元素相等(通过equals()方法比较)的元素,那么添加操作将会失败。

  3. 可导航性TreeSet实现了NavigableSet接口,提供了如first(), last(), lower(), higher(), floor(), ceiling()等方法,这些方法允许在集合中进行导航和范围查询。

  4. 效率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

主要特性
  1. 有序性LinkedHashSet保证元素的迭代顺序与插入顺序相同。这一点对于那些需要保存元素添加顺序的应用场景非常有用。

  2. 唯一性:同其他Set实现一样,LinkedHashSet不允许重复元素。如果尝试添加一个与集合中已有元素相等(通过equals()方法比较)的元素,LinkedHashSet将不会添加这个元素。

  3. 性能LinkedHashSet的性能特征与HashSet类似,在平均情况下,添加、删除和查找操作的时间复杂度为O(1)。然而,由于维护了链表,它的开销略高于HashSet,特别是当频繁执行迭代操作时。

  4. 线程不安全:与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

主要特性
  1. 元素排序PriorityQueue中的元素根据它们的自然排序(如果元素实现了Comparable接口)或者由构造时提供的Comparator进行排序。这意味着每次从队列中移除元素时,都会移除并返回具有最高优先级的元素(默认是最小的元素)。

  2. 无界队列:理论上,PriorityQueue可以无限增长,直到耗尽可用内存。但是,你可以通过构造函数指定一个初始容量,这将影响内部数组的大小。

  3. 不允许null元素PriorityQueue不允许插入null元素,这样做会导致抛出NullPointerException

  4. 线程不安全性PriorityQueue不是线程安全的。如果多个线程同时访问一个PriorityQueue实例,并且至少有一个线程修改了队列,则必须外部同步。否则,结果是不确定的。

  5. 提供头部元素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接口有多种实现,每种实现都有其特点和应用场景:

  1. HashMap:提供了基于哈希表的实现,提供了非常快的存取速度,但不保证映射关系的顺序。HashMap允许键和值为null

  2. LinkedHashMap:继承自HashMap,但保持了元素的插入顺序。LinkedHashMap使用双向链表来维护元素的顺序,这使得它成为实现LRU缓存的理想选择。

  3. TreeMap:提供了基于红黑树的实现,可以按照键的自然顺序或者由Comparator确定的顺序对键进行排序。TreeMap不允许null键,但允许null值。

  4. Hashtable:类似于HashMap,但是线程安全的,也就是说,多个线程可以共享一个Hashtable实例而无需额外的同步措施。不过,Hashtable不允许键或值为null

  5. IdentityHashMap:使用对象的身份而不是其equals()方法来确定键的相等性。这在某些特定的场景下可能很有用,例如当需要基于对象引用而非对象内容来区分键时。

  6. ConcurrentHashMap:线程安全的Map实现,提供了比Hashtable更高的并发性能。它使用了分段锁的技术,允许并行的读写操作。

1.HashMap

主要特性
  1. 非线程安全HashMap不是线程安全的,这意味着在多线程环境中使用时,如果没有适当的同步控制,可能会遇到数据不一致的问题。如果需要线程安全的Map,可以考虑使用ConcurrentHashMap

  2. 动态扩容HashMap的容量初始默认为16,并且当键值对的数量超过容量乘以加载因子(默认为0.75)时,HashMap会自动扩容,通常是当前容量的两倍。

  3. 键值唯一性:在一个HashMap中,键是唯一的,而值可以重复。如果尝试插入一个已经存在的键,新的值会覆盖旧的值。

  4. 存储和检索性能:由于HashMap是基于哈希表实现的,所以它在平均情况下提供了非常快的性能,尤其是当没有哈希冲突时。然而,如果多个键的哈希值相同(哈希冲突),性能会下降到O(n),其中n是冲突的键值对的数量。

  5. 初始化容量和加载因子:在创建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

主要特性
  1. 排序TreeMap中的元素会根据键的自然排序(即键实现Comparable接口)或者通过构造函数传入的Comparator进行排序。这意味着当你遍历TreeMap时,元素总是按照排序的顺序出现。

  2. 线程不安全性TreeMap本身不是线程安全的。如果多个线程同时访问TreeMap,并且至少一个线程修改了TreeMap,则必须外部同步。否则,TreeMap的迭代器可能会抛出ConcurrentModificationException

  3. 性能TreeMap的添加、删除和查找操作的时间复杂度通常为O(log n),其中n是树中节点的数量。这比基于哈希表的Map实现慢,但提供了排序和有序操作的能力。

  4. 导航方法TreeMap实现了NavigableMap接口,提供了高级的导航方法,如firstEntry(), lastEntry(), lowerEntry(), higherEntry(), floorEntry(), 和 ceilingEntry(),这些方法允许你获取集合中的第一个、最后一个、小于或大于给定键的最近的键值对。

  5. 子映射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

主要特性
  1. 插入顺序或访问顺序LinkedHashMap有两种模式,一是保持元素的插入顺序,二是维护访问顺序。在访问顺序模式下,最近访问的元素会移动到链表的末尾,这使得LinkedHashMap非常适合实现LRU(Least Recently Used)缓存策略。

  2. 性能LinkedHashMap的性能与HashMap相似,对于添加、删除和查找操作的时间复杂度通常为O(1),但由于它维护了一个额外的双向链表,所以在大量迭代操作时可能会比HashMap稍微慢一些。

  3. 允许null键和null值LinkedHashMap允许一个null键和任意数量的null值,这一点与HashMap相同。

  4. 线程不安全性LinkedHashMap不是线程安全的,这意味着在多线程环境下使用时,需要进行适当的同步处理。

内部实现

LinkedHashMap的内部结构实际上是一个HashMap加上一个双向链表。双向链表中的每个节点都指向HashMap中的一个条目,这样就可以在保持HashMap高效查找的同时,维护元素的顺序信息。

4.HashTable

主要特性
  1. 线程安全性Hashtable的所有关键操作(如put, get, remove等)都是同步的,这确保了在多线程环境中的数据一致性。然而,这种同步也会带来性能上的开销,因为在高并发情况下,多个线程可能需要等待获得锁才能访问Hashtable

  2. 不允许null键或null值:与HashMap不同,Hashtable不允许任何键或值为null。如果尝试插入null键或null值,Hashtable将抛出NullPointerException

  3. 陈旧性Hashtable是在Java早期版本中设计的,自那以后,更现代、更高效的线程安全Map实现如ConcurrentHashMap已被引入,它们提供了更好的性能和更丰富的功能。

  4. 枚举支持Hashtable提供了一个elements()方法,返回一个Enumeration,这在Java早期版本中很常见,但在现代Java代码中,更倾向于使用Iterator或增强型for循环。

5.ConcurrentHashMap

主要特性
  1. 线程安全性ConcurrentHashMap是线程安全的,意味着多个线程可以同时读写ConcurrentHashMap而不会引发数据不一致问题。这是通过使用锁分段技术实现的,即ConcurrentHashMap将整个哈希表分为多个段(segment),每个段有自己的锁,这样多个线程可以同时在不同的段上进行操作,从而提高了并发性能。

  2. 非阻塞性ConcurrentHashMap的设计采用了非阻塞算法,这减少了线程之间的竞争,增加了整体的吞吐量。例如,读取操作通常不需要锁定,而写操作只锁定涉及到的段。

  3. 允许null值:与Hashtable不同,ConcurrentHashMap允许值为null,但键仍然不能为null

  4. 可配置的并发级别ConcurrentHashMap的构造函数允许指定并发级别的参数,这决定了内部分割的数量,从而影响到性能和内存消耗。较高的并发级别意味着更多的分割,可以提高并发性能,但也可能导致更多的内存消耗。

  5. 原子操作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));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值