Map的实现类有很多,其中较为常见的有HashMap,HashTable,LinkedHashMap,TreeMap,下面分别对这几个类进行简单的分析:

1、HashMap

HashMap的结构数组+链表的组合。

public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}

在创建HashMap时,可以选择设置容量initialCapacity和加载因子loadFactor。

1)

initialCapacity默认值是16。我们可以根据需求自己设置,不过它会把我们设置的值进行处理,看看源码中是怎么处理的:

static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

在二进制中,除了符号位,最高有效位必然是1,经过一连串位移和或预算,n的值将变成11……11的形式,再加1变成100……00的形式,即最后获取的值将是不小于cap的2的最低次幂。

2)

loadFactor的默认值为0.75,当前最大容量为数组长度*加载因子。所以如果我们在创建HashMap对象时不做任何设置,HashMap对象的最大容量为16*0.75=12个,超过则扩容。

扩容是一个非常耗费资源的过程,最好我们最好根据具体需求设置合适的initialCapacity和loadFactor。


在添加键值对时,HashMap会对Key的hash码做均匀化处理:

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);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

将key的hash码处理后的值和数组长度做与运算,确定下标,若数组的这个位置为null,则将键值对生成的新对象放在这个位置,否则,添加到此链表的尾部。

到这里就能看出,HashMap将数组长度设为2的次幂,将key的hash码均匀化处理的原因了,因为这样使整个散列均匀化分布,提高插入和查询的效率。


2、HashTable

hashTable也是数组+链表的结构,它也实现了Map接口,但它继承了陈旧的Dictionary类,Dictionary是java早期设计的类,相比HashMap有很多不足之处。

在初始化的时候,默认的数组长度为11,也可以在创建HashTable对象时自己设定,与HashMap不同的是,HashTable数组长度直接就是我们设置的值,不做其它处理。

下面是HashTable添加新元素时的操作:

private void addEntry(int hash, K key, V value, int index) {
modCount++; Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash(); tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
} // Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

对于索引值它也只是对key的hash码做简单的绝对值取余处理,而且在添加元素时,并不是如同HashMap一样加在链表的末端,而是放在表头。

除此之外,HashTable的key和value都不能是null,HashMap可以。

HashTable是线性安全的,而HashMap非线性安全。


3、LinkedHashMap

LinkedHashMap是HashMap的子类,相比于HashMap它最大的特点是它是有序的。它的顺序可分为两种:插入顺序和访问顺序,因此它常用于Lru算法中。

    public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}

accessOrder为true则为访问顺序;false则为插入顺序。

相比于HashMap,它的Entry增加了前一个Entry对象before和后一个Entry对象after:

    static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}

新插入的对象放在末尾:

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}

访问过的对象放在末尾,前后对象互为前后:

    public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
} void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}

4、TreeMap

TreeMap的结构是红黑树,相比于前面几种Map,要复杂许多。

TreeMap的一大功能就是可以根据设定规则进行排序,下面为一个简单示例:

public class Test {
public static void main(String[] args) {
Comparator<Person> comparator = new Comparator<Person>() { @Override
public int compare(Person o1, Person o2) {
return o1.compareTo(o2);
} };
Map<Person, String> map = new TreeMap<>(comparator);
map.put(new Person(1), "first");
map.put(new Person(2), "second");
map.put(new Person(4), "third");
map.put(new Person(3), "fourth");
iterate(map);
/*
output:
key:1;value:first
key:2;value:second
key:3;value:fourth
key:4;value:third
*/
}
public static <K,V> void iterate(Map<K,V> map){
for(Entry<K, V> entry:map.entrySet()){
System.out.println("key:"+entry.getKey()+";value:"+entry.getValue());
}
}
} class Person implements Comparable<Person> { private int id; public Person(int id) {
this.id = id;
} public void setId(int id) {
this.id = id;
} public int getId() {
return id;
} @Override
public int compareTo(Person person) {
if (this.id < person.getId()) {
return -1;
} else if (this.id == person.id) {
return 0;
} else {
return 1;
}
} @Override
public String toString() {
return id + "";
} }

