write:2022-3-14
update:2022-5-15
本文讲解Java SE的一个重要知识点:集合,学习集合的一个重要思路:两个接口四个分类。
1. 集合
集合用于存储数量不等的对象(只能保存对象),集合类也叫容器类
数组与集合的区别
- 数组的长度是固定的(数组用new语句创建后,长度一般是固定的);集合中的元素的数目可以变化。
- 数组可以存放基本类型的数据,也可以存放引用类型的数据;集合中不能存放基本类型数据,而只能存放对象的引用。
- 数组本身没有操纵元素的方法(只有一个length属性);所有Java集合类都位于java.util包中。Java集合类具有各种操纵集合中元素的方法。
2. 集合的分类
2.1 四种集合
集合大致可以分为Set、List、Queue、Map四种体系:
- Set, 代表无序、不可重复的集合;
- List, 代表有序、可以重复的集合;
- Queue,用于模拟队列这种数据结构;
- Map, 代表着具有映射关系的集合;
(1)概念详解:
Set(集):集合中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对集合中对象按特定方式排序。(无序不重复)
List(列表):集合中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。List与数组有些相似。(有序可重复)
Queue(队列):集合中的对象按照先进先出的规则来排列。在队列的末尾添加元素,在队列的头部删除元素。可以有重复对象。双向队列则允许在队列的末尾和头部添加和删除元素。(用于模拟队列这种数据结构)
Map(映射):集合中的每一个元素包含一对键(Key)对象和值(Value)对象,集合中没有重复的键对象,值对象可以重复。它的有些实现类能对集合中的键对象进行排序。(代表具有映射关系的集合)
(2)概念图理解:
以上四类集合可用下图进行理解:
(3)主要的集合接口和类:
集合类都位于java.util包下,Queue接口,Set接口,List接口是collection接口的子类,其又各有子类
Map接口没有父类,有子类(集合类都位于java.util包下,这些类都是从Collection和Map接口派生而来!)
用以下类图来表示:
2.2 两种接口
2.2.1 Collection接口
Queue接口,,Set接口,List接口是collection接口的子类,其又各有子类或者子接口
2.2.1.1 集合操作方法(针对单个元素)
Collection接口中定义了这三种集合体系都具备的、通用的方法;
collection接口中声明了一些对集合的基本操作:
2.2.1.2 集合的批量操作
在Collection接口中定义了以下方法:
(1)boolean retainAll(Collection<?> c) 修改当前集合,在当前集合中保留那些同时位于参数c集合中的元素,删除其余的元素。如果当前集合最终做了改动,就返回true。 (2)boolean removeAll(Collection<?> c)
删除当前集合中的那些同时位于参数c集合中的元素。
(3)boolean addAll(Collection<? extends E> c) 把参数c集合中的元素加入到当前集合中。 (4)boolean containsAll(Collection<?> c)
判断当前集合中是否存在参数c集合中的元素。
2.2.1.3 遍历集合元素:Iterator
Collection接口的iterator() 方法用于获得集合中的所有元素,返回一个Iterator对象。
Iterator接口隐藏底层集合的数据结构,向客户程序提供了遍历各种类型的集合的统一接口。Iterator接口中声明了如下方法:
boolean hasNext():如果集合还没有迭代完,就返回true。
Object next():返回下一个元素。
void remove():从集合中删除由next()方法返回的当前元素。
遍历元素可以用foreach,也可以用Iterator,区别如下:
eg:
/**
* Iterator
*/
public class CollectionDemo2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("唐僧");
c.add("悟空");
c.add("八戒");
c.add("沙僧");
c.add("白龙");
for (Object obj : c) {
System.out.println(obj);
// if (obj.equals("悟空")) {
// c.remove(obj); //遍历过程中删除元素会报错
// }
}
Iterator iterator = c.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
if (obj.equals("悟空")) {
// c.remove(obj); //遍历过程中删除元素会报错
iterator.remove(); //遍历过程中删除元素不报错
}
}
System.out.println(c);
}
}
因此Iterator 可以在数据遍历过程中加以干预,Iterator 的能力请于foreach
2.2.1.4 自动装箱与拆箱
JDK的高版本会有,省去基本类型与对象转换的操作,方便编程;
2.2.2 Map接口
3. 四种集合详讲
3.1 Set(集)
Set(集):集合中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对集合中对象按特定方式排序。
3.1.1 Set集合中存放的是对象的引用,并且没有重复对象。
eg:
Set<String> set = new HashSet<String>(); //<>是一个泛型标记,里面的是数据类型
String s1 = new String("Hello");
String s2 = s1;
String s3 = new String("world");
set.add(s1);
set.add(s2);
set.add(s3);
打印结果:2(因为s1和s2是同一个对象)
3.1.2 Set类的一个实现类:HashSet类
HashSet是Set接口的典型实现,它具有以下特点:
- HashSet不能保证元素的排列顺序;
- HashSet集合元素的值可以是null;
- HashSet是非线程安全的,多线程环境下须通过代码来保证其同步。
eg:
HashSet students = new HashSet();
students.add("唐僧");
students.add("悟空");
students.add("八戒");
students.add("唐僧"); // false
students.add(null);
System.out.println(students);
打印:
1)HashSet类按照哈希算法来存取集合中的对象,具有很好的存取和查找性能(效率高)。当向集合中加入一个对象时,HashSet会调用对象的hashCode()方法获得哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。
2)在Object类中定义了hashCode()和equals()方法。为了保证HashSet能正常工作,要求当两个对象用equals()方法比较的结果为true时,它们的哈希码也相等。
例如,如果customer1.equals(customer2)为true,那么以下表达式的结果也应该是true:
customer1.hashCode()==customer2.hashCode()
3.1.3 Set类的一个实现类:TreeSet类
TreeSet类实现了SortedSet接口(SortedSet接口也是Set的子类),能够对集合中的对象进行排序。
TreeSet可以保证元素的排列顺序,它比HashSet多了一些方法:
- 返回集合中的第一个(最后一个)元素;
- 返回集合中位于指定元素之前(之后)的元素;
- 返回集合中某个限定范围内的元素组成的子集;
eg1:
Set<Integer> set=new TreeSet<Integer>();
set.add(8); //自动装箱,把8转换为相应的Integer对象,再加入到Set中
set.add(7);
set.add(6);
set.add(9);
for(int i:set) //自动拆箱,把集合中的Integer对象转换为int基本类型数据
System.out.print(i+" ");
以上程序的打印结果为:6 7 8 9
eg2:
TreeSet scores = new TreeSet();
scores.add(83);
scores.add(37);
scores.add(92);
scores.add(54);
scores.add(75);
scores.add(60);
System.out.println(scores);
System.out.println(scores.first() + " - " + scores.last());
System.out.println(scores.lower(60) + " - " + scores.higher(60)); //低于60的一个元素 高于60的一个元素
System.out.println(scores.headSet(60));//取低于60的所有元素成一个子集
System.out.println(scores.tailSet(60));//取大于等于60的所有元素成一个子集
打印:
TreeSet采用红黑树的数据结构来存储元素,它支持两种排序方式:自然排序、定制排序。
1)自然排序:TreeSet能够对数字进行由小到大排序
- 添加时,调用元素的compareTo方法比较元素的大小,并按照升序排列元素;
- 添加到TreeSet中的对象必须实现Comparable接口,该接口定义了compareTo方法;
- Java提供的很多类型均已经实现了Comparable接口,如包装类、String、Date等。
compareTo方法:
obj1.compareTo(obj2):该方法返回一个int类型的整数,返回0代表对象相等,返回正数则代表obj1更大,返回负数则代表obj2更大。
eg:
// 自然排序
TreeSet names = new TreeSet(); //实现Comparable接口
names.add("Lily");
names.add("John");
names.add("Tony");
names.add("Lisa");
names.add("Mary");
System.out.println(names);
打印:
字符串是按照首字母顺序排序
// 没有实现Comparable接口
// TreeSet objs = new TreeSet();
// objs.add(new Object());
// objs.add(new Object());
// objs.add(new Object()); //运行报错
上述代码运行报错;
// 类型不一样,没有可比性.
// TreeSet objs = new TreeSet();
// objs.add("nowcoder");
// objs.add(100);
上述代码运行报错;
2)定制排序:除了自然排序,TreeSet还支持客户化排序
java.util.Comparator接口提供具体的排序方式,指定被比较的对象的类型,Comparator有个compare(T x, T y)方法,用于比较两个对象的大小。
- 创建TreeSet时,传入Comparator接口的实例;
- Comparator接口定义了compare方法,用于比较两个对象的大小;
- TreeSet不再调用compareTo(),转而调用compare()比较大小。
obj1.compare(obj2):该方法返回一个int类型的整数,返回0代表对象相等,返回正数则代表obj1更大,返回负数则代表obj2更大。
当compare(x,y)的返回值大于0,表示x大于y;
当compare(x,y)的返回值小于0,表示x小于y;
当compare(x,y)的返回值等于0,表示x等于y。
eg:
// 定制排序
TreeSet nums = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Number n1 = (Number) o1;
Number n2 = (Number) o2;
if (n1 == null && n2 == null) {
return 0;
} else if (n1 == null) {
return 1;
} else if (n2 == null) {
return -1;
} else if (n1.doubleValue() > n2.doubleValue()) {
return -1;
} else if (n1.doubleValue() < n2.doubleValue()) {
return 1;
} else {
return 0;
}
}
});
nums.add(null);
nums.add(83.5);
nums.add(37);
nums.add(92.5);
nums.add(54);
nums.add(75);
nums.add(60);
System.out.println(nums);
}
打印:
3.1.4 Set类的一个实现类:LinkedHashSet
LinkedHashSet是HashSet的子类,它具备HashSet的一切特点,同时LinkedHashSet采用链表结构维护了元素的插入顺序!
LinkedHashSet采用链表结构维护元素的顺序;
eg:
LinkedHashSet teachers = new LinkedHashSet();
teachers.add("刘备");
teachers.add("关羽");
teachers.add("张飞");
teachers.add("刘备"); // false
teachers.add(null);
System.out.println(teachers);
打印:
3.1.5 三者选择
- HashSet的性能总比TreeSet好,因为TreeSet需要通过红黑树算法来维护元素的顺序;
- 对于插入、删除操作,LinkedHashSet比HashSet略慢,这是由维护链表增加的开销,但因为有了链表,所以遍历时LinkedHashSet更快!
3.2 List(列表)
List(列表):集合中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。List与数组有些相似。(看情况二选一选取使用)
3.2.1 根据索引来访问集合的方法
List代表有序集合,它提供了根据索引来访问集合的方法:
- 将元素插入到集合中指定的索引处;
- 将指定索引处的元素从集合中删除;
- 从集合中返回指定索引处的元素;
- 返回某个元素在集合中的索引值;
- 从集合中,返回起始索引和结束索引之间的元素所组成的子集。
3.2.2 ListIterator接口
List提供了listIterator()方法,以返回一个ListIterator对象;
ListIterator接口继承于Iterator接口,该接口增加了如下的方法:
- boolean hasPrevious() 判断迭代器指向的元素是否有上一个元素;
- Object previous() 返回该迭代器所指向的元素的上一个元素;
- void add(Object o) 在迭代器所指向的元素位置插入一个元素。
eg:
List names = new ArrayList();
names.add("Lily");
names.add("Mary");
names.add("John");
names.add("Tony");
names.add("Lisa");
names.add("Mary");
System.out.println(names);
names.add(1, "Lucy");//在索引为1的位置插入
System.out.println(names);
names.remove(1);//在索引为1的位置删除
System.out.println(names);
打印结果:
for (int i = 0; i < names.size(); i++) {
System.out.println((i + 1) + ": " + names.get(i));
}
打印结果:
System.out.println(names.subList(1, 4));
打印结果:
System.out.println(names.indexOf("Mary") + ", " + names.lastIndexOf("Mary")
打印结果:
ListIterator iterator = names.listIterator();
while (iterator.hasNext()) { //前到后
System.out.print(iterator.next() + " ");
}
System.out.println();
打印结果:
while (iterator.hasPrevious()) { //后往前
System.out.print(iterator.previous() + " ");
}
System.out.println();
打印结果:
// 排序
names.sort(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
if (s1 == null && s2 == null) {
return 0;
} else if (s1 == null) {
return -1;
} else if (s2 == null) {
return 1;
} else {
return s1.compareTo(s2);
}
}
});
System.out.println(names);
}
打印结果:
3.2.3 List类的主要实现类之一:ArrayList类
ArrayList:ArrayList代表长度可变的数组。允许对元素进行快速地随机访问(给定索引 进行访问),但是向ArrayList中插入与删除元素的速度较慢。
- ArrayList是基于数组实现的List接口;
- ArrayList内部封装了一个长度可变的Object[]数组;
- 默认该数组的初始长度为10,也可以通过构造器参数显示指定其初始长度;
- 每当添加的元素个数超出数组的长度,ArrayList会自动对长度进行扩展。
3.2.4 List类的主要实现类之一:Vector类
- Vector也是基于数组的实现,与ArrayList用法相同;
- Vector是线程安全的,ArrayList则是非线程安全的;
- Vector有保证线程安全的开销,所以性能不如ArrayList;
- Vector的子类Stack,用于模拟栈这种数据结构(LIFO)。
Vector、Stack是古老的集合类,性能很差,不建议使用!
1.Collections工具类可以将ArrayList变成线程安全的类;
2.当程序中需要使用栈的数据结构时,推荐使用ArrayDeque。
3.2.5 List类的主要实现类之一:LinkedList类
LinkedList:在实现中采用链表数据结构。对顺序访问进行了优化,向List中插入和删除元素的速度较快,随机访问则相对较慢。随机访问是指检索位于特定索引位置的元素。LinkedList单独具有addFirst()、addLast()、getFirst()、getLast()、removeFirst()和removeLast()方法,这些方法使得LinkedList可以作为堆栈、队列和双向队列使用。
3.2.6 Collections类的sort()排序方法
List只能对集合中的对象按索引位置排序,如果希望对List中的对象按其他特定方式排序,可以借助Comparator接口和Collections类。
Collections类是Java集合类库中的辅助类,它提供了操纵集合的各种静态方法,其中sort()方法用于对List中的对象进行排序:
sort(List list):对List中的对象进行自然排序。
以下程序对List中的Integer对象进行自然排序:
List<Integer> list=new ArrayList<Integer>();
list.add(new Integer(3));
list.add(new Integer(4));
list.add(new Integer(3));
list.add(new Integer(2));
Collections.sort(list); //为列表中的元素进行排序
for(Integer i:list)
System.out.print(i+" ");
以上程序的打印结果为:2 3 3 4
sort(List list,Comparator comparator):对List中的对象进行客户化排序,comparator参数指定排序方式。
3.2.7 把数组包装成List对象
List Arrays.asList(Object… o)
- 该方法可以将多个对象或一个对象数组转换成一个List集合;
- 实际返回的类型是Arrays的内部类,名字也叫ArrayList;
- Arrays.ArrayList是一个固定长度的集合,可以遍历,但不能增加、删除!
java.util.Arrays类的asList()方法能够把一个Java数组包装为一个List对象,这个List对象代表固定长度的数组。
所有对List对象的操作都会被作用到底层的Java数组。由于数组的长度不能改变,因此不能调用这种List对象的add()和remove()方法,否则会抛出java.lang.UnsupportedOperationException运行时异常。
String[] ss={"Tom","Mike","Jack"};
List<String> list=Arrays.asList(ss);
list.set(0,"Jane"); //合法,可以修改某个位置的元素
System.out.println(Arrays.toString(ss)); //打印[Jane, Mike, Jack]
//list.remove("Mike"); 运行时会抛出java.lang.UnsupportedOperationException
//list.add("Mary"); 运行时会抛出java.lang.UnsupportedOperationException
3.3 Queue(队列)
Queue用于模拟队列,它是一种先进先出(FIFO)的容器。
Queue(队列):集合中的对象按照先进先出的规则来排列。在队列的末尾添加元素,在队列的头部删除元素。可以有重复对象。双向队列则允许在队列的末尾和头部添加和删除元素。
多数人都有过在火车站售票大厅排队等待购票的经历。后加入的人排在队列的末尾,排在队列头部的人优先购票后离开队列。
从JDK5开始,用java.util.Queue接口来表示队列。队列的特点是向末尾添加元素,从队列头部删除元素,队列中允许有重复元素。
3.3.1 普通队列
(1)Queue接口具有以下加入元素的方法:
boolean add(E element)
boolean offer(E element)
以上两个方法都向队列的末尾添加元素,如果操作成功就返回true。参数的类型“E”为泛型类型。这两个方法的区别在于,如果队列已满,add()方法会抛出IllegalStateException,而offer()方法返回false。
(2)Queue接口具有以下删除元素的方法:
E remove()
E poll()
以上两个方法都会删除队列头部的元素。这两个方法的区别在于,如果队列为空,remove()方法抛出NoSuchElementException,而poll()方法返回null。
(3)Queue接口具有以下获取元素的方法:
E element()
E peek()
以上两个方法都会返回队列头部的元素,但不删除它。这两个方法的区别在于,如果队列为空,element()方法抛出NoSuchElementException,而peek()方法返回null。
方法表:
eg:
/**
* Queue
*/
public class QueueDemo1 {
public static void main(String[] args) {
Queue queue = new ArrayDeque();
queue.offer("Mary");
queue.offer("Lily");
queue.offer("Tony");
queue.offer("John");
queue.offer("Lucy");
System.out.println(queue);
// 返回头部(不删),队列为空时返回null.
System.out.println(queue.peek());
System.out.println(queue.peek());
System.out.println(queue);
// 返回头部(删除),队列为空时返回null.
while (queue.size() > 0) {
System.out.println(queue.poll());
}
System.out.println(queue);
}
}
打印结果:
3.3.2 Queue 实现类
- PriorityQueue是一种不标准的队列实现,它不是按照加入的顺序来保存元素,而是按照元素的大小排序来保存元素;
- Deque接口代表双端队列,它允许你从队列头/尾的任何一端,来进行入队/出队操作,甚至还支持入栈/出栈的操作;
- ArrayDeque、LinkedList是Deque接口的实现类,前者采用数组实现双端队列,而后者采用链表结构实现双端队列。
3.3.3 双向/端队列 Deque
Queue接口是单向队列,它有一个子接口Deque,表示双向队列。双向队列的特点是在队列的头部和末尾都可以添加或删除元素。
Deque代表双端队列,它允许你从两端来操作队列中的元素,并支持入栈及出栈操作。
Deque在Queue的基础上,增加了两类方法:
◼ 双端队列方法
(1)Deque接口具有以下向队列头部或末尾添加元素的方法:
void addFirst(E element)
void addLast(E element)
boolean offerFirst(E element)
boolean offerLast(E element)
如果队列已满,前两个方法抛出IllegalStateException,而后两个方法返回false。
(2)Deque接口具有以下从队列头部或末尾删除元素的方法:
E removeFirst()
E removeLast()
E pollFirst()
E pollLast()
如果队列为空,前两个方法抛出NoSuchElementException,而后两个方法返回null。
(3)Deque接口具有以下从队列头部或末尾获取元素(不会删除该元素)的方法:
E getFirst()
E getLast()
E peekFirst()
E peekLast()
如果队列为空,前两个方法抛出NoSuchElementException,而后两个方法返回null。
◼ 栈方法
void push(Object e) //入栈
Object pop() //出栈
eg:
import java.util.*;
public class DequeTester{
public static void main(String args[]){
Deque<String> queue=new ArrayDeque<String>();
queue.add("老二"); //向队列末尾添加元素
queue.addFirst("老大"); //向队列头部添加元素
queue.addLast("老三"); //向队列末尾添加元素
queue.add("老四"); //向队列末尾添加元素
System.out.print("遍历双向队列:");
for(String e:queue )
System.out.print(e+" ");
System.out.println("\n删除双向队列的最后一个元素:"+queue.removeLast());
}
}
打印结果如下:
遍历双向队列:老大 老二 老三 老四
删除双向队列的最后一个元素:老四
3.4 Map(映射)
◼ Map用于保存具有映射关系的数据(key-value/键-值);
◼ key和value之间存在单向一对一关系,通过指定的key,总能找到确定的value;
◼ Map的key不允许重复,同一个Map的任何两个key通过equals比较总返回false。
3.4.1 向Map集合中加入元素时,必须提供一对键对象和值对象
从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
eg:以下程序通过Map的put(Object key,Object value)方法向集合中加入元素,通过Map的get(Object key)方法来检索与键对象对应的值对象:
Map<String,String> map=new HashMap<String,String>();
map.put("1","Monday");
map.put("2","Tuesday");
map.put("3","Wendsday");
map.put("4","Thursday");
String day=map.get("2"); //day的值为“Tuesday”
3.4.2 遍历映射
Map<String,String> map=new HashMap<String,String>();
map.put("1","Mon.");
map.put("1","Monday");
map.put("one","Monday");
Set<Map.Entry<String,String>> set=map.entrySet(); //将Map用entrySet方法转换成Set,entrySet取出的是每一个元素即键值对
for(Map.Entry entry : set) //entry表示Map中的一对键与值
System.out.println(entry.getKey()+":"+entry.getValue());
由于第一次和第二次加入Map中的键对象都为“1”,因此第一次加入的值对象将被覆盖,Map集合中最后只有两个元素,分别为:
“1”对应“Monday”
“one”对应“Monday”
3.4.3 Map实现类
◼ HashMap是Map接口的典型实现;
◼ LinkedHashMap采用链表维护键值对的顺序(为key的顺序为主);
◼ Hashtable是古老的Map实现类(线程安全);
◼ Properties常被用于处理属性文件;
◼ SortedMap是Map的子接口(两种排序方式:自然排序和定制排序);
◼ TreeMap采用红黑树结构实现了SortedMap;
其他不常用类:
◼ EnumMap专门用于处理枚举类型的映射关系;
◼ IdentityHashMap与HashMap类似,但该类以严格相等的方式(key1==key2)认定key的相等;
◼ WeakHashMap与HashMap类似,但该类的key采用的是弱引用,而HashMap的key采用的是强引用。
eg:Map的一些常见操作
/**
* Map
*/
public class MapDemo1 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("John", 70);
map.put("Lily", 80);
map.put("Tony", 90);
System.out.println(map);
// value可以重复
map.put("Mary", 80);
System.out.println(map);
// key重复会导致覆盖
map.put("Lily", 85);
System.out.println(map); //Lily打印的还是80
map.remove("Mary"); //删除Mary
System.out.println(map);
System.out.println(map.get("Tony")); //打印90
System.out.println(map.containsKey("Lily")); //打印true
System.out.println(map.containsValue(85)); //打印true
// entrySet()得到所有的键值对
for (Object entry : map.entrySet()) {
Map.Entry e = (Map.Entry) entry;
System.out.println(e.getKey() + ": " + e.getValue()); //打印出键值对
}
// keySet()得到所有的键对
for (Object key : map.keySet()) {
System.out.println(key + ": "); //打印出键
System.out.println(key + ": " + map.get(key)); //打印出键值对
}
// values()得到所有的值对
for (Object value : map.values()) {
System.out.println(value); //打印出值
}
}
}
3.4.4 Map类的主要实现类之一:HashMap
◼ HashMap
- HashMap是非线程安全的,其性能高于Hashtable;
- HashMap允许使用null作为key/value,而Hashtable不允许存入null。
- HashMap按照哈希算法来存取键对象,有很好的存取性能,为了保证HashMap能正常工作,和HashSet一样,要求当两个键对象通过equals()方法比较为true时,这两个键对象的hashCode()方法返回的哈希码也一样。
3.4.5 Map类的主要实现类之一:TreeMap
TreeMap实现了SortedMap接口,能对键对象进行排序。和TreeSet一样,TreeMap也支持自然排序和客户化排序两种方式。
TreeMap是一个红黑树的数据结构,在存储键值对时,它按照key对键值对排序。
◼ 自然排序
- 对key进行比较,并根据key按照由小到大的顺序排列键值对;
- 所有的key应该是同一个类型,且必须实现Comparable接口;
◼ 定制排序 - 创建TreeMap时,传入一个Comparator类型的对象;
- 该对象负责对所有的key进行比较,此时不要求key实现Comparable接口。
eg:
/**
* TreeMap
*/
public class MapDemo4 {
public static void main(String[] args) {
TreeMap map = null;
// 自然排序
map = new TreeMap();
map.put("Lily", 90);
map.put("Mary", 80);
map.put("John", 70);
map.put("Lucy", 60);
map.put("Tony", 50);
map.put("Lisa", 40);
System.out.println(map);
System.out.println(map.firstKey() + ", " + map.lastKey());
System.out.println(map.lowerKey("Lucy") + ", " + map.higherKey("Lucy")); //Lucy前面和后面的key
System.out.println(map.headMap("Lucy"));//Lucy前面的Map(不包括Lucy)
System.out.println(map.tailMap("Lucy"));//Lucy后面的Map(包括Lucy)
System.out.println(map.subMap("Lily", "Mary"));//左闭右开
// 定制排序
map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
if (s1 == null && s2 == null) {
return 0;
} else if (s1 == null) {
return 1;
} else if (s2 == null) {
return -1;
} else {
// return s1.compareTo(s2) * (-1);
return s2.compareTo(s1);
}
}
});
map.put("Lily", 90);
map.put("Mary", 80);
map.put("John", 70);
map.put("Lucy", 60);
map.put("Tony", 50);
map.put("Lisa", 40);
System.out.println(map);
}
}
3.4.6 Map类的主要实现类之一:LinkedHashMap
◼ LinkedHashMap
- LinkedHashMap采用双向链表维护键值对的顺序;
- 相对于HashMap,LinkedHashMap在迭代时性能略高,在插入时性能略低。
3.4.7 Map类的主要实现类之一:Properties
properties文件一般位于resource文件下:
◼ 属性文件
存储属性名与属性值的文件,如“.ini”、“.properties”文件;
◼ Properties
擅长处理属性文件,可以很方便地实现对属性文件的读写操作;
提供load()方法加载属性文件,store()方法保存属性文件;
提供getProperty()方法读取某个属性,setProperty()方法修改某个属性。
eg:
/**
* Properties
*/
public class MapDemo3 {
public static void main(String[] args) {
Properties properties = new Properties();
try {
// 相对路径: 以编译后的项目的根目录为起点.
properties.load(MapDemo3.class.getClassLoader()
.getResourceAsStream("resources/db.properties"));
String s1 = properties.getProperty("username");
String s2 = properties.getProperty("password");
System.out.println(s1 + ", " + s2);
} catch (IOException e) {
throw new RuntimeException("加载资源文件失败!", e);
}
}
}
4. 线程安全的集合
在多线程环境中,可能会有多个线程同时操纵同一个集合,比如一个线程在为集合排序,而另一个线程在不断向集合中加入新的元素。
为了避免并发问题,可以采取以下几种解决措施:
(1)在程序中对可能导致并发问题的代码块进行同步。
(2)利用Collections的synchronizedXXX()方法获得原始集合的同步版本:
Collection synchronizedCollection=
Collections.synchronizedCollection(originalCollection);
程序中确保所有线程只对这个采取了同步措施的集合对象操作。
(3)如果集合只包含单个元素并且不允许被修改,可以用Collections的singletonXXX()方法来构造这样的集合,这可以避免集合被线程错误地修改,而且由于不必采取同步措施,可以提高并发性能。
(4)如果集合的元素不允许被修改,可以用Collections的unmodifiableXXX()方法来生成原始的集合视图,让线程只访问这个集合视图,这可以避免集合被线程错误地修改,而且由于不必采取同步措施,可以提高并发性能。
(5)利用Collections的synchronizedXXX()方法获得原始集合的同步版本后,如果一个线程操纵集合的同步版本,而另一个线程操纵原始的集合,那么仍然会导致并发问题。
为了避免这种情况,可以直接采用java.util.concurrent并发包提供的线程安全的集合,例如:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。这些集合的底层实现采用了复杂的算法,保证多线程访问集合时,既能保证线程之间的同步,又具有高效的并发性能。