JAVA中的集合-ArrayList-LinkedList-HashSet-TreeSet-HashMap

本文详细介绍了Java集合框架中的List、Set和Map接口的实现类,如ArrayList、LinkedList、HashSet、TreeSet和HashMap。讨论了它们的底层实现原理、常用方法、遍历方式、性能分析和线程安全性,以及在不同场景下的适用性比较。
摘要由CSDN通过智能技术生成

集合

为什么要有集合

# 集合在编程中的重要性

集合是编程中的重要组成部分,它们允许我们存储和操作相关数据的组。在Java中,集合是通过使用诸如List、Set和Map等接口来实现的。

使用集合的主要优点之一是,它们提供了一种比传统数组更灵活和高效的存储和访问数据的方式。例如,使用List接口,我们可以轻松地添加或删除列表中的元素,而不必担心调整数组的大小。

集合还提供了广泛的方法来操作和处理数据。例如,我们可以使用forEach()方法遍历集合中的所有元素,并对每个元素执行特定的操作。我们还可以使用filter()方法选择满足特定条件的元素子集。

此外,集合可以用于实现更复杂的数据结构,如树和图。这使我们能够解决计算机科学和工程中的各种问题,集合是任何程序员的重要工具,可以极大地简化存储和操作数据的过程。

常用的Java集合包括:

  1. List(列表):有序集合,可以包含重复元素。常见的实现类有ArrayList和LinkedList。
  2. Set(集合):无序集合,不允许包含重复元素。常见的实现类有HashSet和TreeSet。
  3. Map(映射):键值对的集合,每个键映射到一个值。常见的实现类有HashMap和TreeMap。
List

List是Java中的一个接口,它继承了Collection接口,是一个有序的集合,其中的元素可以重复。List中的元素可以通过下标进行访问,所以它也被称为序列(Sequence)。

List中常用的实现类包括ArrayList、LinkedList和Vector。ArrayList是基于数组实现的,LinkedList是基于链表实现的,Vector则是线程安全的动态数组。

List提供了一系列方法来操作集合中的元素,包括添加元素、删除元素、修改元素、查找元素等。同时,List还提供了一些方法来操作列表中的元素位置,如交换元素位置、排序等。由于List是有序的,它可以很好地支持元素的顺序访问和迭代。

Set

Set是Java中的一种集合类型,它用于存储一组不重复的元素。与List不同,Set中的元素没有顺序,也不允许有重复。Set接口的实现类有HashSet、TreeSet、LinkedHashSet等。

HashSet是基于哈希表实现的Set接口实现类。它利用哈希表实现,因此它的查找、插入和删除的时间复杂度都是O(1)。但是,由于哈希表的特殊性质,HashSet中的元素没有顺序。

TreeSet是基于红黑树实现的Set接口实现类。它是一个有序集合,它按照元素的自然顺序(例如数字按照从小到大的顺序)进行排序。因为它是基于红黑树实现的,所以它的查找、插入和删除的时间复杂度都是O(log n)。

LinkedHashSet是HashSet的子类,它基于哈希表实现,同时保留了元素插入的顺序。因此它既有哈希表的高效性能,又有元素有序的特性。

Set的常见操作有添加元素、删除元素、判断元素是否存在等。可以使用迭代器或者foreach语句对Set进行遍历。

需要注意的是,Set接口中的元素必须实现hashCode()和equals()方法,以便在集合中进行比较和查找。如果两个元素的hashCode()返回值相同,但equals()方法返回值不同,那么它们在集合中会被视为不同的元素。反之亦然。

Map

Map 是 Java 集合框架中的一种数据结构,用于存储键值对(key-value pair)。在 Map 中,每个键都是唯一的,可以用这个键来获取与之关联的值。常见的实现类有 HashMap、TreeMap、LinkedHashMap 等。

