☆技术问答集锦(一)

1 hashCode和equals方法,在HashMap中如何使用?hashCode和equals方法之间的关系及为什么?

Object的equals和hashCode方法。如下:

 // hashCode()方法默认是native方法;
 public native int hashCode();
 
 // equals(obj)默认比较的是内存地址;
 public boolean equals(Object obj) {
     return (this == obj);
 }

hashCode()方法有三个关注点:

关注点1:主要是说这个hashCode方法对哪些类是有用的,并不是任何情况下都要使用这个方法(此时是根本没有必要来复写此方法),而是 当你涉及到像HashMap、HashSet(他们的内部实现中使用到了hashCode方法)等与hash有关的一些类时,才会使用到hashCode方法

关注点2:推荐按照这样的原则来设计,即 当equals(object)相同时,hashCode()的返回值也要尽量相同,当equals(object)不相同时,hashCode()的返回没有特别的要求,但是也是尽量不相同以获取好的性能

关注点3:默认的hashCode实现一般是内存地址对应的数字,所以不同的对象,hashCode()的返回值是不一样的。

在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样,Object提供的hashCode()的缺省实施通过将对象的内存地址对映于一个整数值来生成。

接下来HashMap的源码分析hashCode和equals方法使用过程:

 static final Entry<?,?>[] EMPTY_TABLE = {}; 
 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

HashMap内部是由Entry<K,V>类型的数组table来存储数据的。来看下Entry<K,V>的代码:

 static class Entry<K,V> implements Map.Entry<K,V> { 
     final K key; 
     V value; 
     Entry<K,V> next; 
     // key的hashCode方法的返回值经过hash运算得到的值
     int hash; 

     /** * Creates new entry. */ 
     Entry(int h, K k, V v, Entry<K,V> n) { 
         value = v; 
         next = n; 
         key = k; 
         hash = h; 
     }
     //略
 }

所以我们可以画出HashMap的存储结构:

HashMap存储结构

图中的每一个方格就表示一个Entry<K,V>对象,其中的横向则构成一个Entry<K,V>[] table数组,而竖向则是由Entry<K,V>的next属性形成的链表。

首先看下它HashMap是如何来添加的,即 put(K key, V value)方法:

 public V put(K key, V value) { 
     if (table == EMPTY_TABLE) { 
         inflateTable(threshold);
     } 
     if (key == null) return putForNullKey(value); 
     // 位置[1]
     int hash = hash(key);
     // 位置[2]
     int i = indexFor(hash, table.length); 
     for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
         Object k; 
         // 位置[4] 遍历链表,若链表已存在一致的对象,则替换
         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
             V oldValue = e.value; 
             e.value = value;
             e.recordAccess(this); 
             return oldValue; 
         } 
     } 
     modCount++; 
     // 插入
     addEntry(hash, key, value, i); 
     return null; 
 }

重点关注它的存的过程,位置[1]首先就是计算key的hash值,这个hash计算的过程位置[3]便用到了key对象的hashCode方法,如下:

 final int hash(Object k) { 
     int h = hashSeed; 
     if (0 != h && k instanceof String) { 
         return sun.misc.Hashing.stringHash32((String) k); 
     } 
     // 位置[3]
     h ^= k.hashCode(); 

     h ^= (h >>> 20) ^ (h >>> 12); 
     return h ^ (h >>> 7) ^ (h >>> 4); 
 }

得到这个hash值后,位置[2]紧接着执行了int i = indexFor(hash, table.length);就是找到这个hash值在table数组中的索引值,具体方法indexFor(hash, table.length)为:

 static int indexFor(int h, int length) { 
     return h & (length-1); 
 }

位置[4]判断是否一致的条件是:e.hash == hash && ((k = e.key) == key || key.equals(k)),一定要牢牢记住这个条件。

必须满足的条件1:hash值一样,hash值的来历就是根据key的hashCode再进行一个复杂的运算,当两个key的hashCode一致的时候,计算出来的hash也是必然一样的。

必须满足的条件2:两个key的引用一样或者equals相同。

综上所述,HashMap对于key的重复性判断是基于两个内容的判断,一个就是hash值是否一样(会演变成key的hashCode是否一样),另一个就是equals方法是否一样(引用一样则肯定一样)

所以,hashCode的重写的原则:当equals方法返回true,则两个对象的hashCode必须一样

equals()方法,在get()方法中的使用过程分析:

 public V get(Object key) {    
     Node<K,V> e;
     return (e = getNode(hash(key), key)) == null ? null : e.value;
 }
 
 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 && ((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;
 }

为什么 Override equals()和hashCode()?

由于作为key的对象将通过计算其hashCode来确定与之对应的value的位置,因此任何作为key的对象都必须实现 hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的 hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,hashCode()方法目的纯粹用于提高效率,所以尽量定义好的 hashCode()方法,能加快哈希表的操作。

如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个

hashcode与equals方法使用:http://my.oschina.net/xianggao/blog/90110

2 内存泄漏与内存溢出的区别?

Java虚拟机在执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。如下图所示:

JVM运行时数据区域

虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表,操作数栈,动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应地,方法的返回则表示栈帧的出栈操作。如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。

虚拟机栈结构

注意:函数嵌套调用的次数由栈的大小决定。栈越大,函数嵌套调用次数越多。对一个函数而言,它的参数越多,内部局部变量越多,它的栈帧就越大,其嵌套调用次数就会越少。

可达性分析:Java中对内存对象的访问,使用的是引用的方式。在Java代码中我们维护一个内存对象的引用变量,通过这个引用变量的值,我们可以访问到对应的内存地址中的内存对象空间。在Java程序中,这个引用变量本身既可以存放堆内存中,又可以放在代码栈的内存中(与基本数据类型相同)。GC线程会从代码栈中的引用变量开始跟踪,从而判定哪些内存是正在使用的。如果GC线程通过这种方式,无法跟踪到某一块堆内存,那么GC就认为这块内存将不再使用了(因为代码中已经无法访问这块内存了)。

可达性分析

通过这种有向图的内存管理方式,当一个内存对象失去了所有的引用之后,GC
就可以将其回收。反过来说,如果这个对象还存在引用,那么它将不会被GC回收,哪怕是Java虚拟机抛出OutOfMemoryError。

Java中的内存泄漏,主要指的是是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)

在不涉及复杂数据结构的一般情况下,Java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度。我们有时也将其称为“对象游离”。

3 深入HashMap,及并发读写情况下死循环问题?

深入JDK源码之HashMap类:http://my.oschina.net/xianggao/blog/386697

HashMap多线程并发问题分析:http://my.oschina.net/xianggao/blog/393990

ConcurrentHashMap深入分析:http://my.oschina.net/xianggao/blog/212060

HashMap vs ConcurrentHashMap:http://my.oschina.net/xianggao/blog/394213

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 1,151评论 0 16
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,485评论 0 3
  • 本系列出于AWeiLoveAndroid的分享,在此感谢,再结合自身经验查漏补缺,完善答案。以成系统。 Java基...
    济公大将阅读 1,523评论 1 6
  • Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构如栈,队列等,Java集合还可以用于保存具有映射关...
    小徐andorid阅读 1,915评论 0 13
  • 人会关心的事情要么和生活相关,要么和情感相关。 你也是个普通人,也会有生命的起起伏伏,悲悲喜喜,所以你会关注人物命...
    不见棺材不落泪的人啊阅读 248评论 0 0