Java 集合框架提供了一套性能优良,使用方便的接口和类,其位于 java.util 包中, 所以当使用集合框架的时候需要进行导包。
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合;另一种是图(Map),存储键/值对映射。
说明:
(1)Collection 接口有两个重要的子接口 List、 Set , 他们的实现子类都是单列集合。
(2)Map 接口的实现子类是双列集合,存放的 K-V(键值对)
如下表:
接口 | 描述 |
---|---|
Collection 接口 | Collection 是最基本的集合接口,一个 Collection 代表一组 Object(即 Collection 的元素), Java不提供直接继承自 Collection的类,只提供继承自 Collection 接口的子接口(如 List和 set)。Collection 接口存储一组不唯一,无序的对象(不能通过索引来访问 Collection 集合中的对象)。 |
List 接口 | List 接口继承自 Collection 接口 ,但 List 接口 是一个有序的集合,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(即元素在 List 中的位置,类似于数组的下标)来访问 List 集合中的元素,第一个元素的索引为 0。而且 List 集合中允许有相同的元素。可以说,List 接口的集合存储一组不唯一,有序(插入顺序)的对象。 |
Set 接口 | Set 接口继承自 Collection 接口,具有与 Collection 完全一样的接口,只是方法上有部分不同,和 Collection 接口 相同,Set 接口存储一组唯一,无序的对象。 |
Map 接口 | Map 接口与 Collection 接口同级(彼此没有继承关系),Map 图存储一组 键-值 对象,提供key(键)到value(值)的映射。 |
Set 和 List 接口的区别:
(1)Set 接口集合存储的是无序的,不重复的数据。List 接口集合存储的是有序的,可以重复的元素。
(2)Set 集合 底层使用的是 链表数据结构,其检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 (实现子类有 HashSet , TreeSet 等)。
(3)List 结合 底层和数组类似,但是它可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。其检索元素效率高,插入和删除效率低,插入和删除会引起其他元素位置改变 (实现子类有 ArrayList , LinkedList , Vector 等)。
Java 提供了一套实现了 Collection 接口的标准集合类。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。
如下表:
类名 | 描述 |
---|---|
ArrayList 类 | 该类实现了 List 接口,允许存储 null(空值)元素,且可存储重复元素。该类实现了可变大小的数组,随机访问和遍历元素时,提供了更好的性能。该类是非同步的, 在多线程的情况下不要使用。ArrayList 类在扩容时会扩容当前容量的1.5倍。 |
Vector 类 | 该类和 ArrayList 类非常相似,但该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍。 |
LinkedList 类 | 该类实现了 List 接口,允许存储 null(空值)元素,且可存储重复元素,主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个 LinkedList,则必须自己实现访问同步,解决方法就是在创建 LinkedList 类 时候再构造一个同步的 LinkedList 。 |
HashSet 类 | 该类实现了 Set 接口,不允许存储重复元素,并且不保证集合中元素的顺序,其允许存储 null (空值)元素,但最多只能存储一个。 |
TreeSet 类 | 该类实现了 Set 接口,不允许存储重复元素,并且不保证集合中元素的顺序,其允许存储 null (空值)元素,但最多只能存储一个。该类可以实现排序等功能。 |
如下表:
类名 | 描述 |
---|---|
HashMap 类 | HashMap 类是一个散列表,它存储的内容是键-值对 (key-value) 映射。该类实现了 Map 接口,根据键的 HashCode 值存储元素,具有很快的访问速度,但最多允许一个元素的键为 null (空值),它不支持线程同步。 |
TreeMap 类 | TreeMap 类继承了AbstractMap ,实现了大部分 Map 接口,并且使用一颗树。 |
HashTable 类 | Hashtable 继承自 Dictionary(字典) 类,用来存储 键-值对。 |
Properties 类 | Properties 继承自 HashTable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。 |
特此说明:由于集合框架的内容繁多,因此本文只介绍 Map 图 下的 Map 接口及其重要实现子类的内容,其余集合框架的知识将会在下篇博文分享。
(1)Map 接口与 Collection 接口同级(彼此没有继承关系),Map 图存储一组 键-值 对象,提供 key(键)到 value(值)的映射。
(2)Map 图中的 key 和 value 可以是任何一种引用类型的数据,它们会被封装到 HashMap$Node 对象中。
(3)Map 图中的 key 不可以重复,但 value 可以重复;key 只允许存在一个 null (空值),value 可以存在多个 null(空值)。
(4)String 类常被用作 Map 中的 key。
(5)key 和 value 之间存在单向的一对一关系,即通过指定的 key 一定能找到对应的 value(因为 key 值是唯一的),反之不成立。
public class Map_ {
public static void main(String[] args) {
// Map 接口实现类的特点, 使用实现类 HashMap
Map map = new HashMap();// 创建一个图
// 在 map 中,put 方法可以用来添加和替换元素
map.put("no1", "韩顺平");// 成功
map.put("no2", "张无忌");// 成功
map.put("no1", "张三丰");// 添加失败,当有相同的 key , 原来的 value 就会被替换
// put 方法还会返回被替换的 value,如果成功添加一个新元素,就会返回 null
System.out.println(map.put("no1", "张三丰"));
map.put("no3", "张三丰");// value 相同,但 key 不同,不会发生替换
map.put(null, null); // 成功
map.put(null, "abc"); // 添加失败,当有相同的 key , 原来的 value 就会被替换
map.put("no4", null); // 成功
map.put("no5", null); // 成功
map.put(1, "赵敏");// 成功
map.put(new Object(), "金毛狮王");// 成功
// 通过get 方法,传入 key ,会返回对应的 value
System.out.println(map.get("no2"));// 返回张无忌
System.out.println("map=" + map);
}
}
(1)在实现了 Map 接口的 HashMap 类中,定义了一个静态内部类 Node 类,Node 类中定义了属性 K key 和 V value ,因此,key-value 键值对实质上是存放在 HashMap 类中的 Node 类中的。
(2)静态内部类 Node 类同时又实现了 Map 接口中的局部接口 Entry 接口,因此,Node 类的对象(即结点)可以使用 Entry 接口中定义的方法了。
(3)在创建一个 HashMap 类对象(哈希图)的同时,系统会自动创建一个 存放 Map.Entry 类型元素的 EntrySet 集合。
(4)当把一个 键值对 存放进 HashMap 中时,其实就是创建了一个HashMap$Node 对象(即结点);与此同时,在 EntrySet 集合中会自动创建一个 Map.Entry 类对象,该对象中存储着 HashMap$Node 对象的引用。
public class MapSource_ {
public static void main(String[] args) {
Map map = new HashMap();// 向上转型
map.put("no1", "韩顺平");
map.put("no2", "张无忌");
/* 解读:
1. k-v 实质存储位置: HashMap$Node node = newNode(hash, key, value, null)
2. 为了方便程序员的遍历,系统会自动创建 EntrySet 集合 ,该集合存放的元素的类型为 Map.Entry, 而一个 Entry
对象又指向一个 HashMap$Node。
3. EntrySet 集合中, 存储对象的类型是 Map.Entry ,但实际上存放的还是 HashMap$Node 的引用。
这是因为 static class Node 类 实现了 Map.Entry 接口。
4. 当把 HashMap$Node 对象D的引用 存放到 EntrySet 集合后,就方便我们对 k-v 的遍历, 因为 Map.Entry接口中 提供了重要方法
getKey()和 getValue() 用于对 k-v 的遍历。
5. Map 接口中提供了 keySet() 方法 和 values() 方法,分别用于返回一个只存储 key 的 Set 集合,和一个只存储
value 的 Collection 集合。
*/
Set set = map.entrySet();
System.out.println(set.getClass());// 运行类型为 HashMap$EntrySet
for (Object obj : set) {
System.out.println(obj.getClass()); // 运行类型为 HashMap$Node
// 为了从 HashMap$Node 取出k-v,要 向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
// Map 接口提供的 keySet() 方法,返回一个只存储 key 的 Set 集合
Set set1 = map.keySet();
System.out.println(set1.getClass());
// Map 接口提供的 values() 方法,返回一个只存储 value 的 Collection 集合。
Collection values = map.values();
System.out.println(values.getClass());
}
}
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
// 1. put: 添加和替换,成功添加则返回 null,替换则返回被替换的 value
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
System.out.println("map=" + map);
// 2. remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// 3. get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// 4. size:获取元素个数
System.out.println("k-v=" + map.size());
// 5. isEmpty:判断元素个数是否为0
System.out.println(map.isEmpty());// F
// 6. containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));// T
// 7. clear:清除所有k-v
map.clear();
System.out.println("map=" + map);// []
}
}
Map 接口遍历方式:
方式一:先使用 Map 接口中的 keySet() 方法得到一个 只存储 key 的 Set 集合,再通过 key 调用 get() 方法,得到对应的 value。
方式二:直接使用 Map 接口中的 value() 方法得到一个 只存储 value 的 Collection 集合(不推荐使用该方法,因为得不到映射关系)。
方式三:先使用 Map 接口中的 entrySet() 方法,得到一个存储了 Entry 类对象的 EntrySet 集合,Entry 类对象指向了 key-value 的 HashMap$Node 结点,再调用 Entry 类对象 的getKey() 和 getVal() 方法,得到对应的 key 和 value。
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
// 方式一:先使用 Map 接口中的 keySet() 方法得到一个 只存储 key 的 Set 集合,
// 再通过 key 调用 get() 方法,得到对应的 value。
Set keyset = map.keySet();
// (1) 增强for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
// (2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
// 方式二:直接使用 Map 接口中的 value() 方法得到一个只存储 value 的 Collection 集合
// (不推荐使用该方法,因为得不到映射关系)。
Collection values = map.values();
// 这里可以使用所有的Collections使用的遍历方法
// (1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
System.out.println(value);
}
// (2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//方式三:先使用 Map 接口中的 entrySet() 方法,得到一个存储了 Entry 类对象的 EntrySet 集合,
// Entry 类对象指向了 key-value 的 HashMap$Node 结点,
// 再调用 Entry 类对象 的getKey() 和 getVal() 方法,得到对应的 key 和 value。
Set entrySet = map.entrySet();// EntrySet>
// (1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
for (Object entry : entrySet) {
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
// (2) 迭代器
System.out.println("----使用EntrySet 的 迭代器(第4种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
// System.out.println(next.getClass());// HashMap$Node 类 -实现-> Map.Entry接口 (getKey, getValue)
// 向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
(1)HashMap 类也是一个散列表,该类实现了 Map 接口,它存储的内容是键-值对 (key-value) 映射。它具有很快的访问速度,但不支持线程同步,是不安全的。
(2)HashMap 中的 key 不可以重复,但 value 可以重复;key 只允许存在一个 null (空值),但 value 可以存在多个 null(空值)。
(3)HashMap 类与 HashSet 类一样,存储的元素是无序的(因为它是根据键的 HashCode 值存储元素的),它们的底层都维护了 数组 + 链表 + 红黑树 的数据结构
(1)HashMap 底层维护了 Node 类型的数组 table , 默认容量为 nul。
(2)当创建 HashMap 类的对象时,将加载因子 (loadfactor) 初始化为 0.75。
(3)当添加 key-value 时,通过 key 的哈希值得到在 table 的索引,然后判断该索引位置的结点是否为 null 。
(4)如果为 null 则直接添加 key-value;如果该结点不为 null,继续判断该结点的 key 和准备加入的 key 是否相等;如果相等, 则直接替换两者的 value ; 如果不相等则需要判断是索引连接的结点是树结构还是链表结构。
(5)如果是树结构,就采用树结构的树结构中的方法,此处不深入探讨。
(6)如果是链表结构,就遍历链表中每个结点,如果不存在结点的 key 值与新结点的 key 相等,则将新结点添加在链表尾部,并判断是否要树化;如果存在结点的 key 值 与新结点的 key 值相等,则直接替换两者对应的 value 。
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);// ok
map.put("php", 10);// ok
map.put("java", 20);// 替换value
System.out.println("map=" + map);//
源码剖析:
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行put()方法, 先调用 hash 方法,计算 key 的 hash值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;// 辅助变量
// 如果底层的 table 数组为 null, 或者 length =0 , 就调用 resize()方法 进行扩容到 16 (第一次扩容)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把准备加入的 k-v
// 封装成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果该索引位置的 Node 不为 null,则进入该分支
else {
Node<K,V> e; K k;// 辅助变量
// 如果 table 的索引位置的 key 的 hash 和新元素的 key 的 hash 值相同,
// 并且满足 (table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals返回真)
// 则说明: 新结点和数组中旧结点的 key 相同,
// 就将辅助结点 e 指向 p,并直接替换该结点原有的 value值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果 新结点和数组中旧结点的 key 不相同,且 数组连接的是 红黑树,则进入该分支,不细讲
else if (p instanceof TreeNode)// 如果当前的table的已有的 Node 是红黑树,就按照红黑树的方式处理
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 如果 新结点和数组中旧结点的 key 不相同, 则进入数组连接的链表,
// 循环遍历比较链表中每个结点的 key 与 新结点的 key 是否相同。
else {
for (int binCount = 0; ; ++binCount) {// 死循环
// 首先,辅助结点 e 直接指向 p 的下一个结点,
// 然后判断该结点是否存在 元素,如果不存在,说明 新结点 要添加进链表中
// 且此时的 e == null,不会执行最下面交换 value 的代码
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 加入后,立刻判断当前链表的结点个数,是否已经到8个,到8个后
// 就调用 treeifyBin() 方法对链表进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;// 然后直接退出循环
}
// 如果在循环比较过程中,发现旧结点有相同的 key, 就直接 break,
// 此时的 e 不为 null,所以会进入下面交换 value 的代码
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 如果前面的两个判断条件都不满足,说明要继续向下一个结点进行比较
// 这里更新辅助结点 p 的指向,然后进入下次循环的起始位置,辅助结点 e 的指向也会更新
p = e;
}
}
// 这部分代码负责交换 value,只要进入该代码,都会交换两结点的value
// 只要 e 指向的结点不为空,就代表存在旧结点与新结点的 key 相同
// 无论它们的 value 相不相同,都交换它们的 value。
if (e != null) {
V oldValue = e.value;
// !onlyIfAbsent 始终为真,是传入的参数
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替换 两结点 key 对应的 value
afterNodeAccess(e);
// 退出 putVal()方法,返回被替换的 value,注意size和modcount没有增加。
return oldValue;
}
}
++modCount;// 每增加一个 Node 结点 ,就 size++,modCount++
if (++size > threshold)// 如果size > 临界值,就进行数组扩容
resize();
afterNodeInsertion(evict);
return null;
}
4. 补充:关于树化的过程(链表转成红黑树)
// 进入 putTreeVal()方法后,不会马上树化,而是先判断数组 table 的长度
// 如果 table 为 null ,或者其大小(size)还没有到 64,暂时不树化,而是进行数组扩容.
// 直到满足 size > 64 ,且 某条链表上结点的个数 >= 8 这两个条件
// 才会真正的树化 -> 剪枝。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
}
}
(1)HashMap 使用 put() 方法添加 k-v 时,会调用 resize() 方法进行数组的扩容。
(2)当第一次添加 k-v 时,resize()方法会将数组的大小 (size 变量)扩容到16;并在方法中设置了一个数组临界值 threshold ,是数组大小的 0.75 倍,当数组中存储的元素到达该临界值的时候,就会再次调用 resize()方法对数组进行扩容,默认是扩容到之前数组大小的2倍,然后更新临界值 threshold。
(3)当 HashTable 中的某个数组位置中的链表的结点个数到达一个固定值时(默认为 8个),哈希表就会将普通的单向链表进行红黑树化,但前提是数组的大小已到达64;否则要先对数组扩容(直到数组大小到达64),再进行树化。
public class HashMapIncrement {
public static void main(String[] args) {
/*
HashMap 第一次添加时 k-v 时,table 数组扩容到 16,
临界值(threshold)是 16 * 加载因子(loadFactor == 0.75) = 12
如果table 数组使用到了临界值 12, 就会将数组扩容到 16 * 2 = 32,
新的临界值就是 32 * 0.75 = 24, 依次类推
*/
HashMap map = new HashMap();
// 往哈希图中添加 k-v ,下列的每个 k-v 都是添加到集合的数组中,不会添加到链表中
// 所以哈希图将会一直进行数组的扩容。
for(int i = 1; i <= 100; i++) {
map。put("" + i, i);
}
/*
在Java8中, 如果一条链表的元素个数 >= TREEIFY_THRESHOLD(默认是 8 ),
并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),
否则仍然采用数组扩容机制,意思是说到第11个元素时就会扩容到64了
*/
map = new HashMap(); // 创建一个新的哈希图
// 往哈希图中添加 key ,但与上面不同,这次添加的 k-v 会添加到数组的同一个索引位置,
// 因此数组的大小不会改变,依旧是默认的 16。
// 由于每个结点的 key 都不同,所以这些结点会加入到数组的单向链表中,
// 当链表中的结点增加到8个时,哈希图就要将普通链表进行树化;
// 但此时数组的大小为 16,不满足树化要求的数组大小为 64,所以要先进行数组的扩容;
// 则新加入的第9、10个结点依然是添加到单向链表的后面,此时数组大小扩容到 64;
// 在添加第11 个结点时,数组大小和链表长度都满足了树化的条件,因此哈希图将链表进行树化。
// 注意,本例中的每个结点都是不同的,但他们的 hash 值相同,因此加入的数组的索引位置相同。
for(int i = 1; i <= 12; i++) {
map.put(new A(i), i);
}
/*
当我们向 map 增加一个元素,-> Node -> 加入table , 就算是增加了一个size
在 table中 size > threshold ,就会扩容
*/
map = new HashMap(); // 再次创建一个新的哈希图
// 在 哈希图的某一条链表上添加了 7个 A对象-100 键值对
for(int i = 1; i <= 7; i++) {
map.put(new A(i), 100);
}
// 在另一条链表上添加到第4 个 B对象-200 键值对的时候,size = 12,到达临界值,数组会进行 resize()扩容
// 但是由于为满足某条链表的结点个数 >= 8,所以不会进行树化。
for(int i = 1; i <= 7; i++) {
map.put(new B(i), 200);
}
}
}
class B {
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 200;
}
}
class A {
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
(1)HashTable 也是一个散列表,它存储的内容是键值对(key - value)的映射。
(2)HashTable 继承于 Dictionary 类,实现了Map 接口。
(3)HashTable 是可同步的,这意味着它是线程安全的。
(4)HashTable 的 key 和 value都不可以为 nul,否则会抛出 NullPointerException 异常。
(5)HashTable 的使用方法和 HashMap 的基本一样,可以使用 Map 接口中的所有方法。
(6)底层有数组 Hashtable$Entry[] ,数组默认初始化大小为 11,临界值 threshold = 11 * 0.75 = 8;默认的扩容机制为原先的 2倍 + 1。
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("john", 100); // ok
// table.put(null, 100); // 添加失败,抛出异常 NullPointerException
// table.put("john", null);// 添加失败,抛出异常 NullPointerException
table.put("lucy", 100);// ok
table.put("lic", 100);// ok
table.put("lic", 88);// 100 被替换成 88
System.out.println(table);
}
}
public class Properties_ {
public static void main(String[] args) {
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");// 抛出 空指针异常
//properties.put("abc", null); // 抛出 空指针异常
properties.put("john", 100);
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);// 如果有相同的key , value被替换
System.out.println("properties=" + properties);
// 通过k 获取对应值
System.out.println(properties.get("lic"));// 88
// 删除
properties.remove("lic");
System.out.println("properties=" + properties);
// 修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}
public class TreeMap_ {
public static void main(String[] args) {
// 使用默认的构造器,创建 TreeMap, 是无序的(也没有排序)
// TreeMap treeMap = new TreeMap();
// 使用构造器创建
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// return ((String) o2).compareTo((String) o1); 按照传入的 k(String) 的大小进行排序
return ((String) o2).length() - ((String) o1).length();// 按照K(String) 的长度大小排序
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");// 加入不了,因为hsp 和 tom长度相同
System.out.println("treemap=" + treeMap);
源码分析:
1. 构造器. 把传入的实现了 Comparator 接口的匿名内部类(对象),传给 TreeMap 的 comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 调用 put()方法
2.1 第一次添加 k- v, 把 k-v 封装到 Entry对象,放入 root
Entry<K,V> t = root;
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
2.2 以后再添加 k- v
Comparator<? super K> cpr = comparator;
if (cpr != null) {
// 遍历所有的key , 给当前 key 找到适当位置
do {
parent = t;
cmp = cpr.compare(key, t.key);// 动态绑定到我们的匿名内部类的 compare方法
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else // 如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
return t.setValue(value);
} while (t != null);
}
}
}
(1)Collections 是一个操作 Set、List 和 Map 等 集合/图 的工具类。
(2)Collections 中提供了一系列静态的方法对 集合/图 中的元素进行排序、查询、修改等操作。
public class Collections_ {
public static void main(String[] args) {
//创建ArrayList 集合,用于测试
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
// 1. reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
// 2. shuffle(List):对 List 集合元素进行随机排序
for (int i = 0; i < 5; i++) {
Collections.shuffle(list);
System.out.println("list=" + list);
}
// 3. sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println("自然排序后");
System.out.println("list=" + list);
// 4. sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
// 按照 字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码.
return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("字符串长度大小排序=" + list);
// 5. swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list, 0, 1);
System.out.println("交换后的情况");
System.out.println("list=" + list);
// 6. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
// 7. Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);
// 8. Object min(Collection)
// 9. Object min(Collection,Comparator)
//上面的两个方法,参考max即可
// 10. int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));
// 11. void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
// 为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
for(int i = 0; i < list.size(); i++) {
dest.add("");
}
// 拷贝
Collections.copy(dest, list);
System.out.println("dest=" + dest);
// 12. boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
// 如果list中,有tom 就替换成 汤姆
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("list替换后=" + list);
}
}