关于Java中HashMap相关总结

1. 关于该问题的一些导入问题

1.Immutable 和 final之间有什么不同?
A: 关于该问题在StackOverflow上有相关解答,这里做了总结.

//Ok
String name = "John";
name = "Sam";   //Ok.

//Error
final String name = "John";
name = "Sam";   //Compile error

final: 意味着我们不能改变对象引用,并将其指向一个另一个引用; final保证了对象的地址保持不变.
immutable: 意味着, 实际对象的值是不能修改的,但是我们能改变其指向的的引用;即:Immutable表明在对象创建之后,我们不能改变对象的状态.
final是一个关键字,而Immutable是一种模式.

String s = "abcd";
s = "abcdef";

当我们运行上述代码后,只是将s指向了新的内存区域,但是原始堆中的内容并没有修改.
这里写图片描述
2.为何在hashCode() & equals()方法需要override?它们有什么实际意义?
A: 该问题部分内容参考自StackOverflow, 这里是做的总结.

一些利用Hash技术的集合,如HashSet和HashMap利用对象的hashCode()值决定了其插入过程和取元素操作.
Hashing 取元素get(key)是一个2步的过程.
1. 通过hashCode()方法,找到正确的bucket.
2. 通过equals()方法在该bucket中搜寻正确的元素.

package com.fqyuan.hashcode;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        int result = 1;
        final int prime = 31;
        result = prime * result + age;
        result = prime * result + (name == null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (getClass() != obj.getClass())
            return false;
        Person p = (Person) obj;
        return p.name == this.name && p.age == this.age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
package com.fqyuan.hashcode;

import java.util.HashSet;

public class TestEquals {
    public static void main(String[] args) {
        Person Person = new Person("rajeev", 24);
        Person Person1 = new Person("rajeev", 25);
        Person Person2 = new Person("rajeev", 24);

        HashSet<Person> Persons = new HashSet<Person>();
        Persons.add(Person);
        Persons.add(Person1);
        Persons.add(Person2);

        // If hashCode() not implemented, the following result will be false.
        System.out.println(Persons.contains(Person2));
        System.out.println(Persons.size());
        System.out.println("Person.hashCode():" + Person.hashCode() + "\nPerson1.hashCode():" + Person1.hashCode()
                + "\nPerson2.hashCode():" + Person2.hashCode());
    }
}
//Running result:
true
2
Person.hashCode():1705
Person1.hashCode():1736
Person2.hashCode():1705

为什么说HashMap是非线程安全的?

A: 比如,在HashMap到达一个给定的阈值之后(通过load factor), Hashmap将进行re-size, 比如当 map已经填满了75%时. Java 的 HashMap通过创建一个2倍的bucket array来resize HashMap, 然后把原有元素插入到新的数组中. 这个过程叫做rehasing, 因为该过程中引用了hash函数确定新的bucket位置.
这时,潜在的竞争条件存在了,如果2个线程同时发现HashMap需要resize, 他们同时试图做resize操作,则就会出现问题.
另, bucket中的元素被以相反顺序存储,因为Java并不会把新元素append在尾部,而是插在头部,这是为了防止tail traversing.

2. 关于HashMap的问题

1).HashMap在Java中是如何运作的?get()方法的运作机理?

是基于hash技术(即散列表技术), 通过put(key, value)和get(key) 方法存取数据. 当我们传递key,value对象给 put()函数时, HashMap会调用该Key对象的hashCode()方法生成hashcode值,再把该hash code值作为参数传递给其自身的散列函数得到Key在bucket中的位置. 注意,这里存的是Entry对象.
调用get(key)方法时,首先会利用Key对象的hash code值找到bucket位置, 然后通过遍历该List通过判定equals()找到该Key所对应的值.

2). 如果2个不同的对象有相同的hashcode会出现什么情况?

在插入元素时,如果插入的2个对象有相同的hashcode, 这里可以分为2种情形:
1. 如果是一个新元素时,则在给定bucket中插入该新元素
2. 如果是一个重复元素,则新插入的元素将会覆盖旧元素.

3). 如果2个key有相同的hashcode值,如何get(key)得到想要的value?

这里其实就是HashMap的bucket中存储内容的问题,Entry[] table 中存储的是Entry对象.所以先通过Key的hashCode() 找到bucket,然后通过equals(key)找到给定的key, 返回该key所在位置的value即可.

这里写图片描述

3. 更深入的问题

1). 为什么String,Integer 等Wrapper 类被认为是很好的Key对象的选择?

因为他们本身具备作为键值的天生条件:Immutable/override hashCode()/override equal()方法.
Immutable是为了防止在插入HashMap后其数据域的改变,因为如果我们在插入和获取时得到的hashCode不同那么我们就不能得到正确的 get()结果.
而且, Immutable 也提供了一些其他优势,比如: 线程安全.
当然我们也可以使用其他对象作为KEY值,但是保证override hashCode() && equals()方法.

2). ConcurrentHashMap 和 HashTable有什么异同,前者可以取代后者么?

Yes, 因为HashMap不是线程安全的,所以引入了ConcurrentHashMap类,且提供了更加精细的功能.
不同之处是, HashTable不允许null对象. 而HashMap允许一个null Key对象,多个null Value对象.

3). null 值如何处理的?和hashCode() & equal()方法如何配合的?

null值是被特殊处理的,有2个特殊方法. putForNullKey(V value), getForNullKey(). 简言之, equals() 和 hashCode()方法在处理空值时,压根就没有被用到!

4). 关于构造函数的默认参数。

HashMap<String, Integer> map = new HashMap<>();
//java.util.HashMap.HashMap<String, Integer>(int initialCapacity, float loadFactor)
HashMap<String, Integer> map = new HashMap<>(28, 0.8f);

首先,如果以空的构造函数new一个HashMap时默认以16为HashMap内部的数组的大小,loadFactor=0.75f。

其次,设置的第一个参数会自动变为2的次方的值,如28会建立一个size为32的数组,这是由内部的Hash实现决定的。

如果我们使用默认的构造函数,可能在大量数据时有较大的性能开销,因为在大量数据插入时,Map会进行多次的resize操作,重新将原集合中元素散列,带来性能损失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值