java面试题

2021/6/10 12:20:56

本文主要是介绍java面试题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

HashMap:

HashMap是一种以键值对形式存储数据的结构 它允许一个为null的键作为key,key的值不能重复,如果有重复的key则覆盖 jdk8后采用数组+链表+红黑树的数据结构。当我们想map里面put一个数据时 先与key的Key.hashCode的高16位做异或运算(因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16为做 异或运算使得在做&运算确定数组的插入位置时,此时的低位实际是高位与低位的结合,增加 了随机性,减少了哈希碰撞的次数。)然后判断散列表是否为空 如果散列表为空时,调用resize()初始化散列表 (创建了HashMap但是散列表并没有初始化,只有在第一次put时才会初始化,这样避免创建了 一个map但是没有使用,散列表比较消耗内存,这样减少内存的损失)然后根据路由算法找到要存放在散列表中的位置 然后判断是否产生碰撞(是否为空)如果没有 碰撞(为空)说明这个位置里没有数据 就把他直接放在当前位置里就可以了 

如果不为空,说明发生了碰撞,这个时候有3中情况:

1:若key地址相同或者equals后内容相同,则替换旧值

2:如果是红黑树结构,就调用树的插入方法

3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断 

链表个数是否到达变成红黑树的阙值8;但是在树化之前会判断哈希表里的元素总个数是否大 于64,如果没有就继续扩容不会树化,也可以遍历到有节点与插入元素的哈希值和内容相同, 进行覆盖。最后判断散列表的大小是否大于了阈值8, 大于就resize进行扩容 ,扩容成两倍(小于最大值的情况下),之后在进行将元素重新进行与运算复制到新的散列表中扩容的大小为原来的2倍扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元 素挨个重新hash分配到新结构中去。

HashCode:
HashCode的作用是获取哈希码,也成为散列码,它实际上是返回一个int整数,这个哈希码的作用是确定该对象在哈希表中的位置,hashCode()定义在JDK的Object.Java中,java的任何类都包含有hashCode()函数。

延伸:为什么Map不能存储基本类型的数据:

因为Map存储是基于一个Obejct作为一个key的,它是根据key的hashCode()方法来的 到hash值再和高16位做异或运算,而基本类型是没有hashCode方法的,所以不能用基本类型存储。


接口和抽象类的区别:

抽象类可以个存在普通成员方法,而接口中只能存在public abstaract方法。
抽象类的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
抽象类只能继承一个,接口可以实现多个。

接口的设计目的,是对类的行为进行约束,也就是提供一种机制,可以强制要求不同的类具有
相同的行为。而抽象类的设计目的,只是为了代码复用,当不同的类具有相同的行为,可以让
这些类都派生于一个抽象类。

List和Set的区别:

List:有序,底层是一个Object数组,按对象进入顺序保存对象,可重复,允许多个Null元素对象。
Set:无序,因为底层是用HashMap的key作为存储容器所以不可重复,HashMap只能允许一个
Null作为key,所以只能有一个Null元素对象。

ArrAyList和LindedList的区别:

ArrayList:基于动态数组,连续内存存储,适合下标访问,扩容机制:因为数组长度固定,超出长度存储数据时需要新建数组,然后将原先的数据拷贝到新的数组。如果不是尾部插入数据 还会设计到元素的移动(往后复制一份,插入新元素),使用为插法并指定初始容量可以极大提升性能、甚至超过LinkedList。 
LinkedList:基于链表,可以存储在分散的内存中,适合做数据的插入删除操作,不适合查询 ,因为需要逐一遍历。

HashMap和HashTable的区别:
HashMap方法没有sunchronized修饰,线程不安全,HashTable线程安全(sunchronized修饰)
HashMap允许key和value为null,而HashTable不允许;如何实现一个IOC容器:1、配置文件配置包扫描路径

2、递归包扫描获取.class文件
3、反射,确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入
配置文件中指定需要扫描的包路径
定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解。

从配置文件中获取需要扫描的包路径,获取当前路径下的文件信息及文件夹信息,我们将当前 路径写所有以.class文件结尾的文件添加到一个Set集合中进行存储
遍历这个Set集合,获取在类上有指定注解的类,将其交个IOC容器定义一个安全的Map用来存 储这个对像
遍历这个IOC容器,获取到每一个类的实例,判断里面是否有依赖其他的类的示例然后进行递 注入

java中的异常体系:

java中所有异常都来自顶级父类ThrowableThrowable下有两个子类Exception和Error
Error是程序无法处理的错误,一旦出现这个错误,程序将被迫停止运行。
Exception不会导致程序停止,它又分为两个部分:RunTimeException运行时异常和
CheckedException检查异常。
RunTimeException常常发生在程序运行期间,会导致程序当前线程执行失败,
EceckedException常常发生在程序编译过程中,会导致程序编译不通过。

Lock锁与synchronized的对比

lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自
动释放

lock只有代码块锁,synchronized有代码块锁和方法锁

使用lock锁,JVM将花费较少的时间来调度线程(性能更好)

线程的生命周期,线程有几种状态:

线程通常有五种状态,创建、就绪、运行、阻塞和死亡状态。
阻塞状态又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该方法会释放占用的所有资源,JVM会把该线程放
入"等待池"中,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或用
notifyAll方法才能被唤醒,wait是Object类的方法。
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把
该线程放入"锁池"中。
(3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程设
置为阻塞状态,当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态,sleep时Thread类的方法。

1.新建状态(New):新创建了一个线程对象。

2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行,知道线程进入就绪状态,才有机会转到运行状态。

5.死亡状态(Dead):线程执行完毕了或者异常退出run方法,该线程结束生命周期。

sleep()、wait()、join()、yield()的区别

1.锁池 

所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程的到后会进入就绪队列进行等待cpu资源分配。

2.等待池

当调用wait()方法后,线程会放到等待池中,等待池的线程是不会竞争同步锁,只有调用的notify()或notifyAll()后等待池的线程才会开始竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。

1、sleep是Thread类的静态本地方法,wait则是Object类的本地方法。

2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

3、sleep方法不抵赖于synchronized,当时wait需要依赖于synchornized关键字。

4、sleep不需要被唤醒,(休眠之后退出阻塞),但是wait需要(现在也可以指定时间,但是不指定时间需要别人中断)

5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于线程之间的通讯。

6、sleep会让让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。

yield()执行后线程进入就绪状态,马上释放cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次线程调度还让这个线程获取到执行权继续执行。

join()执行线程后进入阻塞状态。

为什么使用线程池

  • 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。

  • 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。

  • 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。

线程池的参数

corePoolSize: 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程

maxinumPoolSize: 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程 数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数

keepAliveTime : 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会 消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过 setKeepAliveTime 来设置空闲时间

workQueue:用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程

ThreadFactory: 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建 工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂

Handler: 任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这 时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这是也就拒绝


 



这篇关于java面试题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程