集合③
HashSet && TreeSet
Map
哈希表
二叉树
Collections工具类
HashSet && TreeSet
HashSet集合:
无序不可重复。
TreeSet集合存储元素特点:
1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序!称为:可排序集合。
2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
Map
Map作为一个父接口,其特点如下:
1、Map集合和Collection集合没有关系。
2、Map集合以key和value的方式存储数据:
键值对key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品。
3、key和value都是存储java对象的内存地址。
4、所有Map集合的key特点:无序不可重复的。
Map集合的key和Set集合存储元素特点相同。
Map接口常用方法
在Map接口中有许多方法,详情查询API文档。
下面介绍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(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection<V> values() 获取Map集合中所有的value,返回一个Collection
Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)
Set<Map.Entry<K,V>> entrySet()
将Map集合转换成Set集合
假设现在有一个Map集合,如下所示:
map1集合对象
key value
----------------------------
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu
Set set = map1.entrySet();
set集合对象
1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】
2=lisi 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
3=wangwu
4=zhaoliu ---> 这个东西是个什么?Map.Entry
下面是对这些常用方法的代码演示:
public class MapTest01 {
public static void main(String[] args) {
创建Map集合对象
Map<Integer, String> map = new HashMap<>();
向Map集合中添加键值对
map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
通过key获取value
String value = map.get(2);
System.out.println(value);
获取键值对的数量
System.out.println("键值对的数量:" + map.size());
通过key删除key-value
map.remove(2);
System.out.println("键值对的数量:" + map.size());
判断是否包含某个key
contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(map.containsKey(new Integer(4))); // true
判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu"))); // true
获取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
System.out.println(s);
}
清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());
判断是否为空
System.out.println(map.isEmpty()); // true
}
}
Map集合的遍历【非常重要】
对于Map的遍历,有两种思路方法:
1、获取所有的key,然后通过key去获得value完成遍历。
2、Set<Map.Entry<K,V>> entrySet()
这个方法是把Map集合直接全部转换成Set集合。
Set集合中元素的类型是:Map.Entry
下面是对这两种方法的代码实现:
首先创建集合:
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
---------------------------------------------------------------
方法1:
获取所有的key,通过遍历key,来遍历value。所有的key是一个Set集合
先通过keySet()方法取出所有key存储在keys集合中:
Set<Integer> keys = map.keySet();
迭代器完成遍历:
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
取出其中一个key
Integer key = it.next();
通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
foreach也可以完成遍历:
for(Integer key : keys){
System.out.println(key + "=" + map.get(key));
}
---------------------------------------------------------------
方法2:
这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
比较适合于大数据量。
Set<Map.Entry<K,V>> entrySet()通过entrySet()方法去获得:
首先通过entrySet()方法来转换:
Map.Entry其实是个内部类,是个类名,后面带<>是泛型,set是集合名(变量名)
Set<Map.Entry<Integer,String>> set = map.entrySet();
迭代器完成遍历:
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);
}
foreach完成遍历:
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + "--->" + node.getValue());
}
---------------------------------------------------------------
哈希表/散列表 数据结构
首先要知道哪些集合底层是哈希表?
HashMap
HashSet(他的底层是HashMap,进而是哈希表)
HashTable
哈希表/散列表是一个怎样的数据结构?
初步理解:
哈希表是一个将数组和链表结合起来的结构,他结合了他们各自的优点
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
形成了一个新的数据结构。
HashMap集合底层的源代码:
public class HashMap{
HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
静态的内部类HashMap.Node
static class Node<K,V> {
final int hash; 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
final K key; 存储到Map集合中的那个key
V value; 存储到Map集合中的那个value
Node<K,V> next; 下一个节点的内存地址。
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
HashMap集合的key部分特点:
无序,不可重复。
为什么无序? 因为不一定挂到哪个单向链表上。
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。
散列分布均匀需要你重写hashCode()方法时有一定的技巧。
重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
下面是对于哈希表的图片详解:
对于哈希表,重点要掌握的是:
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理!
在上图已经写了这两个方法的原理了。
map.put(k,v)原理:
首先将k,v封装到Node对象当中。
然后底层会调用k的hashCode()方法去得出其哈希值
然后通过哈希函数/哈希算法去将hash值转换为数组的下标
如果下标所指的位置上没有任何元素,就把Node添加到这个位置上
如果此下标对应的位置有链表,就会拿着k去跟链表上每个节点中的k进行equals
如果所有的equals方法都返回false(都不相等),则在链表末尾新添这个节点
如果其中有一个equals方法反悔了true,那么这个节点的value将被覆盖(不可重复)
v = map.get(k)原理:
先调用k的hashCode()方法得出哈希值,然后通过哈希算法转换成数组下标
然后通过数组下标去快速定位到位置
如果这个位置什么也没有,返回null
如果这个位置有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals
如果所有equals方法返回false,那么get方法返回null
只要其中有一个节点的k和参数k equals的时候返回true
那么这个节点的value就是我们要找的value,get方法最终返回这个要找的value
从上面的原理可以看出,如果用哈希表的话,必须去重写两个方法!
放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法!
具体重写使用IDEA工具生成即可
1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。
3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。
4、终极结论:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
5、对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
小TIPS:
HashMap集合key部分允许null吗?
允许
但是要注意:HashMap集合的key null值只能有一个。
Hashtable的key可以为null吗?
Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。
Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理
导致效率较低,使用较少了。
Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1
Properties
目前只看到了Properties属性类对象的相关方法。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。
创建一个Properties对象
Properties pro = new Properties();
需要掌握Properties的两个方法,一个存,一个取。
pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username", "root");
pro.setProperty("password", "123");
通过key获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
二叉树 数据结构
首先要知道哪些集合底层是二叉树?
TreeMap
TreeSet(他的底层是TreeMap,进而是二叉树)
关于二叉树
初步理解:
(基于TreeSet,因为其实现了SortedSet接口,故可排序,TreeMap同理实现SortedMap接口,可排序)
TreeSet集合存储元素特点:
1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序!
称为:可排序集合。
2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
TreeSet集合底层实际上是一个TreeMap
TreeMap集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。
下面图解释了关于二叉树的数据结构
关于此图的解释:
1、TreeSet/TreeMap是自平衡二叉树。遵循左小右大的原则存放。(存放的时候就会进行大小比较)
2、遍历二叉树有三种方式:
前序遍历:根 左 右
中序遍历:左 根 右
后序遍历:左 右 根
前中后说的是 根 的位置。
3、TreeSet集合/TreeMap集合采用的是:中序遍历方式。
Iterator迭代器也是,左 根 右。
例如将一组数据:
100,200,50,60,80,120,140,130,135,180,666,40,50放入自平衡二叉树
流程如下:
先放100,第一个 根
然后200根100比较,200>100,故放在100的右边
然后再50跟100比较,50<100,故放在100的左边
之后每次都是跟100开始比较,然后循环,直到找到一个自己的空位为止
由于存放中已经排了序,故取出的时候自动按照了大小顺序排序。
可自行模拟中序遍历,不再赘述,可以看出是自动排序了。
比较规则
当我们将一些JAVA自带的类型放进TreeSet时,是可以自动排序的。
那如果我们将自定义的类型放入TreeSet,可以自动排序吗?
下面的代码开始试验:
public class TreeSetTest03 {
public static void main(String[] args) {
Person p1 = new Person(32);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(25);
创建TreeSet集合
TreeSet<Person> persons = new TreeSet<>();
添加元素
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
遍历
for (Person p : persons){
System.out.println(p);
}
}
}
class Person {
int age;
public Person(int age){
this.age = age;
}
重写toString()方法
public String toString(){
return "Person[age="+age+"]";
}
}
很遗憾的是,不行!
程序运行的时候出现了这个异常:
java.lang.ClassCastException:class com.bjpowernode.javase.collection.Personcannot be cast to class java.lang.Comparable
程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则。
谁大谁小并没有说!我们需要给程序一个自定义类的排序规则!
如何给一个比较规则?通过翻阅源代码我们可以知道,JAVA自带的类型之所以能够实现排序
是因为他们实现了java.lang.Comparable接口。
或者在创建对象时通过构造方法传入一个比较器Comparator。
下面的代码实现了Comparable接口
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(25);
创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);
遍历
for (Customer c : customers){
System.out.println(c);
}
}
}
放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
@Override
public int compareTo(Customer c) {
// 例如调用方法时写c1.compareTo(c2);
// this是c1
// c是c2
// c1和c2比较的时候,就是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;
or:
return c.age - this.age;
}
public String toString(){
return "Customer[age="+age+"]";
}
}
TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
结论:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。
例如下面的代码:
public class TreeSetTest06 {
public static void main(String[] args) {
创建TreeSet集合的时候,需要使用这个比较器。
这样不行,没有通过构造方法传递一个比较器进去。
TreeSet<WuGui> wuGuis = new TreeSet<>();
给构造方法传递一个比较器。
TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
也可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age - o2.age;
}
});
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for(WuGui wuGui : wuGuis){
System.out.println(wuGui);
}
}
}
乌龟类
class WuGui{
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小乌龟[" +
"age=" + age +
']';
}
}
单独在这里编写一个比较器
比较器实现java.util.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;
}
}
Collections工具类
java.util.Collections 集合工具类,方便集合的操作。
1、线程安全
ArrayList集合不是线程安全的。
List<String> list = new ArrayList<>();
将其变成线程安全的
Collections.synchronizedList(list);
2、排序
Collections.sort(list);
注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
对Set集合怎么排序呢?
将Set集合转换成List集合
Set<String> set = new HashSet<>();
List<String> myList = new ArrayList<>(set);
这种方式也可以排序。(不实现Comparable接口,而是通过比较器)
Collections.sort(list集合, 比较器对象)