目录:
一、Map接口常用方法
二、遍历Map集合的两种方式【重点】
三、哈希表数据结构
四、属性类:Properties类
五、TreeSet集合
六、Collections工具类
.
一、Map接口常用方法
java.util.Map接口中常用的方法:
1. Map和Collection没有继承关系。
2. Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位, value是key的一个附属品。
3. Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(object key) 判断Map中是否包含某key
boolean containsValue(0bject value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
Set<K> keySet() 获取Map集合所有的key (所有的键是一个set集合)
V remove(Object key) 通过key 删除键值对
int size() 获取Map集合中键值对的个数。
Collection<V> values() 获取Map集合中所有的value ,返回一个Collection
Set<Map.Entry<K,V>> entrySet() // 该方法能将Map集合转换成Map.Entry<K,V>类型的Set集合 (泛型)
将Map集合转换成Set集合
假设现在有一个Map集合,如下所示:
map1集合对象
key value
-----------------------------
1 hello
2 world
3 jun Set set =map1.entrySet(); // 调用该方法
转换成Set集合
1=hello 【注意:Map集合通过entrySet()方法转换成这个Set集合,Set集合中元素的类型是Map.Entry】
2=world 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类】
3=jun
注意:该方法的底层相当于节点node(里面有两个属性)一个放数据,一个放下一个node对象的内存地址 (相当于拿到1=hello后还可以调用getKey()的方法拿到相对应的key)
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.*;
public class MapTest01 {
public static void main(String[] args) {
// 创建一个Map集合对象 Integer=> K,String=> V
Map<Integer,String> map =new HashMap<>(); // 因为Map集合中的key 和 value存放的都是引用数据类型
// 所以 key:Integer 相当于new Integer(1); 将基本数据类型成了引用数据类型
// 测试Map集合中的方法
// 1、V put(K key, V value) 向Map集合中添加键值对
map.put(1,"hello"); // 这里相当于自动装箱 new Integer(1);[将基本数据类型 转换成引用数据类型]
map.put(2,"world");
map.put(3,"jun");
map.put(4,"junk");
// 2、V get(Object key) 通过key获取value
String value =map.get(1);
System.out.println(value); // hello
// 3、int size() 获取Map集合中键值对的个数
System.out.println("键值对的数量:"+map.size()); // 4
// 4、Collection<V> values() 获取Map集合中所有的value ,返回一个Collection
Collection<String> values=map.values();
// 对Collection集合中数据进行遍历
for (String data : values){
System.out.println(data);
}
// 5、boolean containsKey(object key) 判断Map中是否包含某key
// 注意:contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法
System.out.println(map.containsKey(1)); // true // 这里相当于 map.containsKey(new Integer(1)); 重写了equals方法
System.out.println(map.containsKey(5)); // false
// 6、boolean containsValue(0bject value) 判断Map 中是否包含某个value
System.out.println(map.containsValue("hello")); // true
}
}
注意 无序不可重复
二、遍历Map集合的两种方式【重点】
第一种方式:先拿到key,通过遍历key来遍历value
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.*;
// 遍历Map集合
public class MapTest02 {
public static void main(String[] args) {
// 创建一个Map对象
Map<Integer,String> map=new HashMap();
// 添加元素
map.put(1,"hello"); // 自动装箱
map.put(2,"world");
map.put(3,"jun");
map.put(4,"junk");
// 第一种遍历Map集合的方式: 获取所有的key,通过遍历key,来遍历value 【因为Map集合有一个通过key可以拿到value的值的方法】
// Set<K> keySet()方法 获取Map集合所有的key (所有的键是一个set集合)
Set<Integer> keys =map.keySet();
// 遍历key 通过key拿到value
// 迭代器方式
Iterator<Integer> it =keys.iterator();
while (it.hasNext()){
Integer key =it.next(); // 可以循环拿到key
System.out.println(map.get(key)); // 通过key拿到相对应的value
}
System.out.println("================================");
// foreach 也可以
for (Integer key: keys){
System.out.println(map.get(key));
}
}
}
输出结果:
第二种方式:通过调用 Set<Map.Entry<K,V>> entrySet() 方法 // 该方法能将Map集合转换成Map.Entry<K,V>类型的Set集合
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapTest03 {
public static void main(String[] args) {
// 创建一个Map集合对象
Map<Integer,String> map =new HashMap();
// 添加元素
map.put(1,"jun");
map.put(2,"junk");
// 第二种:Set<Map.Entry<K,V>> entrySet()方法 // 该方法能将Map集合转换成Map.Entry<K,V>类型的Set集合 将Map集合转换成Set集合(泛型)
Set<Map.Entry<Integer, String>> set1 =map.entrySet();
// 迭代器对象
Iterator<Map.Entry<Integer, String>> i =set1.iterator();
while (i.hasNext()){
System.out.println(i.next().getValue()); // i.next();拿到的是 1=jun 可以再拿value或key (底层相当于node对象)
}
System.out.println("========================");
// 第三种:foreach方式
Set<Map.Entry<Integer, String>> set2 =map.entrySet();
for (Map.Entry<Integer, String> data :set2){ // Map.Entry<Integer, String> 类型数据
System.out.println(data);
System.out.println(data.getValue());
}
}}
输出结果:
总结两种遍历Map集合的方法:
第一种效率会比较低,因为需要先遍历key然后再通过key遍历value,因此效率会下降
单向链表图:
三、哈希表数据结构
HashMap总结:
HashMap集合:
1、HashMap集合底层是哈希表/散列表结构
2、哈希表是一个怎么样的数据结构呢?
哈希表是一个数组和单向链表的结合体
数组:在查询方法效率很高,随机增删方面的效率很低 (通过下标查方法效率高,随机删一个的话 下标还要全部进行重新排序)
单向链表(node):在随机增删方面的效率很高,在查询方法效率方面很低 (基本单元是node,node有两个属性:一个存储数据,一个储存下一个节点node的内存地址
因为是链式 所以查询不像数组那样通过下标查询 所以查询方法效率很低, 但因为有一个属性储存的是下一个节点的内存地址 所以随即增删效率很高)
哈希表将以上两种数据结构融合在一起,充分发挥它们各自的优点~3、HashMap集合底层的源代码:
public class HashMap{
// HashMap底层实际上就是一个数组(一维数组)
Node<K,V>[] table;// 静态内部类HashMap.Node
static class Node<K,V>{
final int hash; // 哈希值,代表数组的下标
final K key; // 储存到Map集合中的key
V value; // 储存到Map集合中的那个Value
Node<K,V> next; // 下一个节点的内存地址
}
}
总结: 哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表(数组和链表的结合体)4、最主要掌握的是:(看图)
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理,是必须掌握的。5、HashMap集合的key部分特点:
无序,不可重复。
为什么无序?因为不一定挂到哪个单向链表上。[有可能挂到数组下标为0的单向链表上 也有可能挂到下表为5的单向链表上]
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。[key重复的话会重新覆盖value]6、哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
纯单向链表 [hashCode()方法能最终得到数组下标如果固定的话那么就只能在数组下标为该固定值的单向链表上添加元素了] 这种情况我们成为:散列分布不均匀。
什么是散列分布均勻?
假设有100个元素, 10个单向链表,那么每个单向链表上有10个节点,这是最好的,
是散列分布均勻的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
也是散列分布不均匀。
散列分布均匀需要你重hashCode()方法时有一定的技巧。7、重点:放在HashMap集合key部分元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法
8、重点:记住:HashMap的初始化容量是16,默认加载因子是0.75 (默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容)
HashMap集合初始化容量必须是2的倍数,这也是官方推荐的HashSet也是和HashMap的扩容一样
测试HashMap集合key部分的元素特点:
代码演示如下:
public class HashMapTest01 {
public static void main(String[] args) {
// 测试HashMap集合key部分的元素特点
// Integer是key,他的hashCode和equals方法都重写了
Map<Integer,String> map =new HashMap<>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"junker");
map.put(4,"junk");
map.put(4,"jun"); // key重复的时候value会自动覆盖
System.out.println(map.size()); // 4 // 证明了不可重复
// 遍历Map集合
Set<Map.Entry<Integer, String>> set =map.entrySet();
for (Map.Entry<Integer,String> data : set) {
System.out.println(data.getKey() + "=" + data.getValue()); // 从结果可知:key重复的时候value会自动覆盖的
}
}
}
输出结果:
测试2:当没有重写hashCode和equals方法时:
终极结论:【记住这个】
放到HashMap集合key部分的,以及放到HashSet集合中的元素,需要同时重写hashCode方法和equals方法
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.Objects;
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.name == student.name;
}
// 重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name);
}
}
package com.bjpowernode.javase.map;
import java.util.HashSet;
import java.util.Set;
/*
向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数組下标。数组下标位置上如果是null , equals不需要执行。直接将该数据放入该下标数组的单向链表当中
get(k)举例,什么时候equals不会调用?
k. hashCode()方法返回哈希值,哈希值经过哈希算法转换成数組下标。数组下标位置上如果是null , equals不需要执行。如果部位null则需要判断是否取该k的单向链表中的数据
终极结论:
放到HashMap集合key部分的,以及放到HashSet集合中的元素,需要同时重写hashCode方法和equals方法
*/
public class HashMapTest02 {
public static void main(String[] args) {
// 创建Student对象
Student s1 =new Student("hello");
Student s2 =new Student("hello");
// Student 没有重写equals方法的时候 调用的是父类Object的equals方法 比较内存地址
// System.out.println(s1.equals(s2)); // false
// 重写equals方法后
// System.out.println(s1.equals(s2)); // true(s1和s2相等)
System.out.println("s1的hashCode ="+s1.hashCode()); // 460141958
System.out.println("s2的hashCode ="+s2.hashCode()); // 1163157884
// s1.equals(s2)结果已经是true,表明s1和s2是一样的,相同的,那么往HashSet集合中放的话
// 按说只能放进去一个(HashSet集合特点:无序不可重复)
Set<Student> set =new HashSet<>();
set.add(s1);
set.add(s2);
System.out.println(set.size()); // 这个结果按理说应该是1,但结果却是2
// 结果说明:向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法 当hashCode不同时说明数组下标不同,那么元素添加到的数组位置也就不同 所以结果为2
// 结果显然不符合HashSet集合储存特点:无序不重复 (因此需要重写hashCode方法)
}
}
重写后的输出结果:
面试问题:
在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据,这种方式也是为了提高检索效率,因为当超过8的时候也是一个一个对比的话一样浪费时间 ,二叉树的检索会再次缩小扫描范围
四、属性类:Properties类
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
Properties被称为属性类对象
Properties是线程安全的
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.Properties;
/*
目前只需要掌握Properties属性类对象的相关方法即可
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
Properties被称为属性类对象
Properties是线程安全的
*/
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个Properties对象
Properties pro =new Properties();
// 需要掌握Properties的两个方法:一个存,一个取
pro.setProperty("username","123");
pro.setProperty("driver","com.mysql.jdbc.Driver");
// 通过key获取value
String s =pro.getProperty("username");
System.out.println(s); // 123
}
}
五、TreeSet集合
1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。
代码演示如下:
public class TreeSetTest01 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts =new TreeSet<>();
// 向集合中添加元素
ts.add("hello");
ts.add("world");
ts.add("append");
ts.add("yeah");
// 遍历TreeSet集合
for (String date:ts){
System.out.println(date);
}
System.out.println("=======================");
TreeSet<Integer> trs =new TreeSet<>();
trs.add(5);
trs.add(3);
trs.add(1);
trs.add(9);
for (Integer date:trs){
System.out.println(date);
}
}
}
输出结果:
对自定义的类型来说,TreeSet可以排序吗? 【重点】
以下程序中对于Person类型来说,无法排序,因为没有指定Person对象之间的比较规则,
谁大谁小并没有说明啊以下程序运行的时候出现这个异常:
com.bjpowernode.javase.map.Person cannot be cast to java.lang.Comparable
出现这个异常的原因是:
Person类没有实现java.lang.Comparable接口(String和Integer类型都实现了此接口 所以可以排序输出)
代码演示如下:
public class TreeSetTest02 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<Person> ts =new TreeSet<>();
// 向集合中添加元素
ts.add(new Person("hello"));
ts.add(new Person("world"));
ts.add(new Person("junk"));
ts.add(new Person("junker"));
// 遍历集合元素
for (Person date:ts){
System.out.println(date); // 异常
}
}
}
class Person{
String name;
public Person(String name) {
this.name = name;
}
}
输出结果:
第一种比较方法: 当我们手动实现Comparable接口后代码演示如下:
package com.bjpowernode.javase.map;
import java.util.TreeSet;
public class TreeSetTest03 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<Student1> ts =new TreeSet<>();
Student1 s1 =new Student1(9);
Student1 s2 =new Student1(5);
Student1 s3 =new Student1(10);
Student1 s4 =new Student1(68);
// 向TreeSet集合中添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
// 遍历
for (Student1 date:ts){
System.out.println(date); // 输出引用Student1需要重写toString方法
}
}
}
class Student1 implements Comparable<Student1>{
int age;
public Student1(int age) {
this.age = age;
}
// 因为接口当中有一个抽象方法,实现该接口必须重写该方法(实例对象当中不能有抽象方法)
// 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么比较
// k.compareTo(t.key)
// 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
public int compareTo(Student1 c){ // s1.compareTo(s2);
// 假定以年龄进行比较【菜鸟比较】
// this是s1
// c是s2
// s1和s2比较,就是this和c比较
/*int age1 =this.age;
int age2 =c.age;
if (age1==age2){
return 0;
}
else if (age1>age2){
return 1;
}
else {
return -1;
}*/
return this.age-c.age; // 同样能判断是否大于小于等于0
// return c.age-this.age; // 降序
}
// 重写toString方法
@Override
public String toString() {
return "Student:["+age+"]";
}
}
输出结果:
例子2:
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.*;
public class Ex {
public static void main(String[] args) {
// 创建一个TreeSet集合对象
Set<A> set = new TreeSet<>(); // TreeSet特点:无序不可重复 但内部对元素进行了排序 当为自定义的类的时候 没有重写comparator方法 没办法进行排序
set.add(new A("jun",60));
set.add(new A("kitty",50));
set.add(new A("lun",90));
set.add(new A("pitty",50));
for (A date: set){
System.out.println(date);
}
}
}
class A implements Comparable<A>{
String name;
int age;
public A(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "name:"+this.name+",age:"+this.age+"";
}
/*
要求:先按照年龄升序,如果年龄一样再按照姓名升序
*/
@Override
public int compareTo(A o) {
// 先按照年龄升序
// 如果年龄一样 按姓名排序
if (this.age ==o.age){
return this.name.compareTo(o.name);
}
// 这里说明年龄不一样
return this.age-o.age;
}
}
输出结果:
第二种比较方法:(实现比较器接口) 优点:可以创建多个比较器满足比较规则
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTest05 {
public static void main(String[] args) {
// 创建TreeSet集合
// TreeSet<wuGui> wuGuis =new TreeSet<>(); 用第二种比较器的方法的话这里创建TreeSet集合就不能这样写了
// TreeSet类有参构造方法能够把比较器对象传过去,在构造方法当中进行比较排序 因此操作如下:
TreeSet<wuGui> wuGuis =new TreeSet<>(new wuGuiComparator());
wuGuis.add(new wuGui(900));
wuGuis.add(new wuGui(800));
wuGuis.add(new wuGui(700));
wuGuis.add(new wuGui(600));
// for遍历
for (wuGui date : wuGuis){
System.out.println(date);
}
}
}
class wuGui{
int age;
public wuGui(int age) {
this.age = age;
}
// 重写toString方法
@Override
public String toString() {
return "小乌龟[" +
"age=" + age +
']';
}
}
// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口 (Comparator是java.lang包下的,Comparator是java.util包下的)
class wuGuiComparator implements Comparator<wuGui>{
@Override
public int compare(wuGui o1, wuGui o2) {
// 按照比较规则
// 按照年龄排序
return o1.age -o2.age;
}
}
// 还可以写其他规则的比较器~
输出结果:
两种比较方式的最终结论:
最终的结论:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。Comparabl e和Comparator怎么选择呢?
当比较规则不会,发生改变的时候,或者说当比较规则只有1个的时候,建议实现comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口(第二种比较器方式)。
六、Collections工具类
代码演示如下:
package com.bjpowernode.javase.map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
/*
java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作
*/
public class CollectionsTest01 {
public static void main(String[] args) {
// ArrayList集合不是线程安全的
ArrayList<String> arrayList =new ArrayList<>(); // String类重写了比较器 不用再写比较器就能排序
// 变成线程安全的
Collections.synchronizedList(arrayList);
// 向集合中添加元素
arrayList.add("a");
arrayList.add("e");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
// 排序
Collections.sort(arrayList);
for (String date:arrayList){
System.out.println(date);
}
System.out.println("=========下列的排序需要写比较器然后进行排序==========");
ArrayList<XiaoWuGui> arrayList1 =new ArrayList<>();
arrayList1.add(new XiaoWuGui(9));
arrayList1.add(new XiaoWuGui(10));
for (XiaoWuGui date :arrayList1){
System.out.println(date);
}
System.out.println("====================================");
// 当为Set集合的时候可以调用方法先把Set集合转换成List集合 然后进行排序操作
}
}
class XiaoWuGui implements Comparable<XiaoWuGui>{
int age;
public XiaoWuGui(int age) {
this.age = age;
}
@Override
public int compareTo(XiaoWuGui o) {
return this.age-o.age;
}
@Override
public String toString() {
return "XiaoWuGui[" +
"age=" + age +
']';
}
}
输出结果: