java容器学习


1.Set

Set(interface): 存入Set的每个元素是唯一的,Set中不存在重复元素,加入Set的元素必须定义equals()方法一确保对象的唯一性,Set接口不保证维护元素的次序。

HashSet(默认): 快速查找,存入元素必须定义hashCode()

TreeSet : 保持次序,底层树结构,提取有序序列需要实现Comparable接口。

LinkedHashSet:  具有HashSet查询速度,并且内部维护链表元素顺序(插入的次序)。迭代遍历结果显示元素插入的次序。元素须定义hashCode()。

import java.util.*;

class SetType {
  int i;
  public SetType(int n) { i = n; }
  public boolean equals(Object o) {
    return o instanceof SetType && (i == ((SetType)o).i);
  }
  public String toString() { return Integer.toString(i); }
}

class HashType extends SetType {
  public HashType(int n) { super(n); }
  public int hashCode() { return i; }
}

class TreeType extends SetType
implements Comparable<TreeType> {
  public TreeType(int n) { super(n); }
  public int compareTo(TreeType arg) {
    return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
  }
}

public class TypesForSets {
  static <T> Set<T> fill(Set<T> set, Class<T> type) {
    try {
      for(int i = 0; i < 10; i++)
          set.add(
            type.getConstructor(int.class).newInstance(i));
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
    return set;
  }
  static <T> void test(Set<T> set, Class<T> type) {
    fill(set, type);
    fill(set, type); // Try to add duplicates
    fill(set, type);
    System.out.println(set);
  }
  public static void main(String[] args) {
    test(new HashSet<HashType>(), HashType.class);
    test(new LinkedHashSet<HashType>(), HashType.class);
    test(new TreeSet<TreeType>(), TreeType.class);
    // Things that don't work:
    test(new HashSet<SetType>(), SetType.class);
    test(new HashSet<TreeType>(), TreeType.class);
    test(new LinkedHashSet<SetType>(), SetType.class);
    test(new LinkedHashSet<TreeType>(), TreeType.class);
    try {
      test(new TreeSet<SetType>(), SetType.class);
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
    try {
      test(new TreeSet<HashType>(), HashType.class);
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
  }
} /* Output: (Sample)
[2, 4, 9, 8, 6, 1, 3, 7, 5, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 5, 0, 8, 8, 8, 6, 5, 1]
[0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable

fill()三次尝试引入重复对象,SetType和TreeType没有重新定义HashCode(),它们放置任何散列实现中都会产生重复值,因为默认的hashCode()是合法,不会有运行错误。


2. Queue

队列,一端进,并且另一端出。

LinkedList

PriorityQueue 实现Comparable控制元素优先级

//: containers/ToDoList.java
// A more complex use of PriorityQueue.
import java.util.*;

class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {
  static class ToDoItem implements Comparable<ToDoItem> {
    private char primary;
    private int secondary;
    private String item;
    public ToDoItem(String td, char pri, int sec) {
      primary = pri;
      secondary = sec;
      item = td;
    }
    public int compareTo(ToDoItem arg) {
      if(primary > arg.primary)
        return +1;
      if(primary == arg.primary)
        if(secondary > arg.secondary)
          return +1;
        else if(secondary == arg.secondary)
          return 0;
      return -1;
    }
    public String toString() {
      return Character.toString(primary) +
        secondary + ": " + item;
    }
  }
  public void add(String td, char pri, int sec) {
    super.add(new ToDoItem(td, pri, sec));
  }
  public static void main(String[] args) {
    ToDoList toDoList = new ToDoList();
    toDoList.add("Empty trash", 'C', 4);
    toDoList.add("Feed dog", 'A', 2);
    toDoList.add("Feed bird", 'B', 7);
    toDoList.add("Mow lawn", 'C', 3);
    toDoList.add("Water lawn", 'A', 1);
    toDoList.add("Feed cat", 'B', 1);
    while(!toDoList.isEmpty())
      System.out.println(toDoList.remove());
  }
} /* Output:
A1: Water lawn
A2: Feed dog
B1: Feed cat
B7: Feed bird
C3: Mow lawn
C4: Empty trash
*///:~


3.Map

HashMap(默认): Map基于散列表实现,代替HashTable。插入和查询key-value开销固定。通过构造器设置容量和负载因子以调整容器性能。

LinkedHashMap: 类似HashMap,迭代遍历顺序是插入顺序,或者是最近最少使用(LRU)次序,比HashMap慢一点,但是迭代访问反而更快,因为链表维护内部次序。

TreeMap: 基于红黑树实现。查看key或者key-value时,会排序(由Comparable或Comparator决定)。其特点是,得到的结构是排序的。TreeMap是唯一带有subMap()的Map,可以返回一个子树。

WeakHashMap: 允许释放指向的对象,如果映射之外没有引用的key,则该Key可以被回收。

ConcurrentHashMap: 线程安全,不涉及同和加锁。

IdentityHashMap: ==代替equals(),对key进行比较散列映射。

4.Hash&HashCode

一个结果错误的例子

public class Groundhog {
  protected int number;
  public Groundhog(int n) { number = n; }
  public String toString() {
    return "Groundhog #" + number;
  }
}
import java.util.*;

public class Prediction {
  private static Random rand = new Random(47);
  private boolean shadow = rand.nextDouble() > 0.5;
  public String toString() {
    if(shadow)
      return "Six more weeks of Winter!";
    else
      return "Early Spring!";
  }
} 
import java.lang.reflect.*;
import java.util.*;
import static net.mindview.util.Print.*;

public class SpringDetector {
  // Uses a Groundhog or class derived from Groundhog:
  public static <T extends Groundhog>
  void detectSpring(Class<T> type) throws Exception {
    Constructor<T> ghog = type.getConstructor(int.class);
    Map<Groundhog,Prediction> map =
      new HashMap<Groundhog,Prediction>();
    for(int i = 0; i < 10; i++)
      map.put(ghog.newInstance(i), new Prediction());
    print("map = " + map);
    Groundhog gh = ghog.newInstance(3);
    print("Looking up prediction for " + gh);
    if(map.containsKey(gh))
      print(map.get(gh));
    else
      print("Key not found: " + gh);
  }
  public static void main(String[] args) throws Exception {
    detectSpring(Groundhog.class);
  }
} /* Output:
map = {Groundhog #3=Early Spring!, Groundhog #7=Early Spring!, Groundhog #5=Early Spring!, Groundhog #9=Six more weeks of Winter!, Groundhog #8=Six more weeks of Winter!, Groundhog #0=Six more weeks of Winter!, Groundhog #6=Early Spring!, Groundhog #4=Six more weeks of Winter!, Groundhog #1=Six more weeks of Winter!, Groundhog #2=Early Spring!}
Looking up prediction for Groundhog #3
Key not found: Groundhog #3

Groundhog自动继承Object,这里使用Object的hashCode()方法,默认是使用对象的地址计算hashCode,Object.equals()默认比较对象地址,所以Groundhog(3)第一个实例与第二个Groundhog(3)的hashCode是不同的

重写hashCode()同时,必须重写equals()

equals()方法满足以下:

1)自反性, 任意x.equals(x): true

2)对称性, x.equals(y):true -> y.equals(x):true

3)传递性, x.equals(y):true, x.equals(z):true -> y.equals(z):true

4)一致性, 对于x, y, 如果xy不变,x.equals(y)一直保持一致

5)x.equals(null) ---> false


上一例子修正

public class Groundhog2 extends Groundhog {
  public Groundhog2(int n) { super(n); }
  public int hashCode() { return number; }
  public boolean equals(Object o) {
    return o instanceof Groundhog2 &&
      (number == ((Groundhog2)o).number);
  }
}
public class SpringDetector2 {
  public static void main(String[] args) throws Exception {
    SpringDetector.detectSpring(Groundhog2.class);
  }
} /* Output:
map = {Groundhog #2=Early Spring!, Groundhog #4=Six more weeks of Winter!, Groundhog #9=Six more weeks of Winter!, Groundhog #8=Six more weeks of Winter!, Groundhog #6=Early Spring!, Groundhog #1=Six more weeks of Winter!, Groundhog #3=Early Spring!, Groundhog #7=Early Spring!, Groundhog #5=Early Spring!, Groundhog #0=Six more weeks of Winter!}
Looking up prediction for Groundhog #3
Early Spring!

4.1 深入hashCode()

import java.util.*;
import net.mindview.util.Countries;

public class SlowMap<K,V> extends AbstractMap<K,V> {

	private List<K> keys = new ArrayList<K>();
	private List<V> values = new ArrayList<V>();
	public V put(K key, V value){
		V oldValue=get(key);
		if(!keys.contains(key)){
			keys.add(key);
			values.add(value);
		}else{
			values.set(keys.indexOf(key), value);
		}
		return oldValue;
	}
	public V get(Object key){
		if(!keys.contains(key))return null;
		return values.get(keys.indexOf(key));
	}
	
	public static void main(String[] args) {
		SlowMap<String,String> m = new SlowMap<String,String>();
		m.putAll(Countries.capitals(15));
		System.out.println(m);
//		m.put("BURUNDI", "SHIT");
		System.out.println(m.get("BURUNDI"));
		System.out.println(m.entrySet());
		System.out.println(m.entrySet().contains(new MapEntry<String,String>("ALGERIA", "Algiers")));
	} 

	
	
	@Override
	public Set<java.util.Map.Entry<K, V>> entrySet() {
		Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
		Iterator<K> ki = keys.iterator();
		Iterator<V> vi = values.iterator();
		while(ki.hasNext()){
			set.add(new MapEntry<K,V>(ki.next(), vi.next()));
		}
		return set;
	}

}

class MapEntry<K,V> implements Map.Entry<K, V>{
	private K key;
	private V value;
	public MapEntry(K key, V valus){
		this.key=key;
		this.value=valus;
	}
	@Override
	public K getKey() {
		return key;
	}

	@Override
	public V getValue() {
		return value;
	}

	@Override
	public V setValue(V v) {
		V result = value;
		value = v;
		return result;
	}
	
	public int hashCode(){
		return (key==null?0:key.hashCode())^(value==null?0:value.hashCode());
	}
	public boolean equals(Object o){
		if(!(o instanceof MapEntry)) return false;
		MapEntry me = (MapEntry)o;
		return (key==null? me.getKey()==null:key.equals(me.getKey())) 
				&& (value==null?me.getValue()==null:value.equals(me.getValue()));
	}
	public String toString(){
		return key + "-" + value;
	}
}
output:
{CHAD=N'djamena, ALGERIA=Algiers, BOTSWANA=Gaberone, BURUNDI=Bujumbura, CONGO=Brazzaville, DJIBOUTI=Dijibouti, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, ANGOLA=Luanda, EGYPT=Cairo, CAPE VERDE=Praia, BURKINA FASO=Ouagadougou, BENIN=Porto-Novo, CENTRAL AFRICAN REPUBLIC=Bangui, CAMEROON=Yaounde}
Bujumbura
[CHAD-N'djamena, ALGERIA-Algiers, BOTSWANA-Gaberone, BURUNDI-Bujumbura, CONGO-Brazzaville, DJIBOUTI-Dijibouti, COMOROS-Moroni, EQUATORIAL GUINEA-Malabo, ANGOLA-Luanda, EGYPT-Cairo, CAPE VERDE-Praia, BURKINA FASO-Ouagadougou, BENIN-Porto-Novo, CENTRAL AFRICAN REPUBLIC-Bangui, CAMEROON-Yaounde]
true
SlowMap问题在于key的查询,key没有按照任何特定顺序保存,所以只能使用最简单,也是最慢的线性查询方式。

Hash的价值在于速度,Hash使得查询得意快速进行。

Hash将key保存在某处,以便能快速定位。存储一组元素最快的数据结构是数组,所以使用它来表示key的信息(不是key本身),

但是数组不能改变大小,而是通过key对象生成一个数字,将其作为数组的下标,这个数字就是hashCode。java中通过覆盖Object.hashCode()来产生。

为了解决数组容量固定的问题,不同的键可以产生相同的下标,这样可能发生冲突。所以数组的容量可以不关心,任何key都能在数组中找到位置。

于是查询的过程变成这样:计算hashCode,根据hashCode查找数组,如果无冲突,完美散列函数。冲突由外部链接处理,即数组不直接保存key,而是保存一个list。然后对list使用equals()方法进行线性查询。

线性查询较慢,但是如果hashCode()写得好(分布均匀),数组的每个位置的list只有较少值。

总结就是不用线性查询这个list,而是通过hashcode快速定位数组某个位置,然后进行很少的元素比较即可完成查询。这便是HashMap快的原因。
根据以上,实现一个HashMap例子:

import java.util.*;

import net.mindview.util.Countries;


public class SimpleHashMap<K,V> extends AbstractMap<K,V> {

	static final int SIZE = 997;
	
	LinkedList<MapEntry<K,V>>[] buckets = new LinkedList[SIZE];
	public V put(K key, V value){
		V oldValue = null;
		int index =  Math.abs(key.hashCode())%SIZE;
		if(buckets[index]==null){
			buckets[index] = new LinkedList<MapEntry<K,V>>();
		}
		LinkedList<MapEntry<K,V>> bucket = buckets[index];
		MapEntry<K,V> pair = new MapEntry<K,V>(key,value);
		boolean found = false;
		ListIterator<MapEntry<K,V>> it = bucket.listIterator();
		while(it.hasNext()){
			MapEntry<K,V> ipair = it.next();
			if(ipair.getKey().equals(key)){
				oldValue = ipair.getValue();
				it.set(pair);
				found=true;
				break;
			}
		}
		if(!found){
			buckets[index].add(pair);
		}
		return oldValue;
	}
	
	public V get(Object key){
		int index = Math.abs(key.hashCode())%SIZE;
		if(buckets[index]==null)return null;
		for(MapEntry<K,V> ipair:buckets[index]){
			if(ipair.getKey().equals(key)){
				return ipair.getValue();
			}
		}
		return null;
	}
	
	@Override
	public Set<java.util.Map.Entry<K, V>> entrySet() {
		Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
		for(LinkedList<MapEntry<K,V>> bucket:buckets){
			if(bucket==null)continue;
			for(MapEntry<K,V> mpair:bucket){
				set.add(mpair);
			}
		}
		return set;
	}

	public static void main(String[] args){
		SimpleHashMap<String, String> m = new SimpleHashMap<String,String>();
		m.putAll(Countries.capitals(100));
		System.out.println(m);
		System.out.println(m.get("ERITREA"));
		System.out.println(m.entrySet());
	}
	
}
output:
{CHAD=N'djamena, ALGERIA=Algiers, BOTSWANA=Gaberone, BURUNDI=Bujumbura, CONGO=Brazzaville, DJIBOUTI=Dijibouti, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, ANGOLA=Luanda, EGYPT=Cairo, CAPE VERDE=Praia, BURKINA FASO=Ouagadougou, BENIN=Porto-Novo, CENTRAL AFRICAN REPUBLIC=Bangui, CAMEROON=Yaounde}
Gaberone
[CHAD-N'djamena, ALGERIA-Algiers, BOTSWANA-Gaberone, BURUNDI-Bujumbura, CONGO-Brazzaville, DJIBOUTI-Dijibouti, COMOROS-Moroni, EQUATORIAL GUINEA-Malabo, ANGOLA-Luanda, EGYPT-Cairo, CAPE VERDE-Praia, BURKINA FASO-Ouagadougou, BENIN-Porto-Novo, CENTRAL AFRICAN REPUBLIC-Bangui, CAMEROON-Yaounde]
设计hashCode()最重要一点是:无论何时,对同一个对象调用hashCode()都应该生产同样的值。如果put()产生一个hashCode,而get()取出来是另外一个hashCode,那么无法重现取得对象。

使用hashCode()不应该依赖具有唯一性的对象信息。比如第一个例子SpringDetector.java,因为使用Object.hashCode()是对象地址。

一个String设计hashCode的参考例子

//: containers/CountedString.java
// Creating a good hashCode().
import java.util.*;
import static net.mindview.util.Print.*;

public class CountedString {
  private static List<String> created =
    new ArrayList<String>();
  private String s;
  private int id = 0;
  public CountedString(String str) {
    s = str;
    created.add(s);
    // id is the total number of instances
    // of this string in use by CountedString:
    for(String s2 : created)
      if(s2.equals(s))
        id++;
  }
  public String toString() {
    return "String: " + s + " id: " + id +
      " hashCode(): " + hashCode();
  }
  public int hashCode() {
    // The very simple approach:
    // return s.hashCode() * id;
    // Using Joshua Bloch's recipe:
    int result = 17;
    result = 37 * result + s.hashCode();
    result = 37 * result + id;
    return result;
  }
  public boolean equals(Object o) {
    return o instanceof CountedString &&
      s.equals(((CountedString)o).s) &&
      id == ((CountedString)o).id;
  }
  public static void main(String[] args) {
    Map<CountedString,Integer> map =
      new HashMap<CountedString,Integer>();
    CountedString[] cs = new CountedString[5];
    for(int i = 0; i < cs.length; i++) {
      cs[i] = new CountedString("hi");
      map.put(cs[i], i); // Autobox int -> Integer
    }
    print(map);
    for(CountedString cstring : cs) {
      print("Looking up " + cstring);
      print(map.get(cstring));
    }
  }
} /* Output: (Sample)
{String: hi id: 4 hashCode(): 146450=3, String: hi id: 1 hashCode(): 146447=0, String: hi id: 3 hashCode(): 146449=2, String: hi id: 5 hashCode(): 146451=4, String: hi id: 2 hashCode(): 146448=1}
Looking up String: hi id: 1 hashCode(): 146447
0
Looking up String: hi id: 2 hashCode(): 146448
1
Looking up String: hi id: 3 hashCode(): 146449
2
Looking up String: hi id: 4 hashCode(): 146450
3
Looking up String: hi id: 5 hashCode(): 146451
4
*///:~



5. 容器比较与选择的理论和研究(标题碉堡了有木有)

List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

LinkedList类
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));

ArrayList类
  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

Vector类
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

Stack 类
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

------------------------------------------------------------------------------------------------------------------------------------------------------

Set接口
Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象。


HashSet
HashSet有以下特点
 不能保证元素的排列顺序,顺序有可能发生变化
 不是同步的
 集合元素可以是null,但只能放入一个null
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。


LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。


TreeSet类
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

------------------------------------------------------------------------------------------------------------------------------------------------------

  Map 接口概述
java.util.Map 接口描述了映射结构, Map 接口允许以键集、值集合或键 - 值映射关系集的形式查看某个映射的内容。
Java 自带了各种 Map 类。 这些 Map 类可归为三种类型:
   1. 通用 Map ,用于在应用程序中管理映射,通常在 java.util 程序包中实现
          * HashMap
          * Hashtable
          * Properties
           * LinkedHashMap
          * IdentityHashMap
          * TreeMap
          * WeakHashMap
          * ConcurrentHashMap
   2. 专用 Map ,您通常不必亲自创建此类 Map ,而是通过某些其他类对其进行访问
          * java.util.jar.Attributes
          * javax.print.attribute.standard.PrinterStateReasons
          * java.security.Provider
          * java.awt.RenderingHints
          * javax.swing.UIDefaults
   3. 一个用于帮助实现您自己的 Map 类的抽象类
          * AbstractMap
 
接口中的重要方法如下:
1,  覆盖的方法
equals(Object o)               // 比较指定对象与此 Map 的等价性
hashCode()                     // 返回此 Map 的哈希码
 
2,  Map 更新方法,可以更改 Map 的内容。
put(Object key, Object value)  // 添加键值对,若键已存在,则覆盖旧值。
putAll(Map t)               // 将指定 Map 中的所有映射复制到此 map
remove(Object key)             // 从 Map 中删除与 key 关联的 value
clear()                       // 从 Map 中删除所有映射
 
3,  返回视图的 Map 方法:使用这些方法返回的对象,你可以遍历和删除 Map 的元素。
Set keySet()                // 返回 Map 中所包含键的 Set 视图。
// 删除 Set 中的 key 元素还将删除 Map 中相应的映射(键和值)
Collection values()         // 返回 map 中所包含值的 Collection 视图。
// 删除 Collection 中的 value 元素还将删除 Map 中相应的映射(键和值)
Set entrySet()             // 返回 Map 中所包含映射的 Set 视图(键值对)。
Set 中的每个元素都是一个 Map.Entry 对象,可以使用 getKey() 和 getValue() 方法(还有一个 setValue() 方法)访问 Map.Entry 对象的键元素和值元素
 
关于 Map.Entry 接口
Map 的 entrySet() 方法返回一个实现 Map.Entry 接口的对象集合。集合中每个对象都是底层 Map 中一个特定的键 / 值对。通过这个集合的迭代器,您可以获得每一个条目 ( 唯一获取方式 ) 的键或值并对值进行更改。
(1) Object getKey(): 返回条目的关键字
   (2) Object getValue(): 返回条目的值
   (3) Object setValue(Object value): 将相关映像中的值改为 value ,并且返回旧值
当条目通过迭代器返回后,除非是迭代器自身的 remove() 方法或者迭代器返回的条目的 setValue() 方法,其余对源 Map 外部的修改都会导致此条目集变得无效,同时产生条目行为未定义。
 
4,  Map 访问和测试方法:这些方法检索有关 Map 内容的信息但不更改 Map 内容。
get(Object key)             // 返回与指定键关联的值 及此对象,若无,返回 null 。
boolean containsKey(Object key)     // 如果 Map 包含指定键的映射,则返回 true
boolean containsValue(Object value)  // 若此 Map 将一个或多个键映射到指定值,返回 true
isEmpty()                // 如果 Map 不包含键 - 值映射,则返回 true
int size()                  // 返回 Map 中的键 - 值映射的数目
 
 
几乎所有通用 Map 都使用哈希映射。 这是一种将元素映射到数组的非常简单的机制,您应了解哈希映射的工作原理,以便充分利用 Map 。
哈希映射结构由一个存储元素的内部数组组成。 由于内部采用数组存储,因此必然存在一个用于确定任意键访问数组的索引机制。 实际上,该机制需要提供一个小于数组大小的整数索引值(即余数)。 该机制称作哈希函数。 在 Java 基于哈希的 Map 中,哈希函数将对象转换为一个适合内部数组的整数。 您不必为寻找一个易于使用的哈希函数而大伤脑筋: 每个对象都包含一个返回整数值的 hashCode() 方法。 要将该值映射到数组,只需将其转换为一个正值,然后在将该值除以数组大小后取余数即可。
哈希函数将任意对象映射到一个数组位置,但如果两个不同的键映射到相同的位置,情况将会如何? 这是一种必然发生的情况。 在哈希映射的术语中,这称作冲突。 Map 处理这些冲突的方法是在索引位置处插入一个链接列表,并简单地将元素添加到此链接列表。



HashMap与HashTable的区别:
1、同步性:Hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的。而HashMap则是异步的,因此HashMap中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率。
2、值:HashMap可以让你将空值作为一个表的条目的key或value,但是Hashtable是不能放入空值的。HashMap最多只有一个key值为null,但可以有无数多个value值为null。
注意:
1、用作key的对象必须实现hashCode和equals方法。
2、不能保证其中的键值对的顺序
3、尽量不要使用可变对象作为它们的key值。


 


LinkedHashMap:
它的父类是HashMap,使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致。LinkedHashMap需要维护元素的插入顺序,so性能略低于HashMap,但在迭代访问元素时有很好的性能,因为它是以链表来维护内部顺序。


 


TreeMap:
Map接口派生了一个SortMap子接口,SortMap的实现类为TreeMap。TreeMap也是基于红黑树对所有的key进行排序,有两种排序方式:自然排序和定制排序。Treemap的key以TreeSet的形式存储,对key的要求与TreeSet对元素的要求基本一致。


1、Map.Entry firstEntry():返回最小key所对应的键值对,如Map为空,则返回null。
2、Object firstKey():返回最小key,如果为空,则返回null。
3、Map.Entry lastEntry():返回最大key所对应的键值对,如Map为空,则返回null。
4、Object lastKey():返回最大key,如果为空,则返回null。
5、Map.Entry higherEntry(Object key):返回位于key后一位的键值对,如果为空,则返回null。
6、Map.Entry lowerEntry(Object key):返回位于key前一位的键值对,如果为空,则返回null。
7、Object lowerKey(Object key):返回位于key前一位key值,如果为空,则返回null。
8、NavigableMap subMap(Object fromKey,boolean fromlnclusive,Object toKey,boolean toInciusive):返回该Map的子Map,其key范围从fromKey到toKey。
9、SortMap subMap(Object fromKey,Object toKey );返回该Map的子Map,其key范围从fromkey(包括)到tokey(不包括)。
10、SortMap tailMap(Object fromkey ,boolean inclusive):返回该Map的子Map,其key范围大于fromkey(是否包括取决于第二个参数)的所有key。
11、 SortMap headMap(Object tokey ,boolean inclusive):返回该Map的子Map,其key范围小于tokey(是否包括取决于第二个参数)的所有key。


 


WeakHashMap:
WeakHashMap与HashMap的用法基本相同,区别在于:后者的key保留对象的强引用,即只要HashMap对象不被销毁,其对象所有key所引用的对象不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对对象。但WeakHashMap的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被回收。WeakHashMap中的每个key对象保存了实际对象的弱引用,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。


 public static void main(String[] args) {
  // TODO Auto-generated method stub
  WeakHashMap w1=new WeakHashMap();
  //添加三个键值对,三个key都是匿名字符串,没有其他引用
  w1 .put("语文", "良好");
  w1 .put("数学", "及格");
  w1 .put("英语", "中等");
  w1 .put("java", "good");//该key是一个系统缓存的字符串对象
  System.out.println(w1 );//输出{java=good, 数学=及格, 英语=中等, 语文=良好}
  //通知系统进行垃圾回收
  System.gc();
  System.runFinalization();
  System.out.println(w1 );//输出{java=good}
   }


 


IdentityHashMap类:
IdentityHashMap与HashMap基本相似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。IdentityHashMap也允许使用null,但不保证键值对之间的顺序。




EnumMap类:
1、EnumMap中所有key都必须是单个枚举类的枚举值,创建EnumMap时必须显示或隐式指定它对应的枚举类。
2、EnumMap根据key的自然顺序,即枚举值在枚举类中定义的顺序,来维护键值对的次序。
3、EnumMap不允许使用null作为key值,但value可以。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值