java面试总结

2019-08-09  本文已影响0人  路得自己走

final
匿名内部类
内部类并不是直接调用方法传递的参数,而是利用构造器对外部类方法形式参数进行复制,而内部类自己方法调用的是自身的字段,不是外部类方法的形式参数。这样做的原因在于匿名方法内部类可以作为返回对象被别人持有,但是这个方法已经结束,方法的变量生命周期也结束了,如果匿名内部类还使用到方法里面的变量的话,会有问题,java采用这种复制的方式!! 但这样的话在内部类改变自身字段不会影响外部类方法形式参数的值,这显然不符合逻辑,他们本来是同一个变量。为了保持参数的一致性,就规定使用final来避免形参的不改变。

链接:https://www.jianshu.com/p/26769546fee0

这要从闭包说起,匿名内部类和外部方法形成了一个闭包,因此,匿名内部类能够访问外部方法的变量,看起来是一种“天经地义”的事情,Java语言当然也需要实现这种特性,但是这里遇到了一个问题。
匿名内部类的生命周期可能比外部的类要长,因此访问外部局部变量有可能是访问不到的。
那怎么办呢?Java语言为了实现这种特性, 只好将外部的局部变量偷偷的赋值了一份给匿名内部类。那这样匿名内部类就可以肆无忌惮的访问外部局部变量了。
问题又来了,这种通过赋值的形式有一个缺陷,匿名内部类不可以修改“原来的局部变量”,因为是一份“复制品”,修改复制品对原变量没什么影响啊。
那怎么办? Java语言干脆强制要求被匿名内部类访问的外部局部变量必须是final的,什么意思呢?就是“一刀切”,不让修改了。

链接:https://www.jianshu.com/p/609ca1c584ac

Q:sleep()和wait()的区别?
sleep()来自Thread类;wait()来自Object类
sleep()用于线程控制自身流程;而wait()用于线程间通信,配合notify()/notifyAll()在同步代码块或同步方法里使用
sleep()的线程不会释放对象锁;wait()会释放对象锁进入等待状态,使得其他线程能使用同步代码块或同步方法

Q: 四大引用区别和引用场景:Java中的四种引用介绍和使用场景

强引用(Strong Reference):一个对象如果具有强引用,那么垃圾回收器绝不会回收它,即使当内存不足时,VM宁愿抛出内存不足的异常,也不会去回收这些对象。

**软引用 (Soft Reference): **如果一个对象只具有软引用,则内存空间足够时,垃圾回收器就不会去回收它;如果内存空间不足时,就会回收这些对象的内存。

弱引用(Weak Reference):弱引用和软引用的区别在于,只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器线程扫描它管辖的内存区域的过程中,一旦发现对象只具有弱引用,不管当前内存空间是否足够,都会回收他的内存。

它比软引用的生命周期更短,和软引用相似,它同样可以和引用队列关联,如果被垃圾回收了,就会加入到这个关联队列中。

虚引用(Phantom Reference)几种引用都不同,虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用的话,那么它就和没有任何的引用一样,在任何时候都可能被垃圾回收器回收。

Q:数组和链表的区别数组:数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。哈希表:那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。哈希表是由数组+链表组成的、HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

hashMap和HashSet都是collection框架的一部分,它们让我们能够使用对象的集合。collection框架有自己的接口和实现,主要分为Set接口,List接口和Queue接口。它们有各自的特点,Set的集合里不允许对象有重复的值,List允许有重复,它对集合中的对象进行索引,Queue的工作原理是FCFS算法(First Come, First Serve)。

首先让我们来看看什么是HashMap和HashSet,然后再来比较它们之间的分别。

什么是HashSet

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

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

什么是HashMap

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

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

你可以阅读这篇文章看看HashMap的工作原理,以及这篇文章看看HashMap和HashTable的区别。

Q:Java集合框架中有哪些类?都有什么特点
可将Java集合框架大致可分为Set、List、Queue 和Map四种体系
Set:代表无序、不可重复的集合,常见的类如HashSet、TreeSet
List:代表有序、可重复的集合,常见的类如动态数组ArrayList、双向链表LinkedList、可变数组Vector
Map:无序 ,不可重复的集合 ,代表具有映射关系的集合,常见的类如HashMap、LinkedHashMap、TreeMap
Queue:代表一种队列集合
有序,无序:比如:‘zs’,‘zs’ 这两个 是不同的人是同一个名字,没顺序你是无法判断是谁的list他是有顺序的,所以你只要知道他们的顺序就完全可以判断是哪个人了,
可重复:key和value都可以重复, 不可重复:key不可以重复,value可以重复

