Java集合
集合框架图
java.lang.Iterable<T> : 实现这个接口允许对象成为 "foreach" 语句的目标
| |方法
| |------- Iterator<T> iterator() 返回一个在一组 T 类型的元素上进行迭代的迭代器
|
|子类
|--- java.util.Collection<E> Collection 层次结构 中的根接口。
| | Collection 表示一组对象,这些对象也称为 collection 的元素
| |
| |
| |子类
| |-------- java.util.List<E>
| | |
| | |------- java.util.ArrayList<E>实现类
| | |------- java.util.LinkedList<E>实现类
| | |------- java.util.Vector<E>实现类
| | | |子类
| | | |------- java.util.Stack<E>
| |子类
| |-------- java.util.Queue<E>
| | |子类
| | |------ java.util.Deque<E>
| | | |实现类
| | | |---- java.util.LinkedList<E>
| |
| | 子类
| |-------- java.util.Set<E>
| | |------- java.util.HashSet<E>实现类
| | |------- java.util.TreeSet<E>
| | |实现NavigableSet,NavigableSet继承SortedSet,SortedSet继承Set
| | |------- java.util.LinkedHashSet<E>实现类并继承HashSet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
java.util.Map<K,V>
|
|------ java.util.HashMap<K,V>实现类
|
|------ java.util.Hashtable<K,V>实现类
| |子类
| |------ java.util.Properties
|
|
|--- java.util.SortedMap<K,V>子类
| |子类
| |-------- java.util.NavigableMap<K,V>
| | |实现类
| | |------- java.util.TreeMap<K,V>
|
|
|--- java.util.concurrent.ConcurrentMap<K,V>子类
| |实现类
| |-------- java.util.concurrent.ConcurrentHashMap<K,V>
Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)。
Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承。Collection继承的是类 Iterable,Iterable里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器。Collections中containsAll,contains,removeAll,remove是根据equals方法定义的。
List、Set和Queue继承Collection。
Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。
HashMap和Hashtable实现接口Map,SortedMap和ConcurrentMap继承接口Map。
List接口特点
1、有序的 collection。
2、可以对列表中每个元素的插入位置进行精确地控制。
3、可以根据元素的索引访问元素,并搜索列表中的元素。
4、列表通常允许重复的元素。
5、允许存在 null 元素。
6、实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。
List排序
1、按照自然顺序排序 : sort( List<T> list )
自然顺序: 实现Comparable接口才能自然排序。 java.lang.Comparable<T>Comparable 接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,Comparable类的 compareTo 方法被称为它的自然比较方法。
public int compareTo( T o ) 如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
package ecut.collection; // 声明 java.lang.Comparable 接口时,<T> 表示类型参数 ( "形参") // 在 实现接口 时,<Panda> 也是类型参数 ( "实参" ) public class Panda implements Comparable<Panda>{ private Integer id; private String name; public Panda(Integer id, String name) { super(); this.id = id; this.name = name; } //确定比较规则,Integer id比较时不能用==,应该用equals方法 @Override public int compareTo( Panda another ) { if( this.id != null && another.id != null ) { return this.id - another.id ; // this.id 和 another.id 不能是负数 /* if( this.id < another.id ){ return -1 ; } else if( this.id.equals( another.id ) ) { return 0 ; } else { return 1 ; } */ } return 0; } @Override public String toString() { return "(id=" + id + ", name=" + name + ")"; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package ecut.collection; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class SortListTest1 { public static void main(String[] args) { List<Panda> list = new ArrayList<>(); list.add( new Panda( 100 , "桂花" ) ); list.add( new Panda( 88 , "花菜" ) ); list.add( new Panda( 200 , "团团" ) ); list.add( new Panda( 99 , "圆圆" ) ); System.out.println( list ); Collections.sort( list );//列表中的所有元素都必须实现 Comparable 接口。 System.out.println( list );//重写了toString方法,因此可以直接输出。 } }
运行结果如下:
[(id=100, name=桂花), (id=88, name=花菜), (id=200, name=团团), (id=99, name=圆圆)]
[(id=88, name=花菜), (id=99, name=圆圆), (id=100, name=桂花), (id=200, name=团团)]
2、按照比较器进行排序: sort(List<T> list, Comparator<? super T> c)
比较器: java.util.Comparator<T> 强行对某个对象 collection 进行整体排序 的比较函数
int compare ( T o1 , T o2 ) 比较用来排序的两个参数:根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
package ecut.collection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class SortListTest2 { public static void main(String[] args) { List<Panda> list = new ArrayList<>(); // "菱形"语法 list.add( new Panda( 100 , "桂花" ) ); list.add( new Panda( 88 , "花菜" ) ); list.add( new Panda( 200 , "团团" ) ); list.add( new Panda( 99 , "圆圆" ) ); System.out.println( list ); // 创建一个用来比较 Panda 类型对象的 比较器 ( 裁判 ) Comparator<Panda> c = new Comparator<Panda>(){ @Override public int compare(Panda o1, Panda o2) { if( o1 != null && o2 != null ) { String name1 = o1.getName() ; String name2 = o2.getName(); if( name1 != null ){ // String 类实现了 Comparable 接口的 compareTo 方法 return name1.compareTo( name2 ); } } return 0; } };//Comparator是接口因此要用匿名内部类实现抽象的方法
Collections.sort( list , c ); // 根据给定的比较器来排序 System.out.println( list ); } }
运行结果如下:
[(id=100, name=桂花), (id=88, name=花菜), (id=200, name=团团), (id=99, name=圆圆)]
[(id=200, name=团团), (id=99, name=圆圆), (id=100, name=桂花), (id=88, name=花菜)]
List接口主要实现类
1、java.util.ArrayList<E> : List 接口的大小可变数组的实现类
- ArrayList 内部基于 数组 存储 各个元素。
- 所谓大小可变数组,是指当 数组容量不足以存放新的元素时,创建新数组,并将原数组中的内容复制过来。
ArrayList类的add方法测试案例:
package ecut.collection; import java.util.ArrayList; import java.util.Arrays; public class ArrayListTest1 { public static void main(String[] args) { int[] x = { 1 , 2 }; System.out.println( Arrays.toString( x ) ); // x.length = 10 ; // The final field x.length cannot be assigned // x[ 2 ] = 3 ; // ArrayIndexOutOfBoundsException ArrayList<String> list = new ArrayList<>(0); // JDK 1.7 开始支持 "菱形" 语法 System.out.println( list ); // list.toString(),list重写了toString方法,因此可以直接输出。 list.add( "hello" ); System.out.println( list ); } }
源码:
/** * 向列表的尾部添加指定的元素(可选操作)。 * * @param e 要添加到列表的元素 * @return <tt>true</tt> (根据 Collection.add(E) 的规定) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
运行结果如下:
[1, 2]
[]
[hello]
add()方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。每个ArrayList实例都有一个容量(Capactity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入之前可以调用ensureCapacity()方法来增加ArrayList容量已提高插入效率确保容量可用后在末尾添加指定元素。
ArrayList类的remove方法测试案例:
package ecut.collection; import java.util.ArrayList; public class ArrayListTest2 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add( 2 ); // auto-boxing : 2 ---> Integer.valueOf( 2 ) list.add( 200 ) ; list.add( 2 ) ; list.add( 20 ); System.out.println( list ); // T remove( int index ) 是 List 接口定义的方法 Integer removed = list.remove( 2 ); // 根据索引删除,而不是根据 Integer 对象删除 System.out.println( "被删除的元素:" + removed ); System.out.println( list ); Object o = 2 ; // auto-boxing // boolean remove( Object o ) 是 Collection 接口定义的方法 boolean result = list.remove( o ) ; // 根据元素来删除 System.out.println( result ); System.out.println( list ); } }
运行结果如下:
[2, 200, 2, 20] 被删除的元素:2 [2, 200, 20] true [200, 20]
自动装箱将基本数据类型包装成Integer对象放入list里。T remove(int index):删除的是下标位置的对象并返回(List接口中定义的方法),boolean remove(Object o):删除对象(Colletion接口中都有的)。
2、java.util.LinkedList<E> :List 接口的实现类和Queue接口子类Deque的实现类
- 内部基于链表实现,增删快、迭代快,随机访问能力较差。
- 链表中的每个节点都是 LinkedList.Node 类型的对象。
- LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。
- 这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
- LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。
LinkedList类的add方法测试案例:
package ecut.collection; import java.util.LinkedList; public class LinkedListTest { public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add( "猴哥" ); list.add( "二师兄" ); list.add( "老沙" ); System.out.println( list ); System.out.println( list.get( 2 ) ); list.add( 1 , "白龙马" ); System.out.println( list ); } }
源码:
private static class Node<E> { E item; // 表示当前节点存放的数据 Node<E> next; // 指向下一个节点的指针 Node<E> previous; // 指向前一个节点的指针 Node(Node<E> previous, E element, Node<E> next) { this.item = element; this.next = next; this.previous = previous; } } /** * 将指定元素添加到此列表的结尾。 * * <p> * 此方法等效于{@link #addLast}. * * @param 要添加到此列表的元素 * @return {@code true} (根据 Collection.add(E) 的规定) */ public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
运行结果如下:
[猴哥, 二师兄, 老沙]
老沙
[猴哥, 白龙马, 二师兄, 老沙]
add方法调用linkLast 方法去实现,找到最后的节点,创建新的节点以最后的节点为前节点,新节点作为最后的节点。 list.get( 2 )链表没有索引的说法,不是直接访问2的位置而是实际遍历整个链表去查找,因此随机访问能力较差。插入和删除的效率比较高。
3、java.util.Vector<E> :List 接口的内部用数组存放元素的实现类
- 内部也是用数组存放元素。
- 与 ArrayList 不同的是 扩容方式不同,Vector 每次增长的容量是固定的,大部分方法都被 synchronized 关键字修饰。
- Vector 是线程安全的。
Vector类的测试案例:
package ecut.collection; import java.util.Enumeration; import java.util.Vector; public class VectorTest { public static void main(String[] args) { // 创建一个 Vector 实例,其初始容量为 10 ,容量的增量为 5 Vector<String> v = new Vector<>( 10 , 5 ); v.add( "hello" ); v.addElement( "world" ); System.out.println( v ); v.add( 1 , "你好" ); System.out.println( v ); v.insertElementAt( "世界" , 2 ); System.out.println( v ); Enumeration<String> e = v.elements();//类似迭代器 while( e.hasMoreElements() ) { String s = e.nextElement(); System.out.println( s ); } } }
源码:
/** * 使用指定的初始容量和容量增量构造一个空的向量。 * * @param initialCapacity 向量的初始容量 * @param capacityIncrement 当向量溢出时容量增加的量 * @throws IllegalArgumentException 如果指定的初始容量为负数 */ public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
运行结果如下:
[hello, world]
[hello, 你好, world]
[hello, 你好, 世界, world]
hello
你好
世界
world
4、java.util.Stack<E>:List 接口实现类Vector的子类
- 表示堆栈。
- 栈的特点: 后进先出。
Stack类的测试案例:
package ecut.collection; import java.util.Stack; public class StackTest { public static void main(String[] args) { Stack<String> stack = new Stack<>(); System.out.println( stack.empty() ); stack.push( "first" ); stack.push( "second" ); stack.push( "third" ); System.out.println( stack ); String top = stack.peek(); System.out.println( "top : " + top ); System.out.println( stack ); top = stack.pop(); System.out.println( "top : " + top ); System.out.println( stack ); System.out.println( stack.empty() ); int index = stack.search( "first" ); // 返回对象在堆栈中的位置,以 1 为基数(只有jdbc和stack.search以 1 为基数) System.out.println( index ); } }
运行结果如下:
true [first, second, third] top : third [first, second, third] top : third [first, second] false 2
Queue接口特点
1、先进先出 ( FIFO , First In , First Out )。
2、Queue接口子类Deque的实现类LinkedList。
3、除了基本的 Collection
操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。
Queue类可能会抛出异常的测试案例:
package ecut.collection;
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest1 {
public static void main(String[] args) {
Queue<String> queue= new LinkedList<>();
queue.add( "林冲" );
queue.add( "晁盖" );
queue.add( "武松" );
System.out.println( queue );
//queue.clear();
//当无法获取到元素时,element 方法抛出 NoSuchElementException
String head = queue.element() ; // 获取队列头部的元素,但不删除
System.out.println( head );
System.out.println( queue );
//queue.clear();
//当无法获取到元素时,remove 方法抛出 NoSuchElementException
head = queue.remove(); // 获取并移除队列头部元素
System.out.println( head );
System.out.println( queue );
}
}
运行结果如下:
[林冲, 晁盖, 武松]
林冲
[林冲, 晁盖, 武松]
林冲
[晁盖, 武松]
add(e)插入,remove()移除,element()检查当无法获取到元素时,方法抛出 NoSuchElementException异常。
Queue类返回一个特殊值的测试案例:
package ecut.collection;
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest2 {
public static void main(String[] args) {
Queue<String> queue= new LinkedList<>();
queue.offer( "林冲" );
queue.offer( "晁盖" );
queue.offer( "武松" );
System.out.println( queue );
queue.clear();
// 当无法获取元素时,返回 null,不会抛出异常
String head = queue.peek() ; // 获取队列头部的元素,但不删除
System.out.println( head );
System.out.println( queue );
// 当无法获取元素时,返回 null,不会抛出异常
head = queue.poll(); // 获取并移除队列头部元素
System.out.println( head );
System.out.println( queue );
}
}
运行结果如下:
[林冲, 晁盖, 武松]
null
[]
null
[]
offer(e)插入,poll()移除,peek()检查当无法获取元素时,返回 null,不会抛出异常。
Queue接口主要实现类
1、java.util.Deque<E> : Queue接口的的实现类,表示双端队列
- 一个线性 collection,支持在两端插入和移除元素。
- 此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作),共12个方法。
- 在将双端队列用作队列时,将得到 FIFO(先进先出)行为。
- 继承Queue的6个方法以及和栈的3个方法,共21个方法需了解。
- 方法:
由于继承了Queue,所以Deque拥有Queue的方法,因此以下方法等价:
add(e) <--> addLast(e)
offer(e) <--> offerLast(e)
remove() <--> removeFirst()
poll() <--> pollFirst()
element() <--> getFirst()
peek() <--> peekFirst()
也可以用于Stack,因此以下方法等价:
push(e) <--> addFirst(e)
pop() <--> removeFirst()
peek() <--> peekFirst()
Deque测试案例:
package ecut.collection; import java.util.Deque; import java.util.LinkedList; public class DequeTest1 { public static void main(String[] args) { // 以 输出 deque 的字符串形式 的 "左侧" 为头 Deque<String> deque = new LinkedList<>(); //将指定的元素插入此双端队列的末尾 deque.offerLast( "曹操" ) ; deque.offerLast( "曹丕" ); deque.offerLast( "曹爽" ); deque.offerLast( "司马炎" ); System.out.println( deque ); //获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。 String head = deque.pollFirst(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); head = deque.pollFirst(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); head = deque.pollFirst(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); } }
运行结果如下:
[曹操, 曹丕, 曹爽, 司马炎]
移除队列头: 曹操
[曹丕, 曹爽, 司马炎]
移除队列头: 曹丕
[曹爽, 司马炎]
移除队列头: 曹爽
[司马炎]
offerLast插入的元素作为最后一个
运行过程:
曹操
曹操, 曹丕
曹操, 曹丕, 曹爽
曹操, 曹丕, 曹爽, 司马炎
以 输出 deque 的字符串形式 的 "左侧" 为头(第一个进入的元素)
Deque测试案例:
package ecut.collection; import java.util.Deque; import java.util.LinkedList; public class DequeTest2 { public static void main(String[] args) { // 以 输出 deque 的字符串形式 的 "右侧" 为头 Deque<String> deque = new LinkedList<>(); //将指定的元素插入此双端队列的开头 deque.offerFirst( "曹操" ) ; deque.offerFirst( "曹丕" ); deque.offerFirst( "曹爽" ); deque.offerFirst( "司马炎" ); System.out.println( deque ); //获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。 String head = deque.pollLast(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); head = deque.pollLast(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); head = deque.pollLast(); System.out.println( "移除队列头: " + head ); System.out.println( deque ); } }
运行结果如下:
[司马炎, 曹爽, 曹丕, 曹操]
移除队列头: 曹操
[司马炎, 曹爽, 曹丕]
移除队列头: 曹丕
[司马炎, 曹爽]
移除队列头: 曹爽
[司马炎]
offerFirst插入的元素作为第一个
运行过程:
曹操
曹丕, 曹操
曹爽, 曹丕, 曹操
司马炎, 曹爽, 曹丕, 曹操
以 输出 deque 的字符串形式 的 "右侧" 为头(第一个进入的元素)
2、java.util.LinkedList<E> :Queue接口的子类Deque的实现类
- 内部基于链表实现,增删快、迭代快,随机访问能力较差。
- 链表中的每个节点都是 LinkedList.Node 类型的对象。
- LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。
- 这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。双端队列也可用作 LIFO(后进先出)堆栈。
- LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。
LinkedList 当作 "栈" 来使用测试案例:
package ecut.collection; import java.util.LinkedList; /** * 将 LinkedList 当作 "栈" 来使用 * 栈: 后进先出 ( Last In , First Out , LIFO ) */ public class LinkedListStackTest { public static void main(String[] args) { LinkedList<String> s = new LinkedList<>(); s.push( "Java" ); s.push( "C++" ); s.push( "Go" ); System.out.println( s ); String top = s.peek() ; // 检查栈顶元素 ( 只获取不删除 ) System.out.println( top ); System.out.println( s ); top = s.pop(); // 弹出栈顶元素 ( 获取并移除 ) System.out.println( top ); System.out.println( s ); top = s.pop(); // 弹出栈顶元素 ( 获取并移除 ) System.out.println( top ); System.out.println( s ); top = s.pop(); // 弹出栈顶元素 ( 获取并移除 ) System.out.println( top ); System.out.println( s ); } }
运行结果如下:
[Go, C++, Java] Go [Go, C++, Java] Go [C++, Java] C++ [Java] Java []
Set接口特点
1、最接近数学中的 "集" 的概念。
2、元素不重复 ( 必须通过元素的 equals 方法来判断是否存在重复元素 )。
3、并且最多包含一个 null 元素(可能有限制)。
Set接口主要实现类
1、java.util.HashSet<E> : Set接口的的实现类
- 由 哈希表(实际上是一个 HashMap 实例)支持。
- 元素可以是 null。
- 元素存放的位置跟添加顺序无关 ( 元素存放的位置 跟 element.hashCode() 有关 )。
- 不支持排序(根据hashcode来确定位置的,所以位置不能改变,因此不支持排序)。
HashSet测试案例:
package ecut.collection; import java.util.HashSet; public class HashSetTest { public static void main(String[] args) { HashSet<String> set = new HashSet<>(); set.add( "张三丰" ); set.add( "张翠山" ); set.add( "殷素素" ); set.add( "张无忌" ); set.add( "谢逊" ); set.add( "赵敏" ); set.add( "张三丰" ); set.add( null ); System.out.println( set ); } }
源码:
private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
HashMap实现的因为key 不能重复因此Set元素不重复,value为固定的PRESENT。
运行结果如下:
[null, 殷素素, 张翠山, 张三丰, 谢逊, 赵敏, 张无忌]
2、Java.util.TreeSet<E>: 基于 TreeMap 来实现 Set 接口 的实现类
- NavigableSet<E>的实现类,NavigableSet<E>继承SortedSet<E>,SortedSet<E>继承Set<E>。
- TreeSet基于 TreeMap 来实现TreeMap 内部是基于 红黑树( Red-Black tree)。
- SortedSet是有顺序因此TreeSet有顺序 ( 根据元素的自然顺序,元素得实现Comparable接口或比较器排序后存放元素)。
- 元素不可以是 null(每添加一个元素都得进行一次排序调用compareTo方法,一次不能为null)。
- 元素存放的位置 跟 添加顺序无关。
TreeSet测试案例:
package ecut.collection; import java.util.TreeSet; public class TreeSetTest1 { public static void main(String[] args) { // 如果构造方法没有指定比较器,则根据元素的 自然顺序 排序 // java.lang.String 支持 自然比较 TreeSet<String> ts = new TreeSet<>(); ts.add( "张三丰" ); ts.add( "张翠山" ); ts.add( "殷素素" ); ts.add( "张无忌" ); ts.add( "谢逊" ); ts.add( "赵敏" ); ts.add( "张三丰" ); System.out.println( ts ); } }
源码:
public TreeSet() { this(new TreeMap<E,Object>()); }
实际上是创建一个TreeMap,因此TreeSet的元素和treeMap的key特点一样。
运行结果如下:
[张三丰, 张无忌, 张翠山, 殷素素, 谢逊, 赵敏]
3、java.util.LinkedHashSet<E>: Set接口的的实现类
- 继承HashSet和LinkedList相似。
- 内部是基于链表。
- 不能重复。
Map接口特点
1、将键映射到值的对象 ( Map 集合中存放的是 key-value 对 ( Map.Entry ) )。
2、一个映射不能包含重复的键 ( Map 集合中的 key 不能重复 )。
3、每个键最多只能映射到一个值 ( Map 集合中的 key 只能对应一个 值 )。
4、几乎所有的map都具有三个视图所有的 key 组成的 Set 集合,所有的 value 组成的 Collection 集合和所有的 entry 对应的 Set 集合
Map接口主要方法
V put( K key, V value ) : 将指定的值(value)与此映射中的指定键(key)关联。
V get( Object key ) : 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
boolean containsKey(Object key): 如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true。。
void clear(): 从此映射中移除所有映射关系(可选操作)。
boolean isEmpty(): 如果此映射未包含键-值映射关系,则返回 true。
int size() :返回此映射中的键-值映射关系数。
V remove(Object key):如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
void putAll( Map<? extends K,? extends V> m ) :从指定映射中将所有映射关系复制到此映射中(可选操作)。
Set<K> keySet() :返回所有的 key 组成的 Set 集合
Collection<V> values(): 返回所有的 value 组成的 Collection 集合
Set<Map.Entry<K,V>> entrySet(): 返回所有的 entry 对应的 Set 集合
Map接口主要方法的测试案例:
package ecut.collection;
import java.util.HashMap;
import java.util.Map;
public class MapTest1 {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
//以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
Integer value = map.put( "Java从入门到精通" , 98 );
System.out.println( "value : " + value );
// 将指定的值(value)与此映射中的指定键(key)关联
value = map.put( "Java从入门到精通" , 108 );
System.out.println( "value : " + value );
// 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
System.out.println( map.get( "Java从入门到精通" ) );
System.out.println( map.containsKey( "Java从入门到精通" ) );
System.out.println( map.containsKey( "Oracle从入门到精通" ) );
System.out.println( map.containsValue( 108 ) );
map.put( "C++大学教程" , 108 );
map.put( "A语言大学教程" , 108 );
System.out.println( map );
}
}
运行结果如下:
value : null
value : 98
108
true
false
true
{Java从入门到精通=108, A语言大学教程=108, C++大学教程=108}
Map接口主要方法的测试案例:
package ecut.collection; import java.util.HashMap; import java.util.Map; public class MapTest2 { public static void main(String[] args) { // map 变量的值 不是 null // map 集合中没有放入 任何键值对时,isEmpty 返回 true Map<String,Integer> map = new HashMap<>(); System.out.println( "size : " + map.size() + " , isEmpty : " + map.isEmpty() ); map.put( "软件工程" , 500 ); map.put( "通信工程" , 200 ); map.put( "土木工程" , 100 ); System.out.println( "size : " + map.size() + " , isEmpty : " + map.isEmpty() ); System.out.println( map ); // 删除指定的 key 对应的 key-value 对,并返回 该 key 对应的 value Integer value = map.remove( "土木工程" ); System.out.println( value ); System.out.println( map ); map.clear(); System.out.println( "size : " + map.size() + " , isEmpty : " + map.isEmpty() ); map.put( "信息科学技术" , 100 ); Map<String,Integer> m = new HashMap<>(); m.put( "幼儿教育" , 5000 ); m.put( "信息科学技术" , 150 ); System.out.println( m ); map.putAll( m ); System.out.println( map ); } }
运行结果如下:
size : 0 , isEmpty : true size : 3 , isEmpty : false {通信工程=200, 土木工程=100, 软件工程=500} 100 {通信工程=200, 软件工程=500} size : 0 , isEmpty : true {信息科学技术=150, 幼儿教育=5000} {信息科学技术=150, 幼儿教育=5000}
Map接口主要方法的测试案例:
package ecut.collection; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * keySet() * values() * entrySet() */ public class MapTest3 { public static void main(String[] args) { Map<String,Integer> map = new HashMap<>(); map.put( "软件工程" , 500 ); map.put( "通信工程" , 200 ); map.put( "土木工程" , 100 ); map.put( "幼儿教育" , 300 ); map.put( "护士护理" , 600 ); // 返回此映射中包含的键的 Set 视图 Set<String> keys = map.keySet(); // 所有的 key 组成的 Set 集合 for( String key : keys ) { Integer value = map.get( key ); System.out.println( key + " : " + value ); } System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~" ); // 返回此映射中包含的值的 Collection 视图 Collection<Integer> values = map.values(); for( Integer v : values ) { System.out.println( v ); } System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~" ); // 返回此映射中包含的映射关系的 Set 视图 Set< Map.Entry<String, Integer> > entries = map.entrySet(); for( Map.Entry<String, Integer> entry : entries ){ System.out.println( entry.getKey() + " : " + entry.getValue() ); } } }
运行结果如下:
通信工程 : 200 土木工程 : 100 护士护理 : 600 软件工程 : 500 幼儿教育 : 300 ~~~~~~~~~~~~~~~~~~~~~~~~ 200 100 600 500 300 ~~~~~~~~~~~~~~~~~~~~~~~~ 通信工程 : 200 土木工程 : 100 护士护理 : 600 软件工程 : 500 幼儿教育 : 300
HashCode测试案例:
package ecut.collection; /** * 对于 Object # hashCode() * * 1、由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。 * (这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) * * 2、这个整数 与 System.identityHashCode 的返回值相同 * * 3、这个整数的意义: Identity Hash Code ( 相当于 每个 对象的 身份证编号 ) * * 4、Object 提供 hashCode 方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能 * * 5、这个整数 在第一次 访问时 才触发产生 * */ public class IdentityHashCodeTest { public static void main(String[] args) { // 数组也是引用类型变量 int[] a = new int[ 10 ]; int[] b = new int[10] ; //返回该对象的哈希码值。 System.out.println( "a : " + a.hashCode() ); System.out.println( "b : " + b.hashCode() ); //这个整数 与 System.identityHashCode 的返回值相同 System.out.println( "b : " + System.identityHashCode( b ) ); System.out.println( "a : " + System.identityHashCode( a ) ); } }
运行结果如下:
a : 366712642 b : 1829164700 b : 1829164700 a : 366712642
Map接口主要实现类
1、java.util.HashMap<K,V> : 基于 哈希表 的 Map 接口的实现类
- key 允许为 null , value 也允许为 null
- 不保证映射的顺序,特别是它不保证该顺序恒久不变
HashMap测试案例:
package ecut.collection; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; /*** * hashCode 相同的字符串: * 重地 - 通话 * 方面 - 树人 * 玉班 - 王环 - 牛顿 * 王八 - 玌兌 * 东理 - 两猎 - 二晶 - 伍囗 - 住仙 * 东华 - 世合 * 德鹏 - 恢覚 * 农丰 - 儿女 * 掌门 - 文境 - 方創 */ public class MapHelper { public static void main(String[] args) { HashMap<String,String> map = new HashMap<String,String>( 1 ); //根据传入的 HashMap 实例获取其内部的 哈希表 的容量 int c = capacity( map ); System.out.println( "容量: " + c ); map.put( "掌门" , "张三丰" ); map.put( "方創" , "大众创业万众等死" ); map.put( "农丰" , "三轮车" ); map.put( "儿女" , "张无忌" ); map.put( "重地" , "仓库重地" ); map.put( "通话" , "通话记录"); map.put( null , null ); //显示给定 HashMap 实例内部的 哈希表中存储的数据 (只处理到链表层次) show( map ); c = capacity( map ); System.out.println( "容量: " + c ); String key = "儿女" ; //计算指定的 key 在给定的 map 集合中的位置(在哈希表中的索引) int p = position(map, key ); System.out.println( key + "在哈希表中的位置: " + p ); key = null ; p = position(map, key); System.out.println( key + "在哈希表中的位置: " + p ); } private static Class<?> hashMapClass ; private static Field tableField; private static Method hashMethod ; static { hashMapClass = HashMap.class; try { // 通过反射来获得 HashMap 内部的 table 属性 tableField = hashMapClass.getDeclaredField( "table" ); // 让本来因为被封装而不能访问的 table 能够被访问 tableField.setAccessible( true ); // 通过反射获得 HashMap 内部的 hash 方法 hashMethod = hashMapClass.getDeclaredMethod( "hash" , Object.class ); // 让本来因为封装而不能访问的 hash 方法能够被访问 hashMethod.setAccessible( true ); } catch (NoSuchFieldException e) { System.out.println( "在 " + hashMapClass.getName() + " 中未找到 table 属性 : " + e.getMessage() ); } catch (SecurityException e) { System.out.println( "无法访问 " + hashMapClass.getName() + " 的 table 属性 : " + e.getMessage() ); } catch (NoSuchMethodException e) { System.out.println( "在 " + hashMapClass.getName() + " 累中未找到 hash 方法 : " + e.getMessage() ); } } /** * 计算指定的 key 在给定的 map 集合中的位置(在哈希表中的索引) * @param map * @param key * @return */ public static int position( HashMap<?,?> map , Object key ) { int position = -1 ; //获取 哈希表的容量 int capacity = capacity( map ); // 根据 键 计算哈希值 int hash = hash( map , key ); // 根据 HashMap 中的实现思路,计算 键 的存储位置 position = ( capacity - 1 ) & hash ; return position ; } /** * 计算指定的 key 的 hash 值 * @param map * @param key */ public static int hash( HashMap<?,?> map , Object key ) { int hash = -1 ; try { // 通过反射调用 HashMap 中的 hash 方法 Object h = hashMethod.invoke( map , key ); if( h != null ) { // 判断 h 是否是 int 类型 或 Integer 类型 if( h.getClass() == Integer.class || h.getClass() == int.class ) { hash = Integer.class.cast( h ) ; // 通过反射方法,实现强制类型转换 } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return hash ; } /** * 根据传入的 HashMap 实例获取其内部的 哈希表 的容量 * @param map */ public static int capacity( HashMap<?,?> map ) { int capacity = 0 ; try { // 获得给定的 map 实例 中的 table 属性的值 (获取到哈希表) Object value = tableField.get( map ); if( value != null ){ Class<?> valueClass = value.getClass(); // 获得 table 属性的 值 的类型 if( valueClass.isArray() ){ // 如果是个数组 capacity = Array.getLength( value ); // 获取数组长度 (获取哈希表的长度) } } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return capacity ; } /** * 显示给定 HashMap 实例内部的 哈希表中存储的数据 (只处理到链表层次) * @param map */ public static void show( HashMap<?,?> map ) { try { // 从参数传入的 map 实例中获得 该实例中的 table 属性的值 Object value = tableField.get( map ); if( value != null ){ Class<?> valueClass = value.getClass(); // 获得 table 属性的 值 的类型 if( valueClass.isArray() ){ // 如果是个数组 System.out.println( "HashMap 实例中的哈希表:" ); StringBuffer buffer = new StringBuffer(); // 遍历数组 for( int i = 0 , n = Array.getLength( value ) ; i < n ; i++ ){ buffer.setLength( 0 ); Object e = Array.get( value , i ) ; // 从数组中获取 下标是 i 的元素 if( e != null ){ buffer.append( i ) ; buffer.append( " : " ); // 获得 当前循环取得的 元素的类型 Class<?> eClass = e.getClass(); Field hashField = eClass.getDeclaredField( "hash" ); // 获得 eClass 中 名称是 hash 的属性 hashField.setAccessible(true); buffer.append( "[ " ); Field keyField = eClass.getDeclaredField( "key" ); // 获得 eClass 中 名称是 key 的属性 keyField.setAccessible(true); Field valueField = eClass.getDeclaredField( "value" ); // 获得 eClass 中 名称是 key 的属性 valueField.setAccessible(true); Field nextField = eClass.getDeclaredField( "next" ); // 获得 eClass 中 名称是 key 的属性 nextField.setAccessible(true); Object hash = hashField.get( e ); buffer.append( "<"); buffer.append( hash ); buffer.append( ">" ); Object k = keyField.get( e ); buffer.append( k ); buffer.append( "=" ); Object v = valueField.get( e ); buffer.append( v ); Object next = nextField.get( e ); while( next != null ) { buffer.append( " , " ); hash = hashField.get( e ); k = keyField.get( next ); v = valueField.get( next ); buffer.append( "<"); buffer.append( hash ); buffer.append( ">" ); buffer.append( k ); buffer.append( "=" ); buffer.append( v ); next = nextField.get( next ) ; // 继续获得下一个节点 } buffer.append( " ]" ); } else { buffer.append( i ); buffer.append( " : [ empty ]" ); } System.out.println( buffer.toString() ); }// end of for loop } } else { System.out.println( "null"); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
package ecut.collection; import java.util.HashMap; public class HashMapTest { public static void main(String[] args) { HashMap<String,Integer> map = new HashMap<>( 20 , 0.8F ); /** this.loadFactor = loadFactor; this.threshold = tableSizeFor( initialCapacity ); **/ // 因为 HashMap 内部用 哈希表来存储 键值对 // 并且 HashMap 采用 key 的hashCode 来确定 键值对 在哈希表中的位置 // 因此,添加键值对的顺序,跟它们在哈希表中的存放位置可能不同 map.put( "软件工程" , 500 ); map.put( "通信工程" , 200 ); map.put( "土木工程" , 100 ); map.put( "幼儿教育" , 300 ); map.put( "护士护理" , 600 ); map.put( "东理" , 600 ); map.put( "两猎" , 600 ); map.put( "二晶" , 600 ); map.put( "伍囗" , 600 ); map.put( "住仙" , 600 ); System.out.println( map ); int capacity = MapHelper.capacity( map ); System.out.println( "HashMap实例内部的哈希表的长度: " + capacity ); int index = MapHelper.position( map , "护士护理" ); System.out.println( "元素在哈希表中的存放位置: " + index ); MapHelper.show( map ); } }
运行结果如下:
{东理=600, 两猎=600, 二晶=600, 伍囗=600, 住仙=600, 土木工程=100, 软件工程=500, 幼儿教育=300, 通信工程=200, 护士护理=600} HashMap实例内部的哈希表的长度: 32 元素在哈希表中的存放位置: 24 HashMap 实例中的哈希表: 0 : [ empty ] 1 : [ empty ] 2 : [ empty ] 3 : [ <649571>东理=600 , <649571>两猎=600 , <649571>二晶=600 , <649571>伍囗=600 , <649571>住仙=600 ] 4 : [ empty ] 5 : [ empty ] 6 : [ <690577222>土木工程=100 ] 7 : [ empty ] 8 : [ empty ] 9 : [ empty ] 10 : [ <1114081802>软件工程=500 ] 11 : [ empty ] 12 : [ empty ] 13 : [ <741421005>幼儿教育=300 ] 14 : [ empty ] 15 : [ empty ] 16 : [ empty ] 17 : [ empty ] 18 : [ empty ] 19 : [ empty ] 20 : [ empty ] 21 : [ <1119401141>通信工程=200 ] 22 : [ empty ] 23 : [ empty ] 24 : [ <774976728>护士护理=600 ] 25 : [ empty ] 26 : [ empty ] 27 : [ empty ] 28 : [ empty ] 29 : [ empty ] 30 : [ empty ] 31 : [ empty ]
2、java.util.Hashtable<K,V>: Map接口的的实现类
- Properties类继承了Hashtable。
- 实现一个哈希表,该哈希表将键映射到相应的值
- 几乎所有的方法都是线程安全的
- Hashtable 的 key 和 value 都不能为 null
Hashtable测试案例:
package ecut.collection; import java.util.Enumeration; import java.util.Hashtable; /** * 一个 Map 接口的实现类的自我修养: * 1、将键映射到值的对象 ( Map 集合中存放的是 key-value 对 ( Map.Entry ) ) 2、一个映射不能包含重复的键 ( Map 集合中的 key 不能重复 ) 3、每个键最多只能映射到一个值 ( Map 集合中的 key 只能对应一个 值 ) Hashtable 的特点: 1、key 和 value 都不能为 null 2、几乎所有的方法都是线程安全的( 几乎所有的方法都被 synchronized 修饰 ) */ public class HashtableTest { public static void main(String[] args) { Hashtable<String,Integer> ht = new Hashtable<>(); //ht.put(null, null);//抛出NullPointerException,key 和 value 都不能为 null ht.put( "罗玉凤" , 250 ); ht.put( "罗玉龙" , 500 ); System.out.println( ht ); // ht.toString() System.out.println( ht.get( "罗玉龙" ) ); Enumeration<String> keys = ht.keys(); while( keys.hasMoreElements() ){ String key = keys.nextElement(); Integer value = ht.get( key ); System.out.println( key + " : " + value ); } } }
Enumeration接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。
运行结果如下:
{罗玉龙=500, 罗玉凤=250} 500 罗玉龙 : 500 罗玉凤 : 250
3、java.util.Properties:Hashtable的子类,表示属性集
- Properties 可保存在流中或从流中加载。
- 属性列表中每个键及其对应值都是一个字符串,应该用 setProperty 和 getProperty 方法来 操作属性 ,而不是使用 put 和 get。
Properties测试案例:
package ecut.collection; import java.util.Properties; public class PropertiesTest1 { public static void main(String[] args) { Properties props = new Properties(); // props.put(key, value) ; props.setProperty( "driver", "com.mysql.jdbc.Driver" ); props.setProperty( "url" , "jdbc:msyql://127.0.0.1:3306/ecut" ); // props.get(key); String d = props.getProperty( "driver" ); System.out.println( d ); String user = props.getProperty( "user" ); System.out.println( user ); // 当指定的属性名在集合中不存在时,使用 第二个参数 当作默认值返回 user = props.getProperty( "user" , "root" ); System.out.println( user ); } }
运行结果如下:
com.mysql.jdbc.Driver null root
Properties测试案例:
jdbc.url = jdbc:oracle:thin:@127.0.0.1:1521:ecut
jdbc.driver = oracle.jdbc.driver.OracleDriver
jdbc.user = ecut
jdbc.password = ecut2017
package ecut.collection; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 1、从类路径下获得任意资源对应的输入流 * 2、用 Properties 加载 属性文件 ( 以 .properties 为后缀,其中的内容形式是 key = value ) */ public class PropertiesTest2 { public static void main(String[] args) { Properties props = new Properties(); System.out.println( "属性个数: " + props.size() ); //任何一个类型都可以通过.class来获得自己的类型对应Class 对象 Class<?> c = PropertiesTest2.class ;// 通过 java.lang.Class 类的 getResourceAsStream 方法获得指定资源名称对应的输入流 // 如果仅仅指定的是 文件名,则该文件必须跟 当前类在同一个包 // 如果在 文件名之前使用了 / 则表示 从 当前的 类路径 的根路径开始寻找 InputStream inStream = c.getResourceAsStream( "/jdbc.properties" ) ; try { props.load( inStream ); // 从指定的流中读取 属性 ,并加入到 属性集合 中 } catch (IOException e) { System.err.println( "加载失败: " + e.getMessage() ); } catch( NullPointerException e ) { System.err.println( "未找到文件: " + e.getMessage() ); } if( props.size() > 0 ) { System.out.println( props.getProperty( "jdbc.url" ) ); System.out.println( props.getProperty( "jdbc.driver" ) ); System.out.println( props.getProperty( "jdbc.user" ) ); System.out.println( props.getProperty( "jdbc.password" ) ); } } }
运行结果如下:
属性个数: 0 jdbc:oracle:thin:@127.0.0.1:1521:ecut oracle.jdbc.driver.OracleDriver ecut ecut2017
HashMap 和 Hashtable 的相同点
1、内部都是基于 哈希表 存储数据(内部都有数组table,HashTable是Entry<?,?>[] table,HashMap是Node<K,V>[] table)。
2、HashMap 和 Hashtable 的迭代器 都是 快速失败 ( fail-fast ) 的
HashMap 和 Hashtable 的区别
1、用来确定元素在哈希表中的位置的方式不同:
HashMap 根据 key.hashCode() 重新计算一个值:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
static final int hash( Object key ) { int h; return (key == null) ? 0 : ( h = key.hashCode() ) ^ ( h >>> 16 ); }
然后再根据这个值来确定元素在哈希表中的位置:
部分源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); ..................................
源码if ((p = tab[i = (n - 1) & hash]) == null)转换:
tab[i] = newNode(hash, key, value, null);
int n = table.length ; int hash = hash( key ) ; table[ ( n - 1) & hash ] = newNode( hash, key, value, null ) ; // 不是源码
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Hashtable 中的实现:
部分源码:
public synchronized V put(K key, V value) { //确保value 不能为空 if (value == null) { throw new NullPointerException(); } // 确保key不在哈希表 Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") ......................................
int hash = key.hashCode(); // key 必须不能为 null,不然抛出空指针异常 int index = (hash & 0x7FFFFFFF) % tab.length;
2、HashMap 不支持线程安全的 ( 所有的方法都没有 synchronized 修饰 )。
Hashtable 支持线程安全的 ( 几乎所有的方法都被 synchronized 修饰 )。
3、HashMap 的迭代器是 快速失败 ( fail-fast ) 的 , Hashtable 的迭代器是 也是 快速失败 ( fail-fast ) 的,
但是 由 Hashtable 的 键 ( keys() ) 和 元素 ( elements() ) 方法返回的 Enumeration 不 是快速失败的,是安全失败。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException(此异常不一定抛出)。
快速失败测试案例:
package ecut.collection; import java.util.HashMap; import java.util.Iterator; import java.util.Set; public class FailFastTest { public static void main(String[] args) { HashMap<String,Integer> map = new HashMap<>(); map.put( "炒粉" , 3 ); map.put( "炒面" , 5 ); map.put( "包子" , 1 ); map.put( "皮蛋瘦肉粥" , 2 ); Set<String> keySet = map.keySet(); Iterator<String> itor = keySet.iterator(); while( itor.hasNext() ){ String key = itor.next() ; Integer value = map.get( key ); System.out.println( key + " : " + value ); // map.put( "罗玉凤" , 250 ); // 尽最大可能抛出 ConcurrentModificationException // map.remove( "包子" ); // 尽最大可能抛出 ConcurrentModificationException if( key.equals( "包子" )){ itor.remove(); // 不抛出 ConcurrentModificationException } } System.out.println( "~~~~~~~~~~~~~~~" ); itor = keySet.iterator(); while (itor.hasNext()) { String key = itor.next(); Integer value = map.get(key); System.out.println(key + " : " + value); } } }
运行结果如下:
包子 : 1 炒粉 : 3 炒面 : 5 皮蛋瘦肉粥 : 2 ~~~~~~~~~~~~~~~ 炒粉 : 3 炒面 : 5 皮蛋瘦肉粥 : 2
添加和移除元素触发了因快速失败抛出的异常ConcurrentModificationException,是因为我们在使用迭代器对map里的元素进行迭代的时候,对map的结构进行了修改,map发生更改,迭代器是不允许的。
快速失败:我们正在使用迭代器对可以获得迭代器的集合(map不能获得迭代器但是对应的keyset视图可以获得迭代器),keyset对他进行的迭代的是,如果同时对他进行修改,map的结构发生改变,触发异常ConcurrentModificationException,称之为快速失败。要避免则只能使用迭代器本身的 remove 方法或 add 方法。
安全失败测试案例:
package ecut.collection; import java.util.Enumeration; import java.util.Hashtable; public class FailSafeTest { public static void main(String[] args) { Hashtable<String,Integer> table = new Hashtable<>(); //Alt+Shift+R快速修改某个变量的名称 table.put( "炒粉" , 3 ); table.put( "炒面" , 5 ); table.put( "包子" , 1 ); table.put( "皮蛋瘦肉粥" , 2 ); Enumeration<String> keys = table.keys(); while( keys.hasMoreElements() ){ String key = keys.nextElement(); Integer value = table.get( key ); System.out.println( key + " : " + value ); table.put( "拌面" , 3 ); } System.out.println( "~~~~~~~~~~~~~~~" ); keys = table.keys(); while( keys.hasMoreElements() ){ String key = keys.nextElement(); Integer value = table.get( key ); System.out.println( key + " : " + value ); } } }
运行结果如下:
包子 : 1
炒粉 : 3
拌面 : 3
炒面 : 5
皮蛋瘦肉粥 : 2
~~~~~~~~~~~~~~~
包子 : 1
炒粉 : 3
拌面 : 3
炒面 : 5
皮蛋瘦肉粥 : 2
次要的区别:
4、父类不同: Hashtable 的 父类 Dictionary ,而 HashMap 的父类是 AbstractMap。
5、对 键集、值集、键值对集 的处理方式不同:
HashMap 和 Hashtable 都具有:
Set<K> keySet() 返回所有的 key 组成的 Set 集合
Collection<V> values() 返回所有的 value 组成的 Collection 集合
Set<Map.Entry<K,V>> entrySet() 返回所有的 entry 对应的 Set 集合
Hashtable 还具有:
Enumeration<K> keys() 返回此哈希表中的键的枚举
Enumeration<V> elements() 返回此哈希表中的值的枚举
6、因为 Hashtable 是线程安全的,因此 在 单线程环 境下比 HashMap 要慢。
7、HashMap允许存在一个为null的key,多个为null的value 。Hashtable的key和value都不允许为null。
SortedMap接口特点
1、进一步提供关于键的总体排序 的 Map
。该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator
进行排序。
2、对有序映射的 collection 视图(由 entrySet、keySet 和 values 方法返回)进行迭代时,此顺序就会反映出来。
3、要采用此排序方式,还需要提供一些其他操作(此接口是 SortedSet
的对应映射)。
SortedMap接口主要实现类
1、java.util.TreeMap<K,V>: SortedMap接口的的实现类
- NavigableMap类继承了SortedMap,TreeMap实现了NavigableMap。
- 基于红黑树( Red-Black tree )来实现 Map 集合 ( 同时实现了 NavigableMap 、SortedMap )。
- 该映射根据其键 ( key ,得实现Comparable接口,支持自然排序) 的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
- TreeMap 的 key 不可以为 null ,而 value 可以为 null。
- TreeMap 中的键值对根据 键来排序 ( 根据自然顺序 或 比较器排序)。
TreeMap测试案例:
package ecut.collection; public class Fox implements Comparable<Fox> { private Integer id ; // 对象标识符 ( Object Identifier ) private String name ; public Fox(Integer id, String name) { super(); this.id = id; this.name = name; } @Override public int compareTo(Fox o) { if( this.id != null && o.id != null ) { if( this.id > o.id ){ return 1 ; } else if( this.id == o.id ){ return 0 ; } else { return -1 ; } } return 0; } @Override public String toString() { return "[id=" + id + ", name=" + name + "]"; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package ecut.collection; import java.util.TreeMap; public class TreeMapTest1 { public static void main(String[] args) { // 创建 TreeMap 对象时,如果没有指定比较器,则默认按照 key 的 自然顺序排序 TreeMap<Fox,Double> tm = new TreeMap<>(); Fox fox1 = new Fox( 100 , "妲己" ); tm.put( fox1 , 1.0 ); Fox fox2 = new Fox( 200 , "褒姒" ); tm.put( fox2 , 0.8 ); System.out.println( tm ); Fox fox3 = new Fox( 150 , "金角大王他干娘" ); tm.put( fox3 , 100.0 ); System.out.println( tm ); } }
运行结果如下:
{[id=100, name=妲己]=1.0, [id=200, name=褒姒]=0.8}
{[id=100, name=妲己]=1.0, [id=150, name=金角大王他干娘]=100.0, [id=200, name=褒姒]=0.8}
TreeMap测试案例:
package ecut.collection; import java.util.Comparator; import java.util.TreeMap; public class TreeMapTest2 { public static void main(String[] args) { Comparator<Fox> c = new Comparator<Fox>(){ @Override public int compare( Fox o1, Fox o2 ) { if( o1 != null && o2 != null && o1.getName() != null && o2.getName() != null ){ String name1 = o1.getName() ; String name2 = o2.getName() ; return name1.compareTo( name2 ) ; } return 0; } }; // 创建 TreeMap 对象时,指定比较器,则按照 比较器 排序 TreeMap<Fox,Double> tm = new TreeMap<>( c ); Fox fox3 = new Fox( 150 , "金角大王他干娘" ); tm.put( fox3 , 100.0 ); System.out.println( tm ); Fox fox2 = new Fox( 200 , "褒姒" ); tm.put( fox2 , 0.8 ); System.out.println( tm ); Fox fox1 = new Fox( 100 , "妲己" ); tm.put( fox1 , 1.0 ); System.out.println( tm ); } }
运行结果如下:
{[id=150, name=金角大王他干娘]=100.0} {[id=200, name=褒姒]=0.8, [id=150, name=金角大王他干娘]=100.0} {[id=100, name=妲己]=1.0, [id=200, name=褒姒]=0.8, [id=150, name=金角大王他干娘]=100.0}
ConcurrentMap接口特点
1、,它可以处理并发访问。
2、ConcurrentMap除了继承自java.util.Map的方法,还有一些自己的原子方法。
ConcurrentMap接口主要实现类
1、java.util.concurrent.ConcurrentHashMap<K,V>: ConcurrentMap接口的的实现类
- ConcurrentMap接口的的实现类。
- ConcurrentHashMap 是 安全失败 ( fail-safe )。
- ConcurrentHashMap 为了提高并发性能而出现的map集合,支持完全的并发操作,默认支持16个线程。
- 不会 抛出
ConcurrentModificationException,因此不保证数据的
一致性。
ConcurrentHashMap测试案例:
package ecut.collection; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * ConcurrentHashMap 是 安全失败 ( fail-safe ) * ConcurrentHashMap 支持完全的并发操作 */ public class ConcurrentHashMapTest { public static void main(String[] args) { ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>(); map.put( "炒粉" , 3 ); map.put( "炒面" , 5 ); map.put( "包子" , 1 ); map.put( "皮蛋瘦肉粥" , 2 ); Set< Map.Entry<String,Integer> > entrySet = map.entrySet(); Iterator< Map.Entry<String,Integer> > itor = entrySet.iterator(); while( itor.hasNext() ){ Map.Entry<String,Integer> entry = itor.next(); System.out.println( entry.getKey() + " : " + entry.getValue() ); map.put( "拌面" , 3 ); } System.out.println( "~~~~~~~~~~~~~~~" ); Enumeration<String> keys = map.keys(); while( keys.hasMoreElements() ){ String key = keys.nextElement(); Integer value = map.get( key ); System.out.println( key + " : " + value ); map.remove( "拌面" ); } } }
运行结果如下:
包子 : 1 炒粉 : 3 拌面 : 3 炒面 : 5 皮蛋瘦肉粥 : 2 ~~~~~~~~~~~~~~~ 包子 : 1 炒粉 : 3 炒面 : 5 皮蛋瘦肉粥 : 2
转载请于明显处标明出处