学习 Map,需要掌握以下几个方面:

  1. 基本概念:Map 中的键值对、键的唯一性、Map 的实现类、键和值的数据类型等。
  2. 常用方法:Map 中常用的方法有 put、get、containsKey、containsValue、size、isEmpty、remove 等。
  3. 遍历方式:Map 中遍历键值对的方式有 keySet、entrySet、values 三种方式,其中 keySet 和 values 返回的是 Set 集合,entrySet 返回的是包含 Map.Entry 元素的 Set 集合。
  4. 底层实现原理:不同的 Map 实现类底层采用的数据结构不同,例如 HashMap 底层采用的是哈希表,TreeMap 底层采用的是红黑树,LinkedHashMap 底层采用的是哈希表和双向链表组合。
  5. 线程安全:Map 中的实现类中有些是线程安全的,有些是线程不安全的,例如 Hashtable 是线程安全的,HashMap 是线程不安全的。
  6. 性能分析:Map 的性能与实现类的选择、键的哈希函数和负载因子等因素有关。在不同场景下,需要根据实际情况选择适合的 Map 实现类和调整相关参数以提高性能。
比较

在Java编程中,List、Set和Map都是常用的集合类型,每种集合类型都有自己的特点和应用场景。下面是它们的比较与应用:

  1. List vs Set
  • List是一个有序的集合,允许存储重复的元素,可以根据元素的索引进行访问和操作。
  • Set是一个无序的集合,不允许存储重复的元素,不能根据元素的索引进行访问和操作。
  • List适合需要有序访问、允许存储重复元素的场景,如列表、队列等。
  • Set适合需要保证元素唯一性、不需要按照特定顺序访问元素的场景,如集合、去重等。
  1. HashMap vs TreeMap
  • HashMap是一种基于哈希表实现的映射表,提供了O(1)时间复杂度的插入、删除、查找操作,不保证元素的顺序。
  • TreeMap是一种基于红黑树实现的映射表,提供了O(logN)时间复杂度的插入、删除、查找操作,保证元素按照Key的顺序排列。
  • HashMap适合需要高效的插入、删除、查找操作,不需要保证元素顺序的场景,如缓存、哈希表等。
  • TreeMap适合需要保证元素顺序、支持高效的查找操作的场景,如字典、排序等。
  1. HashMap vs Hashtable
  • HashMap和Hashtable都是基于哈希表实现的映射表,提供了O(1)时间复杂度的插入、删除、查找操作,不保证元素的顺序。
  • HashMap是非线程安全的,支持null作为Key和Value,允许多个线程并发访问。
  • Hashtable是线程安全的,不支持null作为Key和Value,需要使用synchronized关键字保证线程安全。
  • HashMap适合在单线程环境下使用,Hashtable适合在多线程环境下使用。

ArrayList

  1. 动态扩容:ArrayList可以根据需要动态扩容,当添加的元素数量超过了ArrayList当前容量时,它会自动增加容量,以容纳更多元素。
  2. 可变长度:ArrayList的长度是可变的,可以随时添加或删除元素。
  3. 支持泛型:ArrayList支持泛型,可以保证元素的类型安全。
  4. 快速访问:由于ArrayList底层是基于数组实现的,所以它可以快速地访问数组中的任何一个元素。
ArrayList的底层实现原理

ArrayList的底层实现是基于数组的,内部使用Object[]数组来存储元素。当数组存储不下元素时,需要进行扩容,扩容时一般会将容量增加原容量的一半。例如,原来容量为10,当需要添加第11个元素时,会将容量扩展为15。

在扩容时,ArrayList会创建一个新的数组,然后将原数组中的元素复制到新数组中,最后将新数组赋值给ArrayList内部的数组。这个过程会消耗一定的时间和空间,因此在使用ArrayList时,应该尽量减少扩容次数,以提高性能。

ArrayList的底层实现原理是数组,可以高效地随机访问元素,但是添加、删除操作效率较低,扩容时消耗时间和空间较大。因此,在使用ArrayList时需要根据具体的场景进行选择和优化。

ArrayList的常用方法
  1. add(E element):将指定的元素添加到ArrayList的末尾。
  2. add(int index, E element):将指定的元素添加到ArrayList的指定位置。
  3. remove(int index):移除指定位置的元素。
  4. remove(Object obj):移除第一个匹配给定元素的元素。
  5. set(int index, E element):替换指定位置的元素。返回值为被替换掉的元素。
  6. get(int index):返回指定位置的元素。
  7. size():返回ArrayList的元素个数。
  8. clear():移除ArrayList中的所有元素。
  9. indexOf(Object obj):返回第一个匹配给定元素的索引,若没有匹配元素则返回-1。
  10. lastIndexOf(); 返回最后一个匹配给定元素的索引,若没有匹配元素则返回-1。
  11. contains(Object obj):判断ArrayList中是否包含指定元素。
  12. toArray():将ArrayList转化为数组。
  13. isEmpty(); 判断是否为空
  14. addAll(Collection<? extends E> c):将另一个集合中的所有元素添加到ArrayList的末尾。
  15. containsAll(Collection<?> c):判断ArrayList是否包含另一个集合中的所有元素。
  16. removeAll(Collection<?> c):从ArrayList中移除另一个集合中的所有元素。
  17. retainAll(Collection<?> c):从ArrayList中仅保留另一个集合中的所有元素。
  18. subList(int startIndex,int endIndex):返回一个子列表,包含ArrayList中指定范围内的元素。[startIndex,endIndex)
ArrayList的遍历方式

1.for循环遍历,可以使用for循环结合ArrayList的size()和get()方法来遍历列表。

ArrayList<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");

for (int i = 0; i < list.size(); i++) {
   System.out.println(list.get(i));
}

2,迭代器遍历:使用迭代器可以在遍历时移除元素,避免使用for循环时修改列表造成的异常。

ArrayList<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
   String item = iterator.next();
   System.out.println(item);
}

3.for-each遍历:for-each循环可以直接遍历ArrayList中的元素,简化了代码。

ArrayList<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");

for (String item : list) {
   System.out.println(item);
}

4.Lambda表达式遍历(JDK 8及以上):使用Lambda表达式可以更简洁地遍历ArrayList。

javaCopy codeArrayList<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");

list.forEach(item -> System.out.println(item));
ArrayList的性能分析

ArrayList 是一个基于数组实现的动态数组,因此其具有以下特点:

  1. 随机访问元素的性能较好:由于 ArrayList 内部是一个数组,因此可以根据索引快速定位元素,访问单个元素的时间复杂度为 O(1)。
  2. 在末尾插入或删除元素的性能较好:由于 ArrayList 内部维护一个数组,因此在末尾插入或删除元素时只需要将元素插入或删除数组中的指定位置,时间复杂度为 O(1)。
  3. 在头部或中间插入或删除元素的性能较差:由于在头部或中间插入或删除元素时,需要将数组中的元素向后或向前移动,时间复杂度为 O(n)。当元素数量较大时,性能会受到较大影响。

因此,ArrayList 适合于随机访问元素、在末尾插入或删除元素的场景,不适合在头部或中间插入或删除元素的场景。此外,需要注意的是,由于 ArrayList 内部是一个数组,当元素数量超过数组容量时,需要进行扩容,扩容的时间复杂度为 O(n),因此需要在初始化 ArrayList 时设置一个较合理的容量,以避免不必要的扩容操作。

ArrayList的线程安全

ArrayList是非线程安全的。如果多个线程同时访问同一个ArrayList实例,可能会出现并发修改异常(ConcurrentModificationException)。因此,在多线程环境下,应该使用线程安全的集合类,如Vector、CopyOnWriteArrayList等,或者使用加锁机制保证线程安全。

LinkedList

  1. 链表的基本概念和特点:链表是一种数据结构,由一系列节点(Node)组成,每个节点包含一个数据元素和一个指向下一个节点的指针(或引用)。相对于ArrayList,LinkedList的主要特点是在任意位置的插入和删除操作都具有O(1)的时间复杂度。
  2. LinkedList的实现原理:LinkedList的内部实现采用了双向链表结构,即每个节点除了包含一个指向下一个节点的指针之外,还包含一个指向上一个节点的指针。这种双向链表结构使得LinkedList支持双向遍历,可以从任意一个节点开始,向前或向后遍历链表。
  3. LinkedList的常用方法:LinkedList的方法与ArrayList类似,但是由于其内部实现不同,因此一些操作的时间复杂度也不同。需要重点掌握的方法包括:add、get、remove、addFirst、addLast、removeFirst、removeLast等方法。
  4. LinkedList的性能分析:相对于ArrayList,LinkedList在某些操作上有优势,在某些操作上有劣势。需要分析其时间复杂度和空间复杂度,并结合具体场景选择合适的集合类。
  5. LinkedList与ArrayList的比较:需要对LinkedList和ArrayList进行比较分析,了解它们在不同场景下的优缺点,如何选择合适的集合类。
