集合:
简单的集合大家庭先看一下:
Collection
Collection是描述所有序列容器的共同的根接口,他可能被认为是一个附属接口,即因为要表示其他若干个接口的共性而出现的接口。java.util.AbstractCollection类提供了Collection的默认实现。
Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。
Collection:一个独立的元素序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素,Query按照排队规则来确定对象产生的顺序(通常与插入的顺序相同)(List,Set,Query)
Map:一组成对的“键值对”对象,允许使用键来查找对象。将数字与对象关联,映射表允许我们使用另一个对象来查找某个对象,因此被称为关联数组。(HashMap,TreeMap)
Collection接口里定义了如下操作集合元素的方法:
- boolean add(Object o):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。
- boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。
- void clear():清除集合里的所有元素,将集合长度变为0。
- boolean contains(Object o):返回集合里是否包含指定元素。
- boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。
- boolean isEmpty():返回集合是否为空。当集合长度为0时返回true,否则返回false。
- Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素。
- boolean remove(Object o):删除集合中的指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true。
- boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素(相当于用调用该方法的集合减集合c),如果删除了一个或一个以上的元素,则该方法返回true。
- boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用该方法的集合变成该集合和集合c的交集),如果该操作改变了调用该方法的集合,则该方法返回true。
- int size():该方法返回集合里元素的个数。
- Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
Collection的构造器可以接受另一个Collection,用它来将自身进行初始化。可以使用Arrays.list()来为这个构造器产生输入。但是Collection.addAll()方法运行起来要快,可以构建一个不包含元素的Collection,然后调用Collections.addAll()方法。但是这个不如Arrays.asList()或Collections.addAll()灵活,这两个方法都是使用的是可变参数列表。同样也可以使用Arrays.asList()的输出当做list,但是这个底层是数组,因此不能调整其大小,如果试图使用add()或delete()方法在这种列表中添加或删除元素,就有可能引起Unsupported Operation 的错误
迭代器Iterator:
Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。Iterator接口里定义了如下三个方法。
- boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true。
- Object next():返回集合里的下一个元素。
- void remove():删除集合里上一次next方法返回的元素。
Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。Iterator提供了两个方法来迭代访问Collection集合里的元素,并可通过remove()方法来删除集合中上一次next()方法返回的集合元素
foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。同样,当使用foreach循环迭代访问集合元素时,该集合也不能被改变,否则将引发Concurrent ModificationException异常。
ListIterator:
ListIterator是一个更加强大的iterator的子类型。他只能用于各种list的访问,尽管iterator只能向前移动,而ListIterator可以双向移动,他还可以产生相对于迭代器在列表中指向当前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。
public class ListIterator { List<Pet> pets = Pets.arrayList(8); ListIterator<Pet> it = pets.listIterator(); while(it.hasNext()){ System.out.println(it.next()+","+ite.nextIndex()+","+it.previousIndex()+";");} while(it.hasPrevious()){ System.out.print(it.previous().id()+" ")} it = pets.listIterator(3); while(it.hasNext()){ it.next(); it.set(Pets.randmomPet());}}
集合遍历常见的问题及错误:
参看个人博客总结:
这个说的是比较全面的了
ArrayList和Vector类:
都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initialCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超出了该数组的长度时,它们的initialCapacity会自动增加。
对于通常的编程场景,程序员无须关心ArrayList或Vector的initialCapacity。但如果向ArrayList或Vector集合中添加大量元素时,可使用ensureCapacity(int minCapacity)方法一次性地增加initialCapacity。这可以减少重分配的次数,从而提高性能。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initialCapacity大小。如果创建空的ArrayList或Vector集合时不指定initialCapacity参数,则Object[]数组的长度默认为10。
除此之外,ArrayList和Vector还提供了如下两个方法来重新分配Object[]数组。
- void ensureCapacity(int minCapacity):将ArrayList或Vector集合的Object[]数组长度增加minCapacity。
- void trimToSize():调整ArrayList或Vector集合的Object[]数组长度为当前元素的个数。程序可调用该方法来减少ArrayList或Vector集合对象占用的存储空间。
ArrayList和Vector在用法上几乎完全相同,但由于Vector是一个古老的集合(从JDK 1.0就有了),那时候Java还没有提供系统的集合框架,所以Vector里提供了一些方法名很长的方法,例如addElement(Object obj),实际上这个方法与add (Object obj)没有任何区别。从JDK 1.2以后,Java提供了系统的集合框架,就将Vector改为实现List接口,作为List的实现之一,从而导致Vector里有一些功能重复的方法。
提示:
Vector里有一些功能重复的方法,这些方法中方法名更短的方法属于后来新增的方法,方法名更长的方法则是Vector原有的方法。Java改写了Vector原有的方法,将其方法名缩短是为了简化编程。而ArrayList开始就作为List的主要实现类,因此没有那些方法名很长的方法。实际上,Vector具有很多缺点,通常尽量少用Vector实现类。
除此之外,ArrayList和Vector的显著区别是:ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性;但Vector集合则是线程安全的,无须程序保证该集合的同步性。因为Vector是线程安全的,所以Vector的性能比ArrayList的性能要低。实际上,即使需要保证List集合线程安全,也同样不推荐使用Vector实现类。后面会介绍一个Collections工具类,它可以将一个ArrayList变成线程安全的。
Vector还提供了一个Stack子类,它用于模拟“栈”这种数据结构,“栈”通常是指“后进先出”(LIFO)的容器。最后“push”进栈的元素,将最先被“pop”出栈。与Java中的其他集合一样,进栈出栈的都是Object,因此从栈中取出元素后必须进行类型转换,除非你只是使用Object具有的操作。所以Stack类里提供了如下几个方法。
- Object peek():返回“栈”的第一个元素,但并不将该元素“pop”出栈。
- Object pop():返回“栈”的第一个元素,并将该元素“pop”出
ArrayList与LinkedList
ArrayList擅长于随机访问,但是在List的中间插入和移除元素时比较慢。
LinkedList 他插入删除操作比较廉价,随机访问方面相对比较慢的。LinkedList还添加了可以使用栈,队列或双端队列的方法。
使用LinkedList实现一个栈的功能:
public class Stack<T> { private LinkedList<T> storage = new LinkedList<T>(); public void push(T v) { storage.addFirst(v);} public T peek(){ return storage.getFirst();} public T pop(){ return storage.removeFirst();} public boolean empty() { return storage.isEmpty();} public String toString() {return storage.toSting();}}
使用LinkedList构造一个栈:
public class Stack<T> { private LinkedList<T> storage = new LinkedList<T>(); public void push(T v) { storage.addFirst(v); } public T peek() { return storage.getFirst(); } public T pop() { return storage.removeFirst(); } public boolean empty() { return storage.isEmpty(); } public String toString() { return storage.toString(); }} ///:~public class StackTest { public static void main(String[] args) { Stack<String> stack = new Stack<String>(); for(String s : "My dog has fleas".split(" ")) stack.push(s); while(!stack.empty()) System.out.print(stack.pop() + " "); }} /* Output:fleas has dog My*///:~
可查看:ArrayList与LinkedList
Set集合:
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add方法返回false,且新元素不会被加入。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象;反之,只要两个对象用equals方法比较返回false,Set就会接受这两个对象(甚至这两个对象是同一个对象,Set也可把它们当成两个对象处理,在后面程序中可以看到这种极端的情况
HashSet具有以下特点。
- 不能保证元素的排列顺序,顺序有可能发生变化。
- HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。集合元素值可以是null。
- 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。
- 简单地说,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
- 当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法。其规则是:如果两个对象通过equals()方法比较返回true,这两个对象的hashCode值也应该相同。
如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合的规则有些出入了。
如果两个对象的hashCode()方法返回的hashCode值相同,但它们通过equals()方法比较返回false时将更麻烦:因为两个对象的hashCode值相同,HashSet将试图把它们保存在同一个位置,但又不行(否则将只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。
重写hashCode()方法的基本规则。
- 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
- 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应返回相等的值。
- 对象中用作equals()方法比较标准的Field,都应该用来计算hashCode值
重写equals和hashCOde方法:
public boolean equals(Object obj) { if (this == obj) return true; if (obj != null && obj.getClass() == R.class) { R r = (R) obj; if (r.count == this.count) { return true; } } return false;}public int hashCode() { return this.count;}
HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为他们实现具有不同的元素存储方式,TreeSet将元素存储在红-黑数据结构中,而HashSet使用的是散列函数,LinkedHashList因为查询速度的原因也使用了散列,但是他使用了链表来维护元素的插入顺序。
LinkedHashSet使用了链表记录集合元素的添加顺序,但LinkedHashSet依然是HashSet,因此它依然不允许集合元素重复
如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的Field,这将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整它们的顺序,甚至可能导致TreeSet中保存的这两个对象通过compareTo(Object obj)方法比较返回0。下面程序演示了这种情况。
class R implements Comparable { int count; public R(int count) { this.count = count; } public String toString() { return "R[count:" + count + "]"; }//重写equals()方法,根据count来判断是否相等 public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == Z.class) { R r = (R) obj; if (r.count == this.count) { return true; } } return false; }//重写compareTo()方法,根据count来比较大小 public int compareTo(Object obj) { R r = (R) obj; return count > r.count ? 1 :count < r.count ? -1 : 0; }}public class TreeSetTest3 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add(new R(5)); ts.add(new R(-3)); ts.add(new R(9)); ts.add(new R(-2));//打印TreeSet集合,集合元素是有序排列的 System.out.println(ts); //①//取出第一个元素R first=(R)ts.first();//对第一个元素的count赋值first.count=20;//取出最后一个元素R last=(R)ts.last();//对最后一个元素的count赋值,与第二个元素的count相同last.count=-2; //再次输出将看到TreeSet里的元素处于无序状态,且有重复元素 System.out.println(ts); //②//删除Field被改变的元素,删除失败 System.out.println(ts.remove(new R(-2))); //③ System.out.println(ts);//删除Field没有改变的元素,删除成功 System.out.println(ts.remove(new R(5))); //④ System.out.println(ts); }}
上面程序中的R对象对应的类正常重写了equals()方法和compareTo()方法,这两个方法都以R对象的count实例变量作为判断的依据。当程序执行①行代码时,看到程序输出的Set集合元素处于有序状态;因为R类是一个可变类,因此可以改变R对象的count实例变量的值,程序通过粗体字代码行改变了该集合里第一个元素和最后一个元素的count实例变量的值。当程序执行②行代码输出时,将看到该集合处于无序状态,而且集合中包含了重复元素。
Set和存储顺序
?Set:存入的Set的每一个元素必须是唯一的,因为Set不保存重复的元素。加入Set的元素必须定义equals()方法以确保对象的唯一性,Set与Conllection有完全一样的接口。Set接口不保证维护单元的次序。
?HashSet:为快速查找而设计的Set,存入的Hashset的元素必须定义HashCode()
?TreeSet:保持次序的Set。底层是树结构,使用它可以从Set中提取有序的序列元素必须实现Comparable接口
?LInkedHashSet:具有与HashSet的查询速度,且内部使用链表维护元素的顺序,于是在使用迭代器遍历Set时结果会按元素插入的次序显示。元素也必须定义hashCode()方法。
HasSet应该是默认的选择因为他对速度进行了优化
SortedSet
SortedSet的元素可以保证出于排序状态,这使得他可以通过在SortedSet接口中的下列方法提供附加的功能:Comparator comparator()返回当前Set使用的Comparator;或者返回null,
Object first() 返回容器的第一个元素
Object last() 返回容器的最末一个元素
SortedSet subSet(fromElemny,toElement) 生成set的子集,范围含前不含后
SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成
SortedSet tailset(fromElemnt) 生成此Set的子集 由大于等于fromElement的元素组成。
// A Java program to demonstrate working of SortedSetimport java.util.SortedSet;import java.util.TreeSet; public class Main{ public static void main(String[] args) { // Create a TreeSet and inserting elements SortedSet<String> sites = new TreeSet<>(); sites.add("practice"); sites.add("geeksforgeeks"); sites.add("quiz"); sites.add("code"); System.out.println("Sorted Set: " + sites); System.out.println("First: " + sites.first()); System.out.println("Last: " + sites.last()); // Getting elements before quiz (Excluding) in a sortedSet SortedSet<String> beforeQuiz = sites.headSet("quiz"); System.out.println(beforeQuiz); // Getting elements between code (Including) and // practice (Excluding) SortedSet<String> betweenCodeAndQuiz = sites.subSet("code","practice"); System.out.println(betweenCodeAndQuiz); // Getting elements after code (Including) SortedSet<String> afterCode = sites.tailSet("code"); System.out.println(afterCode); }}// 输出Sorted Set:First: codeLast: quiz
Map:
对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询设计的(HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。
LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的添加顺序。IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本相似的实现,只是它使用==而不是equals()方法来判断元素相等。EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。
Collections
public class SynchronizedTest{public static void main(String[] args){ //下面程序创建了4个同步的集合对象 Collection c=Collections.synchronizedCollection(new ArrayList()); List list=Collections.synchronizedList(new ArrayList()); Set s=Collections.synchronizedSet(new HashSet()); Map m=Collections.synchronizedMap(new HashMap()); }}
实现一个Map 这个Map是说明性的,缺乏效率,并且具有固定的尺寸不灵活:
//: containers/AssociativeArray.java// Associates keys with values.import static net.mindview.util.Print.*;public class AssociativeArray<K,V> { private Object[][] pairs; private int index; public AssociativeArray(int length) { pairs = new Object[length][2]; } public void put(K key, V value) { if(index >= pairs.length) throw new ArrayIndexOutOfBoundsException(); pairs[index++] = new Object[]{ key, value }; } @SuppressWarnings("unchecked") public V get(K key) { for(int i = 0; i < index; i++) if(key.equals(pairs[i][0])) return (V)pairs[i][1]; return null; // Did not find key } public String toString() { StringBuilder result = new StringBuilder(); for(int i = 0; i < index; i++) { result.append(pairs[i][0].toString()); result.append(" : "); result.append(pairs[i][1].toString()); if(i < index - 1) result.append("\n"); } return result.toString(); } public static void main(String[] args) { AssociativeArray<String,String> map = new AssociativeArray<String,String>(6); map.put("sky", "blue"); map.put("grass", "green"); map.put("ocean", "dancing"); map.put("tree", "tall"); map.put("earth", "brown"); map.put("sun", "warm"); try { map.put("extra", "object"); // Past the end } catch(ArrayIndexOutOfBoundsException e) { print("Too many objects!"); } print(map); print(map.get("ocean")); }} /* Output:Too many objects!sky : bluegrass : greenocean : dancingtree : tallearth : brownsun : warmdancing*///:~
Map之间的比较
Map 实现 | 描述 |
HashMap* | 基于哈希表的实现。(使用此类来代替 Hashtable 。)为插入和定位键值对提供了常数时间性能。可以通过构造方法调整性能,这些构造方法允许你设置哈希表的容量和装填因子。 |
LinkedHashMap | 与 HashMap 类似,但是当遍历时,可以按插入顺序或最近最少使用(LRU)顺序获取键值对。只比 HashMap 略慢,一个例外是在迭代时,由于其使用链表维护内部顺序,所以会更快些。 |
TreeMap | 基于红黑树的实现。当查看键或键值对时,它们按排序顺序(由 Comparable 或 Comparator 确定)。TreeMap 的侧重点是按排序顺序获得结果。TreeMap 是唯一使用 |
WeakHashMap | 一种具有 弱键(weak keys) 的 Map ,为了解决某些类型的问题,它允许释放 Map 所引用的对象。如果在 Map 外没有对特定键的引用,则可以对该键进行垃圾回收。 |
ConcurrentHashMap | 不使用同步锁定的线程安全 Map。这在并发的一章中讨论。 |
IdentityHashMap | 使用 |
//: containers/Maps.java// Things you can do with Maps.import java.util.concurrent.*;import java.util.*;import net.mindview.util.*;import static net.mindview.util.Print.*;public class Maps { public static void printKeys(Map<Integer,String> map) { printnb("Size = " + map.size() + ", "); printnb("Keys: "); print(map.keySet()); // Produce a Set of the keys } public static void test(Map<Integer,String> map) { print(map.getClass().getSimpleName()); map.putAll(new CountingMapData(25)); // Map has 'Set' behavior for keys: map.putAll(new CountingMapData(25)); printKeys(map); // Producing a Collection of the values: printnb("Values: "); print(map.values()); print(map); print("map.containsKey(11): " + map.containsKey(11)); print("map.get(11): " + map.get(11)); print("map.containsValue(\"F0\"): " + map.containsValue("F0")); Integer key = map.keySet().iterator().next(); print("First key in map: " + key); map.remove(key); printKeys(map); map.clear(); print("map.isEmpty(): " + map.isEmpty()); map.putAll(new CountingMapData(25)); // Operations on the Set change the Map: map.keySet().removeAll(map.keySet()); print("map.isEmpty(): " + map.isEmpty()); } public static void main(String[] args) { test(new HashMap<Integer,String>()); test(new TreeMap<Integer,String>()); test(new LinkedHashMap<Integer,String>()); test(new IdentityHashMap<Integer,String>()); test(new ConcurrentHashMap<Integer,String>()); test(new WeakHashMap<Integer,String>()); }} /* Output:HashMapSize = 25, Keys: [15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 14, 24, 4, 19, 11, 18, 3, 12, 17, 2, 13, 20, 10, 5, 0]Values: [P0, I0, X0, Q0, H0, W0, J0, V0, G0, B0, O0, Y0, E0, T0, L0, S0, D0, M0, R0, C0, N0, U0, K0, F0, A0]{15=P0, 8=I0, 23=X0, 16=Q0, 7=H0, 22=W0, 9=J0, 21=V0, 6=G0, 1=B0, 14=O0, 24=Y0, 4=E0, 19=T0, 11=L0, 18=S0, 3=D0, 12=M0, 17=R0, 2=C0, 13=N0, 20=U0, 10=K0, 5=F0, 0=A0}map.containsKey(11): truemap.get(11): L0map.containsValue("F0"): trueFirst key in map: 15Size = 24, Keys: [8, 23, 16, 7, 22, 9, 21, 6, 1, 14, 24, 4, 19, 11, 18, 3, 12, 17, 2, 13, 20, 10, 5, 0]map.isEmpty(): truemap.isEmpty(): true...*///:~
HashMap:
jdk 1.7时是数组+链表组成,
jdk1.8时是 数组+ 链表 + 红黑树组成。链表元素大于等于8时会把链表转为树结构,若桶中链的元素个数小于等于6时,树结构还原成链表。当链表的个数为8左右徘徊时就会生成树转链表,链表转树,效率低下。hasMap的负载因子默认为0.75,2^n是为了散列更加均匀。
更多参看:
SortedMap
使用 SortedMap (由 TreeMap 或 ConcurrentSkipListMap 实现),键保证按排序顺序,这允许在 SortedMap 接口中使用这些方法来提供其他功能:
Comparator comparator()
:生成用于此 Map 的比较器, null 表示自然排序。T firstKey()
:返回第一个键。T lastKey()
:返回最后一个键。SortedMap subMap(fromKey,toKey)
:生成此 Map 的视图,其中键从 fromKey(包括),到 toKey (不包括)。SortedMap headMap(toKey)
:使用小于 toKey 的键生成此 Map 的视图。SortedMap tailMap(fromKey)
:使用大于或等于 fromKey 的键生成此 Map 的视图。
这是一个类似于 SortedSetDemo.java 的示例,显示了 TreeMap 的这种额外行为:
// collectiontopics/SortedMapDemo.java// What you can do with a TreeMapimport java.util.*;import onjava.*;public class SortedMapDemo { public static void main(String[] args) { TreeMap<Integer,String> sortedMap = new TreeMap<>(new CountMap(10)); System.out.println(sortedMap); Integer low = sortedMap.firstKey(); Integer high = sortedMap.lastKey(); System.out.println(low); System.out.println(high); Iterator<Integer> it = sortedMap.keySet().iterator(); for(int i = 0; i <= 6; i++) { if(i == 3) low = it.next(); if(i == 6) high = it.next(); else it.next(); } System.out.println(low); System.out.println(high); System.out.println(sortedMap.subMap(low, high)); System.out.println(sortedMap.headMap(high)); System.out.println(sortedMap.tailMap(low)); }}/* Output:{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,9=J0}0937{3=D0, 4=E0, 5=F0, 6=G0}{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0}{3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0}*/复制ErrorOK!
这里,键值对按照键的排序顺序进行排序。因为 TreeMap 中存在顺序感,所以“位置”的概念很有意义,因此可以拥有第一个、最后一个元素或子图。
LinkedHashMap
LinkedHashMap 针对速度进行哈希处理,但在遍历期间也会按插入顺序生成键值对( System.out.println()
可以遍历它,因此可以看到遍历的结果)。此外,可以在构造方法中配置 LinkedHashMap 以使用基于访问的 最近最少使用(LRU) 算法,因此未访问的元素(因此是删除的候选者)会出现在列表的前面。这样可以轻松创建一个能够定期清理以节省空间的程序。下面是一个显示这两个功能的简单示例:
// collectiontopics/LinkedHashMapDemo.java// What you can do with a LinkedHashMapimport java.util.*;import onjava.*;public class LinkedHashMapDemo { public static void main(String[] args) { LinkedHashMap<Integer,String> linkedMap = new LinkedHashMap<>(new CountMap(9)); System.out.println(linkedMap); // Least-recently-used order: linkedMap = new LinkedHashMap<>(16, 0.75f, true); linkedMap.putAll(new CountMap(9)); System.out.println(linkedMap); for(int i = 0; i < 6; i++) linkedMap.get(i); System.out.println(linkedMap); linkedMap.get(0); System.out.println(linkedMap); }}/* Output:{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}*/!
这些键值对确实是按照插入顺序进行遍历,即使对于LRU版本也是如此。但是,在LRU版本中访问前六项(仅限)后,最后三项将移至列表的前面。然后,当再次访问“ 0 ”后,它移动到了列表的后面。
创建不可修改的 Collection 或 Map
通常,创建 Collection 或 Map 的只读版本会很方便。Collections 类通过将原始集合传递给一个方法然后返回一个只读版本的集合。对于 Collection (如果不能将 Collection 视为更具体的类型), List , Set 和 Map ,这类方法有许多变体。这个示例展示了针对每种类型,正确构建只读版本集合的方法:
// collectiontopics/ReadOnly.java// Using the Collections.unmodifiable methodsimport java.util.*;import onjava.*;public class ReadOnly { static Collection<String> data = new ArrayList<>(Countries.names(6)); public static void main(String[] args) { Collection<String> c = Collections.unmodifiableCollection(new ArrayList<>(data)); System.out.println(c); // Reading is OK //- c.add("one"); // Can't change it List<String> a = Collections.unmodifiableList(new ArrayList<>(data)); ListIterator<String> lit = a.listIterator(); System.out.println(lit.next()); // Reading is OK //- lit.add("one"); // Can't change it Set<String> s = Collections.unmodifiableSet(new HashSet<>(data)); System.out.println(s); // Reading is OK //- s.add("one"); // Can't change it // For a SortedSet: Set<String> ss = Collections.unmodifiableSortedSet(new TreeSet<>(data)); Map<String,String> m =Collections.unmodifiableMap(new HashMap<(Countries.capitals(6))); System.out.println(m); // Reading is OK //- m.put("Ralph", "Howdy!"); // For a SortedMap: Map<String,String> sm = Collections.unmodifiableSortedMap( new TreeMap<>(Countries.capitals(6))); }}/* Output:[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,BURUNDI]ALGERIA[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,BURUNDI]{BENIN=Porto-Novo, BOTSWANA=Gaberone, ANGOLA=Luanda,BURKINA FASO=Ouagadougou, ALGERIA=Algiers,BURUNDI=Bujumbura}*/!
为特定类型调用 “unmodifiable” 方法不会导致编译时检查,但是一旦发生转换,对修改特定集合内容的任何方法调用都将产生 UnsupportedOperationException 异常。
在每种情况下,在将集合设置为只读之前,必须使用有意义的数据填充集合。填充完成后,最好的方法是用 “unmodifiable” 方法调用生成的引用替换现有引用。这样,一旦使得内容无法修改,那么就不会冒有意外更改内容的风险。另一方面,此工具还允许将可修改的集合保留为类中的私有集合,并从方法调用处返回对该集合的只读引用。所以,你可以在类内修改它,但其他人只能读它。
同步 Collection 或 Map
synchronized 关键字是多线程主题的重要组成部分,更复杂的内容在并发中介绍。在这里,只需要注意到 Collections 类包含一种自动同步整个集合的方法。语法类似于 “unmodifiable” 方法:
// collectiontopics/Synchronization.java// Using the Collections.synchronized methodsimport java.util.*;public class Synchronization { public static void main(String[] args) { Collection<String> c = Collections.synchronizedCollection( new ArrayList<>()); List<String> list = Collections .synchronizedList(new ArrayList<>()); Set<String> s = Collections .synchronizedSet(new HashSet<>()); Set<String> ss = Collections .synchronizedSortedSet(new TreeSet<>()); Map<String,String> m = Collections .synchronizedMap(new HashMap<>()); Map<String,String> sm = Collections .synchronizedSortedMap(new TreeMap<>()); }}
最好立即通过适当的 “synchronized” 方法传递新集合,如上所示。这样,就不会意外地暴露出非同步版本。
Fail Fast
Java 集合还具有防止多个进程修改集合内容的机制。如果当前正在迭代集合,然后有其他一些进程介入并插入,删除或更改该集合中的对象,则会出现此问题。也许在集合中已经遍历过了那个元素,也许还没有遍历到,也许在调用 size()
之后集合的大小会缩小...有许多灾难情景。Java 集合库使用一种 fail-fast 的机制,该机制可以检测到除了当前进程引起的更改之外,其它任何对集合的更改操作。如果它检测到其他人正在修改集合,则会立即生成 ConcurrentModificationException 异常。这就是“fail-fast”的含义——它不会在以后使用更复杂的算法尝试检测问题(快速失败)。
通过创建迭代器并向迭代器指向的集合中添加元素,可以很容易地看到操作中的 fail-fast 机制,如下所示:
// collectiontopics/FailFast.java// Demonstrates the "fail-fast" behaviorimport java.util.*;public class FailFast { public static void main(String[] args) { Collection<String> c = new ArrayList<>(); Iterator<String> it = c.iterator(); c.add("An object"); try { String s = it.next(); } catch(ConcurrentModificationException e) { System.out.println(e); } }}/* Output:java.util.ConcurrentModificationException*/
异常来自于在从集合中获得迭代器之后,又尝试在集合中添加元素。程序的两个部分可能会修改同一个集合,这种可能性的存在会产生不确定状态,因此异常会通知你更改代码。在这种情况下,应先将所有元素添加到集合,然后再获取迭代器。
ConcurrentHashMap , CopyOnWriteArrayList 和 CopyOnWriteArraySet 使用了特定的技术来避免产生 ConcurrentModificationException 异常。
Set与Map之间的关系非常密切:
Set集合和Map集合的对应关系如下。
■ Set Map
■ EnumSet EnumMap
■ SortedSet SortedMap
■ TreeSet TreeMap
■ NavigableSet NavigableMap
■ HashSet HashMap
■ LinkedHashSet LinkedHashMap
虽然Map中放的元素是key-value对,Set集合中放的元素是单个对象,但如果我们把key-value对中的value当成key的附庸:key在哪里,value就跟在哪里。这样就可以像对待Set一样来对待Map了。事实上,Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑Entry封装的key。从Java源码来看, Java是先实现了Map,然后通过包装一个所有value都为null的Map就实现了Set集合。
如果把Map里的所有value放在一起来看,它们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中的索引不再使用整数值,而是以另一个对象作为索引。如果需要从List集合中取出元素,则需要提供该元素的数字索引;如果需要从Map中取出元素,则需要提供该元素的key索引。因此,Map有时也被称为字典,或关联数组
遍历map用keyset()方法:
public class LinkedHashMapTest { public static void main(String[] args) { LinkedHashMap scores = new LinkedHashMap(); scores.put("语文", 80); scores.put("英文", 82); scores.put("数学", 76); //遍历scores里的所有key-value对 for (Object key : scores.keySet()) { System.out.println(key + "------>" + scores.get(key)); } }}
归纳起来简单地说HashMap:
- HashMap在底层将key-value对当成一个整体进行处理,这个整体就是一个Entry对象。
- HashMap底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据Hash算法来决定其存储位置;当需要取出一个Entry时,也会根据Hash算法找到其存储位置,直接取出该Entry。由此可见,HashMap之所以能快速存、取它所包含的Entry,完全类似于现实生活中的:不同的东西要放在不同的位置,需要时才能快速找到它。
- 当创建HashMap时,有一个默认的负载因子(load factor),其默认值为0.75。这是时间和空间成本上的一种折衷:增大负载因子可以减少Hash表(就是那个Entry数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap的get()与put()方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加Hash表所占用的内存空间
- HashMap时根据实际需要适当地调整load factor的值。如果程序比较关心空间开销,内存比较紧张,可以适当地增加负载因子;如果程序比较关心时间开销,内存比较宽裕,则可以适当减少负载因子。通常情况下,程序员无需改变负载因子的值。
- 如果开始就知道HashMap会保存多个key-value对,可以在创建时就使用较大的初始化容量,如果HashMap中Entry的数量一直不会超过极限容量(capacity * load factor),HashMap就无需调用resize()方法重新分配table数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为capacity的Entry数组),因此创建HashMap时初始化容量设置也需要小心对待
Set与Map更多参看对hashset和Map有一个比较好的认知:
Queue
对列是一个先进先出的容器,即从容器的一端放入事物,从另一端取出来并且事物放入容器的顺序与取出的顺序是相同的,队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。
LinkedList可以用作Queue的一种实现,可以将LinkedList向上转型为Queue。
Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可使用。
使用LinkedList向上转型构造一个Queue
import java.util.*;public class QueueDemo { public static void printQ(Queue queue) { while(queue.peek() != null) System.out.print(queue.remove() + " "); System.out.println(); } public static void main(String[] args) { Queue<Integer> queue = new LinkedList<Integer>(); Random rand = new Random(47); for(int i = 0; i < 10; i++) queue.offer(rand.nextInt(i + 10)); printQ(queue); Queue<Character> qc = new LinkedList<Character>(); for(char c : "Brontosaurus".toCharArray()) qc.offer(c); printQ(qc); }} /* Output:8 1 1 1 5 14 3 1 0 1B r o n t o s a u r u s*///:~
PriorityQueue
先进先出描述了最典型的对列规则。对列规则是指定在给定一组对列中的元素的情况下,确定下一个弹出对列的元素的规则。先进先出是声明的下一个元素应该等待的最长的元素。
当使用PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在对列中被排序。默认的排揎将使用对象在对列中的自然排序、PriorityQueue可以确保当你调用peek(),poll()和remove()方法时,获取的元素将是对列中优先级最高的元素。
集合工具类
集合有许多独立的实用工具程序,在 java.util.Collections 中表示为静态方法。之前已经见过其中一些,例如 addAll()
, reverseOrder()
和 binarySearch()
。以下是其他内容(同步和不可修改的实用工具程序将在后面的章节中介绍)。在此表中,在需要的时候使用了泛型:
方法 | 描述 |
checkedCollection(Collection c, Class type) checkedList(List list, Class type) checkedMap(Map m, Class keyType, Class valueType) checkedSet(Set s, Class type) checkedSortedMap(SortedMap m, Class keyType, Class valueType) checkedSortedSet(SortedSet s, Class type) | 生成 Collection 的动态类型安全视图或 Collection 的特定子类型。当无法使用静态检查版本时使用这个版本。 这些方法的使用在第九章 多态章节的“动态类型安全”标题下进行了展示。 |
max(Collection) min(Collection) | 使用 Collection 中对象的自然比较方法生成参数集合中的最大或最小元素。 |
max(Collection, Comparator) min(Collection, Comparator) | 使用 Comparator 指定的比较方法生成参数集合中的最大或最小元素。 |
indexOfSubList(List source, List target) | 返回 target 在 source 内第一次出现的起始索引,如果不存在则返回 -1。 |
lastIndexOfSubList(List source, List target) | 返回 target 在 source 内最后一次出现的起始索引,如果不存在则返回 -1。 |
replaceAll(List list, T oldVal, T newVal) | 用 newVal 替换列表中所有的 oldVal 。 |
reverse(List) | 反转列表 |
reverseOrder() reverseOrder(Comparator) | 返回一个 Comparator ,它与集合中实现了 comparable 接口的对象的自然顺序相反。第二个版本颠倒了所提供的 Comparator 的顺序。 |
rotate(List, int distance) | 将所有元素向前移动 distance ,将尾部的元素移到开头。(译者注:即循环移动) |
shuffle(List) shuffle(List, Random) | 随机置换指定列表(即打乱顺序)。第一个版本使用了默认的随机化源,或者也可以使用第二个版本,提供自己的随机化源。 |
sort(List) sort(List, Comparator super T> c) | 第一个版本使用元素的自然顺序排序该 List 。第二个版本根据提供的 Comparator 排序。 |
copy(List super T> dest, List extends T> src) | 将 src 中的元素复制到 dest 。 |
swap(List, int i, int j) | 交换 List 中位置 i 和 位置 j 的元素。可能比你手工编写的速度快。 |
fill(List super T>, T x) | 用 x 替换 List 中的所有元素。 |
nCopies(int n, T x) | 返回大小为 n 的不可变 List ,其引用都指向 x 。 |
disjoint(Collection, Collection) | 如果两个集合没有共同元素,则返回 true 。 |
frequency(Collection, Object x) | 返回 Collection 中,等于 x 的元素个数。 |
emptyList() emptyMap() emptySet() | 返回不可变的空 List , Map 或 Set 。这些是泛型的,因此生成的 Collection 可以被参数化为所需的类型。 |
singleton(T x) singletonList(T x) singletonMap(K key, V value) | 生成一个不可变的 List , Set 或 Map ,其中只包含基于给定参数的单个元素。 |
list(Enumeration e) | 生成一个 ArrayList ,其中元素为(旧式) Enumeration ( Iterator 的前身)中的元素。用于从遗留代码向新式转换。 |
enumeration(Collection) | 为参数集合生成一个旧式的 Enumeration 。 |
请注意, min()
和 max()
使用 Collection 对象,而不使用 List ,因此不必担心是否应对 Collection 进行排序。(如前所述,在执行 binarySearch()
之前,将会对 List 或数组进行sort()
排序。)
下面是一个示例,展示了上表中大多数实用工具程序的基本用法:
// collectiontopics/Utilities.java// Simple demonstrations of the Collections utilitiesimport java.util.*;public class Utilities { static List<String> list = Arrays.asList( "one Two three Four five six one".split(" ")); public static void main(String[] args) { System.out.println(list); System.out.println("'list' disjoint (Four)?: " + Collections.disjoint(list, Collections.singletonList("Four"))); System.out.println( "max: " + Collections.max(list)); System.out.println( "min: " + Collections.min(list)); System.out.println( "max w/ comparator: " + Collections.max(list, String.CASE_INSENSITIVE_ORDER)); System.out.println( "min w/ comparator: " + Collections.min(list, String.CASE_INSENSITIVE_ORDER)); List<String> sublist = Arrays.asList("Four five six".split(" ")); System.out.println("indexOfSubList: " + Collections.indexOfSubList(list, sublist)); System.out.println("lastIndexOfSubList: " + Collections.lastIndexOfSubList(list, sublist)); Collections.replaceAll(list, "one", "Yo"); System.out.println("replaceAll: " + list); Collections.reverse(list); System.out.println("reverse: " + list); Collections.rotate(list, 3); System.out.println("rotate: " + list); List<String> source = Arrays.asList("in the matrix".split(" ")); Collections.copy(list, source); System.out.println("copy: " + list); Collections.swap(list, 0, list.size() - 1); System.out.println("swap: " + list); Collections.shuffle(list, new Random(47)); System.out.println("shuffled: " + list); Collections.fill(list, "pop"); System.out.println("fill: " + list); System.out.println("frequency of 'pop': " + Collections.frequency(list, "pop")); List<String> dups = Collections.nCopies(3, "snap"); System.out.println("dups: " + dups); System.out.println("'list' disjoint 'dups'?: " + Collections.disjoint(list, dups)); // Getting an old-style Enumeration: Enumeration<String> e = Collections.enumeration(dups); Vector<String> v = new Vector<>(); while(e.hasMoreElements()) v.addElement(e.nextElement()); // Converting an old-style Vector // to a List via an Enumeration: ArrayList<String> arrayList = Collections.list(v.elements()); System.out.println("arrayList: " + arrayList); }}/* Output:[one, Two, three, Four, five, six, one]'list' disjoint (Four)?: falsemax: threemin: Fourmax w/ comparator: Twomin w/ comparator: fiveindexOfSubList: 3lastIndexOfSubList: 3replaceAll: [Yo, Two, three, Four, five, six, Yo]reverse: [Yo, six, five, Four, three, Two, Yo]rotate: [three, Two, Yo, Yo, six, five, Four]copy: [in, the, matrix, Yo, six, five, Four]swap: [Four, the, matrix, Yo, six, five, in]shuffled: [six, matrix, the, Four, Yo, five, in]fill: [pop, pop, pop, pop, pop, pop, pop]frequency of 'pop': 7dups: [snap, snap, snap]'list' disjoint 'dups'?: truearrayList: [snap, snap, snap]*/复制ErrorOK!
输出解释了每种实用方法的行为。请注意由于大小写的缘故,普通版本的 min()
和 max()
与带有 String.CASE_INSENSITIVE_ORDER 比较器参数的版本的区别。
排序和搜索列表
用于执行排序和搜索 List 的实用工具程序与用于排序对象数组的程序具有相同的名字和方法签名,只不过是 Collections 的静态方法而不是 Arrays 。这是一个使用 Utilities.java 中的 list 数据的示例:
// collectiontopics/ListSortSearch.java// Sorting/searching Lists with Collections utilitiesimport java.util.*;public class ListSortSearch { public static void main(String[] args) { List<String> list = new ArrayList<>(Utilities.list); list.addAll(Utilities.list); System.out.println(list); Collections.shuffle(list, new Random(47)); System.out.println("Shuffled: " + list); // Use ListIterator to trim off last elements: ListIterator<String> it = list.listIterator(10); while(it.hasNext()) { it.next(); it.remove(); } System.out.println("Trimmed: " + list); Collections.sort(list); System.out.println("Sorted: " + list); String key = list.get(7); int index = Collections.binarySearch(list, key); System.out.println( "Location of " + key + " is " + index + ", list.get(" + index + ") = " + list.get(index)); Collections.sort(list, String.CASE_INSENSITIVE_ORDER); System.out.println( "Case-insensitive sorted: " + list); key = list.get(7); index = Collections.binarySearch(list, key, String.CASE_INSENSITIVE_ORDER); System.out.println( "Location of " + key + " is " + index + ", list.get(" + index + ") = " + list.get(index)); }}/* Output:[one, Two, three, Four, five, six, one, one, Two,three, Four, five, six, one]Shuffled: [Four, five, one, one, Two, six, six, three,three, five, Four, Two, one, one]Trimmed: [Four, five, one, one, Two, six, six, three,three, five]Sorted: [Four, Two, five, five, one, one, six, six,three, three]Location of six is 7, list.get(7) = sixCase-insensitive sorted: [five, five, Four, one, one,six, six, three, three, Two]Location of three is 7, list.get(7) = three*/复制ErrorOK!
就像使用数组进行搜索和排序一样,如果使用 Comparator 进行排序,则必须使用相同的 Comparator 执行 binarySearch()
。
该程序还演示了 Collections 中的 shuffle()
方法,该方法随机打乱了 List 的顺序。ListIterator 是在打乱后的列表中的特定位置创建的,用于从该位置删除元素,直到列表末尾。
集合常见的面试题:
Q&A HashMap的组成?
jdk 1.7时是数组+链表组成,
jdk1.8时是 数组+ 链表 + 红黑树组成。链表元素大于等于8时会把链表转为树结构,若桶中链的元素个数小于等于6时,树结构还原成链表。当链表的个数为8左右徘徊时就会生成树转链表,链表转树,效率低下。hasMap的负载因子默认为0.75,2^n是为了散列更加均匀。
Q&A HashMap的key为自定义的类应该怎么办?
如果key为自定义的类应该重写hashcode()和equals()方法。
Q&A HashMap为何线程不安全?
1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。
Q&A HashMap如何保证线程安全?
使用Collections.synchronizedMap()包装一下就可以了,原理就是对所有的修改操作都加上synchronized,保证了线程的安全。
Map map = Collections.synchronizedMap(new HashMap());
Q&A HashMap中的key可以为任意类型吗?
不能使用基本类型,HashMap中key是可以为null, 只能存储一个null, 因为计算key的hash值的时候,如果key为null, 则其hash值为0
之所以key不能为基本数据类型,则是因为基本数据类型不能调用其hashcode()方法和equals()方法,进行比较,所以HashMap集合的key只能为引用数据类型,不能为基本数据类型,可以使用基本数据类型的包装类,例如Integer Double等。
Q&A HashMap 和 HashTable 区别?
HashMap和HashTable都实现了Map接口,HashMap允许键和值是null而HashTable不允许键和值是null,HashTable是同步的,而HashMap不是,因此hashMap适用于单线程环境,而HashTable适用于多线程环境。HashMap提供了可供应用迭代的键的集合。
Q&A HashMap和ConCurrentHashMap区别?
hashMap线程不安全,put时在多线程的情况下会形成环而导致循环。
ConCurrentHashMap是线程安全的,采用分段机制,减少锁粒度。
ConCurrentHashMap是线程安全,在jdk1.7时采用Segment+HashEntry的方式进行实现 lock加上Segment上面。1.7 size计算是线采用不加锁的方式。连续计算元素的个数,最多计算3次。
1.8中取而代之是采用Node+CAS +Synchronized来保证并发安全,1.8实现使用一个volatile类型的变量baseCount记录元素的各少数。当插入新数据或删除新数据时,会通过addCount()方法更新baseCount,通过累计baseCount和CounterCell数组中的数量,即可得到元素的总个数。
Q&A ConcurrentHashMap 和 HashTable 区别?
ConcurrentHashMap
- 底层采用分段的数组+链表实现,线程安全
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
HashTable
- 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
- 初始size为11,扩容:newsize = olesize*2+1
- 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
两则的区别:
hashtable线程安全,采用的是线程同步得方法。
- ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。
- Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
Q&A Linkedhashmap 与 hashmap 的区别?
- HashMap
- HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。
- HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null。
- HashMap不支持线程的同步(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
- Hashtable与 HashMap类似,它继承自Dictionary类。不同的是:Hashtable不允许记录的键或者值为空;它支持线程的同步(即任一时刻只有一个线程能写Hashtable),因此也导致了 Hashtable在写入时会比较慢。
- LinkedHashMap
- 保存插入顺序:LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时带参数,按照应用次数排序。
- 速度慢:在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
Q&A hashmap 与 hashset 区别?
HashSet和HashMap之间有很多相似之处。对于HashSet而言,系统采用Hash算法决定集合元素的存储位置,这样可以保证快速存、取集合元素;对于HashMap而言,系统将value当成key的附属,系统根据Hash算法来决定key的存储位置,这样可以保证快速存、取集合key,而value总是紧随key存储。
HashSet的add()方法添加集合元素时实际上转变为调用HashMap的put()方法来添加 key-value对,当新放入 HashMap的Entry 中key 与集合中原有Entry的key 相同(hashCode()返回值相等,通过equals比较也返回true)时,新添加的Entry的value将覆盖原来Entry的value,但key不会有任何改变。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素(底层由HashMap的key保存)不会覆盖已有的集合元素
Q&A ArrayList和Vector有何异同点?
相同点:(1)两者都是基于索引的,都是基于数组的。(2)两者都维护插入顺序,我们可以根据插入顺序来获取元素。(3)ArrayList 和 Vector 的迭代器实现都是 fail-fast 的。(4)ArrayList 和 Vector 两者允许 null 值,也可以使用索引值对元素进行随机访问。不同点:(1)Vector 是同步,线程安全,而 ArrayList 非同步,线程不安全。对于 ArrayList,如果 迭代时改变列表,应该使用 CopyOnWriteArrayList。(2)但是,ArrayList 比 Vector 要快,它因为有同步,不会过载。(3)在使用上,ArrayList 更加通用,因为 Collections 工具类容易获取同步列表和只读列 表。ArrayList在并发add()可能出现下标越界异常。
Q&A ArrayList 与 LinkedList 区别 ?
ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点:ArrayList 是基于索引的数据接口,它的底层是数组。它可以以 O(1)时间复杂度对元素进行 随机访问。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的 前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是 O(n)。相对于 ArrayList,LinkedList 的插入,添加,删除操作速度更快,因为当元素被添加到集合 任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指 向前一个元素,一个指向下一个元素。
Q&A 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用 Array 而不是 ArrayList?
array包含基本类型和对象类型。Arraylist只能包含对象类型。
Arraylist是采用数组实现的,arraylist是可以自动扩容的。比array提供了更多的特性,比如 addAll(),removeAll() 等。
Q&A 使用ArrayList的迭代器会出现什么问题?单线程和多线程环境下;
常用的迭代器设计模式,iterator 方法返回一个父类实现的迭代器。1、迭代器的 hasNext 方法的作用是判断当前位置是否是数组最后一个位置,相等为 false, 否则为 true。2、迭代器 next 方法用于返回当前的元素,并把指针指向下一个元素,值得注意的是,每次 使用 next 方法的时候,都会判断创建迭代器获取的这个容器的计数器 modCount 是否与此 时 的 不 相 等 , 不 相 等 说 明 集 合 的 大 小 被 修 改 过 , 如 果 是 会 抛 出 ConcurrentModificationException 异常,如果相等调用 get 方法返回元素即可
Properties类:
Properties 继承于 Hashtable。表示一个持久的属性集,属性列表以key-value的形式存在,key和value都是字符串。
Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。我们在很多需要避免硬编码的应用场景下需要使用properties文件来加载程序需要的配置信息,比如JDBC、MyBatis框架等。Properties类则是properties文件和程序的中间桥梁,不论是从properties文件读取信息还是写入信息到properties文件都要经由Properties类。
public static void main(String[] args) throws Exception { Properties props = new Properties(); //向Properties中添加属性 props.setProperty("username", "yeeku"); props.setProperty("password", "123456"); //将Properties中的key-value对保存到a.ini文件中 props.store(new FileOutputStream("a.ini"), "comment line"); //①//新建一个Properties对象 Properties props2 = new Properties(); //向Properties中添加属性 props2.setProperty("gender", "male"); //将a.ini文件中的key-value对追加到props2中 props2.load(new FileInputStream("a.ini")); //② System.out.println(props2); }// 加载获取Connectionpublic Connection getConnection() throws Exception{ Properties info=new Properties(); info.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties")); String driver=info.getProperty("driver"); String jdbcUrl=info.getProperty("jdbcUrl"); String user=info.getProperty("user"); String password=info .getProperty("password"); Class.forName(driver); Connection connection=DriverManager.getConnection(jdbcUrl,user,password); return connection; }
可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型。该类提供了如下三个方法来修改Properties里的key、value值。