Java为数据结构中的映射定义了一个接口java.util.Map,其由诸多实现类,其中四个实现类分别是 HashMap、HashTable、LinkedHashMap和TreeMap

Map用于存储键值对,根据键得到值,因此不允许键重复,值可以重复。


【1】定义与特性

① HashMap

HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。

HashMap最多只允许一条记录的键为null,允许多条记录的值为null。

HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。

如果需要同步,可以用​ ​Collections.synchronizedMap(HashMap map)​ ​方法使HashMap具有同步的能力。或者使用ConcurrentHashMap。


② HashTable

Hashtable与HashMap类似,不同的是: 它不允许记录的键或者值为空;它支持线程的同步 ,为线程安全。

即任一时刻只有一个线程能写Hashtable,然而,这也导致了HashTable在写入时会比HashMap较慢。


③ LinkedHashMap

LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的。

在遍历的时候会比HashMap慢。有HashMap的全部特性。对比HashMap只是有序和无序差别。


④ TreeMap

TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。

当用Iteraor遍历TreeMap时,得到的记录是排过序的。TreeMap的键和值都不能为空。


⑤ ConcurrentHashMap

ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。

HashTable 是使用 synchronize 关键字加锁的原理(就是对对象加锁),多个线程竞争一把锁,容易阻塞。

ConcurrentHashMap JDK 1.7 中使用分段锁(​ ​ReentrantLock + Segment + HashEntry​ ​),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。

JDK 1.8 中使用 ​ ​CAS + synchronized + Node + 红黑树​ ​。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度降低了。

HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。


【2】实例代码

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;


public class TestMap {

public static void init(Map map){
if (map != null){
String key = null;
for (int i=5; i>0; i--){
key = new Integer(i).toString() + ".0";
map.put(key, key.toString());
//Map中的键是不重复的,如果插入两个键值一样的记录,
//那么后插入的记录会覆盖先插入的记录
map.put(key, key.toString() + "0"); }
}
}

public static void output(Map map){
if (map != null){
Object key = null;
Object value = null;
//使用迭代器遍历Map的键,根据键取值
Iterator it = map.keySet().iterator();
while (it.hasNext()){
key = it.next();
value = map.get(key);
System.out.println("key: " + key + "; value: " + value );
}
//或者使用迭代器遍历Map的记录Map.Entry
Map.Entry entry = null;
it = map.entrySet().iterator();
while (it.hasNext()){
//一个Map.Entry代表一条记录
entry = (Map.Entry)it.next();
//通过entry可以获得记录的键和值
//System.out.println("key: " + entry.getKey() + "; value: " + entry.getValue());
}
}
}

public static boolean containsKey(Map map, Object key){
if (map != null){
return map.containsKey(key);
}
return false;
}

public static boolean containsValue(Map map, Object value){
if (map != null){
return map.containsValue(value);
}
return false;
}

public static void testHashMap(){
Map myMap = new HashMap();
init(myMap);
//HashMap的键可以为null
myMap.put(null,"ddd");
//HashMap的值可以为null
myMap.put("aaa", null);
output(myMap);
}

public static void testHashtable(){
Map myMap = new Hashtable();
init(myMap);
//Hashtable的键不能为null
//myMap.put(null,"ddd");
//Hashtable的值不能为null
//myMap.put("aaa", null);
output(myMap);
}

public static void testLinkedHashMap(){
Map myMap = new LinkedHashMap();
init(myMap);
//LinkedHashMap的键可以为null
myMap.put(null,"ddd");
myMap.put(null,"aaa");
//LinkedHashMap的值可以为null
myMap.put("aaa", null);
output(myMap);
}

public static void testTreeMap(){
Map myMap = new TreeMap();
init(myMap);
//TreeMap的键不能为null
//myMap.put(null,"ddd");
//TreeMap的值不能为null
//myMap.put("aaa", null);
output(myMap);
}

public static void main(String[] args) {
System.out.println("采用HashMap");
TestMap.testHashMap();
System.out.println("采用Hashtable");
TestMap.testHashtable();
System.out.println("采用LinkedHashMap");
TestMap.testLinkedHashMap();
System.out.println("采用TreeMap");
TestMap.testTreeMap();

Map myMap = new HashMap();
TestMap.init(myMap);
System.out.println("新初始化一个Map: myMap");
TestMap.output(myMap);
//清空Map
myMap.clear();
System.out.println("将myMap clear后,myMap空了么? " + myMap.isEmpty());
TestMap.output(myMap);
myMap.put("aaa", "aaaa");
myMap.put("bbb", "bbbb");
//判断Map是否包含某键或者某值
System.out.println("myMap包含键aaa? "+ TestMap.containsKey(myMap, "aaa"));
System.out.println("myMap包含值aaaa? "+ TestMap.containsValue(myMap, "aaaa"));
//根据键删除Map中的记录
myMap.remove("aaa");
System.out.println("删除键aaa后,myMap包含键aaa? "+ TestMap.containsKey(myMap, "aaa"));
//获取Map的记录数
System.out.println("myMap包含的记录数: " + myMap.size());
}
}