链表的基本概念和特点

链表是一种常见的数据结构,它由一个个节点组成,每个节点包含两个部分:数据和指向下一个节点的指针(也称为“引用”)。相邻两个节点之间通过指针来连接,从而形成一条链式结构。

链表的特点包括:

  1. 动态性:链表的节点可以动态地添加、删除,不需要预先确定链表的大小。
  2. 内存利用率高:由于链表节点是动态申请的,因此可以灵活利用内存空间。
  3. 随机访问性差:链表只能从头节点开始逐个遍历,不能直接访问某个位置的节点。

需要注意的是,链表与数组不同,数组是一种连续的数据结构,可以通过索引随机访问元素,而链表则是通过节点之间的指针连接起来的,因此不能随机访问节点。

LinkedList的实现原理

LinkedList 是基于双向链表实现的,每个节点包含三个部分:前一个节点的引用、当前节点的数据和后一个节点的引用。因此,每个节点都可以通过前驱节点的引用和后继节点的引用来找到它们。

在 LinkedList 中,双向链表的头结点和尾结点分别由 first 和 last 属性表示,它们都是 Node 类型的引用,Node 类型是 LinkedList 中的内部类,用于表示每个节点。其中,first 表示链表的头节点,last 表示链表的尾节点,它们都可以为空。

双向链表的节点包含了前驱节点、数据和后继节点,它由 Node 类型表示,代码如下:

kotlinCopy codeprivate static class Node<E> {
   E item;  // 存储数据
   Node<E> prev;  // 前驱节点的引用
   Node<E> next;  // 后继节点的引用

   Node(Node<E> prev, E element, Node<E> next) {
       this.item = element;
       this.prev = prev;
       this.next = next;
   }
}

在 LinkedList 中,除了维护链表的头结点和尾结点,还包含了一些其他的操作方法,比如 addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast() 等,它们的实现都是基于双向链表来实现的。

需要注意的是,LinkedList 不是线程安全的,如果多个线程同时对它进行操作,可能会出现线程安全问题。如果需要在多线程环境下使用 LinkedList,可以使用 Collections.synchronizedList() 方法或者使用 java.util.concurrent 包中提供的并发容器来实现。

LinkedList的实现原理

LinkedList的实现原理:LinkedList的内部实现采用了双向链表结构,即每个节点除了包含一个指向下一个节点的指针之外,还包含一个指向上一个节点的指针。这种双向链表结构使得LinkedList支持双向遍历,可以从任意一个节点开始,向前或向后遍历链表。

  • addFirst(E e):O(1)
  • addLast(E e):O(1)
  • add(int index, E element):最坏 O(n/2),平均 O(n),需要先找到插入位置
  • get(int index):最坏 O(n/2),平均 O(n),需要先找到元素所在位置
  • remove(int index):最坏 O(n/2),平均 O(n),需要先找到要删除的元素位置
  • clear():O(n)
  • size():O(1)

需要注意的是,LinkedList 在访问和操作特定索引位置的元素时,由于需要从头开始遍历到相应位置,时间复杂度最坏情况下可以达到 O(n/2),因此在需要大量访问和操作特定位置的场景中,ArrayList 可能更加适用。而在插入和删除元素的场景中,LinkedList 由于其内部实现机制的特殊性质,在某些情况下可能会比 ArrayList 更加高效。’

LinkedList的性能分析

LinkedList的性能分析主要从以下几个方面进行:

  1. 插入和删除操作的性能:

由于LinkedList是基于链表实现的,插入和删除操作在链表中只需要改变相邻节点的指针,不需要像数组那样移动大量元素。因此,在插入和删除操作方面,LinkedList的性能要优于ArrayList。

  1. 随机访问操作的性能:

