1,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;//下一个结点的内存地址
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体)
2,遍历HashMap
public class test25<o> {
public static void main(String[] args) {
HashMap<Integer, String> p = new HashMap();
p.put(1, "a");
p.put(2, "b");
p.put(3, "c");
//第一种
//通过Key获得Value
for (int i = 1; i <= p.size(); i++) {
System.out.println(i + "=" + p.get(i));
}
//第二种
//将Key转为Set, 类型为Integer,再利用迭代器遍历
//每个key,通过key获得value;
Set<Integer> keys = p.keySet();
Iterator<Integer> it = keys.iterator();
while (it.hasNext()) {
Integer key = it.next();
System.out.println(key + "=" + p.get(key));
}
//第三种
//把Map转化成Set,其中类型为Map.Entry <Integer, String>,
//再利用跌代器去遍历每个结点,通过getValue(),getKey()来获得Value和Key
Set<Map.Entry <Integer, String>> set = p.entrySet();
Iterator<Map.Entry<Integer, String>> its = set.iterator();
while(its.hasNext() ){
Map.Entry<Integer,String> node = its.next() ;
String value = node.getValue() ;
Integer key = node.getKey();
System.out.println(key + "=" + value);
}
//第四种
//直接将Map全部转化为Set,然后直接将对应结点通过getValue(),getKey()来获得Value和Key
for(Map.Entry<Integer, String> set2: p.entrySet()){
String value1 = set2.getValue();
Integer key1 = set2.getKey() ;
System.out.println(key1 + "=" + value1);
}
}
}
3,map.put(K, V)的实现原理:
1)先将K,V封装到对象当中。
2)底层会调用K的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,下标位置上如果没有如何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着K和链表上每一个结点中的K进行equals,如所有的equals方法返回都是false,那么这个新结点将被添加到链表的末尾,如果其中有一个equals返回了true,那么这个结点的value将会被覆盖。
public class Test25_1 {
public static void main(String[] args) {
//测试hashMap集合key部分的元素特点
//Integer是key,它的hashCode和equals都重写了
Map<Integer, String> map = new HashMap<Integer,String>();
map.put(1111, "zhangsan");
map.put(6666, "sd");
map.put(8888, "sss");
map.put(2222, "rrr");
map.put(2222, "ffff");
//ffff覆盖了rrr
for(Map.Entry<Integer, String> s: map.entrySet()){
System.out.println(s);
}
}
}
4,V = map.get(K)实现原理:
先调用K的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么都没有,返回NULL,如果这个位置上有单向链表,那么会拿着参数K和链表上的每一个K进行equals,如果所有equals返回false,那么get方法返回NULL,只要其中有一个结点的K和参数K equals的时候返回true,那么此时的这个结点的value就是我们要找的value,get方法最终返回这个要找的value。
5,为什么哈希表的随机增删,以及查询效率都很高?
- 增删是在链表上完成。
- 查询也不需要都扫描,只需要部分扫描。
重点:通过讲解可以得出HashMap集合的key,会先后调用两个方法,一个方法的hashCode(),一个方法是equals(),那么这两个方法都要重写。
6,哈希表HashMap使用不当无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。
假设有100个元素,10个单链表,那么其中一个单链表上全为这100个元素
散列均匀分布需要你重写hashCode()有一定的技巧。
7,重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
8,
HashMap集合默认容量为16,当存储数到达容量的75%时,数组便会自动扩容。
HashMap集合扩容是:原容量2。
HashTable集合默认容量为11。
HashTable集合扩容是:原容量2 + 1。
重点:HashMap集合初始化容量必须是2的倍数,这是因为达到散列均匀,为了提高存储效率。
9,重写equals和hashCode方法
public class Test25_1 {
public static void main(String[] args) {
Student s1 = new Student("ss");
Student s2 = new Student("ss");
System.out.println(s1.equals(s2) );
Set p = new HashSet();
p.add(s1);
p.add(s2);
System.out.println(p.size() );
}
}
class Student
{
public String name;
public Student(String name){
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name ) ;
}
}
10,JDK8之后,在哈希表单向链表上元素如果超过8个,那么单向链表这种数据结构会变成红黑树这种数据结构,检索效率大大提高。
11,
- Hashtable 集合中的key和values不能为空
- HashMap 集合中的key和values可以为空
12,
1)TreeSet集合底层实际上是一个TreeMap。
2)TreeMap集合底层是一个自平衡二叉树。
3)放到TreeSet集合中的元素,等同于放到TreeMap集合的Key部分了。
4)TreeSet集合中的元素无序不可以重复,但可以按照元素大小进行自动排序,称为可排序集合。
public class Test25_1 {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet();
ts.add("z");//如果是自定义类型,必须制定对象之间的比较规则
ts.add("a");//否则无法排序
for (String s : ts) {
System.out.println(s);
}
}
}
13,TreeSet集合(自定义对象,需要制定比较规则)
直接创建一个实现Comparable接口的类
public class Test25_1 {
public static void main(String[] args) {
Customer c1 = new Customer(2);//自定义类型
Customer c2 = new Customer(1);
TreeSet<Customer> p = new TreeSet();
p.add(c1);//开始排序
p.add(c2);//在TreeSet集合中根据Customer类中的compareTo方法排序
//TreeSet底层比较方式为k.compareTo(m);
for(Customer a : p){
System.out.println(a );
}
}
}
class Customer implements Comparable<Customer>
{
public int num;
public Customer(int num) {
this.num = num;
}
//重写Comparable接口中的compareTo方法
public int compareTo(Customer c){
return this.num - c.num;//写排序规则
}//返回>0,会继续在右子树上找,反之,在左子树上找。
public String toString(){
return "Customer = " + num;
}
}
另一种方法制定比较规则(使用比较器的方式)
另外创建一个实现Comparator接口的比较器
public class Test25_1 {
public static void main(String[] args) {
Customer c1 = new Customer(2);
Customer c2 = new Customer(1);
CustomerComparator c = new CustomerComparator();
//传入比较器
TreeSet<Customer> p = new TreeSet(c);
p.add(c1);
p.add(c2);
for(Customer a : p){
System.out.println(a );
}
}
}
class Customer
{
public int num;
public Customer(int num) {
this.num = num;
}
public String toString(){
return "Customer = " + num;
}
}
//单独在这里编写一个比较器
//比较器实现java.util.Comparator接口。
// Comparable是java.lang包下的,Comparator是java.util包下的
class CustomerComparator implements Comparator<Customer>{
public int compare(Customer c1, Customer c2){
return c1.num - c2.num ;
}
}
Comparable接口:当比较规则不会发生改变的时候适合使用
Comparator接口:当比较规则经常改变的时候适合使用,因为只需要实现不同的接口就可以改变不同的规则,符合OCP原则