Java集合类是一种特别有用的工具类,可用于存储数量不等的对象,并可用于实现常用的数据结构,如栈、队列等。Java集合大致可分为Set、List、Queue、Map四种体系,Set代表无序、不可重复的集合;list代表有序、重复的集合;Map代表具有映射关系的集合;Queue代表一种队列集合实现。
一、Java集合概述
为了保存数量不确定的数据,以及保存具有映射关系的数据,Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。Java集合就像一种容器,可以把多个对象丢进该容器中,从Java5增加了泛型以后,Java集合能记住容器中对象中的数据类型,使得编码更加简单、健壮。所有的集合类都位于java.util包下,后来为了处理多线程环境下并发安全问题,Java5提供了一些多线程支持的集合类。集合只能保存对象(实际上是对象的引用变量)。
Java的集合类主要由两个接口派生而出:Collection和Map,collection和map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类;set和list接口是collection接口派生的两个子接口,它们分别代表了无序集合和有序集合;queue是Java提供的队列实现,类似于list。其中,ArrayList、HashSet、LinkedList和TreeSet是经常使用到的集合类。
所有的map实现类用于保存具有映射关系的数据,map保存的每项数据都是key-value对,也就是由key和value两个值组成,map里的key是不可重复的,key用于标识集合里的每项数据。其中,HashMap和TreeMap是经常使用到的集合类。
可以把Java集合分为三大类,其中set集合类似于一个罐子,把一个对象添加到set集合时,set集合无法记住添加这个元素的顺序,所以set里的元素不能重复;list集合非常像一个数组,它可以记住每次添加的顺序、且list的长度可变;map集合也像一个罐子,只是它里面的每项数据都由两个值组成(键值对)。如果访问list集合中的元素,可以直接根据元素的索引来访问;如果访问map集合中的元素,可以根据每项元素的key来访问其value;如果访问set集合中的元素,只能根据元素本身访问。
collection接口定义方法
collection接口是list、set、queue的父接口,所以该接口里面定义的方法可用于操作这三个集合类。
容器的功能主要为添加对象、删除对象、清空容器、判断容器是否为空,返回collection集合的个数等。
遍历集合方式
- 使用lambda表达式遍历集合
- iterator接口被称作迭代器,它是collection接口的父接口,所以可使用iterator遍历集合元素,接口中主要定义了两个方法,hasNext()如果有元素可迭代返回true和next()返回迭代的下一个元素
- 使用foreach遍历集合元素
- 使用stream操作集合
public class Main { public static void main(String[] args) { Collection col = new ArrayList<>(); col.add("世"); col.add("界"); col.add("你"); col.add("好"); //使用lamdba遍历集合 col.forEach(obj-> System.out.print(obj)); Iterator it = col.iterator(); //使用iterator遍历集合,遍历的不是集合元素本身,系统只是把集合元素的值赋值给迭代变量,在循环中修改迭代变量的值没有实际意义 while(it.hasNext()){ String str = (String)it.next(); System.out.print(str); } //使用foreach遍历集合元素,遍历的不是集合元素本身,系统只是把集合元素的值赋值给迭代变量,在循环中修改迭代变量的值没有实际意义 for(Object obj:col){ System.out.print(obj); } //使用stream流遍历集合 ArrayList<String> list = new ArrayList<>(); list.add("世"); list.add("界"); list.add("你"); list.add("好"); list.stream().forEach(System.out::print); } }
注意:当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量(就如同参数传递是值传递,基本数据类型传递的是值,引用类型传递的仅仅是对象的引用变量),所以修改迭代变量的值对集合元素本身没有任何影响。
二、List
List作为Collection接口的子接口,可以使用Collection接口里的全部方法。而且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合元素的方法。
接口中定义的方法:
- void add(int index, Object element)::在列表的指定位置插入指定元素(可选操作)。
- addAll(Collection<? extends E> c) :追加指定集合的所有元素到这个列表的末尾,按他们的指定集合的迭代器返回(可选操作)。
- Object get(index):返回列表中指定位置的元素。
- int indexOf(Object o): 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
- int lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
- remove(int index):移除此列表中指定位置的元素(可选操作)。
- Object set(int index, Object element):用指定元素替换列表中指定位置的元素。
- subList(int fromIndex, int toIndex) :返回一个视图之间的指定 fromIndex,包容,和 toIndex这份名单的部分,独家。
- Object[] toArray(): 返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。
- void replaceAll(UnaryOperator operator):根据operator指定的计算规则重新设置List集合的所有元素。
- void sort(Comparator c):根据Comparator参数对List集合的元素排序。
Arraylist
ArrayList和Vector作为List类的两个典型实现,完全支持之前介绍的List接口的全部功能。ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initalCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超过了该数组的长度时,它们的initalCapacity会自动增加。
ArrayList底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素 ;当使用List<String> list=new ArrayList<>(3)方式创建ArrayList集合时
//动态Object数组,用来保存加入到ArrayList的元素 transient Object[] elementData; /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
当使用List<String> list = new ArrayList<>()方式创建ArrayList,不指定集合的大小
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
在这里可以看出,private static final int DEFAULT_CAPACITY = 10;默认容量为10,当向数组添加元素List.add("1")时;先调用add(E e)方法
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // 数组的长度加1 elementData[size++] = e; return true; }
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
可以看到,ArrayList是一个动态扩容的数组,vector也是这样的,如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initalCapacity的大小,这样可以提高性能。Arraylist和Vector的初始容量都是10,ArrayList的扩容增量方式为原容量的1.5倍+1,比如容量为10,那么第一次扩容后容量是16;Vector的扩容增量方式为原容量的一倍,比如Vector的原容量为10,第一次扩容增量为20。
ArrayList和Vector的区别
1.ArrayList是线程不安全的,Vector是线程安全的。
2.Vector的性能比ArrayList差。
ArrayList三种遍历方式
public class List { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); //通过迭代器遍历 Iterator<Integer> iterator = list.iterator(); if(iterator.hasNext()){ iterator.next(); } //随机访问,通过索引去遍历 int size = list.size(); for(int i=0;i<size;i++){ System.out.println(list.get(i)); } //for遍历循环 for(Integer l:list){ System.out.println(l); } //TODO遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低。 } }
LinkedList
LinkedList的实现机制与ArrayList完全不同。ArrayList内部是以数组的形式来保存集合中的元素的,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色。底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素 。
由于LinkedList双端队列的特性,所以新增了一些方法:
- void addFirst(E e):将指定元素插入此列表的开头。
- void addLast(E e): 将指定元素添加到此列表的结尾。
- E getFirst(E e): 返回此列表的第一个元素。
- E getLast(E e): 返回此列表的最后一个元素。
- boolean offerFirst(E e): 在此列表的开头插入指定的元素。
- boolean offerLast(E e): 在此列表末尾插入指定的元素。
- E peekFirst(E e): 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
- E peekLast(E e): 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
- E pollFirst(E e): 获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
- E pollLast(E e): 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
- E removeFirst(E e): 移除并返回此列表的第一个元素。
- boolean removeFirstOccurrence(Objcet o): 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
- E removeLast(E e): 移除并返回此列表的最后一个元素。
- boolean removeLastOccurrence(Objcet o): 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
LinkedList的本质
/** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last; /** * Constructs an empty list. */ public LinkedList() { }
private static class Node<E> { //表示集合元素的值 E item; //指向下个元素 Node<E> next; //指向上个元素 Node<E> prev; ...................................省略 }
由此可以具体了解链表是如何串联起来并且每个节点包含了传入集合的元素。
LinkedList新增元素的操作,向表尾节点加入新的元素
public boolean add(E e) { linkLast(e); return true; }
如何将“双向链表和索引值联系起来的”?
public E get(int index) { checkElementIndex(index);//检查索引是否有效 return node(index).item; } Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
到此我们便会明白,LinkedList在插入、删除元素时性能比较出色,随机访问集合元素时性能较差。
LinkedList遍历方式
LinkedList支持多种遍历方式。1.通过迭代器遍历LinkedList
2通过快速随机访问遍历LinkedList
3.通过for循环遍历LinkedList
4.通过pollFirst()遍历LinkedList
5.通过pollLast()遍历LinkedList
6通过removeFirst()遍历LinkedList
7.通过removeLast()遍历LinkedList
实现都比较简单,就不贴代码了。
其中采用逐个遍历的方式,效率比较高。采用随机访问的方式去遍历LinkedList的方式效率最低。LinkedList也是非线程安全的。
ArrayList与LinkedList性能对比
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。LinkedList应使用采用逐个遍历的方式遍历集合元素。
如果涉及到“动态数组”、“栈”、“队列”、“链表”等结构,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍。
1、 对于需要快速插入,删除元素,应该使用LinkedList。
2、 对于需要快速随机访问元素,应该使用ArrayList。
3、 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。
三、Set
HashSet
- 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
- HashSet不是同步的,如果多个线程同时访问一个HashSet,则必须通过代码来保证其同步。
- 集合元素值可以是null。
当向HashSet集合中添加一个元素时,HashSet会调用该对象的hashCode()方法获取该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
equals()
- 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象,即两个对象的内存地址是否相同。
- 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle.
hashCode()
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建某个“类"的散列表时,该类的hashCode() 才有用。更通俗地说就是创建包含该类的HashMap,Hashtable,HashSet集合时,hashCode() 才有用。因为HashMap,Hashtable,HashSet就是散列表集合。
在散列表中,hashCode()作用是:确定该类的每一个对象在散列表中的位置;其它情况下类的hashCode() 没有作用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
- 若某个类没有覆盖hashCode()方法,当它的通过hashCode()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象,即两个对象的内存地址是否相同。
- 我们可以覆盖类的hashCode()方法,来让hashCode()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则hashCode()方法返回true;否则,返回fasle。
HashSet中判断集合元素相等
- 如果有两个元素通过equal()方法比较返回false,但它们的hashCode()方法返回不相等,HashSet将会把它们存储在不同的位置。
- .如果有两个元素通过equal()方法比较返回true,但它们的hashCode()方法返回不相等,HashSet将会把它们存储在不同的位置。
-
如果两个对象通过equals()方法比较不相等,hashCode()方法比较相等,HashSet将会把它们存储在相同的位置,在这个位置以链表式结构来保存多个对象。这是因为当向HashSet集合中存入一个元素时,HashSet会调用对象的hashCode()方法来得到对象的hashCode值,然后根据该hashCode值来决定该对象存储在HashSet中存储位置。
- 如果有两个元素通过equal()方法比较返回true,但它们的hashCode()方法返回true,HashSet将不予添加。
public class Set { public static void main(String[] args) { HashSet<R> set = new HashSet<R>(); set.add(new R(5)); set.add(new R(3)); set.add(new R(2)); set.add(new R(1)); System.out.println(set); Iterator<R> iterator = set.iterator(); R next = iterator.next(); next.count = 3; System.out.println(set); set.remove(new R(3)); System.out.println(set); System.out.println(set.contains(new R(3))); System.out.println(set.contains(new R(1))); } } class R{ int count; public R(int count){ this.count = count; } @Override public String toString() { return "R{" + "count=" + count + '}'; } @Override public boolean equals(Object o) { if(this==o){ return true; } if(o!=null&&o.getClass() == R.class){ R r = (R)o; return this.count ==r.count; } return false; } @Override public int hashCode() { return this.count; } }
当程序把可变对象添加到HashSet中之后,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确访问该对象。HashSet底层是一个HashMap(保存数据),实现set接口,默认初始容量为16,加载因子为0.75:当元素个数超过容量的0.75倍时,进行扩容,扩容增量为原容量的1倍,即当容量为16时,第一次扩容为32。
LinkedHashSet
TreeSet
TreeSet是SortedSet接口的实现类,正如SortedSet名字所暗示的,TreeSet可以确保集合元素处于排序状态。TreeSet集合里面的元素必须存放同一种类型的数据,否则是会报错的。存放对象类型的时候,最好是重写COMPARATOR()方法此外,TreeSet还提供了几个额外的方法。
- comparator():返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回null。
- first():返回此 set 中当前第一个(最低)元素。
- last(): 返回此 set 中当前最后一个(最高)元素。
- lower(E e):返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
- higher(E e):返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
- subSet(E fromElement, E toElement):返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
- headSet(E toElement):返回此 set 的部分视图,其元素小于toElement。
- tailSet(E fromElement):返回此 set 的部分视图,其元素大于等于 fromElement。
TreeSet的排序方式
TreeSet中所谓的有序,不同于之前所讲的插入顺序,而是通过集合中元素属性进行排序方式来实现的。
TreeSet支持两种排序方法:自然排序和定制排序。在默认情况下,TreeSet采用自然排序。
1、自然排序
TreeSet中判断集合元素相等
TreeSet是根据红黑树结构找到集合元素的存储位置。
2、定制排序
如果要实现定制排序,则需要在创建TreeSet时,调用一个带参构造器,传入Comparator对象。并有该Comparator对象负责集合元素的排序逻辑,集合元素可以不必实现Comparable接口。
public class Set { public static void main(String[] args) { //倒叙 Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if (o1 > o2) { return -1; } else if (o1 < o2) { return 1; } else { return 0; } } }; TreeSet<Integer> set = new TreeSet<>(comparator); set.add(1); set.add(2); set.add(3); set.add(4); System.out.println(set); } }
总结:无论使用自然排序还是定制排序,都可以通过自定义比较逻辑实现各种各样的排序方式。
注意:如果向TreeSet中添加了一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整它们。总之,推荐不要修改放入TreeSet集合中元素的关键实例变量。
TreeSet也是非线程安全的。
HashSet、TreeSet的性能对比
EnumSet内部以位向量的形式存储,结构紧凑、高效,且只存储枚举类的枚举值,所以最高效。HashSet以hash算法进行位置存储,特别适合用于添加、查询操作。LinkedHashSet由于要维护链表,性能比HashSet差点,但是有了链表,LinkedHashSet更适合于插入、删除以及遍历操作。而TreeSet需要额外的红黑树算法来维护集合的次序,性能最次。
但是具体使用要考虑具体的使用场景:
当需要一个特定排序的集合时,使用TreeSet集合。
当需要保存枚举类的枚举值时,使用EnumSet集合。
当经常使用添加、查询操作时,使用HashSet。
当经常插入排序或使用删除、插入及遍历操作时,使用LinkedHashSet。
四、Map
HashMap与Hashtable的区别
- Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能好一些;但如果有多个线程访问同一个Map对象时,是盗用Hashtable实现类会更好。
-
Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常;但是HashMap可以使用null作为key或value。
public class Map { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<String,Integer>(); map.put("Monday",1); map.put("Tuesday",2); map.put("Wednesday ",3); map.put("Thursday ",4); //判断是否包含指定的key System.out.println(map.containsKey("Monday")); //判断是否包含指定的value System.out.println(map.containsValue(2)); //循环遍历 Set<String> set = map.keySet(); for(String s:set){ System.out.println(s+":"+map.get(s)); } map.remove("Monday"); System.out.println(map); } }
HashMap
HashMap判断key与value相等的标准
key判断相等的标准
类似于HashSet,HashMap与Hashtable判断两个key相等的标准是:两个key通过equals()方法比较返回true,两个key的hashCode值也相等,则认为两个key是相等的。
value判断相等的标准
HashMap与Hashtable判断两个value相等的标准是:只要两个对象通过equals()方法比较返回true即可。
注意:HashMap中key所组成的集合元素不能重复,value所组成的集合元素可以重复。
// 默认构造函数。 HashMap() // 指定“容量大小”的构造函数 HashMap(int capacity) // 指定“容量大小”和“加载因子”的构造函数 HashMap(int capacity, float loadFactor) // 包含“子Map”的构造函数 HashMap(Map<? extends K, ? extends V> map)
容量(capacity)是哈希表的容量,初始容量是哈希表在创建时的容量(即
DEFAULT_INITIAL_CAPACITY = 1 << 4
)。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75(即
DEFAULT_LOAD_FACTOR = 0.75f
), 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 resize操作次数。如果容量大于最大条目数除以加载因子,则不会发生 rehash 操作。HashMap遍历方式
- 遍历HashMap的键值对:第一步:根据entrySet()获取HashMap的“键值对”的Set集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合。
- 遍历HashMap的键:第一步:根据keySet()获取HashMap的“键”的Set集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合。
- 遍历HashMap的值:第一步:根据value()获取HashMap的“值”的集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合。
HashMap的初始容量为16,加载因子为0.75,当元素个数超过初始容量的0.75倍时,进行扩容,扩容为原容量的一倍,即当容量为16时,扩容为32。
LindkedHashMap实现类
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但是因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时有较好的性能。迭代输出LinkedHashMap的元素时,将会按照添加key-value对的顺序输出。
本质上来讲,LinkedHashMap=散列表+循环双向链表
HashTable
是一个古老的Map实现类
TreeMap
TreeMap是SortedMap接口的实现类。TreeMap 是一个有序的key-value集合,它是通过红黑树实现的,每个key-value对即作为红黑树的一个节点。
TreeMap排序方式
TreeMap有两种排序方式,和TreeSet一样。
自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException异常。
定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。
TreeMap中判断两个元素key、value相等的标准
类似于TreeSet中判断两个元素相等的标准,TreeMap中判断两个key相等的标准是:两个key通过compareTo()方法返回0,TreeMap即认为这两个key是相等的。
TreeMap中判断两个value相等的标准是:两个value通过equals()方法比较返回true。
注意:如果使用自定义类作为TreeMap的key,且想让TreeMap良好地工作,则重写该类的equals()方法和compareTo()方法时应保持一致的返回结果:两个key通过equals()方法比较返回true时,它们通过compareTo()方法比较应该返回0。如果两个方法的返回结果不一致,TreeMap与Map接口的规则就会冲突。
除此之外,与TreeSet类似,TreeMap根据排序特性,也添加了一部分新的方法,与TreeSet中的一致。
TreeMap的本质
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
注意:
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
可以说TreeMap的增删改查等操作都是在一颗红黑树的基础上进行操作的。
TreeMap遍历方式
- 遍历TreeMap的键值对:第一步:根据entrySet()获取TreeMap的“键值对”的Set集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合
- 遍历TreeMap的键:第一步:根据keySet()获取TreeMap的“键”的Set集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合。
- 遍历TreeMap的值:第一步:根据value()获取TreeMap的“值”的集合。第二步:通过Iterator迭代器遍历“第一步”得到的集合。
Map实现类的性能分析及适用场景
LinkedHashMap比HashMap慢一点,因为它需要维护一个双向链表。
TreeMap比HashMap与Hashtable慢(尤其在插入、删除key-value时更慢),因为TreeMap底层采用红黑树来管理键值对。
适用场景:
一般的应用场景,尽可能多考虑使用HashMap,因为其为快速查询设计的。
如果需要特定的排序时,考虑使用TreeMap。
如果仅仅需要插入的顺序时,考虑使用LinkedHashMap。
五、Queue
接口中定义的方法
六、操作集合的工具类Collections
Java提供了一个操作set、list和map等集合的工具类:Collections,该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
排序操作
- static void reverse(List<?> list) :反转指定列表中元素的顺序。
- static void shuffle(List<?> list) :随机置换指定列表使用随机默认源。
- void sort(List<T> list) :指定列表为升序排序,根据其元素的 natural ordering。
- static <T> void sort(List<T> list, Comparator<? super T> c) :根据指定的比较器指定的顺序对指定的列表进行排序。
-
static void swap(List<?> list, int i, int j) :在指定的列表中的指定位置上交换元素。
- static void rotate(List<?> list, int distance) :按指定的距离旋转指定列表中的元素。
public class List { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(-5); list.add(3); list.add(0); System.out.println(list);//[2, -5, 3, 0] //将list集合的顺序反转 Collections.reverse(list); System.out.println(list);//[0, 3, -5, 2] //将list按升序排序 Collections.sort(list); System.out.println(list);//[-5, 0, 2, 3] //将list集合元素随机排序 Collections.shuffle(list); System.out.println(list);//[0, 2, 3, -5] } }
查找、替换操作
- static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) :使用二进制搜索算法搜索指定对象的指定列表。
- T max(Collection<? extends T> coll) :返回最大元素的集合,根据其元素的自然排序。
- static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) :返回给定集合的最大元素,根据指定的比较器诱导的顺序。
- T min(Collection<? extends T> coll) :返回最小的元素的集合,根据其元素的自然排序。
- static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) :返回给定集合的最小元素,根据指定的比较器诱导的顺序。
- static <T> void fill(List<? super T> list, T obj) :用指定元素替换指定列表的所有元素。
- static int frequency(Collection<?> c, Object o) :返回指定集合中等于指定对象的元素的数目。
- static int indexOfSubList(List<?> source, List<?> target) :返回指定的源列表中指定的目标列表的第一个发生的起始位置,或-如果没有这样的发生,则- 1。
- static int lastIndexOfSubList(List<?> source, List<?> target) :返回指定的源列表中指定的目标列表的最后一个发生的起始位置,或-如果没有这样的发生,则- 1。
- static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) :将列表中的某一特定值的所有出现替换为另一个。 、
public class List { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(-5); list.add(3); list.add(0); System.out.println(list);//[2, -5, 3, 0] System.out.println(Collections.max(list));//3 System.out.println(Collections.min(list));//-2 System.out.println(Collections.replaceAll(list,0,1));//true System.out.println(list);//[2, -5, 3, 1] System.out.println(Collections.frequency(list,-5));//1 Collections.sort(list); System.out.println(Collections.binarySearch(list,3));//3 } }
同步控制
Collections类中提供了多个synchronizedXxx()方法,该方法可以指定将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayQeque、LinkedList、HashMap和TreeMap都是线程不安全的。如果有多个线程访问他们,有超过一个线程试图修改他们,就会引发线程安全问题。