由于LinkedList是基于链表实现的,因此它的随机访问性能要劣于ArrayList。因为在链表中,要想访问一个节点,必须从链表头开始遍历,直到找到需要的节点。而在数组中,直接根据下标可以立即访问到相应的元素。

  1. 内存占用情况:

LinkedList在每个节点中都要存储两个指针,因此它的内存占用情况要高于ArrayList。而且由于在Java中每个对象都有一个对象头,包含了一些元信息,因此在存储大量数据时,LinkedList的内存占用会更大。

综上所述,LinkedList在插入和删除操作方面的性能要优于ArrayList,在随机访问操作方面的性能要劣于ArrayList,内存占用情况要高于ArrayList。因此,在使用LinkedList时,应该根据具体需求选择合适的数据结构,以获得更好的性能。

LinkedList与ArrayList的比较

LinkedList和ArrayList都是Java中常用的集合类,它们都实现了List接口,但它们的内部实现机制有所不同。

内部实现机制

  • ArrayList底层使用数组实现,LinkedList底层使用双向链表实现;
  • ArrayList适合于随机访问和遍历,而LinkedList适合于频繁的插入和删除操作。

数据访问方式

  • ArrayList支持随机访问,可以通过下标快速访问指定位置的元素;
  • LinkedList不支持随机访问,只能顺序访问,需要遍历链表从头到尾找到指定位置的元素。

插入和删除操作

  • ArrayList插入和删除元素时,需要将插入位置之后的所有元素向后移动一位,删除元素时也需要将删除位置之后的所有元素向前移动一位,因此插入和删除操作时间复杂度为O(n);
  • LinkedList插入和删除元素时,只需要改变相邻两个节点之间的指针,因此插入和删除操作时间复杂度为O(1)。

空间占用

  • ArrayList内部实现是一个固定长度的数组,因此当集合中的元素数量超过数组长度时,需要创建一个新的数组并将原有元素复制到新数组中,这就会造成一定的内存浪费;
  • LinkedList则需要为每个元素额外记录前后指针,因此内存开销相对较大。

综上所述,如果需要频繁插入和删除操作,应该选择LinkedList;如果需要频繁随机访问和遍历操作,应该选择ArrayList。

HashSet

  1. HashSet 的基本概念:了解 HashSet 是什么、有哪些特点以及在什么情况下使用 HashSet。
  2. HashSet 的底层实现原理:了解 HashSet 是如何通过哈希表来实现的,如何解决哈希冲突等问题。
  3. HashSet 的构造方法和常用方法:了解如何创建 HashSet 对象以及常用的方法,如 add、remove、contains 等。
  4. HashSet 的遍历方式:了解如何遍历 HashSet 中的元素,包括 for-each 循环、迭代器等。
HashSet 的基本概念

HashSet 是 Java 中 Set 接口的一种具体实现,它采用哈希表的数据结构来实现。HashSet 中的元素是无序的、不可重复的,且支持 null 值。HashSet 通过 hashCode() 方法和 equals() 方法来判断元素是否相等。当两个元素的 hashCode() 值相等,且 equals() 方法也相等时,HashSet 就认为这两个元素相等,后添加的元素将不会被加入 HashSet 中。

HashSet 的特点:

  • 元素无序:HashSet 中的元素并没有按照添加的顺序来存放,因此无法保证元素的顺序性。
  • 元素不可重复:HashSet 中的元素是唯一的,如果向 HashSet 中添加一个已经存在的元素,那么该元素将不会被加入。
  • 支持 null 值:HashSet 中可以存储 null 值,但只能存储一个。
  • 高效的插入、删除和查询操作:HashSet 中插入、删除和查询操作的时间复杂度均为 O(1)。
HashSet 的构造方法和常用方法

HashSet是Java中的一个基本集合类,它实现了Set接口,提供了一种不允许有重复元素的集合存储结构。

HashSet的构造方法:

  1. HashSet():创建一个空的HashSet实例,该实例的默认初始容量为16,加载因子为0.75。
  2. HashSet(int initialCapacity):创建一个具有指定初始容量的空的HashSet实例。
  3. HashSet(int initialCapacity, float loadFactor):创建一个具有指定初始容量和加载因子的空的HashSet实例。
  4. HashSet(Collection<? extends E> c):创建一个包含指定集合中的元素的新HashSet实例。

