• 重写hashcode()和equals()->解决HashMap内存溢出问题


    Object.class

    //此方法是操作系统提供的本地方法,java不做实现 https://blog.csdn.net/cjf1002361126/article/details/52750528
    public native int hashCode();
    
    //默认比较内存地址
    public boolean equals(Object obj) {
       return (this == obj);
    }

    HashMap 1.8源码

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    static final int hash(Object key) {
          int h;
       // hashCode()方法决定了向数组哪个位置的桶内放数据,重写它让具有相同url的对象对应相同的数组位置
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } public boolean containsKey(Object key) {
      // 根据hash()找到桶下标,循环遍历桶比较Node的key与指定的key是否相同
    return getNode(hash(key), key) != null; }

    final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
    (first = tab[(n - 1) & hash]) != null) {
    if (first.hash == hash && // always check first node
    ((k = first.key) == key || (key != null && key.equals(k))))
    return first;
    if ((e = first.next) != null) {
    if (first instanceof TreeNode)
    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
    do {
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    return e;
    } while ((e = e.next) != null);
    }
    }
    return null;
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
       // equals()方法比较相同则覆盖旧值,所以重写它让具有相同url的对象达到比较结果相同的目的
    if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; ...... }

     未重写hashCode()和equals()导致的内存溢出BUG

    hashCode():保证获取正确的数组下标

    equals():保证Node的key能正确匹配,保证不重复创建节点、更新节点的值

    https://zhuanlan.zhihu.com/p/172388495

    bug在于没有红色部分的代码

    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicLong;
    
    public class Monitor {
        public static class MonitorKey {
            String url;
            String desc;
    
            public MonitorKey(String url, String desc) {
                super();
                this.url = url;
                this.desc = desc;
            }
    
            public String getUrl() {
                return url;
            }
    
            @Override
            public int hashCode() {
                return url.hashCode();
            }
    
            @Override
            public boolean equals(Object obj) {
                if (obj instanceof MonitorKey) {
                    MonitorKey key = (MonitorKey)obj;
                    return url.equals(key.getUrl());
                }
                return false;
            }
        }
    
        public static class MonitorValue {
            AtomicInteger count = new AtomicInteger();
            float avgTime;
            AtomicLong totalTime = new AtomicLong();
        }
    
        private Map<MonitorKey, MonitorValue> monitors = new ConcurrentHashMap<>();
    
        public void visit(String url, String desc, long timeCost) {
            MonitorKey key = new MonitorKey(url, desc);
            /* BUG:此处具有相同url的MonitorKey计算出的hashCode值不同,导致ConcurrentHashMap内存溢出 */
            // putIfAbsent:key不存在则上锁(CAS+Synchronized)添加节点
            MonitorValue oldValue = monitors.putIfAbsent(key, new MonitorValue());
            MonitorValue newValue = monitors.get(key);
            newValue.count.getAndIncrement();
            newValue.totalTime.getAndAdd(timeCost);
            newValue.avgTime = newValue.totalTime.get() / newValue.count.get();
        }
    
        public static void main(String[] args) {
            Monitor monitor = new Monitor();
            monitor.visit("key1", "key1desc", 1);
            monitor.visit("key2", "key2desc", 1);
            monitor.visit("key1", "key1desc", 1);
            monitor.visit("key2", "key2desc", 1);
            System.out.println(monitor.monitors.size());// 无红色部分代码:4.有红色部分代码:2
        }
    }
  • 相关阅读:
    spring(三):ApplicationContext
    android Xutils dbutils 注解
    android 录音的断点续传
    android 原生dialog对话框
    android asyncTask 详解
    自定义的dialog
    fragment 添加menu
    android baseApplication 基类
    看项目得到info_freeCsdn-01闪屏页面
    开源项目 github
  • 原文地址:https://www.cnblogs.com/yfzhou528/p/13518505.html
Copyright © 2020-2023  润新知