目录
Map集合
在创建一个Set集合时,底层实际上创建了一个Map集合。向Set集合内添加元素,实际上添加到Map集合的key部分。
常用方法
default void | replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。 |
V | put(K key, V value) 返回值为旧的 value 向Map集合中添加键值对。 |
V | get(Object key) 通过key获取value。 |
V | remove(Object key) 通过key删除键值对。 |
void | putAll(Map<? extends K,? extends V> m) 从指定映射中将所有映射关系复制到此映射中(可选操作)。 |
boolean | containsKey(Object key) 判断Map中是否包含某个key。 |
boolean | containsValue(Object value) 判断Map中是否包含某个value。 |
Set<K> | (HashSet集合的元素存储在HashMap中的key部分) |
Collection<V> | values() //value可重复 获取Map集合中所有的value,返回一个collection集合 |
Set<Map.Entry<K,V>> | entrySet() 将Map集合转换成Set集合 |
void | clear() 从此映射中移除所有映射关系。 |
int | size() 返回此映射中的键-值映射关系数。 |
boolean | isEmpty() 判断集合元素个数是否为0。 |
- 代码测试
//创建Map集合对象
Map<Integer,String> map = new HashMap<>();
//向Map集合添加键值对
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
//通过key获取value
System.out.println(map.get(2)); //李四
//获取键值对数量
System.out.println("键值对数量:"+map.size()); //键值对数量:3
//获取所有values
Collection<String> values = map.values();
for(String s : values){
System.out.println(s); //张三 李四 王五
}
//通过key删除value
map.remove(2);
System.out.println(map.get(2)); //null
System.out.println("键值对数量:"+map.size()); //键值对数量:2
//判断是否包含某个key、value
// (contains方法底层调用的是equals,所以自定义类型需要重写equals方法)
System.out.println(map.containsKey(2)); //false
System.out.println(map.containsValue("李四")); //false
//清空map集合
map.clear();
//判断是否为空
System.out.println(map.isEmpty()); //true
遍历Map集合
- 方式1:获取key的set集合,通过遍历获取value(效率较低)
// 第一种方式:获取所有的key,通过遍历key,通过key获取value
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"王五");
map.put(3,"赵六");
//先获取所有的key,所有的key是一个set集合
Set<Integer> keys = map.keySet();
//遍历key,通过key获取value
//迭代器 或 foreach
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(value);
}
- 方式2:通过entrySet()转换为Set集合,取出每个node(节点)的key、value值。(效率较高)
// 第二种方式:Set<Map.Entry<K,V>> entrySet() 效率较高
//以上方法是将Map集合直接全部转换成Set集合
//Set集合中的元素类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Set集合,每一次取出一个Node,获取key value
//(1)迭代器的方式遍历
Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value); //1=张三2=王五3=赵六
}
//(2)foreach方式遍历
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + "=" + node.getValue());
}
HasMap(重点)
链表的时间复杂度是On,红黑树是Logn
-
HashMap集合底层是哈希表(散列表)的数据结构,无序不可重复。
-
哈希表是一个数组和单向列表的结合体
-
数组:在查询方面效率很高,随机增删效率较低
-
单向链表:在随机增删方面效率很高,在查询方面效率很低。
-
哈希表将以上的两种数据融合在一起,充分发挥他们各自的优点。
-
-
通过构造器初始化对象时,初始容量是多少?
-
空参构造器:开始未初始化底层数组,在添加第一个元素时,默认初始化容量为16。
-
有参构造器:指定初始化容量,但未初始化数组,在第一次添加元素时,初始化容量为比指定容量大的 最小的2的次幂。
-
-
哈希算法:哈希表添加元素时,如何得到元素要添加的位置?
-
计算key的哈希值 -- 做到不同的元素哈希值尽量不同。
-
二次哈希 : 将哈希值的高低16位进行异或运算。 -- 减少哈希冲突。
-
二次哈希的值再与容量进行取模运算。 (如容量为16, n%16 = n&15与运算)。
-
-
链表长度达到 8、容量达到64时 转为红黑树,以进一步提升查询效率(很少出现)
当红黑树节点减少到 6 时退化回链表。 -
关于HashMap集合的扩容: 何时扩容?如何扩容?
-
第一次添加元素时默认初始化容量是16,默认加载因子是0,75(数组容量达到75%时扩容),扩容为原容量的2倍,这是为了重新分布效率。(0.75,时间与空间折中,泊松分布 <0.75浪费空间 >0.75冲突增加,浪费时间)。
-
扩容后重新计算哈希值,均衡分布。(重新分布后原红黑树链表节点可能不足6,退化回链表)。重新分布后其位置保持不动 或 换为当前位置的二倍。
-
-
JDK8: 引入红黑树,链表的头插法改为尾插法。
-
HashMap集合底层源代码:
publi class HashMap{ Node<K,V>[] table; //HashMap底层实际上就是一个一维数组 static class Node<K,V>{ //静态的内部类HashMap.Node final int hash; //哈希值(哈希值是key的hashCode()方法的执行结果,哈希值通过算法可以转换为数组下标) final K ker; //存储到Map集合中的key元素 V value; //存储到Map集合中的value元素 Node<K,V> next; //下一个节点的内存地址。}} static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认初始容量 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子 static final int TREEIFY_THRESHOLD = 8; //链表转红黑树临界值 static final int UNTREEIFY_THRESHOLD = 6; //红黑树退化链表临界值 //链表转为红黑树的另一个条件 static final int MIN_TREEIFY_CAPACITY = 64; //链表转红黑树链表容量 //-------------------------------------------- transient Node<K,V>[] table; //底层的数组 Node节点 transient Set<Map.Entry<K,V>> entrySet; transient int size; //元素个数 transient int modCount; //修改次数 int threshold; //扩容阈值 容量*加载因子 final float loadFactor; //实际加载因子
-
哈希表(散列表)数据结构
哈希表(散列表):一维数组,数组中的每一个元素是一个单向链表。(数组和链表的结合体)
HashMap集合中ker部分特点:无序、不可重复
为什么无序? 因为不知道会挂到哪个单向链表上。(哈希值取余长度得出结论)
为什么不可重复? queals方法保证HashMap集合的key不可重复,
如果key重复了,value会被覆盖
重点:放在HashMap集合Key部分和HashSet集合中的元素需要同时重写hashCode和equals方法
为什么要重写重写hashCode和equals方法?
HashMap使用不当时,将无法发挥其性能!
假设所有的hashCode()方法返回值都设定为相同的值,会导致底层哈希表变成了单向链表。
假设将所有的hashCode()方法返回值都设定为不同的值,会导致底层变成一维数组。
以上情况我们称为散列分布不均匀。散列分布均匀:假设有100个元素,10个单向链表,每个链表上有10个节点,这便是散列分布均匀的。需要重写hashCode()方法时有一定技巧。
- 如何重写hashCode() 效率更高?
@Override //IDEA提供的方法 (默认版)
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age; //(质数冲突概率及低,31底层效率高,)
return result;
}
//让hashCode值与元素的内容相关
//相同内容的元素哈希值一定不同
//不同内容的哈希值尽量不同
-
HashMap 无序不可重复,遍历出来却变的有序?
HashMap的Key部分如果存储是为Integer类型,其hashCode便是其本身,那么每一个桶中将只有一个元素,结构会变成一个一维数组,因此取出时会变得有序。
Integer.hashCode 源码:
Returns a hash code for this Integer.
Returns: a hash code value for this object, equal to the primitive int value represented by this Integer object.返回此整数的哈希码。
返回: 该对象的哈希码值,等于由这个Integer对象表示的原始int值。
- 常用方法(JDK1.8新增)
V | compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 将BIFunction的结果值赋予key关联的映射值(如果没有当前映射为 |
V | computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与值相关联(或映射到 |
void | forEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。 |
V | merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。 |
void | putAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此地图。 |
LinkedHashMap
- 特点:kay 有序、不可重复
- 底层结构:哈希表基础上维护一个链表,用于维护元素的迭代顺序
- HashMap 的子类 效率略低于 HashMap
TreeMap---->SortedMap、TreeSet
-
TreeMap集合底层是一个 红黑二叉树 (相对平衡)
-
可排序集合:TreeSet集合中的元素无序不可重复,但是可以按照元素的大小自动排序。
-
TreeSet 对引用类型排序,引用类型需要自定义类实现Comparable接口,重写compareTo方法。或在参数传入Comparator比较器,自定义比较方法。
当Comparable 与 Comparator同时存在,底层优先调用 Comparator -
放到TreeSet集合的元素,等同于放到TreeMap集合Key部分了,HashMap的使用和HashSet的自定义对象比较方法是一样的,这里不再赘述。(HashMap对key部分排序。)
TreeMap() 使用键的自然顺序构造一个新的、空的树映射。 |
TreeMap(Comparator<? super K> comparator) 构造一个新的、空的树映射,该映射根据给定比较器进行排序。 |
TreeSet() 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。 |
TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。 |
//创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
//添加String
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
//遍历
for (String s : ts){
System.out.println(s); //lisi wangwu zhangsan
}
TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(300);
ts2.add(200);
ts2.add(100);
for (Integer s : ts2){
System.out.println(s); //100 200 300
}
TreeSet、TreeMap实现自定义对象的排序
放到TreeSet集合或TreeMap集合key部分的元素要想进行排序,可使用两种方式
-
放在集合中的元素实现java.lang.Comparable接口
-
在构造TreeSet或者TreeMap集合时传入一个比较器(Comparator)对象
Comparable和Comparator如何选择?
当比较规则不会发生改变 或 比较规则只有一个时,建议使用Comparable接口
当比较规则有多个,并且需要多个比较规则频繁切换,建议Comparator接口
Comparator符合OCP原则
注:Comparable是java.lang包下的,Comparator是java.util包下的
传入Comparable接口
-
compareTo(Object o)方法是java.lang.Comparable接口中的方法,当需要对某个类的对象进行排序时,该类需要实现Comparable接口,并且重写public int compareTo(T o)方法。
-
compareTo方法的返回值很重要,返回0表示相同,value会覆盖;返回>0,会继续在又子数上找;返回<0,会继续在左子树上找。
-
自定义类型实现Comparable接口,重写compareTo方法范例
class Customer implements Comparable<Customer>{ int age; public Customer(int age){ this.age = age; } //需要在这个方法中编写比较的逻辑、规则,按什么进行比较? //k.compareTo(t.key) //用参数k和集合中每一个k进行比较,返回值可能是>0 <0 =0 @Override public int compareTo(Customer c) { // c1.compareTo(c2) return this.age - c.age; //age - this.age则为降序 //c1和c2比较时,就是this和c比较 } //重写toString public String toString(){ return"Customer[age="+age+"]"; } }
public class TreeSetTest01 { public static void main(String[] args) { Customer c1 = new Customer(32); Customer c2 = new Customer(20); Customer c3 = new Customer(30); //创建TreeSet集合 TreeSet<Customer> cus = new TreeSet<>(); //添加元素 cus.add(c1); //Customer cannot be cast to Comparable cus.add(c2); cus.add(c3); //遍历 for(Customer c : cus){ System.out.println(c); //成功升序排序 } } }
- 例二
//先按照年龄升序,如果年龄一样再按照姓名升序。 class Vip implements Comparable<Vip>{ String name; int age; @Override //写排序规则,按照什么排序 public int compareTo(Vip v) { if(this.age == v.age){ //年龄不同按照姓名排序 姓名是String类型,调用compareTo方法比较 return this.name.compareTo(v.name); }else{ //年龄相同按照年龄排序 return this.age - v.age; } } //构造器 public Vip(String name, int age) { this.name = name; this.age = age; } //重写toString @Override public String toString() { return "Vip{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
Vip v1 = new Vip("zhangSan",30); Vip v2 = new Vip("zhangSan",25); TreeSet<Vip> vip = new TreeSet<>(); vip.add(v1); vip.add(v2); for(Vip v : vip){ System.out.println(v); //成功升序排序 }
编写Comparator(比较器)
-
TreeSet集合中元素可排序的第二种方式:编写一个比较器,传入TreeSet集合构造方法
-
注:Comparable是java.lang包下的,Comparator是java.util包下的
//乌龟 第一种方式:impements Comparable 实现接口重写compareTo();
class WuGui {
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小乌龟[" + "age=" + age + ']';
}
}
//单独编写一个比较器
//比较器实现java.tuil.Comparator接口(Comparable是java.lang包下的,Comparator是java.util包下的)
class WuGuiComparator implements Comparator<WuGui>{
@Override
public int compare(WuGui o1, WuGui o2) {
//指定比较规则,按照年龄排序
return o1.age - o2.age;
}
}
public static void main(String[] args) {
//创建TreeSet集合的时候,需要使用这个比较器
//TreeSet<WuGui> wugui = new TreeSet<>(); 这样没有通过构造方法传递比较器
//↓给构造方法传递一个比较器↓
TreeSet<WuGui> wugui = new TreeSet<>(new WuGuiComparator());
WuGui g1 = new WuGui(300);
WuGui g2 = new WuGui(500);
WuGui g3 = new WuGui(100);
wugui.add(g1);
wugui.add(g2);
wugui.add(g3);
for(WuGui wg :wugui){
System.out.println(wg); //成功排序
}
//getMap集合 key部分排序
TreeMap<WuGui,Integer> wgMap = new TreeMap(new WuGuiComparator());
wgMap.put(g1,300);
wgMap.put(g2,500);
wgMap.put(g3,100);
//遍历map数组
Set<Map.Entry<WuGui,Integer>> s = wgMap.entrySet();
for(Map.Entry<WuGui,Integer> n: s){
System.out.println(n.getKey() ); //成功排序
}
}
- 使用匿名内部类:
//匿名内部类的方式,直接new接口,省略创建WuGuiComparator类的步骤
TreeSet<WuGui> wugui = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age - o2.age;
}
});
Properties 属性集 (了解)
详情请看:Properties通常用于编写配置文件
Properties类的用法总结_源码复兴号的博客-CSDN博客_properties
-
Properties 表示了一个持久的属性集,可保存在流中或从流中加载。Properties的key和value都是String类型,并不允许为null。
-
Properties是一个Map集合,继承 Hashtable。
-
Properties被称为属性类对象,主要用来存储配置信息。
-
Properties是线程安全的
-
常用方法
Object | setProperty(String key, String value) 调用 Hashtable 的方法 put 。 存 |
String | getProperty(String key) 用指定的键在此属性列表中搜索属性。 取 |
void | store(OutputStream out, String comments) 以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。 |
void | store(Writer writer, String comments) 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 |
void | load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。 |
void | load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 |
Set<String> | stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。 |
Enumeration<?> | propertyNames() 返回属性列表中所有键的枚举,如果在主属性列表中未找到同名的键,则包括默认属性列表中不同的键。 |
//创建一个Properties集合
Properties pro = new Properties();
//添加元素
pro.setProperty("username","code");
pro.setProperty("password","123abc");
//根据key获取value
System.out.println(pro.getProperty("username")); //code
System.out.println(pro.getProperty("password")); //123abc
- System 系统类方法
static String | getProperty(String key) 获取指定键指示的系统属性。 |
static Properties | getProperties() 确定当前的系统属性。 |
//获取属性集
Properties prop = System.getProperties();
//获取keySet,遍历
Set<Object> keySet = prop.keySet();
for (Object o : keySet) {
System.out.println(prop.get(o));
}
自动平衡二叉树数据结构
底层结构:TreeSet 里面维护了一个TreeMap,都是基于红黑树实现的。
红黑树是一种相对平衡的二叉树,查询效率高于链表。(平衡二叉树追求绝对平衡,左右子树差值不能超过1)
二叉树的遍历方式有:
* 前序遍历:中-左-右
* 中序遍历:左-中-右
* 后序遍历:左-右-中