HashSet常用方法:

  1. add(E e):向HashSet中添加元素e,如果添加成功返回true,否则返回false。
  2. remove(Object o):从HashSet中移除元素o,如果移除成功返回true,否则返回false。
  3. contains(Object o):如果HashSet包含元素o,则返回true,否则返回false。
  4. isEmpty():如果HashSet不包含任何元素,则返回true,否则返回false。
  5. size():返回HashSet中元素的数量。
  6. clear():从HashSet中移除所有元素。

HashSet的常用方法与其他集合类的方法类似,不再赘述。

除了基本的操作之外,HashSet还有一些其他的方法,例如:

  1. iterator():返回一个在HashSet中的元素上进行迭代的迭代器。
  2. addAll(Collection<? extends E> c):将指定集合中的所有元素添加到HashSet中。

需要注意的是,HashSet是无序的,它不保证元素的顺序与插入的顺序相同。此外,HashSet中不允许有重复元素,如果将重复元素添加到HashSet中,只会保留一个元素。

HashSet 的遍历方式

HashSet的遍历方式有以下几种:

使用迭代器(Iterator)进行遍历:通过调用HashSet的iterator()方法获取到迭代器对象,然后使用while循环和next()方法遍历HashSet中的元素。

示例代码:

vbnetCopy codeSet<String> hashSet = new HashSet<>();
// 添加元素
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("orange");

// 使用迭代器进行遍历
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
   String element = iterator.next();
   System.out.println(element);
}

使用foreach循环进行遍历:通过使用Java 5引入的foreach循环语法,直接遍历HashSet中的元素。

示例代码:

csharpCopy codeSet<String> hashSet = new HashSet<>();
// 添加元素
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("orange");

// 使用foreach循环进行遍历
for (String element : hashSet) {
   System.out.println(element);
}

使用Lambda表达式进行遍历:Java 8引入了Lambda表达式,可以使用Lambda表达式进行HashSet的遍历。

示例代码:

csharpCopy codeSet<String> hashSet = new HashSet<>();
// 添加元素
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("orange");

// 使用Lambda表达式进行遍历
hashSet.forEach(element -> {
   System.out.println(element);
});

注意:HashSet本身并不保证元素的顺序,因此遍历的结果是无序的。如果需要有序的遍历,可以使用LinkedHashSet。

TreeSet

  1. 基本概念:了解TreeSet是什么,以及它的特点和用途。
  2. 构造方法和常用方法:学习TreeSet的构造方法和常用方法,包括如何添加元素、删除元素、遍历集合等。
  3. 底层实现原理:了解TreeSet的底层实现原理,以及它与其他集合类的区别。
TreeSet的基本概念

TreeSet是Java集合框架中的一个有序集合,它继承了AbstractSet类并实现了NavigableSet接口。TreeSet中的元素是有序的,并且根据元素的自然顺序进行排序。也可以自定义排序方式来创建TreeSet。

与HashSet不同的是,TreeSet底层使用的是红黑树(一种自平衡的二叉搜索树)来存储元素。因此,TreeSet中的元素是有序的,可以在O(log n)时间内查找元素、插入元素、删除元素。同时,TreeSet还提供了许多操作红黑树的方法,比如获取第一个/最后一个元素、返回某个元素的前驱/后继元素等。

由于TreeSet中的元素是有序的,因此它对于要求有序元素的问题非常有用。同时,TreeSet还实现了SortedSet接口,提供了许多与排序相关的方法,比如subSet、headSet、tailSet等。这些方法可以帮助我们实现更加灵活的元素操作。

TreeSet的构造方法和常用方法

TreeSet 是基于 TreeMap 实现的,使用红黑树(一种自平衡二叉搜索树)来存储元素。TreeSet 中的元素必须是可比较的,即实现了 Comparable 接口或在构造 TreeSet 时提供了一个比较器(Comparator)。

TreeSet 的构造方法:

  • TreeSet(): 构造一个空 TreeSet,元素按照其自然排序(从小到大)进行排序。
  • TreeSet(Collection<? extends E> c): 构造一个包含给定集合中所有元素的 TreeSet,元素按照其自然排序进行排序。
  • TreeSet(Comparator<? super E> comparator): 构造一个空 TreeSet,使用指定比较器来对元素进行排序。
  • TreeSet(SortedSet<E> s): 构造一个包含给定有序集合中所有元素的 TreeSet,元素按照相同的顺序进行排序。

