map的特点
Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
map的体系
代码:
public class mapDemo {
public static void main(String[] args) {
//创建Map对象
Map<String, String> map = new HashMap<String,String>(); //数据采用的哈希表结构
//给map中添加元素
map.put("星期一", "Monday");
map.put("星期二", "Tuesday");
map.put("星期三", "Wednesday");
map.put("星期日", "Sunday");
//{星期二=Tuesday, 星期三=Wednesday, 星期日=Sunday, 星期一=Monday}
System.out.println(map);
//当给Map中添加元素,会返回key对应的原来的value值,若key没有对应的值,返回null
System.out.println(map.put("星期一", "Mon")); // Monday
//{星期二=Tuesday, 星期三=Wednesday, 星期日=Sunday, 星期一=Monday}
System.out.println(map);
//根据指定的key获取对应的value Sunday
System.out.println(map.get("星期日"));
//根据key删除元素,会返回key对应的value值 Sunday
System.out.println(map.remove("星期日"));
//{星期二=Tuesday, 星期三=Wednesday, 星期一=Mon}
System.out.println(map);
//是否包含指定的key true
System.out.println(map.containsKey("星期三"));
// //是否包含指定的Value false
System.out.println(map.containsValue("星期三"));
//判断为空 false
System.out.println(map.isEmpty());
//返回map里面所有的value组成的collection
// Tuesday Wednesday Mon
Collection<String> values = map.values();
for(String v:values){
System.out.print(v+"\t");
}
System.out.println();
//遍历1: keySet()
//返回map里面所有的key组成的collection
//星期二 星期三 星期一
Collection<String> keys = map.keySet();
for(String v:keys){
System.out.print(v+"\t");
}
System.out.println();
Iterator<String> it =keys.iterator();
//星期二=Tuesday
//星期三=Wednesday
//星期一=Mon
while(it.hasNext()) {
//得到每一个key
String key = it.next();
//通过key获取对应的value
String value = map.get(key);
System.out.println(key + "=" + value);
}
//遍历2 .entrySet()
//返回map所包含的键值所组成的set集合,每个集合元素都是map.entry对象
//星期二=Tuesday 星期三=Wednesday 星期一=Mon
Set<Map.Entry<String, String>> entries = map.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+"\t");
}
System.out.println();
//遍历3
for (Map.Entry<String, String> entry: map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"---"+value);
}
}
}
注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。
HashMap的实现特点
1. HashMap和HashTable比较相似,区别是:
- HashMap线程不安全
- HashMap可以存储null键和null值
- 由于线程不安全,所以相对的性能较高
2. HashMap存储不保证顺序存储
3. HashMap迭代整个集合的时间和容量及键值对有关,HashMap的一个实例有两个影响其性能的参数:初始容量和负载因子。
4. HashMap是以哈希桶方式存储的。
5. 其Iterator迭代器采用的是快速失败机制
6. 如果使用自定义类当作Key,需要注意其equals方法和hashCode方法
7. HashMap有线程安全版:ConcurrentHashMap
8.,jdk8:HashMap底层是(数组和链表 )散列表+红黑树,创建HashMap对象,没有初始table(类型是Node),仅仅只是加载因子,只有第一个添加的时候容量才变为16,当链表的节点>=8 && 桶的个数(table容量)>=64,会把链表结构变成红黑树。;jdk7:数组和链表 ,创建HashMap对象时,table(Entry)容量才变为16,不论链表的节点数是多少都不会变成树。
9.装载因子*初始容量<散列表元素,散列表扩容2倍;装载因子默认是0.75,装载因子初始值大了,可以减少扩容次数,但是散列冲突可能性变大;装载因子小了,散列冲突可能性变小,扩容次数增加;初始容量默认16,初始容量过大,遍历影响速度,过小扩容次数增多。
哈希桶
我们在利用哈希值存放数据时,会遇到冲突,常用的解决冲突的方法有:开放定址、链地址、溢出区,其中链地址就是哈希桶。
我们会将元素的哈希值设为桶,哈希值相同的元素放入一个桶中,就是如下图。根据哈希值和值找元素也是此步骤,找到哈希值的桶,如果桶中第一个元素是要找的则结束,否则继续找下一个元素,直至找到元素。
上面讲的是Iterator迭代器的快速失败机制(fail-fast),这个不单单对于HashMap,所有Collection集合的Iterator迭代器都是快速失败,一旦在迭代过程中对集合进行结构化修改,除非是迭代器中的remove方法,其余都会抛出异常。因此遇到并发修改那么就会快速干净的失败,避免在未来的未确定时间发生非确定性行为的风险。并且需要注意,迭代器的快速失败机制行为无法得到保证,比如在有些情况,对迭代器中元素进行删除可能不会引发异常,但一定不要以为可以在迭代时进行删除元素。对于HashMap、ArrayList等集合,迭代时进行结构化操作都会导致异常,只要在操作特定元素时才不会抛出异常,如果想深入了解可以去查看一下这些集合的实现代码。
HashMap和HashTable的区别
TreeMap
底层:红黑树。时间复杂度log(n)
实现了NavigableMap接口,而NavigableMap接口继承SortMap接口,所以TreeMap有序。
如果构造方法中传递了Comparator对象,那么就以Comparator对象的方法进行排序,否则,使用Comparable的compareTo(T o)。如果使用compareTo(T o)方法比较,key不能为null,并且实现Comparable接口,即使传入Comparator对象,不用compareTo(T o)方法比较,key也是不能为null。
非同步,想要同步使用Conllections进行封装。
代码示例:
public class TreeD {
public static void main(String[] args) {
TreeMap map=new TreeMap();
map.put(new Yuan("科比", 100000, 24),100000);
map.put(new Yuan("韦德", 50000, 3),50000);
map.put(new Yuan("邓肯", 80000, 21),80000);
map.put(new Yuan("麦迪", 60000, 1),60000);
Set set = map.entrySet();
for(Object obj : set){
Map.Entry entry= (Map.Entry) obj;
System.out.println(entry.getKey().toString());
}
}
}
------------------------------------------------------------------------------
public class Yuan implements Comparable<Yuan>{
private String name;
private int salay;
private int id;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Yuan)) return false;
Yuan yuan = (Yuan) o;
return getSalay() == yuan.getSalay() &&
getId() == yuan.getId() &&
Objects.equals(getName(), yuan.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getSalay(), getId());
}
public Yuan(String name, int salay, int id) {
this.name = name;
this.salay = salay;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalay() {
return salay;
}
public void setSalay(int salay) {
this.salay = salay;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Yuan{" +
"name='" + name + '\'' +
", salay=" + salay +
", id=" + id +
'}';
}
@Override
public int compareTo(Yuan o) {
if(this.id>o.id){
return 1;
}else if(this.id<o.id){
return -1;
}
return Integer.compare(this.salay,o.salay);
}
}
Map的其它类:
IdentityHashMap和HashMap的具体区别,IdentityHashMap使用 == 判断两个key是否相等,而HashMap使用的是equals方法比较key值。有什么区别呢?
对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等; 如果作用于引用类型的变量,则比较的是所指向的对象的地址。
对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
(6)小结:
HashMap 非线程安全
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
适用场景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。
HashMap:适用于Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
5.线程安全集合类与非线程安全集合类
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的。
数据结构
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序