一、Map接口
Map接口中存储数据是通过key->value的方式成对存储的,可以通过key找到value。
二、Map接口常用子类
1.HashMap
HashMap是无序存放的,key不允许重复,但值可以重复。如果key重复,后来的value会覆盖之前的value。
import java.util.HashMap; import java.util.Map; public class TestHashMpa { public static void main(String[] args) { Map<Integer,String> m = new HashMap<Integer,String>(); m.put(3,"C"); m.put(1,"A"); m.put(2,"B"); m.put(3,"D"); m.put(4,"A"); System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4)); } }
运行结果:
1=A 2=B 3=D 4=A
这里面有两个方法一个是public V put(K key,V value),此处的K,V泛型。put方法向map中放入k—>v.并返回value。
还有一个是public V get(Object K),获得key对应的value。
上面代码中键值3重复了,所以先添加进去C被后添加进去的D覆盖,此时get(3)得到的是D。
value有两个A,但是它们的键没有重复所以不影响。
三、常用方法简介
1.判断内容是否存在
1.1 public boolean containsKey(Object key)判断key是否存在。
1.2 public boolean containsValue(Object key)判断value是否存在。
import java.util.HashMap; import java.util.Map; public class TestHashMpa { public static void main(String[] args) { Map<Integer,String> m = new HashMap<Integer,String>(); m.put(3,"C"); m.put(1,"A"); m.put(2,"B"); m.put(3,"D"); m.put(4,"A"); System.out.println(m.containsKey(1));//判断key为1是否存在,此处为true System.out.println(m.containsKey(5)); System.out.println(m.containsValue("A"));//判断Value"A"是否存在,此处为true System.out.println(m.containsValue("E")); } }
运行结果: true false true false
2.根据key删除value
import java.util.HashMap; import java.util.Map; public class TestHashMpa { public static void main(String[] args) { Map<Integer,String> m = new HashMap<Integer,String>(); m.put(3,"C"); m.put(1,"A"); m.put(2,"B"); m.put(3,"D"); m.put(4,"A"); System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4)); m.remove(4); System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4)); } }
1=A 2=B 3=D 4=A 1=A 2=B 3=D 4=null
根据键值删除对应的value。
3.public void putAll(Map<? extends K,? extends V> t)
将一个Map集合中的内容加入到另一个Map
import java.util.HashMap; import java.util.Map; public class TestHashMpa { public static void main(String[] args) { Map<Integer,String> m = new HashMap<Integer,String>(); Map<Integer,String> ma = new HashMap<Integer,String>(); m.put(3,"C"); m.put(1,"A"); m.put(2,"B"); m.put(3,"D"); m.put(4,"A"); ma.put(5, "D"); ma.put(6, "E"); ma.putAll(m); System.out.println("1="+ ma.get(1)+" 2="+ma.get(2)+" 3="+ma.get(3)+" 4="+ma.get(4)+ " 5="+ ma.get(5)+" 6="+ ma.get(6)); } }
运行结果:
1=A 2=B 3=D 4=A 5=D 6=E
m本身有A、B、D,ma有A、D、E,将m添加到ma后,ma就有A、B、D、A、D、E。
四、HashMap底层实现
我们不妨想一想HashMap底层如何实现,
假如我们用数组,这样好像也可以,但是我们用数组的话。检查Key是否重复,包括根据Key查找Value每次都要从头开始遍历,这样效率太低浪费资源。
我们能否有一种更好的方式,可以减少查询时间?
当然是有的,同时也是HashMap的实现方式,数组+链表。
数组中每一个元素存放一个链表。
这里还需要用到hashCode(),计算hash码的函数,我们暂且先不管这些。
我们先来假设下:
假设有一个无限大的数组arr,向这个数组放入东西必须成对放入(key,value)。
假设有一个方法h,可以根据key计算出一个值,且不同key计算出来的值不一样,相同key计算出来的值一样。
那么我们放数据的时候,先通过key.h()计算一个值,然后判断arr[key.h()]是否为空,如果为空则直接放入,
如果不为空则将原有的value替换成新来的value.
要通过key获得value时,直接返回arr[key.h()].value.
假设上列说明是HashMap的底层实现,是不是感觉很方便,取出数据基本只需要一次操作,读取非常快。
上面的思路之所以快取决于两点的相互配合,一是数组无穷大的,二、每一个不同的key都有一个不同值。
这两点在实际中很难做到,近乎可以说不可能做到。
但这种思路确实很方便。
那么我们能否根据这个思路想一个折中的方法?
首先,数组不可能无穷大,那我们只能定义一有限长度的数组。
其次,每一个不同的key都有一个不同值,这个也不好实现,我们就允许不同的key可以出现相同值.
基本思路确定了,我们就要解决这个思路中有问题的部分,既然不同的key可以出现相同值,那我们存放
数据时怎么存放呢?答案是用链表。首先通过计算key.h()确定数组下标,然后遍历链表比较key,
如果有相同的key则替换其中的value,如果没有则将数据添加到链表中。
这样效率虽然没有无穷数组的快,但也提升了不少。
假设这里的数组长度为1000,每一个数组元素存放一个链表。
我们有10000个数据,假设均匀分布,那么最坏的情况也只需要遍历10次,
如果不是均匀分布的也比遍历10000次会好很多。
数组+链表
上面的h()方法就是hashCode(),hashCode()方法就是根据当前对象采用一定的算法计算出一个值。
通过这个值来减少查找等操作的次数。
要注意一点:
比较对象是否相等一般是用过equals方法。
如果hashCode不相等,则equals为false。
如果hashCode相等,则equals不定,可能false也可能为true.
hashCode()可参考:http://www.cnblogs.com/dolphin0520/p/3681042.html
下面我们来实现一个简易的HashMap(只写了put,get方法),该实现只为便于理解HashMap,
与JDK中具体实现HashMap有差异,由于是简易版的有很多不足,只为理解数组加链表的思想。
为了便于理解程序,链表中只提供了实现HashMap(put,get)所需要的方法。
MyHashMap及MyLinkedList中的其他方法有兴趣可以自行补充.
1 public class TestMyHashMap { 2 public static void main(String[] args) { 3 MyHashMap<Integer, String> m = new MyHashMap<>(); 4 m.put(1, "A"); 5 m.put(1, "C"); 6 m.put(2, "B"); 7 System.out.println(m.get(1)); 8 } 9 } 10 11 12 class MyHashMap<K,V>{ 13 int size = 0; 14 @SuppressWarnings("unchecked") 15 MyLinkedList <K,V>[] arr = new MyLinkedList[999];//存放数据的数组 16 17 public void put(K key,V value){ 18 int hash = key.hashCode()%999; //根据当前key计算hashC并对999取余, 19 if(arr[hash]==null){ //如果对应arr[hash]为空则创建一个链表对象。 20 MyLinkedList<K,V> l = new MyLinkedList<>(); 21 arr[hash] = l; //将创建好的链表添加到数组中 22 arr[hash].add(key, value);//并且将key,value添加到链表中 23 }else{ 24 if(arr[hash].isKeyValue(key, value)); //遍历链表,如果有相同的key则用新的value覆盖原有的value 25 else{ 26 arr[hash].add(key, value); //如果没有重复的key,则直接将数据添加到链表后面。 27 } 28 } 29 } 30 public V get(K key){ //通过key获取value 31 return arr[key.hashCode()].KeygetValue(key);//进去arr[key.hashCode()]中遍历, 32 } //如果有指定的key则返回对应value。 33 } //如果没有则返回null 34 35 36 class MyLinkedList<K,V>{/这个详细解释可参照https://www.cnblogs.com/huang-changfan/p/9583784.html 37 int size = 0; 38 private Node<K,V> first; 39 private Node<K,V> last; 40 public void add(K key,V value){ 41 if(first == null){ 42 Node<K,V> n = new Node<K,V>(); 43 n.setKey(key); 44 n.setValue(value); 45 n.setFirst(null); 46 n.setLast(null); 47 first = n; 48 last = n; 49 size++; 50 }else{ 51 Node<K,V> n = new Node<K,V>(); 52 n.setFirst(last); 53 last.setLast(n); 54 n.setKey(key); 55 n.setValue(value); 56 n.setLast(null); 57 last = n; 58 size++; 59 } 60 } 61 62 public boolean isKeyValue(K key,V value){//遍历链表,如果有key相等则覆盖原有value 63 Node<K,V> f = new Node<K,V>(); 64 f= first; //获取链表头结点 65 if(f.getKey().equals(key)){//判断当前结点key的值是否等于传递进来的key 66 f.value = value; //如果相等,替换原有value并返回true 67 return true; 68 } 69 while(f.getLast()!= null){ //判断链表下一结点是否为空 70 f = f.getLast(); //不为空则指向下一结点继续判断。 71 if(f.getKey().equals(key)){ 72 f.value = value; 73 return true; 74 } 75 } 76 return false; //如果链表遍历结束,没有相同项则返回false 77 } 78 79 public V KeygetValue(K key){//通过key返回value 80 Node<K,V> f = new Node<K,V>();//与上述差不过,遍历链表相同则返回对应的value。 81 f= first; 82 if(f.getKey().equals(key)) 83 return f.value; 84 while(f.getLast()!= null){ 85 f = f.getLast(); 86 if(f.getKey().equals(key)) 87 return f.value; 88 } 89 return null; 90 } 91 92 } 93 94 class Node<K,V>{ //链表中的结点 95 private Node<K,V> first;//记录头结点的位置 96 private K key; //存放key value 97 V value; 98 private Node<K,V> last; //记录尾结点位置 99 public Node(){}; 100 public Node(K key,V value){//下面是构造方法和一些set,get方法。 101 this.key = key; 102 this.value = value; 103 } 104 public Node<K, V> getFirst() { 105 return first; 106 } 107 public void setFirst(Node<K, V> first) { 108 this.first = first; 109 } 110 public K getKey() { 111 return key; 112 } 113 public void setKey(K key) { 114 this.key = key; 115 } 116 public V getValue() { 117 return value; 118 } 119 public void setValue(V value) { 120 this.value = value; 121 } 122 public Node<K, V> getLast() { 123 return last; 124 } 125 public void setLast(Node<K, V> last) { 126 this.last = last; 127 } 128 }
运行结果:
CB
Map中还有一个子类,是TreeMap,这个子类中的数据是有序的,根据key排序。
但是,如果是放入的key是自己创建的类时需要实现Comparator接口。
因为系统已经定义好的类已经实现了Comparator接口指定了排序规则,而自己 定义的类没有
实现Comparator接口指定排序规则会出现问题。有兴趣的可以去了解下比较器。
public class TestMap{ public static void main(String[] args) { Set<String> s = new HashSet<>(); Map<String,String> mh = new HashMap<>(); Map<String,String> mt = new TreeMap<>(); mh.put("ZS", "张三"); mh.put("LS","李四"); mh.put("WW", "王五"); mh.put("ZL", "赵六"); mt.put("ZS", "张三"); mt.put("LS","李四"); mt.put("WW", "王五"); mt.put("ZL", "赵六"); System.out.println(mh); System.out.println(mt); } }
运行结果: {WW=王五, ZL=赵六, LS=李四, ZS=张三} {LS=李四, WW=王五, ZL=赵六, ZS=张三}
HashMap输出的是无序的,二TreeMap则根据key进行了排序,
可以看出这里的key是根据字母的顺序来进行排序的,L在前面,Z在最后面
当第一个Z相同时,比较后一个字母的顺序,L在S前面所以赵六在张三前面。
这里的规则是系统指定好的,如果是自己创建的类,需要自己实现接口并规定排序方法。
五、Set接口
Set接口中不允许加入重复的元素。
1.Set接口 常用子类
1.1 HashSet
大家想一想HashSet和HashMap命名方式很像,而且Set接口中不允许加入重复读的元素。
我们回忆下Map中的key是不是也不允许重复?那这两者有何关联呢?
我们看下HashSet的源码:
HashSet的底层就是新建一个HshMap对象,
我们来看下添加方法。
添加方法也是调用map的put方法,不过是把key作为hashSet数据存放位置。
value存放的是
所以只用到了key,把key作为Set的数据。
import java.util.HashSet; import java.util.Set; public class TestSet { public static void main(String[] args) { Set<String> s = new HashSet<>(); s.add("张三"); s.add("李四"); s.add("王五"); s.add("赵六"); System.out.println(s); } }
运行结果:
[李四, 张三, 王五, 赵六]
同样HashMap是无序的,上面可以看出HashSet中的add方法就是调用HashMap中的put方法。
HashSet中很多方法其实内部都是通过调用Map来实现的。
2.2TreeSet
TreeSet和TreeMap相似,底部也是通过TreeMap来实现的。
import java.util.Set; import java.util.TreeSet; public class TestSet { public static void main(String[] args) { Set<String> s = new TreeSet<>(); s.add("ZS"); s.add("LS"); s.add("WW"); s.add("ZL"); System.out.println(s); } }
运行结果:
[LS, WW, ZL, ZS]
我们会发现这里的排序结果与之前TreeMap排序结果相同。