static/final,HashMap/Hashtable/ConcurrentHashMap

本文深入探讨了Java中static和final的使用细节,分析了HashMap、Hashtable及ConcurrentHashMap的工作原理与区别,并通过示例代码解释了如何正确使用可变对象作为HashMap的键,以及面对hash冲突攻击时的应对策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:http://blog.youkuaiyun.com/cmershen/article/details/51774644

7. static和final的用法

static可以修饰变量,方法,代码块。 
static修饰的变量在内存中只有一份,在类加载的时候被完成初始化,且被该类的所有实例共享。 
static修饰的方法必须实现,不能用abstract修饰。 
static修饰的代码块在类加载完成后就会执行代码块的内容。 
执行顺序:(重要,好多面试题都考) 
父类静态代码块-子类静态代码块-父类非静态代码块-父类构造方法-子类非静态代码块-子类构造方法

final修饰的变量,引用不可变,但引用的内容是可变的。 
final修饰的方法,不能被继承,不能被子类修改。 
final修饰的类不能被继承。 
final修饰的形参不可变。

8.HashMap和Hashtable的区别

Hashtable是线程同步的,而HashMap是未经同步的。(就像ArrayList和Vector,Vector是同步的,而ArrayList不同步) 
Hashtable的值不可以是null(key和value都不可以),而HashMap的key和value都可以是null 
Hashtable直接使用对象的hashCode做key,而HashMap则不是。 
Hashtable.Java部分代码如下:

 public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();//可见这里直接用了key的hashCode
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

而HashMap.java是这样做的:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

HashMap的key不可以是可变对象,否则当对象的hashCode值发生变化时,会出现一些诡异的情况。 
例如用HashMap

public class Student {
    private int id;

    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Student(int id) {
        this.id = id;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

那么如果学生的学号改变了,就像这样:

public class Main {
    public static void main(String[] args) {
        HashMap<Student, Integer> map = new HashMap<>();
        Student s1=new Student(1);
        Student s2=new Student(2);
        map.put(s1, 100);
        map.put(s2, 98);
        s1.setId(2);
        System.out.println(map);
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

就会出现{Student@2=100, Student@2=98}的输出结果,一个学生对应了两个学号,且学生1的成绩就会找不到。 
不过有一个诡异的问题: 
如果Main函数这样写:

    public static void main(String[] args) {
        HashMap<Student, Integer> map = new HashMap<>();
        Student s1=new Student(1);
        Student s2=new Student(2);
        map.put(s1, 100);
        map.put(s2, 98);
        s1.setId(3);
        System.out.println(map);
        System.out.println(map.get(s1));
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

会看到这样的输出:

{Student@3=100, Student@2=98}
null
 
 
  • 1
  • 2
  • 1
  • 2

奇怪了,明明s1的学号改成3,且map里面也记录了学号为3的成绩是100分,怎么变成null了? 
读HashMap的源码发现,在执行 map.put(s1,100)时,系统为hashCode=1的key找到一个地址,存储对象s1和value值100,map.put(s2, 98);这一行也同理。但当s1的hashCode值变为3时,系统是到HashCode值为3对应的地址去找,显然这一地址对应的Entry为null。 
那么改为s1.setID(2);呢? 
结论也是输出null。因为HashMap.java中有如下片段:

 do {
     if (e.hash == hash &&
         ((k = e.key) == key || (key != null && key.equals(k))))
         return e;
 } while ((e = e.next) != null);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

只有当HashMap中对应地址中存储的key和输入的key是同一个对象时,才会返回这个e。 
而hashcode=2的地址中存储的对象是s2,和s1不是同一个类,故返回null。 
而若重写equals方法使得s1和s2被判为相等,则返回98。 
所以,如果必须要用可变对象做key,务必保证这个对象的属性改变时,不得改变它的HashCode。

9.HashMap和ConcurrentHashMap

ConcurrentHashMap是线程安全的,它的底层实现是将大的数组分成几段小的segment,每个小的segment上都有锁,在插入元素时,先找到要插入到哪个segment里面,再获取这个segment上的锁。 
在高并发时,Hashtable的效率很低下,因为所有线程都竞争同一个锁,而ConcurrentHashMap则不是,当一个线程占用了一个segment的锁,其他段的数据也能被其他线程访问。

//原文中说get方法在读到的值是空的时候要加锁重读,我看了好几遍源码也没看出锁在哪,求高人指点。
public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
             while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

10.如果故意构造相同hash的字符串进行攻击,怎么处理?那jdk7呢? 
还是前面的代码,稍加改动一下:

    public static void main(String[] args) {
        HashMap<Student, Integer> map = new HashMap<>();
        Student s1=new Student(1);
        Student s2=new Student(2);
        map.put(s1, 100);
        map.put(s2, 98);
        Student s3 = new Student(2);
        System.out.println(map.get(s3));

    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果知道了Student的实现,想得到学生2的成绩,就可以新建一个对象s3把它的id设置为2,然后map.get(s3)得到的就变成了s2的成绩。因此程序输出s2的成绩98。 
因此,HashMap要求hashcode相等且对象的equals返回true 才认为是同一个key,输出value。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值