TreeSet 常用方法:

  • add(E e): 将指定元素添加到此 set(如果它尚未存在)。
  • remove(Object o): 将指定元素从 set 中删除,如果存在。
  • size(): 返回此 set 中的元素数量。
  • isEmpty(): 如果此 set 不包含任何元素,则返回 true。
  • contains(Object o): 如果此 set 包含指定元素,则返回 true。
  • clear(): 从此 set 中移除所有元素。
  • iterator(): 返回此 set 中元素的迭代器。元素按照其自然顺序遍历。

TreeSet 还提供了一些其他方法,如 first()last()headSet()tailSet()subSet() 等,用于获取指定子集的元素。

TreeSet的底层实现原理

TreeSet底层是通过红黑树实现的,红黑树是一种自平衡二叉查找树,它的每个节点上都有一个存储的值和一些其他信息,节点通过指针链接在一起,根据节点的值来比较大小,满足二叉查找树的性质:左子树所有节点的值小于父节点的值,右子树所有节点的值大于父节点的值。

为了满足红黑树的性质,每个节点还需要存储一个颜色,即红色或黑色。红黑树需要满足以下5个性质:

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL节点,空节点)是黑色的。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点。

在TreeSet中,元素是按照升序排序的,默认情况下使用元素的自然顺序进行排序,也可以通过实现Comparator接口来指定排序方式。可以通过调用TreeSet的构造方法来创建一个空的集合,也可以通过传递一个Collection对象来创建一个包含指定元素的集合。常用方法包括add()、remove()、contains()、size()等。TreeSet还提供了一些其他方法,例如first()、last()、ceiling()、floor()等,可以用于获取集合中的第一个元素、最后一个元素、大于等于给定值的最小元素、小于等于给定值的最大元素等。

HashMap

  1. 基本概念:了解HashMap是什么,以及它的基本特点和用途。
  2. 构造方法和常用方法:学习HashMap的构造方法和常用方法,如put()、get()、size()等等。
  3. 底层实现原理:掌握HashMap的底层实现原理,包括hash值的计算、数组和链表的结构、扩容机制等等。
  4. 线程安全性:了解HashMap的线程安全性问题,并学习如何在多线程环境下正确地使用HashMap。
  5. 性能分析:分析HashMap的性能表现,包括时间复杂度和空间复杂度等方面。
  6. 比较与应用:比较HashMap和其他类型的Map,如TreeMap、LinkedHashMap等,并学习在实际开发中如何选择合适的Map类型。
HashMap的基本概念

HashMap 是 Java 中的一个集合类,它实现了 Map 接口,提供了键值对映射关系的存储和查找功能。HashMap 的基本概念包括:

  1. 键值对:HashMap 存储的数据是由键值对构成的,一个键对应一个值,每个键值对之间是互不相同的。
  2. 散列函数:HashMap 使用散列函数将键值对映射到数组中的某个位置,通过散列函数,可以快速地查找键值对。
  3. 桶:散列函数将键值对映射到数组中的某个位置,这个位置称为桶,每个桶可以存放多个键值对。
  4. 碰撞:由于散列函数将多个键值对映射到同一个桶中,可能会出现键值对冲突的情况,这种情况称为碰撞。
  5. 负载因子:HashMap 中存储的键值对数量不能超过桶的数量和负载因子的乘积,如果超过了这个限制,就需要对 HashMap 进行扩容。
  6. 扩容:当 HashMap 中存储的键值对数量超过了桶的数量和负载因子的乘积,就需要对 HashMap 进行扩容,扩容会重新计算散列函数,重新映射键值对,以保证散列函数的性能。
HashMap的构造方法和常用方法

HashMap类的构造方法和常用方法如下:

