[TOC]
集合框架
程序开发并不是解决了业务的基本功能就完成了,很多时候程序运行的环境是有限制的。比如内存小,CPU频率低,或者是像手机这样的设备,能源供应有限。在这种环境下,就需要程序能够在有限的环境中提升效率。这就需要使用数据结构和算法。
但是数据结构与算法即便是学过,也未必在工作时能够用好,而且通用性、性能等等也都是问题。加上学习程序开发的受众群体越来越广,让程序员全部自己实现数据结构与算法不是一个好的主意。所以现在很多语言为了能够提升学习效率,降低学习门槛,同时也为了让程序有更好的执行效率和通用性,就自带了各种实现了数据结构与算法的API集合。在Java中,这就是我们现在要学习的「集合框架」
与现在常见到的数据结构类库一样,Java也是将集合类库的接口(interface)与实现(implementation)分离。所以我们的学习方式一般都是先搞明白接口的分类和关系,然后根据不同的接口来学习对应的实现类。
使用集合做什么
- 搬运数据,集合可以存储数据,然后通过API调用很方便就可以传递大量数据
- 数据处理,集合中可以直接对数据进行操作,比如统计、去重
- 排序,可以将数据按照需求进行各种排序,然后再传递给调用者
集合的分类
[图片上传失败...(image-95e5-1620641115118)]
Java的集合从 Collection 接口和 Map 接口入手
Map 接口和 Collection 没有交集,它有自己的方式,只要标准库后缀不是Map 结尾的,都是直接或者间接实现了Collection接口。
Collection 接口中常见的操作是数据的添加、删除
- add / addAll
- remove / removeAll / removeIf
借助 Iterator 接口,Collection 还具备了数据的循环
public interface Collection<E> extends Iterable<E>{
//...
// 对数据循环
Iterator<E> iterator();
}
通过 Iterable 接口, 标准库中的集合都可以使用 forEach 循环。
具体的实现类
集合类型 | 描述 |
---|---|
ArrayList | 一种可以动态增长和缩减的索引序列 |
LinkedList | 一种可以在任何位置进行高效地插人和删除操作的有序序列 |
ArrayDeque | 一种用循环数组实现的双端队列 |
HashSet | 一种没有重复元素的无序集合 |
TreeSet | 一种有序集 |
EnumSet | 一种包含枚举类型值的集 |
LinkedHashSet | 一种可以记住元素插人次序的集 |
PriorityQueue | 一种允许高效删除最小元素的集合 |
HashMap | 一种存储键/ 值关联的数据结构 |
TreeMap | 一种键值有序排列的映射表 |
EnumMap | 一种键值属于枚举类型的映射表 |
LinkedHashMap | 一种可以记住腱/ 值项添加次序的映射表 |
WeakHashMap | 一种其值无用武之地后可以被垃圾回收器回收的映射表 |
IdentityHashMap | 一种用 == 而不是用equals 比较键值的映射表 |
Collection
[图片上传失败...(image-9a1f26-1620641115118)]
Map
[图片上传失败...(image-f64378-1620641115118)]
虽然类很多 ,但是我们在教授中只需要交几个类就可以了,分别是下面:
- ArrayList
- LinkedList
- HashSet
- HashMap
- TreeMap
然后加上工具类2个:
- Collections
- Arrays
List
有序集合,可以精确控制列表中每个元素的插入位置。通过整数索引获取列表中的元素。List允许出现重复的值 , 并可以精确控制列表中每个元素的插入位置,通过整数索引获取列表中的元素。
方法名 | 说明 |
---|---|
add(E e) | 增加单个数据 |
addAll(Collection<? extends E> c) | 将一个 Collection 集合的数据添加到现在的集合中 |
remove(Object o) | 删除指定的元素 |
contains(Object o) | 判断集合是否包含指定的元素 |
size() | 得到集合的数据总数 |
isEmpty() | 判断集合是否有数据 |
get(int index) | 通过索引获取对应的数据元素 |
set(int index, E element) | 通过索引和新的元素替换原有内容 |
clear() | 清空数据 |
toArray() | 将List转为对象数组 |
ArrayList
ArrayList 是List 接口的大小可变数组的实现。实现了所有可选列表操作, 并允许包括null 在内的所有元素。除了实现List 接口外, 此类还提供一些方法来操作内部用来存储列表的数组的大小( 此类大致上等同于 vector 类, 但 vector 是同步的) 。
ArrayList 的底层是使用数组实现的,看下面的图
[图片上传失败...(image-a48b21-1620641115118)]
可以看到,数组中的每一个元素,都存储在内存单元中,并且元素之间紧密排列,既不能打乱元素的存储顺序,也不能跳过某个存储单元进行存储。
ArrayList 底层既然是使用数组实现,那么特点就和数组一致:查询速度快,增删速度慢
每个ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小, 它总是至少等于列表的大小。随着向Array L ist 中小断添加元素, 其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。我们可以使用默认构造函数创建容量为 10 的列表, 也可以初始化指定容量大小。
ArrayList 指定初始容量大小的构造器方法
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);
}
}
常用的操作
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 添加数据
list.add(123);
list.add(346);
// 替换数据
list.set(1, 777);
// 将list中的所有数据放到 list2 中
List<Integer> list2 = new ArrayList<>();
list2.addAll( list );
// 循环list2中所有的数据
for (Integer integer : list2) {
System.out.println( integer );
// 删除循环出的对象
list2.remove(integer);
}
// list 集合是否有数据
if( !list.isEmpty() ) {
System.out.println("list.size = "+ list.size());
// 清空 list
list.clear();
}
// 在清空list后,再看list现在有多少数据
System.out.println("list.size = "+ list.size());
}
DEFAULT_CAPACITY = 10; 默认的容量 = 10
Array 数组
List 列表
element 元素
Data 数据
specified 指定的
Null 空
Pointer 指针
add 添加
batchRemove 批量移除
batch 批量
check 检查
clone 克隆
contains 包含
ensure 保证
capacity 容量
grow 生长
equals 等于 相等
fast 快
Clear清除 干净
isEmpty 是否为空
size 大小,长度,尺码
Iterator 迭代器
last 最后一个,从后往前的
IndexOf 索引
Range 范围
outOfBounds 超出范围
msg (message) 消息
remove 移除
replace 替换
set 设置
sort 排序
sub 子
DEFAULT 默认的
CAPACITY 容量
Position 位置
index 索引
node 节点
offer 提供
peek 查看
pick 选中
poll 拿出第一个
Retrieves 索取
push 推出
Spliterator 拆分
super 超
link 链接
unlink 断开连接
Duplicate 重复的
LinkedList
LinkedList 是一个链表结构,可当作堆栈、队列、双端队列 。链表是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
单链表
[图片上传失败...(image-77956a-1620641115118)]
单向链表的每一个节点包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。正如地下党的联络方式,一级一级,单线传递:
private static class Node {
int data;
Node next;
}
双链表
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针
[图片上传失败...(image-f2518e-1620641115118)]
和数组不同,链表存储数据的时候,则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。
[图片上传失败...(image-f4a1d4-1620641115118)]
LinkedList 做数据的添加和删除操作时速度很快,但是做查询的时候速度就很慢,这和 ArrayList 刚好相反。
适用 LinkedList 独有的方法,在集合前后做数据插入
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(123);
// 添加相同的数据
list.add(123);
list.add(456);
list.add(789);
list.add(101);
// 将数据 111 添加到 list 的末尾
list.addLast(111);
// 将数据 999 添加到 list的最前面
list.addFirst(999);
for (int i = 0; i < list.size(); i++) {
System.out.println( list.get(i) );
}
}
注意代码中声明和实现都是 LinkedList , 如果声明的是 List 接口 , addFirst 和 addLast 方法是不可见的
Set
Set 的特点是去重,如果相同的数据只会保留一个。集合内的数据是一个无序的状态,。当然也有例外,TreeSet就是有序的。 确定性 互异性 无序性
方法名 | 说明 |
---|---|
add(E e) | 增加单个数据 |
addAll(Collection<? extends E> c) | 将一个 Collection 集合的数据添加到现在的集合中 |
remove(Object o) | 删除指定的元素 |
contains(Object o) | 判断集合是否包含指定的元素 |
size() | 得到集合的数据总数 |
isEmpty() | 判断集合是否有数据 |
clear() | 清空数据 |
toArray() | 将List转为对象数组 |
可以看到,Set 和 List 中的方法都是相同的,作用也一致。但是 Set 是没有直接获取数据的方法。我们更多的时候使用的是 List 和 Map。
HashSet 由哈希表( 实际上是一个HashM ap 实例) 支持, 为基木操作提供了稳定性能, 这些基本操作包括add() 、remove() 、contains() 和size() , 假定哈希函数将这些元素正确地分布在桶中。对此 set() 进行迭代所需的时间与HashSet 实例的大小( 元素的数量) 和底层HashMap 实例( 桶的数量)的“ 容量” 的和成比例。因此, 如果迭代性能很重要, 则不要将初始容量没置得太高( 或将加载因了设置得太低) 。
public static void main(String[] args) {
HashSet set = new HashSet<>();
set.add( new Object() );
set.add( new Object() );
set.add( "hello" );
set.add( "hello" );
set.add( "你好" );
set.add( 123 );
set.add( 123 );
set.add( 234 );
set.remove("你好");
set.remove( new Object() );
for (Object object : set) {
System.out.println( object );
}
}
以上代码经输出可以看到:输出的内容没有按照输入时的顺序进行输出。
李四
8890
张三
azxcv
ABC
TreeSet
Set 集合中的另类,存储的数据时有序的
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>();
set.add("8890");
set.add("123");
set.add("张三");
set.add("李四");
set.add("ABC");
set.add("azxcv");
for (String string : set) {
System.out.println( string );
}
}
输出的结果:
123
8890
ABC
azxcv
张三
李四
可以发现数据是按照数字、大写字母、小写字母、汉字进行排序的,但是汉字相关的数据本身没有排序,可以尝试将李四放在张三的数据上面看输出的结果。
add 添加
addAll 添加一个新的集合
clear 清除所有元素
clone 克隆
contains 包含
first 第一个元素
isEmpty 是否为空
iterator 迭代器
last 最后一个
pollFirst 抽取第一个
pollLast 抽取最后一个
remove 移除
size 求元素的多少
subSet 子集合
tailSet 尾部的集合
Map (映射)一一对应的关系 一对一、一对多
Map 是一种把键对象和值对象进行关联的容器, 而一个值对象又可以是一个Map, 依次类推,这样就可形成一个多级映射。
想想学习英语使用的词典软件,输入英文(key)后,软件会显示出对应的中文(value)。Map就是在内存中的这种结构。
Key(键):
- 和 set— 样,键对象不允许重复,这是为了保持查找结果的一致性。 如果有两个键对象一样, 那你想得到那个键对象所对应的值对象时就有问题了。
- 在使用过程中某个键所对应的值对象可能会发生变化, 这时会按照最后一次修改的值对象与键对应(就是key同一个key有多次值绑定,最后一个就会覆盖之前的)
- 可以使用 null 作为 Key
Value(值):
- 值对象没有唯一性的要求, 你可以将任意多个键都映射到一个值对象上, 这不会发生任何问题( 不过对使用却可能会造成不便, 你不知道你得到的到底是那一个键所对应的值对象,所以请不要这样做)
- 可以使用 null 作为 Value
Map 有两种比较常用的实现: HashMap 和 TreeMap
常用的方法
方法名 | 说明 |
---|---|
put(key , value) | 储存数据 |
get(key) | 通过key得到值 |
remove(key) | 通过key删除对应的值(key当然也会删除) |
entrySet() | 获取Map所有的Key,返回一个Set集合 |
values() | 获取Map所有的value,返回一个List 集合 |
containsKey(key) | 判断Map中是否有输入的参数:key |
containsValue(value) | 判断Map中是否有输入的参数:value |
size() | 判断Map中数据的总数 |
clear() | 清空Map中所有的数据 |
isEmpty() | 判断Map中是否有数据 |
HashMap
HashMap 用到了哈希码的算法, 以便快速查找一个键。
public static void main(String[] args) {
HashMap<String, String> zsInfo = new HashMap<>();
zsInfo.put("name", "张三");
zsInfo.put("height", "173CM");
zsInfo.put("sex", "男性");
for (Map.Entry<String, String> info : zsInfo.entrySet()) {
System.out.println( info );
}
}
负载因子,当容量使用到75%时,触发扩容的操作
DEFAULT_LOAD_FACTOR = 0.75 负载因子 = 0.75
DEFAULT_INITIAL_CAPACITY = 16 默认的初始化容量=16,必须是2的整数次幂,原因是便于Hash运算,提高效率
TREEIFY_THRESHOLD = 8 树化_阈值 = 8 当长度大于8时,链表的查询会变慢,优化为树
TreeMap
TreeMap 是对键按序存放, 因此它便有一些扩展的方法, 比如 firstKey() 、lastKey() 等, 可以从TreeMap 中指定一个范围以取得其子Map
public static void main(String[] args) {
TreeMap<String, String> tree = new TreeMap<>();
tree.put("name", "Jack");
tree.put("age", "22");
tree.put("身高", "173");
tree.put("sex", "man");
tree.put("体重", "70KG");
System.out.println("-------------------");
for (Map.Entry<String, String> entry : tree.entrySet()) {
System.out.println( entry );
}
System.out.println("-------------------");
System.out.println("firstKey = "+ tree.firstKey());
System.out.println("firstEntry = "+ tree.firstEntry());
System.out.println("lastKey = "+ tree.lastKey());
System.out.println("lastEntry = "+ tree.lastEntry());
}
树:红黑树
性质1. 结点是红色或黑色。 [3]
性质2. 根结点是黑色。 [3]
性质3. 所有叶子都是黑色。(叶子是NIL结点) [3]
性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点。 [3]
数组、List、Set、Map的数据转换
不同类型的集合相互转换在程序开发中都是非常常见的 API 操作,这些基础操作一定要熟悉。
数组转 List
/*
* 数组转List,但是这里要注意,Arrays.asList方法转出来的虽然也叫ArrayList,
* 但不是java.util.ArrayList,而是Arrays中的一个内部类ArrayList。两者之间并不相等。
* 内部类ArrayList没有实现remove、add等方法,使用的话会报错。
*/
System.out.println("使用Arrays.asList将数组转换为List--------------------------------");
String str[] = { "中文", "计算机", "ABC", "123", "qq@qq.com" };
List<String> strList = Arrays.asList(str);
for (String string : strList) {
System.out.println(string);
}
System.out.println("使用ArrayList构造器方式再由Arrays.asList将数组转换为List----------");
// 使用下面的代码的写法就不会有问题了。
ArrayList<String> stringsList = new ArrayList<String>(Arrays.asList(str));
// 使用remove方法
stringsList.remove(3);
for (String string : stringsList) {
System.out.println(string);
}
System.out.println("使用List.toArray方法将一个List转换为数组(默认为Object数组)----------");
Object[] strArr = stringsList.toArray();
for (Object object : strArr) {
System.out.println(object.toString());
}
数组和Set
System.out.println("使用Arrays.asList将数组转换为List--------------------------------");
String str[] = { "中文", "计算机", "ABC", "123", "qq@qq.com" };
System.out.println("下面是数组转Set ----------------------------------");
Set<String> strSet = new HashSet<String>(Arrays.asList(str));
for (String string : strSet) {
System.out.println(string);
}
System.out.println("下面是将Set集合转换为数组-------------");
Object[] objArr = strSet.toArray();
for (Object string : objArr) {
System.out.println(string);
}
从Map中得到Set和List
Map<String, String> map = new HashMap<String, String>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");
// 输出所有的key , keySet()方法其实返回的就是一个Set集合。
System.out.println("通过keySet输出所有的key:" + map.keySet());
// 把map的值转为一个List
List<String> stringList = new LinkedList<String>(map.values());
for (String string : stringList) {
System.out.print(string + " ");
}
散列表(哈希表)的基本原理
上面 HashSet 和 HashMap 都使用了 散列表来做数据的检索,那么什么是散列表 ,这个又有什么优点呢 ?
散列表也叫作哈希表(hash table),这种数据结构提供了键(Key)和值(Value)的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1)。
是不是想到了 Map ?
散列表是如何根据Key来快速找到它所匹配的 Value 呢 ?
在目前所学的数据结构中,数组的查询效率是最高的。散列表本质就是一个数组。不同的是数组的下标是数字,但是散列表的下标是字符串。这就需要设计一个中转站,通过某种方式,把Key和数组下标进行转换。这个中转站就叫作哈希函数。
[图片上传失败...(image-ff884-1620641115118)]
在不同的语言中,哈希函数的实现是不一样的。我们以Java的HashMap为例,来看看哈希函数的实现。
在Java及大多数面向对象的语言中,每一个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的类型是什么,它们的hashcode都是一个整型变量。
既然都是整型变量,想要转化成数组的下标也就不难实现了。最简单的转化方式是什么呢?是按照数组长度进行取模运算
index = HashCode (Key) % Array.length
其实JDK中的哈希函数为了提升效率,使用的是位运算。我们就假设使用的是模运算
通过哈希函数,我们可以把字符串或其他类型的Key,转化成数组的下标index。如给出一个长度为8的数组,则当
key=001121时
index = HashCode ("001121") % Array.length = 1420036703 % 8 = 7
而当key=this时,
index = HashCode ("this") % Array.length = 3559070 % 8 = 6
散列表的读写操作
有了哈希函数,就可以在散列表中进行读写操作了。
写操作(put)
写操作就是在散列表中插入新的键值对(在JDK中叫作Entry)。如调用hashMap.put("002931", "王五"),意思是插入一组Key为002931、Value为王五的键值对。具体该怎么做呢?
通过哈希函数,把Key转化成数组下标5。
-
如果数组下标5对应的位置没有元素,就把这个Entry填充到数组下标5的位置
[图片上传失败...(image-26fc5-1620641115118)]
由于数组的长度是有限的,当插入的Entry越来越多时,不同的Key通过哈希函数获得的下标有可能是相同的。例如002936这个Key对应的数组下标是2;002947这个Key对应的数组下标也是2。
[图片上传失败...(image-2dd00-1620641115118)]
这种情况,就叫作哈希冲突
解决哈希冲突的方法主要有两种,一种是开放寻址法,一种是链表法
- 开放寻址法的原理很简单,当一个Key通过哈希函数获得对应的数组下标已被占用时,我们可以“另谋高就”,寻找下一个空档位置
- HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点。每一个Entry对象通过next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链表中即可
[图片上传失败...(image-b25227-1620641115118)]
读操作(get)
读操作就是通过给定的Key,在散列表中查找对应的Value。例如调用 hashMap.get("002936"),意思是查找Key为002936的Entry在散列表中所对应的值
- 通过哈希函数,把Key转化成数组下标2。
- 找到数组下标2所对应的元素,如果这个元素的Key是002936,那么就找到了;如果这个Key不是002936也没关系,由于数组的每个元素都与一个链表对应,我们可以顺着链表慢慢往下找,看看能否找到与Key相匹配的节点。
Iterator
iterator是为了实现对Java容器(collection)进行遍历功能的一个接口。
在iterator实现了Iterator接口后,相当于把一个Collection容器的所有对象,做成一个线性表(List),而iterator本身是一个指针,开始时位于第一个元素之前。
方法 | 说明 |
---|---|
hasNext() | 判断 iterator 内是否存在下1个元素,如果存在,返回true,否则返回false。(注意,这时上面的那个指针位置不变) |
next() | 返回 iterator 内下1个元素,同时上面的指针向后移动一位。如果不断地循环执行next()方法,就可以遍历容器内所有的元素了 |
remove() | 删除 iterator 内指针的前1个元素,前提是至少执行过1次next() |
遍历1个ArrayList 和Linklist是十分容易的,遍历1个Tree容器也不难,但是实现机制是完全不同,而遍历1个Set容器就无从下手了。
Iterator 这个接口让各种容器自己去重写里面的hasNext()和next()方法。 不用关心各种容器的遍历机制,只要使用Iterator,会让人觉得各种容器的遍历方法都是一样的,这就是Java接口的重要意义。
HashMap zsInfo = new HashMap<>();
zsInfo.put("name", "张三");
zsInfo.put("height", "173CM");
zsInfo.put("sex", "男性");
zsInfo.put("skin-color", "白色");
zsInfo.put("height", "183CM");
zsInfo.remove("skin-color");
System.out.println("Iterator 循环------------------");
Iterator iterator = zsInfo.entrySet().iterator();
while( iterator.hasNext() ) {
Map.Entry next = (Entry) iterator.next();
System.out.println( next );
}
Collections
Collections 是 Java 提供对Set 、List 和Map 等集合操作的工具类。
该工具类里提供了大量方法,除了对集合元素进行排序、查询和修改等操作;还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
排序
ArrayList nums = new ArrayList<>();
nums.add(2);
nums.add(0);
nums.add(-5);
nums.add(0);
nums.add(3);
System.out.println( nums );
Collections.reverse( nums );
System.out.println( nums);
Collections.sort( nums );
System.out.println( nums );
Collections.shuffle( nums );
System.out.println( nums );
查找
ArrayList nums = new ArrayList<>();
nums.add(2);
nums.add(0);
nums.add(-5);
nums.add(0);
nums.add(3);
System.out.println( nums );
System.out.println( Collections.max( nums ));
System.out.println( Collections.min( nums ));
Collections.replaceAll(nums, 0 , 1);
System.out.println( nums );
System.out.println( Collections.frequency(nums, 1));
Collections.sort(nums);
System.out.println( nums );
System.out.println( Collections.binarySearch(nums, 3 ));
排序
集合的排序有2种方式:
-
集合内部就拥有排序的能力。
需要排序的能力的类实现 Comparable 接口的方法
-
集合本身没有排序的能力,可以通过外部指定排序的方式。
在排序方法中,指定一个实现了 Comparator 接口的类的对象
Comparable是需要比较的对象来实现接口。这样对象调用实现的方法来比较。对对象的耦合度高(需要改变对象的内部结构,破坏性大)。
Comparator相当于一通用的比较工具类接口。需要定制一个比较类去实现它,重写里面的compare方法,方法的参数即是需要比较的对象。对象不用做任何改变,解耦。
List 集合排序
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(999);
list.add(101);
list.add(33);
list.add(76);
Collections.sort(list);
for (Integer integer : list) {
System.out.println( integer );
}
直接就可以得到自然升序,我们在上面的代码中没有看到任何接口,原因是List集合中的 Integer 类本身就实现了 Comparable 接口
public final class Integer extends Number implements Comparable<Integer> {
//...
}
TreeMap 排序
treeMap 的示例中,我们使用了外部指定排序方式:Comparator
TreeMap<String, String> tree = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1) ; //
}
});
tree.put("name", "Jack");
tree.put("age", "22");
tree.put("身高", "173");
tree.put("sex", "man");
tree.put("体重", "70KG");
System.out.println("-------------------");
for (Map.Entry<String, String> entry : tree.entrySet()) {
System.out.println( entry );
}
自定义类排序
当我们自定义了一个类,也希望能够进行排序的话,就需要实现 Comparator接口或者是 Comparable 接口了。
public class User {
private Integer uid;
private String uname;
//....
}
外部指定排序
User 类没有实现 Comparable 接口,通过 Collections 工具类的 sort 方法进行排序,在 sort 方法的第二个参数上指定排序的规则
List<User> list = new ArrayList<User>();
list.add( new User(110 ,"Mark") );
list.add( new User(101 ,"李四") );
list.add( new User(100 ,"张三") );
list.add( new User(111 ,"Jack") );
// Comparator 接口因为是外部排序,所以需要知道对比的2个对象
Collections.sort(list , new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getUid() - o1.getUid() ;
}
});
for (User user : list) {
System.out.println( user );
}
本身拥有排序
因为是内部排序,所以只需要传入被排序的对象即可,另一个排序对象当然是对象本身,使用 this 指向即可。
public class User implements Comparable<User> {
private Integer uid;
private String uname;
public User(int i, String string) {
this.uid = i;
this.uname = string;
}
@Override
public int compareTo(User o) {
return this.getUid() - o.getUid() ;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
@Override
public String toString() {
return "User [uid=" + uid + ", uname=" + uname + "]";
}
}
排序
List<User> list = new ArrayList<User>();
list.add( new User(110 ,"Mark") );
list.add( new User(101 ,"李四") );
list.add( new User(100 ,"张三") );
list.add( new User(111 ,"Jack") );
Collections.sort(list);
for (User user : list) {
System.out.println( user );
}
输出结果
User [uid=100, uname=张三]
User [uid=101, uname=李四]
User [uid=110, uname=Mark]
User [uid=111, uname=Jack]