目录
Map与Collection并列存在的。用于保存具有映射关系的数据 key-value
Map中的key和value可以是任何引用数据类型,会封装到HahsMap$Node对象中
Map中的key不允许重复
Map中的value可以重复
Map的key可以为null value也可以为null,注意key为null,只能有一个;value为null,可以有多个
常用String类作为Map的key
key和value之间存在单向一对一关系,即通过指定的key总能找到对应value
Map存放数据的 一对k-v 是放在一个Node中的,又因为Node类实现了Entry接口,所以也可说是k-v就是一个Entry
为了方便遍历Map的实现类都有EntrySet、KeySet 、Values三个内部类
(他们三个其实不存放数据,真正存放数据的地方还是在数组中,只不过他们是使用了迭代器,实现了指向数组table中key value )
- public class MapSource {
- public static void main(String[]args){
- Map map = new HashMap<>();
- map.put("no1","张三");
- map.put("no2","李四");
-
- /*
- 1.k-v 最后是通过 newNode(hash, key, value, null); 存放在Node中的
-
- 2.为了方便程序员的遍历,还会在底层创建EntrySet集合,
- 该集合存放元素的类型是Entry,而一个Entry对象就包含了key和value
- transient Set
> entrySet; -
- 3.entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HahsMap$Node
- 这是因为Node实现了Map.Entry
- static class Node
implements Map.Entry -
- 4.这样当把 HahsMap$Node 存放在entrySet方便我们的遍历
- 因为Map.Entry提供了两个很重要的方法
- getKey() 和 getValue()
- */
- //1、包含key和value的 Entry接口 其实放的是Node类
- //Set
> - Set set = map.entrySet();
- //HashMap$EntrySet
- System.out.println(set.getClass());
- for (Object o : set) {
- Map.Entry entry = (Map.Entry)o;
- System.out.print("key:"+entry.getKey());
- System.out.println(" - value:"+entry.getValue());
- }
- //2、包含key的set集合
- Set set1 = map.keySet();
- //3、包含value的Collecion即可
- Collection values = map.values();
- }
- }
-
put 添加
remove 根据键值删除映射关系
get 根据键获取值
size 获取元素个数
isEmpty 判断个数是否为0
clear清除
containsKey查找键是否存在
- public class HashMapFor {
- public static void main(String[]args){
- Map map = new HashMap();
- map.put("1","张三");
- map.put("2","李四");
- map.put("3","王五");
- map.put("4","黄六");
- map.put(null,"黄六");
- map.put("科技","信管");
-
- //第一组:keySet 先取出所有的key,通过key求出对应的value
- Set set = map.keySet();
- System.out.println("======1====");
- //(1)增强for循环
- for (Object o : set) {
- //value
- Object o1 = map.get(o);
- System.out.println(o+"-"+o1);
- }
-
- //(2)迭代器
- System.out.println("======2====");
- Iterator iterator = set.iterator();
- while (iterator.hasNext()){
- //key
- Object next = iterator.next();
- //value
- Object o = map.get(next);
- System.out.println(next+"-"+o);
- }
- //第二组 取出values
- Collection values = map.values();
- //(3)增强for
- System.out.println("======3====");
- for (Object value : values) {
- System.out.println(value);
- }
- //(4)迭代器
- System.out.println("======4====");
- Iterator iterator1 = values.iterator();
- while (iterator1.hasNext()){
- System.out.println(iterator1.next());
- }
-
- //第三组 使用entrySet
- //(5)增强for
- System.out.println("======5====");
- Set set1 = map.entrySet();
- for (Object o : set1) {
- Map.Entry entry =(Map.Entry)o;
- Object key = entry.getKey();
- Object value = entry.getValue();
- System.out.println(key+"-"+value);
- }
- //(6)迭代器
- System.out.println("======6====");
- Iterator iterator2 = set1.iterator();
- while (iterator2.hasNext()) {
- Map.Entry entry =(Map.Entry) iterator2.next();
- Object key = entry.getKey();
- Object value = entry.getValue();
- System.out.println(key+"-"+value);
- }
-
- }
- }
总结
HashMap是Map接口中使用频率最高的实现类
HashMap是以k-v键值对的方式来存储数据
key不能重复,但是值可以重复,允许使用null键和null值
如果添加相同的key,则会覆盖原来的k-val 等同于修改 (key不会替换,vlaue会进行替换,并讲旧得值返回)
if执行完毕后直接执行下面的语句
5.与HashSet一样,不保证映射的顺序,因为底层是以Hash表的方式存储的
6.HashMap没有实现同步,因此是线程不安全的
HashMap底层维护了Node类型的数组table 默认为null
当创建对象的时候,将加载因子(loadfactor)初始化为0.75
当添加key-val的时候,通过key的哈希值得到table的索引。然后判断该索引处是否有元素。如果没有元素直接添加。如果该索引处有元素,进行比较,如果equals相等就进行替换value返回旧的值,如果不相等需要判断是否是树结构还是链表结构,如做出响应的处理。如果发现容量超过阈值就进行扩容
第一次添加 需要扩容为16 临界值(threshold)为12
以后在扩容,则扩容table容量的二倍,临界值也是原来的二倍
在jdk8 如果一条链表元素个数大于等于默认8个,并且table数组的大小大于等于64就会转换为红黑树,如果数组长度小于就进行扩容
存放的元素是键值对
Hashtable的键和值都不能是null
Hashtable使用方法基本与HashMap是一样的
Hashtable是线程安全的,HashMap是线程不安全的
- @SuppressWarnings("all")
- public class HashTable_ {
- public static void main(String[]args){
- Map map = new Hashtable();
- map.put("jack",100); //ok
- // map.put(null,100);// 空指针异常
- // map.put("jack",null);//空指针异常
- map.put("tom",22);//ok
- map.put("lic",33);//ok
- map.put("lic",48);//替换
- System.out.println(map);
- /*
- 1.底层有数组 Hashtable$Entry[] 初始就是11
- public Hashtable() {
- this(11, 0.75f);
- }
-
- public Hashtable(int initialCapacity, float loadFactor) {
- this.loadFactor = loadFactor; //0.75
- table = new Entry,?>[initialCapacity];
- threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
- }
- 2. 临界值 threshold为 11*0.75 = 8 容量为11
- 3. value不能为null 否则抛出空指针异常
- key也不能为null 因为调用了key.hashCode() 否则抛出空指针异常
- public synchronized V put(K key, V value) {
- // Make sure the value is not null
- if (value == null) {
- throw new NullPointerException();
- }
-
- // Makes sure the key is not already in the hashtable.
- Entry,?> tab[] = table;
- int hash = key.hashCode();
- int index = (hash & 0x7FFFFFFF) % tab.length;
- @SuppressWarnings("unchecked")
- Entry
entry = (Entry)tab[index]; - for(; entry != null ; entry = entry.next) {
- if ((entry.hash == hash) && entry.key.equals(key)) {
- V old = entry.value;
- entry.value = value; //进行替换的代码
- return old;
- }
- }
-
- addEntry(hash, key, value, index);
- return null;
- }
-
- //3.扩容 if (count >= threshold) 就调用 rehash();
- 扩容长度原来二倍+1 //(int)临界值为新的容量*0.75
- private void addEntry(int hash, K key, V value, int index) {
- modCount++;
-
- Entry,?> tab[] = table;
- //如果超过 threshold就进行扩容
- 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
e = (Entry) tab[index]; - tab[index] = new Entry<>(hash, key, value, e);
- count++;
- }
-
- protected void rehash() {
- int oldCapacity = table.length;
- Entry,?>[] oldMap = table;
-
- // overflow-conscious code
- //扩容原来二倍+1
- int newCapacity = (oldCapacity << 1) + 1;
- //临界值为新的容量*0.75
- threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
- */
- }
- }
Properties类继承自Hashtable类并且实现Map接口,也是键值对的形式来保存数据
它的使用特点和Hashtable类似
Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
工作后xxx.properties文件通常作为配置文件
- public class Properties_ {
- public static void main(String[]args){
- /*
- 1. Properties 继承Hashtable
- 2.可以通过k-v 存放数据,当然key和value不能为null
-
- */
- Properties pro = new Properties();
- pro.put("jack",100);
- // pro.put(null,100);// 空指针异常
- // pro.put("jack",null);//空指针异常
- pro.put("tom",22);//ok
- pro.put("lic",33);//ok
- pro.put("lic",48);//替换
- pro.put("mm","ww");//替换
- System.out.println(pro);
-
- //通过key,获取对应的值
- System.out.println(pro.get("jack"));
-
- //删除 remove
- pro.remove("jack");
- System.out.println(pro);
- //注意getProperty 得到如果不是String 就返回null
- //只有是String才能得到结果
- /*
- public String getProperty(String key) {
- Object oval = super.get(key);
- String sval = (oval instanceof String) ? (String)oval : null;
- return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
- }
- */
- System.out.println(pro.getProperty("tom"));//null
- System.out.println(pro.getProperty("mm"));//ww
- }
- }
是有序的 底层是红黑树(又名自平衡二叉查找树)
- package com.sofwin.controller;
-
- import java.util.Comparator;
- import java.util.Set;
- import java.util.TreeSet;
-
- /**
- * @packageName: com.sofwin.controller
- * @author: wentao
- * @date: 2022/11/1 22:27
- * @version: 1.0
- * @email 1660420659@qq.com
- * @description: TreeSet
- */
- public class TreeSet_ {
- public static void main(String[]args){
- //当我们使用无参构造器的时候 默认是升序
- //如果想要自定义比较的 使用TreeSet提供的构造器,可以传入一个比较器(匿名内部类)
- //从大到小
-
- // Set set = new TreeSet();
- Set set = new TreeSet(new Comparator() {
- @Override
- public int compare(Object o1, Object o2) {
- //调用String的compareTo方法进行比较
- return ((String)o2).compareTo(((String)o1));
- }
- });
- set.add("jack");
- set.add("tom");
- set.add("sp");
- set.add("a");
- // set.add(1);
- // set.add(10);
- // set.add(-1);
- // set.add(3);
- System.out.println(set);
- /*
- 1.构造器底层 会将写的比较器对象 给到TreeMap的一个属性comparator
- public TreeSet(Comparator super E> comparator) {
- this(new TreeMap<>(comparator));
- }
-
- public TreeMap(Comparator super K> comparator) {
- this.comparator = comparator;
- }
-
- 2. 在调用TreeSet的add方法的时候 底层会执行
- //cpr就是我们匿名内部类
- Comparator super K> cpr = comparator;
- if (cpr != null) {
- do {
- parent = t;
- // 动态绑定我们匿名内部类中写的compare方法
- cmp = cpr.compare(key, t.key);
- if (cmp < 0)
- t = t.left;
- else if (cmp > 0)
- t = t.right;
- else //如果=0 即相等就设置value 也就是加入不了了(替换)
-
- return t.setValue(value);
- } while (t != null);
- }
- */
- }
- }
-
先判断存储的类型 是一个对象,还是是一对键值对
一组对象 Collection接口
允许重复 List
增删多:LinkedList 【底层维护了一个双向链表】
改查多:ArrayList 【底层维护了Object类型的可变数组】
不允许重复 Set
无序 HashSet 【底层HashMap 维护了一个哈希表 数组+链表+红黑树】
排序 TreeSet
插入和取出顺序一致 LinkedHashSet 数组+双向链表+红黑树
一组键值对 Map接口
键无序:HashMap 【底层是哈希表 jdk7 数组+链表 jdk8 数组+链表+红黑树】
键排序 TreeMap
键插入和取出顺序一致 LinkedHashMap
读取文件 Properties
Collections是一个操作Set、List、Map等集合的工具类
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
reverse: 反转List中元素的顺序
shuffle :对List集合元素进行随机排序
sort :根据元素的自然排序对指定的List集合按照升序排序
sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序
swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换
- public class Collections_ {
- public static void main(String[]args){
- //创建ArrayList用于测试
- List list = new ArrayList();
- list.add("to");
- list.add("jack");
- list.add("swich");
- list.add("swicz");
- list.add("mki");
- System.out.println("反转前元素:"+list);
- // 1. reverse: 反转List中元素的顺序
- Collections.reverse(list);
- System.out.println("反转后元素:"+list);
- // 2. shuffle :对List集合元素进行随机排序 每次调用都会进行打乱
- /* Collections.shuffle(list);
- System.out.println("第一次打乱过后元素:"+list);
- Collections.shuffle(list);
- System.out.println("第二次打乱过后元素:"+list);*/
- // 3. sort :根据元素的自然排序对指定的List集合按照升序排序
- Collections.sort(list);
- System.out.println("自然排序后"+list);
- // 4. sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序
- //希望按照字符串长度进行排序
- Collections.sort(list, new Comparator() {
- @Override
- public int compare(Object o1, Object o2) {
- return ((String)o1).length()-((String)o2).length();
- }
- });
- System.out.println("自定义排序规则:"+list);
- // 5. swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换
- Collections.swap(list,0,2); //越界会报错
- System.out.println("将第一个元素和第三个元素交换后:"+list);
- }
- }
max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素
max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
min 跟最大相同
int frequency(Collection,Object) 返回指定集合中指定元素的出现次数
copy(List dest ,List src) 将src中内容赋值到dest中
replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
- //创建ArrayList用于测试
- List list = new ArrayList();
- list.add("to");
- list.add("jack");
- list.add("swich");
- list.add("swicz");
- list.add("mki");
- System.out.println("反转前元素:"+list);
-
- // 1. max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素
- Comparable max = Collections.max(list);
- System.out.println("自然顺序的最大元素"+max);
- // 2. max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
- Object max1 = Collections.max(list, new Comparator() {
- @Override
- public int compare(Object o1, Object o2) {
- return ((String) o1).length() - ((String) o2).length();
- }
- });
- System.out.println("长度最大的元素:"+max1);
- // 3. min 跟max相同
- // 4. int frequency(Collection,Object) 返回指定集合中指定元素的出现次数
- int to = Collections.frequency(list, "to");
- System.out.println("to出现的次数"+to);
- list.add("to");
- int to2 = Collections.frequency(list, "to");
- System.out.println("to出现的次数"+to2);
- // 5. copy(List dest ,List src) 将src中内容赋值到dest中
- ArrayList dest = new ArrayList<>();
- System.out.println("拷贝前dest元素"+dest);
- //会抛出 IndexOutOfBoundsException
- /* 如果源数组list集合的长度 大于填充数组的长度dest就抛出异常
- int srcSize = src.size();
- if (srcSize > dest.size())
- throw new IndexOutOfBoundsException("Source does not fit in dest");
- */
- //因此为了完成拷贝,我们要先给dest进行赋值
- for (int i =0; i
- dest.add(null);
- }
- Collections.copy(dest,list);
- System.out.println("拷贝后desc元素"+dest);
- // 6. replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
- System.out.println("替换前:"+list);
- Collections.replaceAll(list,"to","王五");
- System.out.println("替换后"+list);
-
面试题: HashSet和TreeSet的去重机制
1.HashSet的底层是HashMap 数组+链表+红黑树 set的元素存储在key中,value是set的常量PRESENT
通过hashCode+equals,底层先通过存入对象进行运算,调用hashCode得到hash值,通过hash值计算出在数组table中索引位置,如果发现该索引位置没有元素就加入,如果发现该索引位置存在元素了或者有链表了,就逐个通过equals进行比较,如果都不相同就加入到尾部(尾插法) 如果过程中有一个相等就不加入,只是将value进行替换,但是value都一样也就相当于不改变
2.TreeSet的底层是TreeMap 是数组+红黑树,如果在TreeSet的时候传入了Compartor匿名对象,就使用实现了compare方法去重,如果该方法返回0就进行value值的替换,也就是去重。如果创建TreeSet的时候没有传入Compartor匿名对象,就使用元素本身实现Compareable接口的compareTo方法去重
面试题2
- package com.sofwin.controller;
-
- import java.util.TreeSet;
-
- /**
- * @author: wentao
- * @date: 2022/11/2 13:53
- * @version: 1.0
- */
- public class Work1 {
- public static void main(String[]args){
- TreeSet set = new TreeSet();
- set.add(new Person()); //此处会出现异常 ClassCastException 类型转换异常
-
- //原因: 当调用无参构造器的时候,没有传入Compartor匿名内部类
- //TreeSet底层会使用数据转换成Comparable 使用它的compareTo方法
- // put中的这一行 Comparable super K> k = (Comparable super K>) key;
- //但是由于Person没有实现Comparable接口 因此会抛出类型准换异常
- }
- }
- class Person {
-
- }
集合总结
List集合 底层结构 线程安全 扩容机制 ArrayList 可变数组 线程不安全 无参: 第一次扩容容量为10 第二次按照1.5倍扩容 有参: 容量是指定大小 扩容为1.5倍 Vector 可变数组 线程安全 无参: 默认10 每次扩容2倍 注意:Vector创建对象的时候就进行初始化数组 有参: 容量是指定大小 每次扩容2倍 LinkedList 双向链表 线程不安全
Set集合 底层结构 线程安全 扩容机制 阈值 HashSet HashMap 数组+链表+红黑树 线程不安全 有参: 第一次 容量按照指定大小的最接近2^n次 有参:第一次 容量的0.75倍 有参:第二次 按照原来的二倍 有参:第二次 按照原来的二倍 无参: 第一次默认是16 无参:阈值是 容量的0.75倍 12 无参: 第二次 按照原来的二倍 无参: 第二次 按照原来的二倍 LinkedHashSet(HashSet的子类) LinkedHashMap 数组+双向链表+红黑树 线程不安全的 跟HashSet一样 跟HashSet一样 TreeSet TreeMap 红黑树 线程不安全的
Map集合 底层结构 线程安全 扩容机制 阈值 HashMap 数组+链表+红黑树 线程不安全 有参: 第一次 容量按照指定大小的最接近2^n次 有参:第一次 容量的0.75倍 有参:第二次 按照原来的二倍 有参:第二次 按照原来的二倍 无参: 第一次默认是16 无参:阈值是 容量的0.75倍 12 无参: 第二次 按照原来的二倍 无参: 第二次 按照原来的二倍 LinkedHashMap(HashMap的子类) 数组+双向链表+红黑树 线程不安全的 跟HashSet一样 跟HashSet一样 Hashtable (t小写) 数组+双向链表+红黑树 线程安全的 有参: 直接是指定的大小 有参: //(int)临界值为新的容量*0.75 注意:Hashtable创建对象的时候就进行初始化数组 无参: 扩容长度原来二倍+1 无参: //(int)临界值为新的容量*0.75 TreeSet TreeMap 红黑树 线程不安全的