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可以有多个是nullmap.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可以有多个是nullmap.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是不可重复的,故会返回对应的valueSystem.out.println(map.get("2"));//囿于painSystem.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$EntrySetfor(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 和 valuesIterator iterator = set1.iterator();while(iterator.hasNext()){Object obj = iterator.next();System.out.println(obj);}//输出
// 1
// 2Iterator 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"));//jackonmap.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 entrySetSystem.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--增强forSet keySet = map.keySet();System.out.println("==========第一种遍历方式===========");for (Object key : keySet) {//先获取valueObject 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;}@Overridepublic 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关键字修饰