构造方法

  • HashMap(): 构造一个空的HashMap对象。
  • HashMap(int initialCapacity): 构造一个具有指定初始容量的HashMap对象。
  • HashMap(int initialCapacity, float loadFactor): 构造一个具有指定初始容量和负载因子的HashMap对象。
  • HashMap(Map<? extends K, ? extends V> m): 构造一个HashMap对象,将指定Map对象中的所有映射复制到新的HashMap对象中。

常用方法

  • put(K key, V value): 将指定的键值对添加到HashMap中。
  • get(Object key): 返回与指定键相关联的值,如果没有则返回null。
  • remove(Object key): 删除HashMap中与指定键相关联的键值对。
  • containsKey(Object key): 如果此HashMap包含指定键的映射,则返回true。
  • containsValue(Object value): 如果此HashMap将一个或多个键映射到指定值,则返回true。
  • isEmpty(): 如果此HashMap不包含任何键值映射,则返回true。
  • size(): 返回此HashMap中的键值映射的数量。
  • clear(): 从此HashMap中删除所有映射。
  • keySet(): 返回此HashMap中包含的键的Set视图。
  • values(): 返回此HashMap中包含的值的Collection视图。
  • entrySet(): 返回此HashMap中包含的映射的Set视图。

以上方法只是HashMap类中的部分方法,还有其他一些方法,具体可以查看Java API文档。

HashMap的底层实现原理

HashMap底层是基于数组和链表(或红黑树)实现的。其基本原理是,通过将key的hashCode经过哈希函数计算后得到的值作为数组下标,将value存储在数组中。

当存储的元素越来越多,哈希冲突的概率也就越来越大,这时就需要解决哈希冲突的问题。HashMap通过链表来解决冲突,如果哈希值相同的元素已经存在,那么新的元素会被插入到链表的尾部。在Java8之后,当链表中的元素超过一定数量时,链表就会转换为红黑树,以保证查询的效率。

在HashMap中,当存储的元素数量超过了数组大小与负载因子的乘积时,数组会进行扩容操作,将数组大小扩大一倍,并将原来的元素重新计算哈希值,存储到新的数组中。扩容操作会耗费一定的时间,因此,应尽量避免在HashMap中存储大量元素。

另外,HashMap还有一个重要的参数是负载因子,表示哈希表中元素个数与数组大小的比率。负载因子过高会导致哈希冲突的概率增大,而过低则会浪费存储空间。

HashMap线程安全性

HashMap在多线程情况下不是线程安全的,因为它的底层实现是基于数组和链表的,对于多线程并发修改的情况下,可能会出现数据不一致的情况,例如链表成环、链表断裂等问题。如果需要在多线程环境中使用HashMap,可以使用ConcurrentHashMap或者使用synchronized或Lock等机制来保证线程安全。

HashMap的性能分析

HashMap 是一个基于哈希表实现的 Map 接口的数据结构,它提供了 O(1) 复杂度的插入、查找和删除操作。HashMap 的性能分析主要从以下几个方面来看:

  1. 哈希碰撞:当多个键值对映射到同一个哈希桶时,就会发生哈希碰撞。如果哈希碰撞过多,会导致哈希桶变得过于拥挤,查找、插入和删除操作的复杂度也会相应增加。为了解决哈希碰撞问题,HashMap 采用链表和红黑树结构来存储键值对,当链表长度过长时,会将链表转化为红黑树,从而提高操作效率。
  2. 初始容量和负载因子:HashMap 的构造方法中可以指定初始容量和负载因子。初始容量是哈希表的初始大小,负载因子是哈希表的最大填充因子。在哈希表中存储的键值对数量超过初始容量和负载因子的乘积时,哈希表就会进行扩容操作,将哈希表大小翻倍,并重新计算哈希桶的位置。
  3. 并发性:HashMap 是非线程安全的,多线程并发访问时需要考虑线程安全性。可以使用 ConcurrentHashMap 来代替 HashMap,在保证高性能的同时提供线程安全保障。
  4. 数据结构:HashMap 是基于数组和链表(或红黑树)实现的数据结构,查询操作的复杂度取决于哈希桶的数量和链表的长度。在哈希冲突较少的情况下,HashMap 的查询操作性能非常优秀,可以达到 O(1) 的复杂度。当哈希冲突较多时,HashMap 的性能会下降,但仍能保持 O(log n) 的复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值