java之Map的使用的更多相关文章

  1. 错误:java.util.Map is an interface, and JAXB can't handle interfaces.

    问题: 在整合spring+cxf时报错java.util.Map is an interface, and JAXB can't handle interfaces. 解决方法: 将服务端的serv ...

  2. Java中Map常用方法总结以及遍历方式的汇总

    一.整理: 看到array,就要想到角标. 看到link,就要想到first,last. 看到hash,就要想到hashCode,equals. 看到tree,就要想到两个接口.Comparable, ...

  3. Java 基础 Map 练习题

    第一题 (Map)利用Map,完成下面的功能: 从命令行读入一个字符串,表示一个年份,输出该年的世界杯冠军是哪支球队.如果该 年没有举办世界杯,则输出:没有举办世界杯. 附:世界杯冠军以及对应的夺冠年 ...

  4. java 遍历map 方法 集合 五种的方法

    package com.jackey.topic; import java.util.ArrayList;import java.util.HashMap;import java.util.Itera ...

  5. JAVA/Android Map与String的转换方法

    在Android开发中 Map与String的转换在,在一些需求中经常用到,使用net.sf.json.JSONObject.fromObject可以方便的将string转为Map.但需要导入jar包 ...

  6. java中map插入相同的key

    测试用例: package test; import org.junit.Test; import po.Person; import java.util.HashMap; import java.u ...

  7. JAVA ,Map接口 ,迭代器Iterator

    1.    Map 接口概述 java.util.Map 接口描述了映射结构, Map 接口允许以键集.值集合或键 - 值映射关系集的形式查看某个映射的内容. Java 自带了各种 Map 类. 这些 ...

  8. jmap命令(Java Memory Map)(转)

    JDK内置工具使用 一.javah命令(C Header and Stub File Generator) 二.jps命令(Java Virtual Machine Process Status To ...

  9. Java遍历Map的3种方式

    package test; import java.util.Collection; import java.util.HashMap; import java.util.Map; import ja ...

  10. 【Java】Map杂谈,hashcode()、equals()、HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap

    参考的优秀文章: <Java编程思想>第四版 <Effective Java>第二版 Map接口是映射表的结构,维护键对象与值对象的对应关系,称键值对. > hashco ...

随机推荐

  1. Java基础:运算符

    算数运算符:+,-,*,/,%,++,-- 赋值运算符:= 关系运算符:>,<,>=,<=,==,!=,instanceof 逻辑运算符:&&,||,! 位运算 ...

  2. OpenCV图像处理中的“机器学习"技术的使用

    注意,本文中所指"机器学习"(ML)技术,特指SVM.随机森林等"传统"技术. 一.应用场景        相比较当下发展迅速的各路"端到端" ...

  3. Hibernate&MyBatis

    ORM程序技术 概念:ORM是对象(Object).关系(Relational).映射(Mapping)的缩写. 作用:用于替代JDBC 用法:数据库类名与表名对应:字段名和属性名对应: 优势: 更方 ...

  4. vue 快速入门 系列 —— 侦测数据的变化 - [基本实现]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [基本实现] 在 初步认识 vue 这篇文章的 hello-world 示例中,我们通过修改数据(app.seen = false),页面中 ...

  5. Makefile基本用法

    来源 https://www.gnu.org/software/make/manual/make.pdf 简单的例子 其中的cc通过链接,间接指向/usr/bin/gcc. Makefile文件中列出 ...

  6. OLAP引擎:基于Druid组件进行数据统计分析

    一.Druid概述 1.Druid简介 Druid是一款基于分布式架构的OLAP引擎,支持数据写入.低延时.高性能的数据分析,具有优秀的数据聚合能力与实时查询能力.在大数据分析.实时计算.监控等领域都 ...

  7. GAMES101作业2

    作业任务: 填写并调用函数 rasterize_triangle(const Triangle& t). 即实现光栅化 该函数的内部工作流程如下: 创建三角形的 2 维 bounding bo ...

  8. 201871010109-胡欢欢-实验一-软件工程的准备(初识github及《现代软件工程-构建之法》)

    项目 内容 课程班级博客链接 2021年春软件工程课程班(2018级计算机科学与技术) 这个作业要求链接链接 实验一软件工程准备 我的课程学习目标 了解github的基本使用,学习markdown编辑 ...

  9. SQL语句通过身份证号计算年龄

    SQL语句通过身份证号计算年龄 1.截取身份证号上的出生日期 身份证一般为18位数和15位数 18位数身份证的第7-10位数是出生年份,第11-14位数是出生月日,所以18位身份证的年龄计算如下 su ...

  10. 7. Centos7 yum 出现could not retrieve mirrorlist 最终解决方案

    Step 1: sudo vi /etc/sysconfig/network-scripts/ifcfg-ens33 Step 2:  将ONBOOT改为yes,wq!保存退出 Step 3:重启ne ...