/*
TreeMap :
存储键值对数据,key是唯一的,去重的,无序的(存放的顺序与内部真实存储的顺序不保证一致)
底层结构 : 红黑树
特点 : 根据key做自动升序排序(比较器)
去重与升序排序 : 根据key比较规则做去重与排序
新增方法 : 新增了一些与比较大小相关的方法
注意 : Map集合下所有的实现类,去重|排序都是根据key实现的,与value无关
练习 :
定义TreeMap集合存储键值对数据
键为Double类型,值为String类型,测试使用
键为Employee类型,值为员工所在公司名字,测试使用
*/
public static void main(String[] args) {
//TreeMap<String,Double> map = new TreeMap<>(); 使用key的类型的默认比较器进行对键值对key做比较大小,做升序排序
//TreeMap(Comparator<? super K> comparator) 构造一个新的空树图,根据给定的比较器排序。
//TreeMap<Person,Double> map = new TreeMap<>(new Impl()); //根据参数传递的比较规则对key做比较去重与排序
//匿名内部类
/*TreeMap<Person,Double> map = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});*/
//lambda
TreeMap<Person,Double> map = new TreeMap<>((o1, o2)->{
return o1.getAge()-o2.getAge();
});
map.put(new Person("zhangsan",18),10000.0);
map.put(new Person("wangwu",27),8888.0);
map.put(new Person("lisi",26),9999.0);
map.put(new Person("zhangsan",28),12000.0);
System.out.println(map);
//Map.Entry<K,V> ceilingEntry(K key) 返回与大于或等于给定键的最小键关联的键 - 值映射,如果没有此键,则 null 。
//K ceilingKey(K key) 返回大于或等于给定键的 null键,如果没有这样的键,则 null 。
System.out.println(map.ceilingKey(new Person("lisi",27)));
System.out.println(map.get(map.ceilingKey(new Person("lisi",27))));
//Map.Entry<K,V> floorEntry(K key) 返回与小于或等于给定键的最大键关联的键 - 值映射,如果没有此键,则 null 。
//K floorKey(K key) 返回小于或等于给定键的最大键,如果没有这样的键,则 null 。
System.out.println("--->"+map.floorKey(new Person("lisi",27)));
//Map.Entry<K,V> lowerEntry(K key) 返回与严格小于给定键的最大键相关联的键 - 值映射,如果没有这样的键,则 null 。
//K lowerKey(K key) 返回严格小于给定键的最大键,如果没有这样键,则返回 null 。
System.out.println("--->"+map.lowerKey(new Person("lisi",27)));
//Map.Entry<K,V> firstEntry() 返回与此映射中的最小键关联的键 - 值映射,如果映射为空,则 null 。
//K firstKey() 返回此映射中当前的第一个(最低)键。
System.out.println(map.firstKey());
//Map.Entry<K,V> lastEntry() 返回与此映射中的最大键关联的键 - 值映射,如果映射为空,则 null 。
//K lastKey() 返回此映射中当前的最后一个(最高)键。
}
/*
HashMap :
基于哈希表的Map接口的实现。 此实现提供了所有可选的映射操作,并允许null值和null键。
底层结构 : 哈希表(jdk8之前:数组+链表 jdk8及之后:数组+链表+红黑树)
特点 : 查询增删效率高,去重根据键值对的key实现
应用场景 : 适合应用在存储多个键值对数据的情况下,查询增删效率高应用场景
新增方法 : 无新增方法
遍历方式 : 1)values() 2)keySet() 3)entrySet()
初始容量 :默认static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; --> 可以通过构造器指定
加载因子 : 默认static final float DEFAULT_LOAD_FACTOR = 0.75f; --> 可以通过构造器指定
扩容阈值 : threshold 存储数据的个数size > 数组的容量*加载因子
扩容机制 : 每次扩容原容量的2倍 newCap = oldCap << 1
哈希表中存储键值对的个数 : size
存储过程 :
1.调用put方法添加键值对key,value数据
2.根据key计算hash值
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
3.调用putVal(hash,key,value)实现存储键值对数据
4.执行putVal方法,第一步就判断哈希表底层的节点数组是否==null,或者底层数组的长度==0,如果是证明这是第一次添加,底层没有数组或者没有带有容量的数组,先调用resize()方法实现创建新数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
5.根据key的hash值计算位桶索引int index = (n - 1) & hash,判断table[index]==null是否存在单向链表的首节点
if ((p = tab[i = (n - 1) & hash]) == null)
6.如果=null,证明不存在单向链表,直接创建新节点,存储这一次要添加的键值对数据,直接放在数组table[index]作为单向链表的首节点
tab[i] = newNode(hash, key, value, null); --> new Node<>(hash, key, value, next)
7.如果!=null,证明存在单向链表的首节点,遍历这个链表,判断链表中每一个节点的存储的key与这一次要添加的键值对的key比较是否相等,
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
如果相同value覆盖
V oldValue = e.value;
e.value = value;
return oldValue;
如果不相同创建新节点,挂在原单向链表的最后
p.next = newNode(hash, key, value, null);
8.如果以上的步骤中执行到了new Node()创建新节点放入哈希表中,数据个数+1,判断是否>扩容阈值threshold,如果满足,就调用resize方法实现扩容
if (++size > threshold)
resize();
注意 :
哈希表底层节点数组的长度为2的整数次幂
当单向链表的节点个数>=8(static final int TREEIFY_THRESHOLD = 8;),并且同时哈希表的节点数组的容量>=64(static final int MIN_TREEIFY_CAPACITY = 64;),就把单向链表优化成红黑树
但是如果数组的容量<64,就调用resize()实现数组的扩容
*/
public class Class004_HashMap {
public static void main(String[] args) {
HashMap<String,Character> map = new HashMap<>();
map.put("yinwei",'z');
map.put("laoxue",'w');
map.put("lucy",'u');
map.put("lisa",'a');
System.out.println(map);
map.put("lisa",'s');
System.out.println(map);
map.remove("lisa");
System.out.println(map);
}
}
/*
Hashtable 与 HashMap之间区别 :
共同点 :
都是Map接口的实现类,都存储键值对的数据,底层都是哈希表,特点与应用场景类似
异同点 :
1.继承体系不同
Hashtable-->父类-->java.util.Dictionary<K,V>
HashMap -->父类-->java.util.AbstractMap<K,V>
2.线程安全|不同问题
HashMap : 线程不安全|不同步的,但是效率相对较高
Hashtable : 线程安全|同步,但是效率相对较低
3.null值的处理不同
Hashtable : 任何非null对象都可以用作键或值。
HashMap : 并允许null值和null键。
4.初始容量与扩容机制不同
初始容量 :
Hashtable : 11
HashMap : 16
扩容机制
Hashtable : 每次扩容原容量的2倍+1 int newCapacity = (oldCapacity << 1) + 1;
HashMap : 每次扩容原容量的2倍 newCap = oldCap << 1
5.计算hash值与位桶索引index的方式不同
Hashtable :
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap :
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
int index = (n - 1) & hash;
*/
/*
Collections :
void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。
void shuffle(List) //对List容器内的元素进行随机排列
void reverse(List) //对List容器内的元素进行逆续排列
void fill(List, Object) //用一个特定的对象重写整个List容器
int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象
定义一个List集合存储Computer类型数据,要求根据电脑的价格做降序排序
如何处理HashMap线程不安全问题 :
1.Hashtable代替HashMap
2.使用Collections工具类的static <K,V>Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。
3.juc高级并发编程包ConcurrentHashMap<K,V> --> 推荐
*/
public static void main(String[] args) {
List<Integer> list = Arrays.asList(3,1,5,2,4);
System.out.println(list);
//void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。
Collections.sort(list);
System.out.println(list);
//void shuffle(List) //对List容器内的元素进行随机排列
Collections.shuffle(list);
System.out.println(list);
//void reverse(List) //对List容器内的元素进行逆续排列
Collections.reverse(list);
System.out.println(list);
//int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象
//先要求升序排序
Collections.sort(list);
System.out.println(Collections.binarySearch(list,14)); //-6 -插入点-1
ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
map.put("haha",123);
map.put("hehe",234);
map.put("haha",432);
System.out.println(map);
}
/*
Properties :
Properties类表示一组持久的属性
Properties可以保存到流中或从流中加载。
属性列表中的每个键及其对应的值都是一个字符串。
注意 :
应用场景 : 从properties格式的配置文件中读取键值对数据
步骤 :
1.定义xx.properties的配置文件
2.构建Properties类型的对象
3.调用load方法实现从指定流中加载
4.调用getProperty方法根据key获取value从文件中加载数据
*/
public static void main(String[] args) throws IOException {
Properties pro = new Properties();
pro.setProperty("嘻嘻","123");
pro.setProperty("哈哈","456");
System.out.println(pro);
//1)通过Properties对象从数据源文件中加载键值对数据(属性数据)
//把pro对象与流建立联系,从流中加载数据
//void load(InputStream inStream) 从输入字节流中读取属性列表(键和元素对)。
//pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("classname.properties"));
//根据key获取value,根据属性名获取属性值
//System.out.println(pro.getProperty("xixi"));;
//System.out.println(pro.getProperty("haha"));;
//System.out.println(pro.getProperty("hehe"));;
//2)把Properties存储的键值对数据写出到文件中-->了解
//pro对象与流建立联系,把键值对数据写出到目的地properties文件中
//void store(OutputStream out, String comments)
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("src/dest.properties"));
pro.store(os,"注释xixihaha");
os.flush();
os.close();
}