目录
11.4.5 Iterator迭代器的快速失败(fail-fast)机制
1、ConcurrentModificationException异常
第11章 集合与迭代器
11.1 Collection集合
11.1.1 集合的概念
集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组既然都是容器,它们有啥区别呢?
-
数组的长度是固定的。集合的长度是可变的。
-
数组中可以存储基本数据类型值,也可以存储对象,而集合中只能存储对象
集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。
11.1.2 Collection接口
Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如 Set无序集合 和 List有序集合、Queue队列)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
Collection<E>是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
1、添加元素
(1)add(E obj):添加元素对象到当前集合中
(2)addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other
2、删除元素
(1) boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
(2)boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll
(3)boolean removeIf(Predicate<? super E> filter) :删除满足给定条件的此集合的所有元素。
(4)boolean retainAll(Collection<?> coll):从当前集合中删除两个集合中不同的元素,使得当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集,即this = this ∩ coll;
3、查询与获取元素
(1)boolean isEmpty():判断当前集合是否为空集合。
(2)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(3)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。
(4)int size():获取当前集合中实际存储的元素个数
(5)Object[] toArray():返回包含当前集合中所有元素的数组
11.2 List 有序集合
List集合代表一个元素有序、可重复的集合。集合中每个元素都有对应的顺序索引。
11.2.1 新增方法
作为Collection子接口,自然可以使用父接口的所有方法,当然也有新增的方法
方法名 | 说明 |
---|---|
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) | 通过索引和新的元素替换原有内容 |
toArray() | 将List转为对象数组 |
11.2.2 ArrayList
ArrayList 是List 接口的大小可变数组的实现。实现了所有可选列表操作, 并允许包括null 在内的所有元素。除了实现List 接口外, 此类还提供一些方法来操作内部用来存储列表的数组的大小( 此类大致上等同于 vector 类, 但 vector 是同步的) 。
ArrayList 底层使用数组实现,特点就和数组一致:查询速度快,增删速度慢。
11.2.3 LinkedList
LinkedList 和ArrayList不同,是一个链表结构。可当作堆栈、队列、双端队列 。链表是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。链表有单链表和双链表之分。
1、单向链表
单向链表的每一个节点包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。正如地下党的联络方式,一级一级,单线传递
2、双向链表
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的 prev 指针
3、删除元素
思考题:LinkedList是单向链表还是双向链表,为什么?
11.3 Set 无序集合
Set 集合就像是一个罐子,将数据丢进来就行,因为是丢进来的,Set集合通常不会记忆元素的添加顺序(无序)。 Set接口的方法和Collection基本相同,但Set不允许包含重复的元素。
下面使用HashSet和TreeSet两个类来演示。
11.3.1 HashSet
HashSet是Set接口的典型实现,大多时候使用Set集合的时候都是使用这个实现类。因为使用Hash算法来储存元素,因此有很好的存储于查找性能。
1、HashSet 的特点
不能保证元素的排列顺序, 顺序可能与添加顺序不同, 顺序也有可能发生变化 HashSet 不是同步的, 如果多个线程同时访问一个HashSet , 假设有两个或者两个以上线程同时修改了HashSet 集合时, 则必须通过代码来保证其同步 集合元素值可以是null
当向HashSet 集合中存入一个元素时,会调用该对象的hashCode() 方法来得到该对象的hashCode 值,然后根据该hashCode 值决定该对象在HashSet 中的存储位置。
如果有两个元素通过equals() 方法比较返回true,但它们的hashCode()方法返回值不相等, HashSet 将会把它们存储在不同的位置,依然可以添加成功。
也就是说 HashSet 集合判断两个元素相等的标准是两个对象通过equals() 方法比较相等, 并且两个对象的hashCode() 方法返回值也相等。
Set 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 );
}
11.3.2 TreeSet
介绍Set集合的时候说过,Set基本是无序的,但凡事总有例外,TreeSet就是Set中的例外,它储存的数据是有序的!
Set 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 );
}
11.4 Iterator迭代器
11.4.1 Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。Iterator
接口也是Java集合中的一员,但它与Collection
、Map
接口有所不同,Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
-
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
下面介绍一下迭代的概念:
-
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法如下:
-
public E next()
:返回迭代的下一个元素。 -
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
接下来我们通过案例学习如何使用Iterator迭代集合中元素:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIterator {
public void test01(){
Collection coll = new ArrayList();
coll.add("张三");
coll.add("李四");
coll.add("王五");
Iterator iterator = coll.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
}
public void test02(){
Collection coll = new ArrayList();
coll.add("张三");
coll.add("李四");
coll.add("王五");
Iterator iterator = coll.iterator();//获取迭代器对象
while(iterator.hasNext()) {//判断是否还有元素可迭代
System.out.println(iterator.next());//取出下一个元素
}
}
}
提示:在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
11.4.2 迭代器的实现原理
我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。
在调用Iterator的next方法之前,迭代器指向第一个元素,当第一次调用迭代器的next方法时,返回第一个元素,然后迭代器的索引会向后移动一位,指向第二个元素,当再次调用next方法时,返回第二个元素,然后迭代器的索引会再向后移动一位,指向第三个元素,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
11.4.3 Iterable接口与Iterator接口
Java5(JDK1.5)中增加了java.lang.Iterable接口,实现这个接口允许对象成为 "foreach" 语句的目标。 Java 5时Collection接口继承了java.lang.Iterable接口,因此Collection系列的集合就可以直接使用foreach循环遍历。
java.lang.Iterable接口的抽象方法:
-
public Iterator iterator(): 获取对应的迭代器,用来遍历数组或集合中的元素的。
从上面的方法定义可以看出,其实foreach循环其实就是使用Iterator迭代器来完成元素的遍历的。
import java.util.ArrayList;
import java.util.Collection;
public class TestForeach {
public void test01(){
Collection coll = new ArrayList();
coll.add("张三");
coll.add("李四");
coll.add("王五");
for (Object o : coll) {
System.out.println(o);
}
}
}
11.4.4 使用Iterator迭代器删除元素
java.util.Iterator迭代器中有一个方法:
void remove() ;
那么,既然Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?
因为在JDK1.8之前Collection接口没有removeIf方法,即无法根据条件删除。
例如:要删除以下集合元素中的偶数
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIteratorRemove {
public void test01(){
Collection coll = new ArrayList();
coll.add(1);
coll.add(2);
coll.add(3);
coll.add(4);
// coll.remove(?)//没有removeIf方法无法实现删除“偶数”
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
Integer element = (Integer) iterator.next();
if(element%2 == 0){
iterator.remove();
}
}
System.out.println(coll);
}
}
11.4.5 Iterator迭代器的快速失败(fail-fast)机制
如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情。因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制。
1、ConcurrentModificationException异常
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestConcurrentModificationException {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("haha");
coll.add("mysql");
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
String str = (String)iterator.next();
if(str.contains("a")){
coll.remove(str);//foreach遍历集合过程中,调用集合的remove方法
}
}
/*for (Object o : coll) {
String str = (String) o;
if(str.contains("a")){
coll.remove(o);//foreach遍历集合过程中,调用集合的remove方法
}
}*/
}
}
2、modCount变量
那么迭代器如何实现快速失败(fail-fast)机制的呢?
-
在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。
-
当我们给集合添加和删除操作时,会导致modCount++。
-
然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:
int expectedModCount = modCount;
,并且在迭代器每次next()迭代元素时,都要检查expectedModCount != modCount
,如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。
下面以AbstractList<E>和ArrayList.Itr迭代器为例进行源码分析:
AbstractList<E>类中声明了modCount变量:
/** * The number of times this list has been <i>structurally modified</i>. * Structural modifications are those that change the size of the * list, or otherwise perturb it in such a fashion that iterations in * progress may yield incorrect results. * * <p>This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * <i>fail-fast</i> behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * * <p><b>Use of this field by subclasses is optional.</b> If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */ protected transient int modCount = 0;
翻译解释:modCount是这个list被结构性修改的次数。子类使用这个字段是可选的,如果子类希望提供fail-fast迭代器,它仅仅需要在add(int, E),remove(int)方法(或者它重写的其他任何会结构性修改这个列表的方法)中添加这个字段。调用一次add(int,E)或者remove(int)方法时必须且仅仅给这个字段加1,否则迭代器会抛出伪装的ConcurrentModificationExceptions错误。如果一个实现类不希望提供fail-fast迭代器,则可以忽略这个字段。
11.5 Collections工具类
参考操作数组的工具类:Arrays。
Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法:
-
public static <T> boolean addAll(Collection<? super T> c,T... elements)将所有指定元素添加到指定 collection 中。
-
public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
-
public static <T> int binarySearch(List<? extends T> list,T key,Comparator<? super T> c)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。
-
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
-
public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者
-
public static void reverse(List<?> list)反转指定列表List中元素的顺序。
-
public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
-
public static <T extends Comparable<? super T>> void sort(List<T> list)根据元素的自然顺序对指定 List 集合元素按升序排序
-
public static <T> void sort(List<T> list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
-
public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数
-
public static <T> void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中
-
public static <T> boolean replaceAll(List<T> list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值
-
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
-
Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。
11.6Map
11.6.1 概述
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map<K,V>
接口。
我们通过查看Map
接口描述,发现Map<K,V>
接口下的集合与Collection<E>
接口下的集合,它们存储数据的形式不同。
-
Collection
中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。 -
Map
中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。 -
Collection
中的集合称为单列集合,Map
中的集合称为双列集合。 -
需要注意的是,
Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值(这个值可以是单个值,也可以是个数组或集合值)。
11.6.2 Map常用方法
1、添加操作
-
V put(K key,V value)
-
void putAll(Map<? extends K,? extends V> m)
2、删除
-
void clear()
-
V remove(Object key)
3、元素查询的操作
-
V get(Object key)
-
boolean containsKey(Object key)
-
boolean containsValue(Object value)
-
boolean isEmpty()
4、元视图操作的方法:
-
Set<K> keySet()
-
Collection<V> values()
-
Set<Map.Entry<K,V>> entrySet()
5、其他方法
-
int size()
import java.util.HashMap;
public class TestMapMethod {
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("小好", "小谷");
map.put("钥匙", "锁");
map.put("上联", "下联");
map.put("许仙", "白蛇");
System.out.println(map);
//String remove(String key)
System.out.println(map.remove("小好"));
System.out.println(map);
// 想要查看 黄晓明的媳妇 是谁
System.out.println(map.get("钥匙"));
System.out.println(map.get("上联"));
}
}
tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
11.6.3 Map集合的遍历
Collection集合的遍历:(1)foreach(2)通过Iterator对象遍历
Map的遍历,不能支持foreach,因为Map接口没有继承java.lang.Iterable<T>接口,也没有实现Iterator iterator()方法。只能用如下方式遍历:
(1)分开遍历:
-
单独遍历所有key
-
单独遍历所有value
(2)成对遍历:
-
遍历的是映射关系Map.Entry类型的对象,Map.Entry是Map接口的内部接口。每一种Map内部有自己的Map.Entry的实现类。在Map中存储数据,实际上是将Key---->value的数据存储在Map.Entry接口的实例中,再在Map集合中插入Map.Entry的实例化对象,如图示:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TestMapIterate {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("许仙", "白娘子");
map.put("董永", "七仙女");
map.put("牛郎", "织女");
map.put("许仙", "小青");
System.out.println("所有的key:");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key);
}
System.out.println("所有的value:");
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
System.out.println("所有的映射关系");
Set<Map.Entry<String,String>> entrySet = map.entrySet();
for (Map.Entry<String,String> entry : entrySet) {
// System.out.println(entry);
System.out.println(entry.getKey()+"->"+entry.getValue());
}
}
}
11.6.4 Map的实现类们
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中HashMap是 Map 接口使用频率最高的实现类。
1、HashMap和Hashtable
HashMap和Hashtable都是哈希表。HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
-
Hashtable是线程安全的,任何非 null 对象都可以用作键或值。
-
HashMap是线程不安全的,并允许使用 null 值和 null 键。
示例代码:添加员工姓名为key,薪资为value
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
public class TestHashMap {
public void test01(){
HashMap<String,Double> map = new HashMap<>();
map.put("张三", 10000.0);
//key相同,新的value会覆盖原来的value
//因为String重写了hashCode和equals方法
map.put("张三", 12000.0);
map.put("李四", 14000.0);
//HashMap支持key和value为null值
String name = null;
Double salary = null;
map.put(name, salary);
Set<Map.Entry<String, Double>> entrySet = map.entrySet();
for (Map.Entry<String, Double> entry : entrySet) {
System.out.println(entry);
}
}
public void test02(){
Hashtable<String,Double> map = new Hashtable<>();
map.put("张三", 10000.0);
//key相同,新的value会覆盖原来的value
//因为String重写了hashCode和equals方法
map.put("张三", 12000.0);
map.put("李四", 14000.0);
//Hashtable不支持key和value为null值
/*String name = null;
Double salary = null;
map.put(name, salary);*/
Set<Map.Entry<String, Double>> entrySet = map.entrySet();
for (Map.Entry<String, Double> entry : entrySet) {
System.out.println(entry);
}
}
}
2、LinkedHashMap
LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
示例代码:添加员工姓名为key,薪资为value
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class TestLinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<String,Double> map = new LinkedHashMap<>();
map.put("张三", 10000.0);
//key相同,新的value会覆盖原来的value
//因为String重写了hashCode和equals方法
map.put("张三", 12000.0);
map.put("李四", 14000.0);
//HashMap支持key和value为null值
String name = null;
Double salary = null;
map.put(name, salary);
Set<Map.Entry<String, Double>> entrySet = map.entrySet();
for (Map.Entry<String, Double> entry : entrySet) {
System.out.println(entry);
}
}
}
3、TreeMap
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
代码示例:添加员工姓名为key,薪资为value
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.junit.Test;
public class TestTreeMap {
@Test
public void test1() {
TreeMap<String,Integer> map = new TreeMap<>();
map.put("Jack", 11000);
map.put("Alice", 12000);
map.put("zhangsan", 13000);
map.put("haogu", 14000);
map.put("Lucy", 15000);
//String实现了Comparable接口,默认按照Unicode编码值排序
Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
System.out.println(entry);
}
}
@Test
public void test2() {
//指定定制比较器Comparator,按照Unicode编码值排序,但是忽略大小写
TreeMap<String,Integer> map = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
map.put("Jack", 11000);
map.put("Alice", 12000);
map.put("zhangsan", 13000);
map.put("haogu", 14000);
map.put("Lucy", 15000);
Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
System.out.println(entry);
}
}
}
4、Properties
Properties 类是 Hashtable 的子类,Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。
代码示例:
import org.junit.Test;
import java.util.Properties;
public class TestProperties {
@Test
public void test01() {
Properties properties = System.getProperties();
String fileEncoding = properties.getProperty("file.encoding");//当前源文件字符编码
System.out.println("fileEncoding = " + fileEncoding);
}
@Test
public void test02() {
Properties properties = new Properties();
properties.setProperty("user","lee");
properties.setProperty("password","123456");
System.out.println(properties);
}
}
11.6.5 Set集合与Map集合的关系
Set的内部实现其实是一个Map。即HashSet的内部实现是一个HashMap,TreeSet的内部实现是一个TreeMap,LinkedHashSet的内部实现是一个LinkedHashMap。
部分源代码摘要:
HashSet源码:
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
//这个构造器是给子类LinkedHashSet调用的
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
LinkedHashSet源码:
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);//调用HashSet的某个构造器
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);//调用HashSet的某个构造器
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);//调用HashSet的某个构造器
addAll(c);
}
TreeSet源码:
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
但是,咱们存到Set中只有一个元素,又是怎么变成(key,value)的呢?
以HashSet中的源码为例:
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
原来是,把添加到Set中的元素作为内部实现map的key,然后用一个常量对象PRESENT对象,作为value。
这是因为Set的元素不可重复和Map的key不可重复有相同特点。Map有一个方法keySet()可以返回所有key。