文章目录
集合框架
Collection和Map两大体系介绍
Java集合可分为Collection和Map两大体系:
- Collection接口:用于存储一个个的数据,也被称为"单列数据集合"。
- List子接口:用来存储有序的、可重复的数据,主要用于替代数组,实现了动态数组的功能。
- 实现类:ArrayList(主要实现类)、LinkedList、Vector。
- Queue子接口:用于存储一组元素,并支持在集合的一端添加元素、在另一端移除元素。它通常按照特定的规则进行元素的添加和移除,例如先进先出(FIFO)或者优先级排序。
- 实现类包括LinkedList(实现了双端队列)
- PriorityQueue(实现了优先级队列)。
- Deque子接口:是Queue接口的子接口,表示"双端队列"(Double Ended Queue)。它支持在两端进行元素的插入、删除和检索操作。
- 实现类包括LinkedList(主要实现类)。
- Set子接口:用来存储无序的、不可重复的数据,类似于高中讲的"集合"。
- 实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet。
- EnumSet类:是Set接口的一个特殊实现类,用于存储枚举类型的元素。它基于位向量实现,具有高效的性能和内存利用率。
- SortedSet接口:是Set接口的子接口,用于存储有序的、不重复的元素。它按照元素的自然顺序或者自定义的比较器进行排序。实现类包括TreeSet(主要实现类)。
- NavigableSet接口:是SortedSet接口的子接口,提供了一系列用于导航和操作有序集合的方法。它支持根据元素查找、范围查找以及获取最小/最大元素等操作。实现类包括TreeSet(主要实现类)。
- List子接口:用来存储有序的、可重复的数据,主要用于替代数组,实现了动态数组的功能。
- Map接口:用于存储具有映射关系的"键-值"对集合,即一对一对的数据,也被称为"双列数据集合"。类似于高中的函数、映射关系(例如,(x1, y1),(x2, y2) —> y = f(x))。
- 实现类:HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties。
- ConcurrentHashMap类:是Map接口的一个实现类,它是线程安全的哈希表实现,支持高并发的读写操作。相比于HashMap,它在并发环境下提供了更好的性能和线程安全性。
- SortedMap接口:是Map接口的子接口,用于存储有序的键值对。它按照键的自然顺序或者自定义的比较器进行排序。实现类包括TreeMap(主要实现类)。
- NavigableMap接口:是SortedMap接口的子接口,提供了一系列用于导航和操作有序映射的方法。它支持根据键查找、范围查找以及获取最小/最大键等操作。实现类包括TreeMap(主要实现类)。
Iterator(迭代器)接口
- Iterator接口:用于遍历集合中的元素,并提供了一种统一的方式来访问集合中的元素,而不需要了解底层的数据结构。它定义了一些方法,如
hasNext()
、next()
和remove()
,用于遍历和操作集合中的元素。
方法:
boolean hasNext()
: 返回一个布尔值,表示集合中是否还有下一个元素可以遍历。如果有,则返回true;否则返回false。E next()
: 返回集合中的下一个元素,并将迭代器的指针向后移动。如果没有下一个元素可遍历,将抛出NoSuchElementException异常。void remove()
: 从集合中移除通过迭代器最后一次返回的元素(可选操作)。该方法只能调用一次,每次调用必须在调用next()之后进行。如果在调用next()之前或者已经调用了remove(),再次调用remove()将会抛出IllegalStateException异常。
注意事项:
- 迭代器在遍历过程中,可以使用remove()方法从集合中移除元素。但如果在调用next()之前或者连续两次调用remove(),将会抛出IllegalStateException异常。
- 如果在迭代器的遍历过程中,通过集合的其他方式(例如直接调用集合的remove()方法)修改了集合的结构,迭代器将变为不合法状态,继续使用迭代器的方法可能会导致未定义的行为。
- 迭代器是通过集合的iterator()方法获取的,例如
Iterator<E> iterator()
。每次调用iterator()方法都会返回一个新的迭代器实例,它的初始位置位于集合的第一个元素之前。 - 迭代器是设计用于单向遍历的,不支持逆向遍历。如果需要逆向遍历集合,可以考虑使用ListIterator接口。
Collection——List接口
特点:
- 有序性:List中的元素按照插入的顺序进行存储,并且可以根据索引访问和操作元素。这意味着元素在List中的位置是可以确定的。
- 可重复性:List允许存储重复的元素,即可以包含多个相同的元素。
- 索引访问:通过索引可以直接访问List中的元素。索引的范围是从0到size()-1,其中size()方法返回List中的元素个数。
- 可变性:List是可变的,可以根据需要插入、删除和修改元素。可以使用add()方法在指定位置插入元素,使用remove()方法删除指定位置的元素,使用set()方法修改指定位置的元素。
- 可以使用foreach循环进行遍历:List可以通过foreach循环遍历其中的元素,也可以使用迭代器(Iterator)进行遍历。
常用方法:
boolean add(E element)
: 将指定的元素添加到List的尾部。void add(int index, E element)
: 在指定的位置插入指定的元素。boolean remove(Object element)
: 从List中删除指定的元素,如果存在多个相同的元素,只删除第一个匹配的元素。E remove(int index)
: 删除指定位置的元素,并将其返回。E get(int index)
: 返回指定位置的元素。E set(int index, E element)
: 修改指定位置的元素,并返回被替换的元素。int indexOf(Object element)
: 返回指定元素在List中第一次出现的索引,如果不存在则返回-1。int size()
: 返回List中的元素个数。boolean isEmpty()
: 判断List是否为空,如果为空则返回true,否则返回false。
注意事项:
- List接口是一个接口,不能直接实例化,需要使用它的实现类如ArrayList、LinkedList等进行实例化。
- List接口中的索引从0开始,因此访问第一个元素的索引是0,访问最后一个元素的索引是size()-1。
- List接口允许存储null元素。
- List接口继承自Collection接口,因此也继承了Collection接口中的一些方法,如
boolean contains(Object element)
、void clear()
等。 - List接口可以通过迭代器(Iterator)进行遍历,也可以使用foreach循环遍历。
List接口的实现类
ArrayList类:
- 特点:基于动态数组实现,内部使用数组来存储元素,支持快速随机访问和修改,适用于随机访问和频繁修改元素的场景。
- 优点:读取、修改元素的时间复杂度为O(1),插入、删除元素的时间复杂度为O(n)。
- 缺点:在插入和删除元素时,需要移动其他元素的位置,性能较LinkedList差。
LinkedList类:
- 特点:基于双向链表实现,内部使用链表来存储元素,支持快速插入和删除操作,适用于频繁插入和删除元素的场景。
- 优点:插入、删除元素的时间复杂度为O(1),读取、修改元素的时间复杂度为O(n)。
- 缺点:随机访问元素的性能较ArrayList差。
Vector类:
- 特点:与ArrayList类似,基于动态数组实现,是ArrayList的线程安全版本,支持快速随机访问和修改。
- 优点:读取、修改元素的时间复杂度为O(1),插入、删除元素的时间复杂度为O(n)。
- 缺点:线程安全的开销较大,在单线程环境下性能较ArrayList差。
Stack类:
- 特点:继承自Vector类,表示堆栈(后进先出)数据结构,支持将元素压栈和弹栈操作。
Collection——Set接口
特点:
- 不允许重复元素:Set接口中不允许包含重复的元素。如果试图将重复元素添加到Set中,添加操作将被忽略,不会抛出异常。
- 无序性:Set接口不保证元素的顺序,即元素在Set中的存储顺序与添加顺序可能不同。具体的实现类可能会根据自身的规则对元素进行排序。
- 基于哈希表或树结构实现:Set接口的具体实现类可以基于哈希表(如HashSet、LinkedHashSet)或树结构(如TreeSet)来存储元素。
- 哈希表实现:
- HashSet类:基于哈希表实现,使用哈希函数来存储元素,具有较快的添加、删除和查找操作的性能。
- LinkedHashSet类:基于哈希表和链表实现,除了具有HashSet的特点外,还保持了元素的插入顺序。
- 树结构实现:
- TreeSet类:基于红黑树(自平衡二叉查找树)实现,元素按照特定的顺序进行存储,可以提供有序的集合视图。
- 元素的判定依据:Set接口使用元素的equals()和hashCode()方法来判断元素的重复性。因此,对于自定义对象,需要正确实现equals()和hashCode()方法以确保正确的去重和查找。
- 支持null元素:Set接口允许存储一个null元素(某些实现类除外),但只能存储一个null元素,不能存储多个null元素。
Set接口的实现类:
HashSet类
-
特点:
-
- HashSet基于哈希表实现,利用哈希函数存储元素,不保证元素的顺序,具有快速的添加、删除和查找操作性能。
- 插入、删除和查找元素的时间复杂度为O(1)。
- HashSet不保证元素的顺序,不适用于需要有序集合的场景。
-
HashSet的其他特点包括:
-
- 不能保证元素的排列顺序。
- HashSet不是线程安全的。
- 集合元素可以为null。
-
HashSet判断两个元素相等的标准是:
-
- 通过hashCode()方法获取的哈希值相等。
- 通过equals()方法比较返回true。
-
HashSet是Set接口的主要实现类,通常在使用Set集合时使用HashSet。
-
HashSet按照哈希算法存储集合中的元素,因此具有良好的存储、查找和删除性能。
-
HashSet集合中元素的无序性不等同于随机性。无序性与元素的添加位置有关,元素在数组中的存储位置由元素的hashCode()方法返回的哈希值决定,导致数组中的元素并非连续存放,呈现一定的无序性。
向HashSet集合中添加元素的过程包括:
- 当向HashSet集合存入元素时,HashSet会调用该对象的hashCode()方法获取哈希值,并根据哈希值通过散列函数确定该对象在底层数组中的存储位置。
- 如果存储位置上没有元素,则直接添加成功。
- 如果存储位置上已经有元素,则继续比较:
- 如果两个元素的hashCode值不相等,则添加成功。
- 如果两个元素的hashCode值相等,则继续调用equals()方法:
- 如果equals()方法返回false,则添加成功。
- 如果equals()方法返回true,则添加失败。
当程序运行时,同一个对象多次调用hashCode()方法应返回相同的值。当两个对象的equals()方法返回true时,它们的hashCode()方法的返回值也应相等。对象中用于equals()方法比较的字段应参与计算hashCode值。
在重写equals()方法时,通常需要同时重写hashCode()方法。推荐使用Eclipse/IDEA等开发工具提供的快捷键自动重写equals()和hashCode()方法。
为什么使用31作为hashCode计算中的系数?
选择较大的系数可以减少冲突,提高查找效率。31只占用5个bits,相乘造成数据溢出的概率较小。31可以表示为i*31=(i<<5)-1,这种表示在现代虚拟机中进行优化。此外,31是一个素数,因为用一个数字乘以素数,结果只能被素数本身、乘数和1整除,有助于减少冲突。
LinkedHashSet类:
-
特点:
- LinkedHashSet基于哈希表和链表实现,继承了HashSet的特点,并保持了元素的插入顺序。
- 元素的插入顺序在集合中得以保持,这是通过使用双向链表来维护元素次序实现的。
优点:
- 在LinkedHashSet中,插入、删除和查找元素的时间复杂度均为O(1)。这意味着这些操作具有很高的效率。
- LinkedHashSet不允许集合中存在重复的元素。每个元素在集合中都是唯一的。
-
LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用
双向链表
维护元素的次序,这使得元素看起来是以添加顺序
保存的。 -
LinkedHashSet
插入性能略低
于 HashSet,但在迭代访问
Set 里的全部元素时有很好的性能。
TreeSet类:
- 特点:
- 基于红黑树(自平衡二叉查找树)实现,元素按照特定的顺序进行存储,提供有序的集合视图。
- TreeSet是SortedSet接口的实现类,可以按照添加的元素的指定属性的大小顺序进行遍历。
- 优点:
- 元素按照自然排序或指定的比较器进行排序,支持快速的插入、删除和查找操作。
- 缺点:
- 插入、删除、查找元素的时间复杂度为O(log n),因为涉及红黑树的平衡调整操作。
- TreeSet底层使用红黑树结构存储数据。
- TreeSet不允许重复元素,并且实现了排序功能(自然排序或定制排序)。
- TreeSet提供了两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
- 自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
- 如果试图将一个对象添加到TreeSet时,该对象的类必须实现Comparable接口。
- 实现Comparable的类必须实现compareTo(Object obj)方法,通过该方法的返回值来比较对象的大小。
- 定制排序:如果元素所属的类没有实现Comparable接口,或者不希望按照升序(默认情况)的方式排列元素,或者希望按照其他属性的大小进行排序,则可以考虑使用定制排序。定制排序通过Comparator接口实现,需要重写compare(T o1, T o2)方法。
- 利用compare(T o1, T o2)方法比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
- 要实现定制排序,需要将实现Comparator接口的实例作为参数传递给TreeSet的构造器。
- 自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
- 向TreeSet中添加元素时,由于只有相同类的两个实例才会比较大小,因此应该添加同一个类的对象。
- 对于TreeSet集合而言,判断两个对象是否相等的唯一标准是通过compareTo(Object obj)方法或compare(Object o1, Object o2)方法的返回值来比较。如果返回值为0,则认为两个对象相等。
EnumSet类:
- 特点:专门用于存储枚举类型的集合,内部使用位向量实现,具有非常高的性能和内存效率。
- 优点:支持高效的按位操作,只能存储同一枚举类型的元素。
Map接口
Java中的Map接口表示一种键值对的映射关系
Map接口的特点:
- Map接口用于存储键值对,其中每个键都是唯一的。
- Map接口不允许键重复,但允许值重复。
- Map中的键值对没有固定的顺序,可以根据需要进行排序。
- Map接口允许使用null键和null值。
Map接口的主要方法:
Map信息查询方法:
int size()
: 返回Map中键值对的数量。boolean isEmpty()
: 判断Map是否为空。
Map元素判断方法:
boolean containsKey(Object key)
: 判断Map中是否包含指定的键。boolean containsValue(Object value)
: 判断Map中是否包含指定的值。
Map元素获取方法:
V get(Object key)
: 根据键获取对应的值,若键不存在则返回null。
Map元素添加和修改方法:
V put(K key, V value)
: 将指定的键值对添加到Map中,并返回之前与该键关联的值(如果存在)。
Map元素删除方法:
V remove(Object key)
: 根据键删除对应的键值对,并返回该键关联的值。
Map批量操作方法:
void putAll(Map<? extends K, ? extends V> m)
: 将指定的Map中的所有键值对添加到当前Map中。
Map清空操作方法:
void clear()
: 清空Map中的所有键值对。
Map元素遍历方法:
Set<K> keySet()
: 返回Map中所有键的集合。Collection<V> values()
: 返回Map中所有值的集合。Set<Map.Entry<K, V>> entrySet()
: 返回Map中所有键值对的集合。
Map接口的实现类
Map接口有多个实现类,其中常见的包括:
HashMap
:基于哈希表实现,不保证元素的顺序。LinkedHashMap
:基于哈希表和链表实现,保持元素的插入顺序。TreeMap
:基于红黑树实现,按照键的自然顺序或自定义比较器进行排序。Hashtable
:线程安全的实现类,与HashMap类似,但支持同步操作。ConcurrentHashMap
:线程安全的实现类,支持高并发环境下的操作。
在使用Map接口时,可以根据具体的需求选择合适的实现类。需要注意的是,对于无序性要求较高的情况,可以选择HashMap或LinkedHashMap;对于有序性要求较高的情况,可以选择TreeMap。在多线程环境下,需要考虑使用线程安全的实现类Hashtable或ConcurrentHashMap。
此外,Map接口的键对象需要正确实现equals()
和hashCode()
方法,以确保正确的键值对查找和存储操作。通常情况下,使用具有良好散列分布的键对象可以提高性能。
HashMap类
特点:
- HashMap类使用哈希表实现,通过键的哈希码来确定键值对的存储位置。
- HashMap类不保证元素的顺序,即元素的顺序可能不同于插入的顺序。
- HashMap允许使用null作为键和值。
- HashMap类不是线程安全的,如果在多线程环境下使用,需要进行额外的同步处理。
- HashMap
判断两个key相等的标准
是:两个 key 的hashCode值相等,通过 equals() 方法返回 true。 - HashMap
判断两个value相等的标准
是:两个 value 通过 equals() 方法返回 true。
性能:
- 在HashMap中,插入、删除和查找元素的时间复杂度通常为O(1),具有很高的效率。
- HashMap的性能与负载因子(load factor)有关。负载因子是指HashMap在自动扩容之前可以达到的填充比例。默认负载因子为0.75,可以在构造HashMap时指定。
- 当HashMap中的元素数量接近负载因子与容量的乘积时,将会自动扩容,以保持较低的填充比例,提高性能。
ConcurrentHashMap类
特点:
- ConcurrentHashMap类是线程安全的,可以在多线程环境下进行并发访问和修改。
- ConcurrentHashMap类采用分段锁(Segment)的机制来实现线程安全,不同的段可以被不同的线程同时访问,提高了并发性能。
- ConcurrentHashMap类不保证元素的顺序,即元素的顺序可能与插入的顺序不同。
性能:
- ConcurrentHashMap类在多线程环境下提供高效的并发操作。
- ConcurrentHashMap类将数据分割成多个段(Segment),每个段都有自己的锁,不同的线程可以同时访问不同的段,从而提高了并发性能。
- ConcurrentHashMap类的性能与并发级别有关。可以在构造ConcurrentHashMap时指定并发级别,默认为16。并发级别影响了ConcurrentHashMap内部的段(Segment)数量,可以根据并发访问的线程数量进行调整。
LinkedHashMap类
特点:
- LinkedHashMap类继承自HashMap类,具有HashMap的特性,如高效的查找和插入操作。
- LinkedHashMap类通过双向链表来维护元素的插入顺序,可以保持元素的迭代顺序与插入顺序一致。
- LinkedHashMap类不保证元素的顺序与键的自然顺序或自定义排序顺序一致。
迭代顺序:
- LinkedHashMap类可以按照插入顺序进行迭代,即迭代顺序与元素的插入顺序一致。
- 如果使用构造函数
LinkedHashMap(int initialCapacity)
创建LinkedHashMap,迭代顺序将与插入顺序一致。 - 如果使用构造函数
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
创建LinkedHashMap,并将accessOrder参数设置为true,迭代顺序将根据访问顺序进行调整,即最近访问的元素将排在最后。
TreeMap类
特点:
- TreeMap类是基于红黑树实现的,它可以保持键的有序性。
- TreeMap中的键必须实现Comparable接口或者在构造TreeMap时提供Comparator比较器来定义键的顺序。
- TreeMap类不允许使用null作为键,但允许使用null作为值。
性能:
- 在TreeMap中,插入、删除和查找元素的时间复杂度为O(logN),其中N为TreeMap中元素的数量。
- TreeMap根据键的顺序进行存储和遍历,所以适用于需要按照键的顺序进行操作的场景。
- 当需要对键进行排序或按范围进行检索时,TreeMap是一个很好的选择。
Hashtable类
特点:
- Hashtable**是线程安全的,即多线程环境下的操作是同步的。**可以通过在多个线程之间共享Hashtable对象来实现线程安全的操作。
- Hashtable不**允许使用null作为键或值。**如果尝试插入null键或值,会抛出NullPointerException异常。
- Hashtable使用哈希表来存储键值对,通过键的哈希值进行快速查找和插入操作。
性能:
- Hashtable中的插入、删除和查找操作的时间复杂度为O(1),在大多数情况下是常数时间。
- 由于Hashtable在多线程环境下是线程安全的,因此在并发访问场景下,性能可能受到一定的影响。如果不需要线程安全性,使用Hashtable可能会带来额外的开销。在单线程环境下,推荐使用HashMap来获得更好的性能。
Properties类
特点:
- Properties类用于处理属性文件,属性文件是一种简单的文本文件,包含以键值对形式表示的配置信息。
- Properties类继承自Hashtable类,因此具有Hashtable的特性,如使用哈希表存储键值对、支持快速查找和插入操作。
- Properties类对外提供了一些特殊的方法,以方便加载和存储属性文件的操作。
作用:
可以通过getProperty方法获取特定键对应的值。同时,Properties类也支持将属性数据以XML格式存储和加载,以满足不同的需求。
需要注意的是,Properties类是线程安全的,可以在多线程环境下使用。但在多线程环境下,如果同时对属性文件进行读写操作,可能需要额外的同步措施来确保数据的一致性。
Collection的工具类Collections类
Collections类:
- Collections类是Java集合框架提供的一个实用类,包含了一系列静态方法,用于操作和处理集合对象。
- Collections类提供了各种算法和方法,用于对集合进行排序、查找、反转、替换等操作。
- Collections类中的方法都是静态方法,可以直接通过类名调用,而不需要创建实例。
- Collections类还提供了一些方法来创建线程安全的集合,如
synchronizedCollection()
、synchronizedList()
等。
总结:
Collection
是一个接口,定义了集合类的通用操作和行为。Collections
是一个实用类,提供了各种静态方法,用于操作和处理集合对象。Collection
接口和Collections
类是Java集合框架中的重要组成部分,用于处理和操作集合数据。