输出结果:
采用HashMap
key: null; value: ddd
key: 3.0; value: 3.00
key: aaa; value: null
key: 4.0; value: 4.00
key: 1.0; value: 1.00
key: 5.0; value: 5.00
key: 2.0; value: 2.00
采用Hashtable
key: 4.0; value: 4.00
key: 1.0; value: 1.00
key: 3.0; value: 3.00
key: 5.0; value: 5.00
key: 2.0; value: 2.00
采用LinkedHashMap
key: 5.0; value: 5.00
key: 4.0; value: 4.00
key: 3.0; value: 3.00
key: 2.0; value: 2.00
key: 1.0; value: 1.00
key: null; value: aaa
key: aaa; value: null
采用TreeMap
key: 1.0; value: 1.00
key: 2.0; value: 2.00
key: 3.0; value: 3.00
key: 4.0; value: 4.00
key: 5.0; value: 5.00
新初始化一个Map: myMap
key: 3.0; value: 3.00
key: 4.0; value: 4.00
key: 1.0; value: 1.00
key: 5.0; value: 5.00
key: 2.0; value: 2.00
将myMap clear后,myMap空了么? true
myMap包含键aaa? true
myMap包含值aaaa? true
删除键aaa后,myMap包含键aaa? false
myMap包含的记录数: 1

源码分析

遍历Map有两种方法:

(1)map的keySet()方法获得键的集合,再调用键集合的iterator方法获得键的迭代器,以此迭代地取出Map中的键,用get方法获得键对应的值,便完成了Map的遍历。

代码如下所示:

//使用迭代器遍历Map的键,根据键取值
Iterator it = map.keySet().iterator();
while (it.hasNext()){
key = it.next();
value = map.get(key);
System.out.println("key: " + key + "; value: " + value );
}

(2)使用Map的entrySet方法获得Map中记录的集合,每条对象都是一个Map.Entry对象,使用其getKey方法获得记录的键,使用其getValue方法获得记录的值。

代码如下所示:

//或者使用迭代器遍历Map的记录Map.Entry
Map.Entry entry = null;
it = map.entrySet().iterator();
while (it.hasNext()){
//一个Map.Entry代表一条记录
entry = (Map.Entry)it.next();
//通过entry可以获得记录的键和值
//System.out.println("key: " + entry.getKey() + "; value: " + entry.getValue());

【3】 区别图示

类别\特性

速度

线程安全

键值为null

次序

HashMap



允许一条记录键为null,允许多条记录的值为null

无序

HashTable

比HashMap慢


不允许记录的键或者值为空

无序

LinkedHashMap

比HashMap慢


允许一条记录键为null,允许多条记录的值为null

保持了插入的次序

TreeMap

比HashMap慢


键和值都不能为空

默认键升序排列,也可以自定义排序

ConcurrentHashMap

比HashMap慢


不允许记录的键或者值为空

无序


【4】 总结

一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。

但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列。

如果想实现并发中安全,则使用ConcurrentHashMap。

java中预约系统代码 java代码预热

背景某服务调用,因服务器性能问题,无法直接使用最大qps进行调用,需要动态加速逻辑设置最大加速时间,设置允许加速到的最大qps代码import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicLong; * 预热控制器 * @author chuny

MySQL表格路径 mysql数据路径

# MySQL的数据目录1. MySQL8的主要目录结构1.1 数据库文件的存放路径MySQL数据库文件的存放路径:/var/lib/mysql/MySQL服务器程序在启动时会到文件系统的某个目录下加载一些文件,之后在运行过程中产生的数据也都会存储 到这个目录下的某些文件中,这个目录就称为数据目录. MySQL把数据都存到哪个路径下呢?其实数据目录对应着一个系统变量datadir,我们在使用客户端

之前通过在java环境中使用opencv库对矩形进行了识别,本次将继续使用java语言利用opencv库对图片中的人脸进行识别。  首先在主函数中要加载本地库,否则将会抛出java.lang.UnsatisfiedLinkError异常。 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);接下来通过文件路径加载文件

(string1==string2) 但在java中,这个代码即使在两个字符串完全相同的情况下也会返回false Java中必须使用string1.equals(string2)来进行判断 string s1="Hello"; string s2="Hello";