Java面试题汇总

一、HashMap

1.存储结构的理解

  • Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
  • Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所的entry

2.常用方法

  • 添加:put(Object key,Object value)
  • 删除:remove(Object key)
  • 修改:put(Object key,Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet() / values() / entrySet()

3.内存结构说明

1>HashMap在JDK 7.0中的实现原理

HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
…可能已经执行过多次put…
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:

如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。

2>HashMap在JDK 8.0中相较于JDK 7.0在底层方面实现的不同

  1. new HashMap():底层没创建一个长度为16的数组
  2. dk 8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

3>HashMap底层典型属性的说明:

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
  • threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

4.底层有数组,为啥需要链表,链表又是怎么样子的呢?

数组⻓度是有限的,在有限的⻓度⾥⾯我们使⽤哈希,哈希本身就存在概率性,使用hash有⼀定的概率会⼀样,那就形成了链表。

5.头插法和尾插法,为什么使用尾插法

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更⼤⼀点,提升查找的效率。
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引⽤关系。
Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引⽤关系。
改用尾插法的原因:(因为是新数据指向旧数据,所以扩容时有可能导致新数据先插入到链表,旧数据再插到新数据前,使旧数据指向新数据)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.扩容方法

  1. 创建⼀个新的Entry空数组,⻓度是原数组的2倍。
  2. 遍历原Entry数组,把所有的Entry重新Hash到新数组。
    为什么要hash?是因为扩容之后hash的规则也随之改变

二、其他面试题汇总

1. HashMap和HashTable区别

  1. 线程安全性不同
    HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronize的,在多线程并发的情况下,可以直接使用HashTabl,但是使用HashMap时必须自己增加同步处理。
  2. 是否提供contains方法
    HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。
  3. key和value是否允许null值
    Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
  4. 数组初始化和扩容机制
    HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
    Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

2. TreeSet和HashSet区别

HashSet是采用hash表来实现的。其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为O(1)的方法。
TreeSet是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法。它还提供了一些方法来处理排序的set,如first(),last(),headSet(),tailSet()等等。

3. String buffer和String build区别

1、StringBuffer与StringBuilder中的方法和功能完全是等价的。
2、只是StringBuffer中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的。
3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低

4. Final、Finally、Finalize

final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。
finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。

5. ==和Equals区别

== : 如果比较的是基本数据类型,那么比较的是变量的值
如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)
equals:如果没重写equals方法比较的是两个对象的地址值。
如果重写了equals方法后我们往往比较的是对象中的属性的内容

6.ArrayList和LinkedList的区别?

ArrayList 底层是数组,查询效率高,增删效率低,默认初始长度为10,数组扩容新=1.5*旧,然后把原数组的数据,原封不动的复制到新数组中(用的是 Arrays.copyOf() 方法 ),LinkedList 增删快,底层为双向链表,遍历ArrayList要比LinkedList快,遍历的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。

7.Java的IO模型?BIO和NIO的区别?

BIO:面向流,是阻塞IO,在读取数据时,用户线程发出IO请求后,内核会检查数据是否就绪,没有就绪的话就继续等待数据就绪,用户线程处于阻塞状态并交出CPU,就绪后,内核将数据拷贝到用户线程,接触阻塞状态。

NIO:同步非阻塞,用户线程发起读请求之后,不需要等待,立刻会得到一个结果,结果是error时,说明数据还没准备好,可以再次发送读请求,一旦数据准备好之后,并且再次收到了读请求,就将数据拷贝到用户线程。

AIO:异步非阻塞,用户发起一个IO之后立即返回,也不用自己去轮询,当IO完成之后,内核会给用户线程发信号,告知IO已完成

8.讲一下java中的集合

  • Java中的集合分为value、key-value(Conllection Map)两种
  • 存储值又分为List和Set
  • List是有序的index,可以重复的
  • Set是无序的,不可以重复的。根据equals和hashcode判断
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值