面试题总结-Java基础

本文深入探讨了Java中的哈希概念,包括哈希表、哈希码的作用以及equals和hashcode的关系。文章还详细讲解了HashSet、HashMap和HashTable的区别,以及它们的底层数据结构和操作复杂性。此外,还介绍了常见的排序算法,如冒泡排序、快速排序和归并排序的原理和复杂性。
摘要由CSDN通过智能技术生成

1.Interger=200 Integer==200 true还是false

Integer的范围是“-128~127”,当i在这个范围内是从IntegerCache中直接获取,当超越这个范围才会new 一个新的Integer对象
一个Integer 与 int比较,先将Integer转换成int类型,再做值比较,所以返回的是true。

2.Hash

hash

hash是一个函数,该函数中的实现就是一种算法,就是通过一系列的算法来得到一个hash值。这个时候,我们就需要知道另一个东西,hash表,通过hash算法得到的hash值就在这张hash表中,也就是说,hash表就是所有的hash值组成的,有很多种hash函数,也就代表着有很多种算法得到hash值,如上面截图的三种,等会我们就拿第一种来说。
在这里插入图片描述

hashcode

hashcode就是通过hash函数得来的,通俗的说,就是通过某一种算法得到的,hashcode就是在hash表中有对应的位置。
每个对象都有hashcode,对象的hashcode怎么得来的呢?
首先一个对象肯定有物理地址,在别的博文中会hashcode说成是代表对象的地址,这里肯定会让读者形成误区,对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的对象存放在内存中的地址,那么对象如何得到hashcode呢?
通过对象的内部地址(也就是物理地址)转换成一个整数,然后该整数通过hash函数的算法就得到了hashcode。所以,hashcode是什么呢?就是在hash表中对应的位置。

hashcode有什么作用呢

为什么hashcode就查找的更快,比如:我们有一个能存放1000个数这样大的内存中,在其中要存放1000个不一样的数字,用最笨的方法,就是存一个数字,就遍历一遍,看有没有相同得数,当存了900个数字,开始存901个数字的时候,就需要跟900个数字进行对比,这样就很麻烦,很是消耗时间,用hashcode来记录对象的位置,来看一下。

equals方法和hashcode的关系

用个例子说明:上面说的hash表中的8个位置,就好比8个桶,每个桶里能装很多的对象,对象A通过hash函数算法得到将它放到1号桶中,当然肯定有别的对象也会放到1号桶中,如果对象B也通过算法分到了1号桶,那么它如何识别桶中其他对象是否和它一样呢,这时候就需要equals方法来进行筛选了。
1、如果两个对象equals相等,那么这两个对象的HashCode一定也相同
2、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

为什么equals方法重写的话,建议也一起重写hashcode方法

哈希表

哈希表(hash table)也叫散列表,是一种非常重要的数据结构,我们先来看一下其他数据结构的特点。

数组:

采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)

链表:

对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)

数组与链表有何区别?

  • 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
  • 链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
    我们可以发现,数组和链表几乎是两个极端,一个查找效率高,一个插入删除效率高,那么有没有一种数据结构融合两者的优点呢?没错,就是哈希表。

在哈希表中进行添加,删除,查找等操作,性能都非常高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),那么是如何做到的呢?首先,哈希表的主干为数组,例如我们要增加或查找某个元素,我们可以将当前元素通过某个函数映射到数组中的某个位置,通过数组下标直接定位即可。这个函数被称为哈希函数。

哈希函数的设计至关重要,好的哈希函数会尽可能地保证散列的地址分布均匀,但是再好的哈希函数也会出现冲突的情况,比如我们的两个元素通过哈希函数得到同一个存储地址,那么该如何解决呢?哈希冲突的解决方案有很多种,而HashMap采用了链地址法,就是数组+链表的方式,所有通过哈希函数得到同一地址的元素通过链表加在后面即可。

单向链表和双向链表

  • 单链表只有一个指向下一结点的指针,也就是只能next
  • 双链表除了有一个指向下一结点的指针外,还有一个指向前一结点的指针,可以通过prev()快速找到前一结点,顾名思义,单链表只能单向读取,采用二分法查询。多一个指针需要的内存空间更多,查找速度更快。

3.Hashset和Hashmap的底层数据结构

Hashmap

  • HashMap是一个存储键值对的集合,允许存储null键和null值,线程不安全。HashTable不允许存储null值,线程安全,效率很差。

  • HashMap的主干是一个Node数组。Node是HashMap的基本组成单元,每一个Node包含一个key-value键值对。

4.HashTable和HashMap的区别

  • 两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全

Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,
在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理)

  • HashMap可以使用null作为key,而Hashtable则不允许null作为key

虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事,HashMap以null作为key时,总是存储在table数组的第一个节点上

  • HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类

  • HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75
    HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1

  • 两者计算hash的方法不同

Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸

	static int hash(int h) {
           
		// This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);        
        return h ^ (h >>> 7) ^ (h >>> 4);
    } 
    static int indexFor(int h, int length) {
   
            return h & (length-1);
    }
  • HashMap和Hashtable的底层实现都是数组+链表结构实现

5.HashSet和HashMap的区别

HashSet

实现了Set接口,它不允许集合中有重复的值,当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。

public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。

HashMap

实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。

Map m = Collections.synchronizeMap(hashMap);

public Object put(Object Key,Object value)方法用来将元素添加到map中。

HashMap的工作原理

HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象

当两个对象的hashcode相同会发生什么

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中

如果两个键的hashcode相同,你如何获取值对象

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置。找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象

如果HashMap的大小超过了负

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值