Java面试知识点(五)hashmap、hashtable和hashset
1. 关于 HashMap 的一些说法:
a) HashMap 实际上是一个 “链表散列” 的数据结构,即数组和链表的结合体。HashMap 的底层结构是一个数组,数组中的每一项是一条链表。
b) HashMap 的实例有俩个参数影响其性能: “初始容量” 和 装填因子。
c) HashMap 实现不同步,线程不安全。 HashTable 线程安全
d) HashMap 中的 key-value 都是存储在 Entry 中的。
e) HashMap 可以存 null 键和 null 值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过 hashCode () 方法和 equals 方法保证键的唯一性
f) 解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap 是采用拉链法解决哈希冲突的。
注:
链表法是将相同 hash 值的对象组成一个链表放在 hash 值对应的槽位;
用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查 (亦称探测) 技术在散列表中形成一个探查 (测) 序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址 (即该地址单元为空) 为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为 m,则可将散列表定义为一个由 m 个头指针组成的指针数 组 T [0..m-1]。凡是散列地址为 i 的结点,均插入到以 T [i] 为头指针的单链表中。T 中各分量的初值均应为空指针。在拉链法中,装填因子 α 可以大于 1,但一般均取 α≤1。拉链法适合未规定元素的大小。
2. Hashtable 和 HashMap 的区别:
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口。主要的区别有:线程安全性,同步 (synchronization),以及速度。
a) 继承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
b) Hashtable 中的方法是同步的,而 HashMap 中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用 Hashtable,但是要使用 HashMap 的话就要自己增加同步处理了。
c) Hashtable 中, key 和 value 都不允许出现 null 值。 在 HashMap 中, null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。
d) 两个遍历方式的内部实现上不同。Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式 。
e) 哈希值的使用不同,HashTable 直接使用对象的 hashCode。而 HashMap 重新计算 hash 值。
f) Hashtable 和 HashMap 它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中 hash 数组默认大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。
注: HashSet 子类依靠 hashCode () 和 equal () 方法来区分重复元素。
HashSet 内部使用 Map 保存数据,即将 HashSet 的数据作为 Map 的 key 值保存,这也是 HashSet 中元素不能重复的原因。而 Map 中保存 key 值的,会去判断当前 Map 中是否含有该 Key 对象,内部是先通过 key 的 hashCode, 确定有相同的 hashCode 之后,再通过 equals 方法判断是否相同。
3. hashmap和hashtable源码分析
//HashMap的源码
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
//Hashtable的源码
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
很明显,都实现了Map接口
public V put(K key, V value) //HashMap的put方法,没有同步
public synchronized V put(K key, V value) //Hashtable的put方法
//当然,Hashtable的其他方法,如get,size,remove等方法,
//都加了synchronized关键词同步操作
在多线程编程中,synchronized 关键字非常常见,当我们需要进行 “同步” 操作时,我们很多时候需要该该关键字对代码块或者方法进行锁定。被 synchronized 锁定的代码块,只能同时有一条线程访问该代码块。
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
//那么,我们再来看下HashMap的put方法中,有如下语句
//调用某个方法直接把key为null,值为value的键值对插入进去。
if (key == null)
return putForNullKey(value);
//以下是Hashtable的方法
public synchronized boolean contains(Object value)
public synchronized boolean containsKey(Object key)
public boolean containsValue(Object value)
//以下是HashMap中的方法,注意,没有contains方法,
//HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey。因为 contains 方法容易让人引起误解。
public boolean containsKey(Object key)
public boolean containsValue(Object value)
4. HashSet
HashSet 实现了 Set 接口,它不允许集合中有重复的值,当我们提到 HashSet 时,第一件事情就是在将对象存储在 HashSet 之前,要先确保对象重写 equals () 和 hashCode () 方法,这样才能比较对象的值是否相等,以确保 set 中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。
public boolean add (Object o) 方法用来在 Set 中添加元素,当元素值重复时则会立即返回 false,如果成功添加的话会返回 true。
HashSet 内部使用 Map 保存数据,即将 HashSet 的数据作为 Map 的 key 值保存,这也是 HashSet 中元素不能重复的原因。而 Map 中保存 key 值前,会去判断当前 Map 中是否含有该 key 对象,内部是先通过 key 的 hashCode,确定有相同的 hashCode 之后,再通过 equals 方法判断是否相同。
散列码是由对象导出的一个整数值。在 Object 中有一个 hashCode 方法来得到散列码。基本上,每一个对象都有一个默认的散列码,其值就是对象的内存地址。但也有一些对象的散列码不同,比如 String 对象,它的散列码是对内容的计算结果
String s=new String ("OK");// 散列码: 3030
String t="Ok"; // 散列码: 3030
StringBuffer sb=new StringBuffer (s); // 散列码:20526976
StringBuffer tb=new StringBuffer (t); // 散列码:20527144
注意:
HashSet 要求不能存储相同的对象,HashMap 要求不能存储相同的键
5. java中判断相同的根据——hashcode()和equal()
在 Java 中任何一个对象都具备 equals (Object obj) 和 hashcode () 这两个方法,因为他们是在 Object 类中定义的。 equals (Object obj) 方法用来判断两个对象是否 “相同”,如果 “相同” 则返回 true,否则返回 false。 hashcode () 方法返回一个 int 数,在 Object 类中的默认实现是 “将该对象的内部地址转换成一个整数返回”。
规范 1:若重写 equals (Object obj) 方法,有必要重写 hashcode () 方法,确保通过 equals (Object obj) 方法判断结果为 true 的两个对象具备相等的 hashcode () 返回值。说得简单点就是:“如果两个对象相同,那么他们的 hashcode 应该 相等”。(注意不是强制)
规范 2:如果 equals (Object obj) 返回 false,即两个对象 “不相同”,并不要求对这两个对象调用 hashcode () 方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的 hashcode 可能相同”。
推论:
1、如果两个对象 equals,Java 运行时环境会认为他们的 hashcode 一定相等。 2、如果两个对象不 equals,他们的 hashcode 有可能相等。
3、如果两个对象 hashcode 相等,他们不一定 equals。
4、如果两个对象 hashcode 不相等,他们一定不 equals。
因为hashmap要求键值不能相同,java判断是否相同,先调用hashcode()判断hash值是否相同,在调用equal来判断
示例
public class Test {
public static void main(String[] args) {
Map<Xsj,Integer> test1 = new HashMap<>();
test1.put(new Xsj(),1);
test1.put(new Xsj(),2);
System.out.println(test1.size());
}
}
重写xsj类的hashcode方法和equal方法,map的size会出现不同的变化
public class Xsj {
@Override
public int hashCode() {
return 1;
}
@Override
public boolean equals(Object obj) {
return false;
}
}
当equal方法返回false的时候,size为2,当返回true的时候,size为1,因为hashmap的键值不能相同,当equal和hashcode全部相同的时候,java认为这是同一个值,就不会插入两次。
6.关于equal和 ==
- '==' 是用来比较两个变量(基本类型和对象类型)的值是否相等的, 如果两个变量是基本类型的,那很容易,直接比较值就可以了。如果两个变量是对象类型的,那么它还是比较值,只是它比较的是这两个对象在栈中的引用(即地址)。
- 对象是放在堆中的,栈中存放的是对象的引用(地址),由此可见 '==' 是对栈中的值进行比较的。如果要比较堆中对象的内容是否相同,那么就要重写 equals 方法了。
- Object 类中的 equals 方法就是用 '==' 来比较的,所以如果没有重写 equals 方法(string是重写了equal方法的),equals 和 == 是等价的。 通常我们会重写 equals 方法,让 equals 比较两个对象的内容,而不是比较对象的引用(地址)因为往往我们觉得比较对象的内容是否相同比比较对象的引用(地址)更有意义。
- Object 类中的 hashCode 是返回对象在内存中地址转换成的一个 int 值(可以就当做地址看)。所以如果没有重写 hashCode 方法,任何对象的 hashCode 都是不相等的。通常在集合类的时候需要重写 hashCode 方法和 equals 方法,因为如果需要给集合类(比如:HashSet)添加对象,那么在添加之前需要查看给集合里是否已经有了该对象,比较好的方式就是用 hashCode。
- 注意的是 String、Integer、Boolean、Double 等这些类都重写了 equals 和 hashCode 方法,这两个方法是根据对象的内容来比较和计算 hashCode 的。(详细可以查看 jdk 下的 String.java 源代码),所以只要对象的基本类型值相同,那么 hashcode 就一定相同。
在 object 类中,hashcode () 方法是本地方法,返回的是对象的引用(地址值)
总的来说,Java 中的集合(Collection)有两类,一类是 List,再有一类是 Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals 方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有 1000 个元素,那么第 1001 个元素加入集合时,它就要调用 1000 次 equals 方法。这显然会大大降低效率。 于是,Java 采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode 方法实际上返回的就是对象存储的物理地址(实际可能并不是)。 这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的 equals 方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次。 所以,Java 对于 eqauls 方法和 hashCode 方法是这样规定的:1、如果两个对象相同,那么它们的 hashCode 值一定要相同;2、如果两个对象的 hashCode 相同,它们并不一定相同 上面说的对象相同指的是用 eqauls 方法比较。
Java面试知识点(五)hashmap、hashtable和hashset的更多相关文章
- Java 面试知识点解析(五)——网络协议篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java面试知识点汇总
Java面试知识点汇总 置顶 2019年05月07日 15:36:18 温柔的谢世杰 阅读数 21623 文章标签: 面经java 更多 分类专栏: java 面试 Java面试知识汇总 版权声明 ...
- Java 面试知识点解析(四)——版本特性篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java 面试知识点解析(二)——高并发编程篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java 面试知识点解析(六)——数据库篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java 面试知识点解析(七)——Web篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java面试知识点之线程篇(三)
前言:这里继续对java线程相关知识点进行总结,不能间断. 1.yield()方法 yield()的作用是让步.它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执 ...
- Java面试知识点之线程篇(二)
前言:接上篇,这里继续对java线程相关知识点进行总结. 1.notify和notifyall的区别 notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的 ...
- Java 面试知识点解析(三)——JVM篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- Java面试知识点之线程篇(一)
前言:在Java面试中,一定会遇到线程相关问题,因此笔者在这里总结Java中有关线程方面知识点,多数从网上得来(文中会贴出主要参考链接),有些也是笔者在面试中所遇到的问题,如有错误,请不吝指正.主要参 ...
随机推荐
- WPF 使用 Silk 的 Direct2D 入门
在上一篇博客的基础上,使用 dotnet 基金会新开源的 Silk.NET 库,让 Silk.NET 创建的 DX 设备和 WPF 对接渲染.接下来本文将告诉大家如何使用 Silk.NET 提供的 D ...
- CentOS-7卸载了python2.7,yum不可用的解决方法
1.mount挂载iso镜像 [root@localhost software]# mount -t iso9660 -o loop CentOS-7-x86_64-DVD-2003.iso /med ...
- pdo类
testmysql.php <?php require_once "./mypdo.php"; //do something... //查一行 $id = 3; //$sql ...
- Ubuntu实现与主机Windows复制粘贴(安装VMware Tools)
若不能实现主机与客户机间粘贴复制执行以下命令 sudo apt-get autoremove open-vm-tools sudo apt-get install open-vm-tools sudo ...
- Selenium4自动化测试3--元素定位By.NAME,By.LINK_TEXT 和通过链接部分文本定位,By.PARTIAL_LINK_TEXT,css_selector定位,By.CSS_SELECTOR
4-通过名称定位,By.NAME name属性为表单中客户端提交数据的标识,一个网页中name值可能不是唯一的.所以要根据实际情况进行判断 import time from selenium impo ...
- C#语言:散修笔记
文章目录 前言 数组的几种定义方法 out 和 ref 的区别 可变参数params 静态方法与非静态方法 >❀什么时候使用静态和非静态 构造函数 >❀类中方法的重载 >❀在类中输出 ...
- C#的GroupBy方法是如何工作的
前言:先贴结果 GroupBy方法是如何工作的? 一.准备6个待分组的学生对象 class student { public string name;//姓名 public int grade;//年 ...
- linux常见的网络操作命令
1 linux在某个网卡上面添加一条明细路由命令如下 命令的意思是在这台服务器上面添加一条网段为192.168.1.0/24,网关为192.168.2.1,通过eth0这个网卡口出去 ip rout ...
- linux常用关机/重启命令:shutdown,init 0,init 6
目录 一.使用shutdown关机,重启,定时关机 二.使用init关机/重启 一.使用shutdown关机,重启,定时关机 1.设置计算机10分钟之后关机 [root@node5 ~]# shutd ...
- es 排序突然很慢的原因
今天突然之间发现一个访问es的查询很慢.由刚上线之前测试的100ms直接到了5s左右.瞬间懵逼. 这个用户索引大概200w的数据. 查询语句如下 GET /user/_search{"fro ...