从数据结构角度看集合的区别有如下:

动态数组:ArrayList 内部是动态数组,HashMap 内部的链表数组也是动态扩展的,ArrayDeque 和 PriorityQueue 内部也都是动态扩展的数组。

链表:LinkedList 是用双向链表实现的,HashMap 中映射到同一个链表数组的键值对是通过单向链表链接起来的,LinkedHashMap 中每个元素还加入到了一个双向链表中以维护插入或访问顺序。

哈希表:HashMap 是用哈希表实现的,HashSet, LinkedHashSet 和 LinkedHashMap 基于 HashMap,内部当然也是哈希表。

排序二叉树:TreeMap 是用红黑树(基于排序二叉树)实现的,TreeSet 内部使用 TreeMap,当然也是红黑树,红黑树能保持元素的顺序且综合性能很高。

堆:PriorityQueue 是用堆实现的,堆逻辑上是树,物理上是动态数组,堆可以高效地解决一些其他数据结构难以解决的问题。

循环数组:ArrayDeque 是用循环数组实现的,通过对头尾变量的维护,实现了高效的队列操作。

位向量:EnumSet 是用位向量实现的,对于只有两种状态且需要进行集合运算的数据使用位向量进行表示、位运算进行处理,精简且高效。

41.简单说说 HashMap 的底层原理?

答案:

当我们往 HashMap 中 put 元素时,先根据 key 的 hash 值得到这个元素在数组中的位置(即下标),然后把这个元素放到对应的位置中,如果这个元素所在的位子上已经存放有其他元素就在同一个位子上的元素以链表的形式存放,新加入的放在链头,从 HashMap 中 get 元素时先计算 key 的 hashcode,找到数组中对应位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素,所以 HashMap 的数据结构是数组和链表的结合。

解析:

HashMap 底层是基于哈希表的 Map 接口的非同步实现,实际是一个链表散列数据结构(即数组和链表的结合体)。
首先由于数组存储区间是连续的,占用内存严重,故空间复杂度大,但二分查找时间复杂度小(O(1)),所以寻址容易,插入和删除困难。而链表存储区间离散,占用内存比较宽松,故空间复杂度小,但时间复杂度大(O(N)),所以寻址困难,插入和删除容易。
所以就产生了一种新的数据结构------哈希表,哈希表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便,哈希表有多种不同的实现方法,HashMap 采用的是链表的数组实现方式,具体如下:

首先 HashMap 里面实现了一个静态内部类 Entry(key、value、next),HashMap 的基础就是一个 Entry[] 线性数组,Map 的内容都保存在 Entry[] 里面,而 HashMap 用的线性数组却是随机存储的原因如下:

// 存储时
int hash = key.hashCode(); //每个 key 的 hash 是一个固定的 int 值
int index = hash % Entry[].length;
Entry[index] = value;

// 取值时
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
当我们通过 put 向 HashMap 添加多个元素时会遇到两个 key 通过hash % Entry[].length计算得到相同 index 的情况,这时具有相同 index 的元素就会被放在线性数组 index 位置,然后其 next 属性指向上个同 index 的 Entry 元素形成链表结构(譬如第一个键值对 A 进来,通过计算其 key 的 hash 得到的 index = 0,记做 Entry[0] = A,接着第二个键值对 B 进来,通过计算其 index 也等于 0,这时候 B.next = A, Entry[0] = B,如果又进来 C 且 index 也等于 0 则 C.next = B, Entry[0] = C)。
当我们通过 get 从 HashMap 获取元素时首先会定位到数组元素,接着再遍历该元素处的链表获取真实元素。
当 key 为 null 时 HashMap 特殊处理总是放在 Entry[] 数组的第一个元素。
HashMap 使用 Key 对象的 hashCode() 和 equals() 方法去决定 key-value 对的索引,当我们试着从 HashMap 中获取值的时候,这些方法也会被用到,所以 equals() 和 hashCode() 的实现应该遵循以下规则:
如果o1.equals(o2)则o1.hashCode() == o2.hashCode()必须为 true,或者如果o1.hashCode() == o2.hashCode()则不意味着o1.equals(o2)会为true。

关于 HashMap 的 hash 函数算法巧妙之处可以参见本文链接:http://pengranxiang.iteye.com/blog/543893

Synchronized与Lock的区别

image.png

synchronized锁源码分析

对于synchronized来说,其实没有什么所谓的源码去分析,synchronized是Java中的一个关键字,他的实现时基于jvm指令去实现的下面我们写一个简单的synchronized示例

image

synchronized与Lock的区别

两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

上一篇下一篇

猜你喜欢

热点阅读