一、Map
Map:键值对的集合|映射的集合 映射关系 key--value 一个key只能对应一个value
key:唯一的,无序的 Set
value:无序的,可重复的 Collection
!!Map接口下所有的实现去重与无序都是根据键值对的key实现的
Map的常用方法
Map<Integer,String> map = new HashMap<>(); //V put(K key, V value) //将指定值与此映射中的指定键关联(可选操作)。 key相同value覆盖 map.put(71,"因为"); map.put(70,"施哥"); map.put(69,"Lisa"); map.put(69,"Lisa"); map.put(69,"李四"); //void putAll(Map<? extends K,? extends V> m) //将所有映射从指定映射复制到此映射(可选操作)。 //boolean containsKey(Object key) //如果此映射包含指定键的映射,则返回true 。 System.out.println(map.containsKey(69)); System.out.println(map.containsValue("Lisa")); //boolean containsValue(Object value) //如果此映射将一个或多个键映射到指定值,则返回true 。 //void clear() //从此映射中删除所有映射(可选操作)。 System.out.println(map); //static <K, V> Map<K,V> of(K k1, V v1, K k2, V v2) //返回包含两个映射的不可修改映射。 Map<String,Integer> map2 = Map.of("zhangsan",18,"lisi",17); System.out.println(map2); //map2.put("",10); //java.lang.UnsupportedOperationException //V remove(Object key) //如果存在,则从此映射中删除键的映射(可选操作)。 System.out.println(map.remove(70)); System.out.println("map = " + map); //default V replace(K key, V value) //仅当当前映射到某个值时才替换指定键的条目。 //default boolean replace(K key, V oldValue, V newValue) //仅当当前映射到指定值时才替换指定键的条目。 System.out.println(map.replace(71,"施哥")); System.out.println(map); //V get(Object key) //返回指定键映射到的值,如果此映射不包含该键的映射,则返回null 。 //int size() //返回此映射中键值映射的数量。 System.out.println(map.size());
Map的遍历:value keySet entrySet
//遍历方式 //Collection<V> values() 获取所有的value返回 Collection<String> values = map.values(); for(String s:values){ System.out.println(s); } //Set<K> keySet() 返回此映射中包含的键的Set视图。 Set<Integer> keys = map.keySet(); for(int i:keys){ System.out.println(i+"-->"+map.get(i)); } //Set<Map.Entry<K,V>> entrySet() 返回此地图中包含的映射的Set视图。 Set<Map.Entry<Integer,String>> entrys = map.entrySet(); for(Map.Entry<Integer,String> entry:entrys){ System.out.println(entry.getKey()+"--->"+entry.getValue()); }
二、TreeMap
底层:红黑树 TreeSet的底层是TreeMap维护的
特点:可以根据键值对做升序排序
去重与排序:都是根据键值key实现,关注key的比较器
新增方法:新增了一些比较大小相关的方法
遍历方式 : values keySet entrySet
public class TreeMap001 { public static void main(String[] args) { //TreeMap() --> key的内部比较器 //使用其键的自然顺序构造一个新的空树映射。 TreeMap<Double,String> treeMap001=new TreeMap<>(); treeMap001.put(2344.3,"abs"); treeMap001.put(23233.3,"ab"); treeMap001.put(1927.3,"a"); treeMap001.put(2344.3,"wer"); System.out.println(treeMap001); //TreeMap(Comparator<? super K> comparator) -->key的外部比较器 //构造一个新的空树映射,根据给定的比较器排序。 TreeMap<Student2,String> maps=new TreeMap<>((x,y)->x.getName().compareTo(y.getName())); maps.put(new Student2("zhangsan",18),"厦门大学"); maps.put(new Student2("wangwu",19),"浙江大学"); maps.put(new Student2("李四",21),"集美大学"); maps.put(new Student2("zhangsan",18),"武汉大学"); maps.put(new Student2("zhangsan",19),"南开大学"); System.out.println(maps); //Map.Entry<K,V> ceilingEntry(K key) //返回与大于或等于给定键的最小键关联的键值映射,如果没有这样的键,则null 。 //K ceilingKey(K key) //返回大于或等于给定键的最小键,如果没有这样的键,则null 。 System.out.println(maps.ceilingKey(new Student2("wangwu",19))); System.out.println(maps.floorEntry(new Student2("wangliu",19))); //Map.Entry<K,V> firstEntry() //返回与此映射中的最小键关联的键值映射,如果映射为空,则null 。 //K firstKey() //返回此映射中当前的第一个(最低)键。 //Map.Entry<K,V> lastEntry() //返回与此映射中最大键关联的键值映射,如果映射为空,则null 。 //K lastKey() //返回此映射中当前的最后一个(最高)键。 System.out.println(treeMap001.firstEntry()); System.out.println(treeMap001.firstKey()); //Map.Entry<K,V> higherEntry(K key) //返回与严格大于给定键的最小键关联的键值映射,如果没有这样的键,则null 。 //K higherKey(K key) //返回严格大于给定键的最小键,如果没有这样的键,则返回null 。 //Map.Entry<K,V> lowerEntry(K key) //返回与严格小于给定键的最大键关联的键值映射,如果没有这样的键,则null 。 //K lowerKey(K key) //返回严格小于给定键的最大键,如果没有这样的键,则返回null 。 System.out.println(treeMap001.higherKey(4000.0)); System.out.println(treeMap001.lowerEntry(4000.0)); //Map.Entry<K,V> pollFirstEntry() //删除并返回与此映射中最小键关联的键值映射,如果映射为空,则null 。 //Map.Entry<K,V> pollLastEntry() //删除并返回与此映射中最大键关联的键值映射,如果映射为空,则null 。 System.out.println(treeMap001.pollFirstEntry()); System.out.println(treeMap001.pollLastEntry()); } } class Student2 { private String name; private Integer age; public Student2() { } public Student2(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Student2{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
三、HashMap
HashMap:Map接口的基于哈希表的实现 此实现提供所有可选的映射操作 允许null值和null值
底层逻辑:哈希表
jdk1.7及之前:数组+链表
jdk1.8及之后:数组+链表+红黑树
当单向链表节点个数>8,要求节点数组长度>64,会将单向链表变成红黑树
初始容量(底层数组容量):默认16|可以通过构造器指定
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
加载因子(计算扩容阈值)loadFactor:默认0.75|可以通过构造器指定
static final float DEFAULT_LOAD_FACTOR = 0.75f;
阈值(扩容临界值)threshold:capacity*loadFactor
扩容机制(底层数组扩容机制):每次扩容原容量2倍
newCap = oldCap << 1
数组的长度为2的整数次幂 此实现不同步
没有新增方法
HashMap存储过程 : 1.对要存储的键值对的key调用hash方法,返回key的hash值 int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 2.调用putVal方法,同时传递key的hash值,key,value,在方法内部实现添加键值对数据 3.执行putVal方法 1)判断哈希表底层节点数组是否为null|是否长度为0 ? 如果是直接调用resize方法实现扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; 2)根据key的hash值与数组长度计算位桶索引 int index = (n - 1) & hash; 3)判断节点数组[index] 是否存在数据,如果不存在,直接创建新节点放入节点数组[index]位置,作为单向链表首节点 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); 4)如果存在首节点,p a) : 判断首节点p是否为key相同的节点,如果是key相同的节点,记录它e if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) e = p; b) : 判断首节点p是否为红黑树中的节点,如果是调用putTreeVal方法实现添加,如果不是继续向下判断 c) : 顺着单向链表首节点p遍历整个链表,直到找到key相同的节点e或者遍历到原链表的最后创建新节点挂上去结束 找到key相同的节点 : if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) break; 遍历到原链表的最后创建新节点挂上去 : if ((e = p.next) == null) p.next = newNode(hash, key, value, null); 5)判断是否存在key相同的节点,如果存在覆盖,返回原value值,方法结束 if (e != null) { V oldValue = e.value; e.value = value; return oldValue; 6)如果不存在key相同的节点,肯定在之前创建新节点放入到了哈希表中,长度+1,判断是否需要扩容,最后返回null if (++size > threshold) resize(); return null;
四、HashTable
HashTable与HashMap的区别:
相同点:都是Map接口的实现类,都存储键值对数据
底层结构都是哈希表,特点相同
不同点:1.继承关系
HashMap继承自java.util.AbstractMap<K,V>
HashTable继承自java.util.Dictionary<K,V>
2.对null处理不同
HashMap 允许null值和null键
HashTable 任何非null对象都可以作用键或值
3、同步问题
HashMap 线程不安全|不同步的
HashTable 线程安全|同步的
4、计算hash值与位桶索引的方式不同
HashMap
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
int index = (n - 1) & hash;
HashTable
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
5、初始容量与扩容机制不同
HashMap:
初始容量:16|可以通过构造器指定
扩容机制:每次扩容原来的2倍 newCap = oldCap << 1
HashTable:
初始容量:11|可以通过构造器指定
扩容机制:每次扩容原容量2倍+1 int newCapacity = (oldCapacity << 1) + 1;
如何处理HashMap线程不安全问题 :
1.Hashtable
2.Collections-->static <K, V> Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。
3.juc-->ConcurrentHashMap ==> 推荐
五、properties
Properties : Properties类表示一组持久的属性。 Properties可以保存到流中或从流中加载。 属性列表中的每个键及其对应的值都是一个字符串。
一般使用Properties的作用都是从配置文件中加载键值对数据
public class Class001_Properties { public static void main(String[] args) throws IOException { Properties pro = new Properties(); pro.setProperty("username","zhangsan"); pro.setProperty("password","1234"); //void store(OutputStream out, String comments) //将此Properties表中的属性列表(键和元素对)以适合使用load(InputStream)方法加载到Properties表的格式写入输出流。 OutputStream os = new BufferedOutputStream(new FileOutputStream("src/dest.properties")); pro.store(os,"hahahehe"); os.flush(); os.close(); System.out.println(pro.getProperty("username")); //从流中加载 | 从配置文件中加载键值对数据 //void load(InputStream inStream) //从输入字节流中读取属性列表(键和元素对)。 pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties")); System.out.println(pro.getProperty("driver")); System.out.println(pro.getProperty("username")); System.out.println(pro.getProperty("password")); } }