LinkedHashSet
-
LinkedHashSet是HashSet的子类
-
LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
而在之前讲的HashSet中的链表是单向的哈,注意区分!
-
LinkedHashSet根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,
这使得元素看起来是以插入的顺序保存的
-
LinkedHashSet不允许添加重复元素
-
第一次向其中添加元素时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry
数组table是HashMap$Node[ ],但是存放的数据/元素
是LinkedHashMap$Entry类型的,这其实就是数组的多态的体现;
static class Entry<K,V> extends HashMap.Node<K,V> { //此中的before和after是进行双向链表的连接的 //其继承关系是在内部类完成的 Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
补充:
-
当编写一个LinkedHashSet的应用实例时,比如让你创建一个Car类,然后创建好几辆车,有各自的名字和价格,要求如果名字和价格相等的话,无法加入到LinkedHashSet中去,这时候就要重写equals和hashCode方法,那么,有个问题,我如果只写二者其一,可不可以呢?即只保留equals或者hashCode; 答案是:不行,因为LinkedHashSet底层是HashMap,所以实现去重的源码部分是一样的,必须要equals和hashCode二者的值都相等,才符合去重条件,才会拒绝第二个,第三个equals和hashCode值都相等的Car对象的加入
//注意,equals和hashCode两个方法少一个都无法实现去重, //因为其底层判断的时候就是要二者都相同才符合去重条件 /** if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; */
Map接口和常用方法
Map接口
- Set和Map其实二者都是K-V存储,但是不同于Map,Set中的V采用的是PRESENT,所以我们只能在Set中自定义存储K,V会由底层自动实现,而Map中,K、V都是可以自定义存放的,so 这也是为什么说Set其实就是Map的原因;
- Map与Collection并列存在,即二者是两大类,是同级关系,Map用于保存具有映射关系的数据:key-value
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","kerwin");
map.put("2","囿于pain");
map.put("1","jas");//当有相同K时,就会等价替换
System.out.println("map ="+map);
//{1=jas, 2=囿于pain}
//再加个重复的value,试试
map.put("3","jas");
System.out.println("map ="+map);
// map ={1=jas, 2=囿于pain, 3=jas}
}
- Map中的value可以重复
- Map的key可以为null,value也可以为null,注意key为null时,只能有一个,value为null时,可以有多个
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","kerwin");
map.put("2","囿于pain");
map.put("1","jas");//当有相同K时,就会等价替换
System.out.println("map ="+map);
//{1=jas, 2=囿于pain}
//再加个重复的value,试试
map.put("3","jas");
System.out.println("map ="+map);
// map ={1=jas, 2=囿于pain, 3=jas}
map.put(null,null);
System.out.println(map);
// {null=null, 1=jas, 2=囿于pain, 3=jas}
//key只能有一个是null,重复加入,会发生替换
//value可以有多个是null
map.put("4",null);
map.put("5",null);
map.put(null,"fulture");
System.out.println(map);
// {null=fulture, 1=jas, 2=囿于pain, 3=jas, 4=null, 5=null}
}
- 常用String类作为Map的key
- key和value之间存在单向一对一的关系,即通过指定的key总能找到对应的value
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","kerwin");
map.put("2","囿于pain");
map.put("1","jas");//当有相同K时,就会等价替换
System.out.println("map ="+map);
//{1=jas, 2=囿于pain}
//再加个重复的value,试试
map.put("3","jas");
System.out.println("map ="+map);
// map ={1=jas, 2=囿于pain, 3=jas}
map.put(null,null);
System.out.println(map);
// {null=null, 1=jas, 2=囿于pain, 3=jas}
//key只能有一个是null,重复加入,会发生替换
//value可以有多个是null
map.put("4",null);
map.put("5",null);
map.put(null,"fulture");
System.out.println(map);
// {null=fulture, 1=jas, 2=囿于pain, 3=jas, 4=null, 5=null}
//通过传入的key,由于key是不可重复的,故会返回对应的value
System.out.println(map.get("2"));//囿于pain
System.out.println(map.get(null));//fulture
}
- Map存放数据的key-value示意图,一对k-v是放在一个Node上的,又因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
....
}
- K-V 为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型是Entry, 而一个Entry对象就有k,v ,即Entry由Entr组成,而每个Entry包含一对K-V, EntrySet<Entry<K,V>> , 源码如下:
transient Set<Map.Entry<K,V>> entrySet;
-
entrySet中,定义的类型是Map.Entry ,但是实际上存放的还是HashMap$Node,这是因为Node实现了Map.Entry,即接口的多态,接口引用指向了实现了接口的类,源码即:
static class Node<K,V> implements Map.Entry<K,V> {
-
当把HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为 Map.Entry接口提供了重要的方法
K getKey() V getValue()
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","kerwin");
map.put("2","jackon");
Set set = map.entrySet();
System.out.println(set.getClass());
//HashMap$EntrySet
for(Object obj : set){
// System.out.println(obj.getClass());
//为了从HashMap$Node 中取出k-v
//1.先做一个向下转型
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey()+" - "+entry.getValue());
// 1 - kerwin
// 2 - jackon
}
}
- HashMap中的table表采用数组+链表+红黑树来维护,数组中存放Node类型数据,但是为了方便管理,其在底层也做了一层控制,将每个Node封装成一个Entry,然后将一个个Entry,放到EntrySet集合中去,这样实现的主要目的是方便管理;除此之外,还提供了一个KeySet集合,其中单独地封装了一个个的Node中的key,形成KeySet集合,同理,将所有的value抽取出来,封装成了一个Collection集合,源码如下:
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","kerwin");
Set set1 = map.keySet();
System.out.println(set1.getClass());//HashMap$KeySet 是HashMap的内部类,负责管理键
Collection values = map.values();
System.out.println(values.getClass());//HashMap$Values 是HashMap的内部类,负责管理值
//遍历下set1 和 values
Iterator iterator = set1.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
//输出
// 1
// 2
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object obj = iterator2.next();
System.out.println(obj);
}
//输出
// kerwin
// jackon
}
常用方法
- put添加
- remove根据键删除映射关系
- get根据键获取值
- size获取元素个数
- isEmpty判断个数是否为0
- clear清除
- containsKey查找键是否存在
@SuppressWarnings({"all"})
public class Journey {
public static void main(String[] args) {
Map map = new HashMap();
//添加
map.put("1","kerwin");
map.put("2","jackon");
map.put(true,123);
//删除
map.remove(true);
System.out.println(map);
//{1=kerwin, 2=jackon}
System.out.println(map.get("2"));//jackon
map.put(null,123);
map.put(false,null);
//获取元素个数
System.out.println(map.size());//4
//判断是否为空
System.out.println(map.isEmpty());//false
//查找键是否存在
System.out.println(map.containsKey(false));//true
//清除
map.clear();
System.out.println(map);//{}
}
}
Map接口遍历方法
- containsKey 查找键是否存在
- keySet 获取所有的键
- entrySet 获取所有的关系
- values 获取所有的值
public static void main(String[] args) {
Map map = new HashMap();
//添加
map.put("1", "kerwin");
map.put("2", "jackon");
map.put(true, 123);
map.put(null, 123);
map.put(false, null);
//查找键是否存在
System.out.println(map.containsKey(false));//true
//直接打印keySet values entrySet
System.out.println(map.keySet());
//[null, 1, 2, false, true]
System.out.println(map.entrySet());
//[null=123, 1=kerwin, 2=jackon, false=null, true=123]
System.out.println(map.values());
//[123, kerwin, jackon, null, 123]
System.out.println("===================================");
//迭代遍历打印
Set keys = map.keySet();
Iterator iterator = keys.iterator();
System.out.println("keySet:");
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
Collection values = map.values();
Iterator iterator1 = values.iterator();
System.out.println("values:");
while (iterator1.hasNext()) {
Object obj = iterator1.next();
System.out.println(obj);
}
Set entry = map.entrySet();
Iterator iterator2 = entry.iterator();
System.out.println("entrySet:");
while (iterator2.hasNext()) {
Object obj = iterator2.next();
System.out.println(obj);
}
/**
* ===================================
* keySet:
* null
* 1
* 2
* false
* true
* values:
* 123
* kerwin
* jackon
* null
* 123
* entrySet:
* null=123
* 1=kerwin
* 2=jackon
* false=null
* true=123
*/
}
练习
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class CodingDemo {
public static void main(String[] args) {
HashMap map = new HashMap();
Employee emp1 = new Employee("小A", 20000, 1);
Employee emp2 = new Employee("小B", 10000, 2);
Employee emp3 = new Employee("小C", 15000, 6);
Employee emp4 = new Employee("小D", 19000, 10);
map.put(emp1.getId(),emp1);
map.put(emp2.getId(),emp2);
map.put(emp3.getId(),emp3);
map.put(emp4.getId(),emp4);
//实现遍历--keySet--增强for
Set keySet = map.keySet();
System.out.println("==========第一种遍历方式===========");
for (Object key : keySet) {
//先获取value
Object o = map.get(key);
Employee emp = (Employee)o;
if(emp.getSal()>18000){
System.out.println(emp);
}
}
//实现遍历--EntrySet---迭代器
Set entrySet = map.entrySet();
System.out.println("===========第二种遍历方式==============");
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
Map.Entry entry = (Map.Entry)obj;
Employee emp = (Employee) entry.getValue();
if(emp.getSal()>18000){
System.out.println(emp);
}
}
}
}
@SuppressWarnings({"all"})
class Employee{
private String name;
private double sal;
private int id;
public Employee(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", id=" + id +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
HashMap小结
- Map接口的常用类:HashMap、Hashtable、Properities
- HashMap是Map接口使用频率最高的实现类
- HashMap是以key-value 对的方式(HashMap$Node类型)来存储数据
- key不能重复,但是值可以重复,允许使用null键和null值
- 如果添加相同的key,则会覆盖原来的key-value,等同于修改(key不会替换,value会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
- HashMap没有实现同步,因此是线程不安全的,方法上没有做同步互斥操作,没有synchronized关键字修饰