上半部分见 集合和集合工具类(一)
10.HashMap和HashSet集合
HashMap的数据在底层上实际上是放到了HashSet中,主要是key在起作用value相当于只是key的附属品
HashSet
- 无序不可重复,即存取顺序不同
- 存放在HashSet中的数据实际上是存放在HashMap中的key部分中
- HashSet集合中的元素要重写equals()和hashCode()方法,与HashMap集合中的key部分的元素使用方式是一样的
HashMap
-
无序不可重复,即存取顺序不同,但是存储的元素可以自动按大小排序称为可排序集合
-
HashMap集合的底层是哈希表的数据结构(数组+单向链表)
-
哈希表是一个数组和单向链表的结合体将数组和单向链表的优点结合在一起,充分发挥它们的优点.
-
HashMap底层是一个一维数组(
Node<K,V>[] table
),数组中的每一个元素是一个单向链表,还有一个静态内部类HashMap.Node
-
为什么哈希表的随机增删,以及查询效率很高?
增删是在链表上完成的。查询也不需要一个一个扫描,只需要部分扫描
-
HashMap集合的默认初始化容量是16,默认加载因子为0.75(当HashMap集合底层数组容量达到75%的时候,数组就开始扩容)。HashMap集合初始化容量必须是2的倍数,这是为了达到散列分布均匀和 提高存取效率所必须的
HashMap中put方法的实现原理
- 先将K、V封装到Node对象中
- 底层会调用key的hashCode()方法算出哈希值,然后通过哈希算法或者哈希函数,将哈希值转换为数组的下标,下标的位置上如果没有任何元素,就将Node节点对象添加到该位置上;如果下标对应的位置有链表,此时会拿着Node中的key和链表中的每一个节点的可以进行比较(equals方法),如果所有的equals返回值都为false,那么新节点将会被添加到链表的末尾,如果其中有一个equals返回了true,那么会将该位置的value将会被新的value值覆盖
HashMap中get(k)方法的实现原理
- 先调用k的hashCode()方法的出哈希值
- 通过哈希算法将哈希值转为数组下标
- 通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,就返回null;如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行比较(equals),如果所有的比较结果都为false,那么get方法返回null;如果其中有一个节点的k和参数的k进行比较的结果是true,那么此时该节点的value值就是要找的value的值,最终返回节点的value。
以上两点得出HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals方法,那么这两个方法都需要重写。
hashCode()与equals()
为什么放在HashMap集合的key元素要重写hashCode()和equals()方法?
- equals()重写是因为equals方法默认比较的是两个对象的内存地址,而在此处要比较的是内容。
- hashCode()重写是因为
HashMap集合中的key的特点是无序不可重复,那么无序是怎么造成的?不可重复是怎么保证的?
- 添加元素时,存放的位置不是按顺序依次存放的,而是根据由key通过计算得到的下标决定的,每次存放的位置是不一定的。
- equals()保证HashMap集合中的key不可重复,如果key重复了value的值会被覆盖
hashCode()方法需要重写,在重写时返回一个固定值可以吗?会出现什么问题?
不可以,此时返回的哈希值都是一样的,导致添加数据时会将所有的数据放在同一个单向链表上,就会导致底层的哈希表变成了单向链表(哈希表使用不当)无法发挥单向链表的性能。(这种情况我们称为散列分布不均匀)
注意
- 如果一个类的equals()方法重写了,那么hashCode()方法必须要重写。并且equals()方法返回值如果是true(表示在同一个单向链表上),hashCode()方法返回的值必须一样。
- 同一个单向链表上的所有节点的hash值是相同的,因为他们的数组下标是相同的,但同一个链表上比较的结果为false,肯定是不相等。
- 放在HashMap集合中的key部分的元素其实就是放到了HashSet集合中了,所以HashSet集合中的元素也要同时重写equals()和hashCode()方法。即HashMap集合中的key部分的元素和HashSet集合中的元素都要重写equals()和hashCode()方法。
- 若两个内容相同对象不同(对象的内存地址不同)在HashMap中两个都可以添加进去,说明有可能是你的hashCode()没有重写。
- HashMap集合允许key和value值为null,也可以通过空的key值null来获取value。
jdk8对HashMap集合的改进
加上了树的门限值为8:如果单向链表上的元素超过8个,再想存入时就会把单向链表变为二叉树(或者为红黑树)。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表。
HashMap和HashTable的区别
11.HashTable集合
-
HashTable的key和value都不能为null。
-
HashTable方法都带有synchronized,线程安全的,但效率较低。
-
HashTable的底层也为哈希表。
-
HashTable集合的默认初始化容量是11,默认加载因子是0.75。
-
HashTable扩容是
原容量*2+1
常用方法与HashMap的常用方法相同
12.Properties类
- Properties是一个Map的集合,继承于HashTbale,被称为属性类对象
- Properties的key和value都是String类型
- Properties是线程安全的。
Properties的创建
Properties p=new Properties();
Properties的方法
Object setProperties(String s1,String s2);
存Object getProperties(String key);
通过key获取value
13.TreeMap和TreeSet集合
-
TreeSet集合底层实际上是一个TreeMap
-
TreeMap集合底层是一个二叉树。
-
放到TreeSet集合中的元素相当于放到了TreeMap集合中的key部分了
-
TreeSet集合中的元素无序不可重复,但可以按照元素大小自动排序称为可排序集合。例如:编写程序从数据库当中取出数据,在页面展示用户信息时按照生日的降序或升序进行排列,这时就可以使用TreeSet集合,因为TreeSet集合放进去取出来时就是有顺序的。
TreeSet<String> te=new TreeSet(); te.add("123"); te.add("345"); te.add("567"); te.add("234"); te.add("456"); for (String s:te){ System.out.println(s); } /* 执行的结果为 123 234 345 456 567 */
-
TreeSet和TreeMap是自平衡二叉树。存放数据时,遵循左小右大的原则进行存放(所以存放时要进行比较)。
-
TreeSet集合和TreeMap集合中的树采用的是中序遍历方式(左根右–>根据左中右的顺序进行遍历),Iterator迭代器采用的是中序遍历方式(左根右)。
对于自定义的类型来说,TreeSet可以进行排序吗?
//以下不会进行排序,没有指定排序规则
Users u=new Users(12);
Users u1=new Users(23);
Users u2=new Users(22);
Users u3=new Users(10);
TreeSet U=new TreeSet();
U.add(u);
U.add(u2);
U.add(u1);
U.add(u3);
for (Object m:U){
System.out.println(m);
}
class Users{
private int name;
public Users(){}
public Users(int name) {
this.name = name;
}
@Override
public String toString() {
return "Users{" +
"name='" + name + '\'' +
'}';
}
}
/*
执行结果为
java.lang.ClassCastException: com.CollectionDemo.Users cannot be cast to java.lang.Comparable
*/
转换失败的原因
自定义类型没有实现Comparable接口,且没有重写compareTo方法–没有指定比较规则
由于Integer,String类等等实现了Comparable接口所以可以进行比较。
实现排序的方法
1.Comparable接口
- 自定义类型实现Comparable接口
- 重写compareTo方法,指定排序规则。
如何重写comparaTo方法见:比较器(Comparable与Comparator接口)
public class CollectionDemo {
public static void main(String[] args) {
Users u=new Users(12);
Users u1=new Users(23);
Users u2=new Users(22);
Users u3=new Users(10);
TreeSet U=new TreeSet();
U.add(u);
U.add(u2);
U.add(u1);
U.add(u3);
for (Object m:U){
System.out.println(m);
}
}
}
class Users implements Comparable<Users>{
private int name;
public Users(){}
public Users(int name) {
this.name = name;
}
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
@Override
public String toString() {
return "Users{" +
"name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Users o) {
return o.getName()-this.getName();//若要进行降序可将o.getName()和this.getName()进行互换
}
}
/*降序排列
执行结果为
Users{name='23'}
Users{name='22'}
Users{name='12'}
Users{name='10'}
*/
2.使用比较器,单独编写一个比较器(Comparable)
在创建TreeSet集合时,需要使用这个比较器,给构造方法传递一个比较器
//给构造方法传递一个比较器public class CollectionDemo {
public static void main(String[] args) {
Users u=new Users(12);
Users u1=new Users(23);
Users u2=new Users(22);
Users u3=new Users(10);
TreeSet<Users> U=new TreeSet(new UsersComparable());//给构造方法传递一个比较器
U.add(u);
U.add(u2);
U.add(u1);
U.add(u3);
for (Object m:U){
System.out.println(m);
}
}
}
class Users{
private int name;
public Users(){}
public Users(int name) {
this.name = name;
}
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
@Override
public String toString() {
return "Users{" +
"name='" + name + '\'' +
'}';
}
}
class UsersComparable implements Comparator<Users>{
@Override
public int compare(Users o1, Users o2) {
return o1.getName()-o2.getName();
}
}
/*
执行结果为
Users{name='10'}
Users{name='12'}
Users{name='22'}
Users{name='23'}
*/
14.集合工具类
在Java的java.util.Collections类下
常用方法
-
Collections.synchronizedList(List<T> list)
将线程不安全的List集合变为线程安全的 -
Collections.sort(List<T> list, Comparator<? super T> c);
排序,Comparator也可以没有。如果没有Comparator对List集合进行排序需要保证list集合中的元素的类型实现了Comparable接口.如果要对Set集合进行排序可将Set集合转为List集合再进行排序 -
Collections.binarySearch(List<? extends Comparable<? super T>> list, T key);
查找list集合中的key,必须先排序在查找,返回值为intint n=Collections.binarySearch(list,18); System.out.println(n);
-
Collections.reverse(List<?> list);
反转list集合,无返回值 -
Collections.swap(List<?> list, int n, int m);
交换list集合中的n位置和m位置,无返回值;
部分方法案例
public class CollectionDemo {
public static void main(String[] args) {
List l=new ArrayList();
l.add("123");
l.add("345");
l.add("asxdc");
l.add("65");
Collections.synchronizedList(l);
Collections.sort(l);//以默认规则进行排序
for (int i = 0; i < l.size(); i++) {
System.out.println(l.get(i));
}
Set s=new HashSet();
s.add("123");
s.add("345");
s.add("asxdc");
s.add("65");
List list=new ArrayList(s);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
arch(list,18);
System.out.println(n);
4. ` Collections.reverse(List<?> list);`反转list集合,无返回值
5. ` Collections.swap(List<?> list, int n, int m); `交换list集合中的n位置和m位置,无返回值;
## 部分方法案例
```java
public class CollectionDemo {
public static void main(String[] args) {
List l=new ArrayList();
l.add("123");
l.add("345");
l.add("asxdc");
l.add("65");
Collections.synchronizedList(l);
Collections.sort(l);//以默认规则进行排序
for (int i = 0; i < l.size(); i++) {
System.out.println(l.get(i));
}
Set s=new HashSet();
s.add("123");
s.add("345");
s.add("asxdc");
s.add("65");
List list=new ArrayList(s);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}