一、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中的其他方法有兴趣可以自行补充.

 public class TestMyHashMap {
public static void main(String[] args) {
MyHashMap<Integer, String> m = new MyHashMap<>();
m.put(1, "A");
m.put(1, "C");
m.put(2, "B");
System.out.println(m.get(1));
}
} class MyHashMap<K,V>{
int size = 0;
@SuppressWarnings("unchecked")
MyLinkedList <K,V>[] arr = new MyLinkedList[999];//存放数据的数组 public void put(K key,V value){
int hash = key.hashCode()%999; //根据当前key计算hashC并对999取余,
if(arr[hash]==null){ //如果对应arr[hash]为空则创建一个链表对象。
MyLinkedList<K,V> l = new MyLinkedList<>();
arr[hash] = l; //将创建好的链表添加到数组中
arr[hash].add(key, value);//并且将key,value添加到链表中
}else{
if(arr[hash].isKeyValue(key, value)); //遍历链表,如果有相同的key则用新的value覆盖原有的value
else{
arr[hash].add(key, value); //如果没有重复的key,则直接将数据添加到链表后面。
}
}
}
public V get(K key){ //通过key获取value
return arr[key.hashCode()].KeygetValue(key);//进去arr[key.hashCode()]中遍历,
} //如果有指定的key则返回对应value。
}                               //如果没有则返回null class MyLinkedList<K,V>{/这个详细解释可参照https://www.cnblogs.com/huang-changfan/p/9583784.html int size = 0;
private Node<K,V> first;
private Node<K,V> last;
public void add(K key,V value){
if(first == null){
Node<K,V> n = new Node<K,V>();
n.setKey(key);
n.setValue(value);
n.setFirst(null);
n.setLast(null);
first = n;
last = n;
size++;
}else{
Node<K,V> n = new Node<K,V>();
n.setFirst(last);
last.setLast(n);
n.setKey(key);
n.setValue(value);
n.setLast(null);
last = n;
size++;
}
} public boolean isKeyValue(K key,V value){//遍历链表,如果有key相等则覆盖原有value
Node<K,V> f = new Node<K,V>();
f= first; //获取链表头结点
if(f.getKey().equals(key)){//判断当前结点key的值是否等于传递进来的key
f.value = value; //如果相等,替换原有value并返回true
return true;
}
while(f.getLast()!= null){ //判断链表下一结点是否为空
f = f.getLast(); //不为空则指向下一结点继续判断。
if(f.getKey().equals(key)){
f.value = value;
return true;
}
}
return false; //如果链表遍历结束,没有相同项则返回false
} public V KeygetValue(K key){//通过key返回value
Node<K,V> f = new Node<K,V>();//与上述差不过,遍历链表相同则返回对应的value。
f= first;
if(f.getKey().equals(key))
return f.value;
while(f.getLast()!= null){
f = f.getLast();
if(f.getKey().equals(key))
return f.value;
}
return null;
} } class Node<K,V>{ //链表中的结点
private Node<K,V> first;//记录头结点的位置
private K key; //存放key value
V value;
private Node<K,V> last; //记录尾结点位置
public Node(){};
public Node(K key,V value){//下面是构造方法和一些set,get方法。
this.key = key;
this.value = value;
}
public Node<K, V> getFirst() {
return first;
}
public void setFirst(Node<K, V> first) {
this.first = first;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Node<K, V> getLast() {
return last;
}
public void setLast(Node<K, V> last) {
this.last = last;
}
}
运行结果:
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排序结果相同。

5.2类集(java学习笔记)Map,Set接口的更多相关文章

  1. Java学习笔记之---比较接口与抽象类

    Java学习笔记之---比较接口与抽象类 抽象类是描述事物的本质,接口是描述事物的功能 接口与抽象类的异同 1.一个类只能继承一个父类,但是可以有多个接口 2.抽象类中的抽象方法没有方法体,但是可以有 ...

  2. Java学习笔记——继承、接口、多态

    浮点数的运算需要注意的问题: BigDecimal operand1 = new BigDecimal("1.0"); BigDecimal operand2 = new BigD ...

  3. Java学习笔记-抽象类与接口

    抽象类用于在类中不用具体实现,而在子类中去实现的类 抽象类 抽象类概述 抽象定义:抽象就是从多个事物中将共性的,本质的内容抽取出来 抽象类:Java中可以定义没有方法体的方法,该方法的具体实现由子类完 ...

  4. Java学习笔记:03面向对象-接口_多态

    1.类的概念 一堆具有共同的成员变量(属性)和成员方法(功能)对象的集合 2.接口的概念 接口是功能的集合,就是方法的集合 接口中只能定义方法,不能定义普通的成员变量 而且接口中的成员方法,必须是抽象 ...

  5. 3.3常用类(java学习笔记)Runtime与Process

    一.Runtime 我们来看下文档中对Runtime的说明: 每一个java程序都有一个属于Runtime类的实例,它允许程序连接到程序运行环境. 当前runtime可以用getRuntime()方法 ...

  6. 3.2常用类(java学习笔记)String与StringBuffer

    一.String String又称不可变字符序列. 我们看JDK源码中用于字符存储的数组有final修饰,final修饰变量就代表变量不能改变. 我们可以看API文档中对String的描述. Stri ...

  7. 3.1常用类(java学习笔记)包装类及日期类

    一.包装类 java是一门面向对象的语言,秉承一切皆对象的思想. 可java中有一些基本数据类型并不是对象,有时可能需要将它们变为对象. 这时就需要用到我们的包装类了. 基本数据类型 包装类 int ...

  8. Java学习笔记——Map接口

    Map接口 Map接口 Map接口中键和值一一映射. 可以通过键来获取值. 异常 NoSuchElementException:访问的值不存在 ClassCastException:对象类型错误 Un ...

  9. java学习笔记——Collection集合接口

    NO 方法名称 描述 1 public boolean add(E e) 向集合中保存数据 2 public void clear() 清空集合 3 public boolean contains(O ...

  10. Java学习笔记(十)面向对象---接口

    理解 初期理解 可以认为接口是一个特殊的抽象类.当接口中的方法都是抽象的,那么该类可以通过接口的形式来表示. class用于定义类 interface用于定义接口 格式特点 接口中常见定义:常量,抽象 ...

随机推荐

  1. Qt5 界面中文乱码问题

    1.文件所在项目文件  xxx.pro 中添加: QMAKE_CXXFLAGS += -execution-charset:utf- 2.文件以 UTF-8 编码保存 3.添加  utf-8 BOM

  2. [04] css 选择器

    1.元素选择器 常见的html标签元素 h1 { color: red; } body { background: red; } 2.分组选择器 例如body和h2标签的字体颜色都是red,使用逗号将 ...

  3. 图片和base64编码字符串 互相转换,图片和byte数组互相转换

    图片和base64编码字符串 互相转换 import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.*; ...

  4. Linux动态库生成以及调用

    Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件. 在链接动态库生成可执行文件时,并不会把动态库的代码复制到执行文件中,而是 ...

  5. Linux基础(1)

    一.Linux的安装及相关配置 1.VMware Workstation安装CentOS7.0 图文版 详细步骤可以看连接:http://blog.csdn.net/alex_my/article/d ...

  6. 【Foreign】字符串匹配 [KMP]

    字符串匹配 Time Limit: 10 Sec  Memory Limit: 256 MB Description Input Output Sample Input 3 3 6 3 1 2 1 2 ...

  7. bzoj4759 [Usaco2017 Jan]Balanced Photo

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4759 [题解] 排序,从大到小插入,树状数组统计. # include <vector ...

  8. BZOJ1082_栅栏_C++

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1082 题解:http://www.cnblogs.com/hadilo/p/5924546.h ...

  9. C# 从服务器下载文件

    一.//TransmitFile实现下载 protected void Button1_Click(object sender, EventArgs e) { /* 微软为Response对象提供了一 ...

  10. setInterval的使用和停用

    var res = self.setInterval(function(){ if(typeof(UE.getEditor('editor').body.innerHTML) != "und ...