武汉小米面试八股文

少于 1 分钟阅读

武汉小米面试题

Object await。notify。区别,直接可以用await吗?

wait() - 方法wait()的作用是使当前执行代码的线程进行等待,它是Object类的方法,该方法用来将当前线程置入预执行队列中,并且在wait所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。

notify() - 同wait方法一样,也需要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。 waitnotify调用时,如果没有持有适当的锁,将会抛出IllegalMonitorStateException的异常。它是一个RuntimeException的子类。

hashcodeequals 区别,有什么作用?

  • equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
  • hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
  • 一致性,即:当两个对象 equals 比较为 true,那么 hashcode 值应当相等,反之亦然,因为当两个对象hashcode 值相等,但是 equals 比较为 false,那么在 HashMap 中会产生链表,影响查询性能。
  • 成对重写,即重写 equals 就应当重写 hashcode。实际上这只是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。一般一个类的对象如果会存储在HashTableHashSet,HashMap等散列存储结构中,那么重写equals后最好也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据),因为map结构中的key可以是对象,两个对象的值相同但地址不同,则取出value会出现问题。而如果确定不会存储在这些散列结构中,则可以不重写hashCode

线程池运行原理

首先线程池有几个核心的参数概念:

  • 最大线程数maximumPoolSize
  • 核心线程数corePoolSize
  • 活跃时间keepAliveTime
  • 阻塞队列workQueue
  • 拒绝策略RejectedExecutionHandler

当提交一个新任务到线程池时,具体的执行流程如下:

  1. 当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务
  2. 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队
  3. 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁
  4. 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理.

多线程同步的几种方式

参考: https://blog.csdn.net/yoonerloop/article/details/81154596

  1. 通过Object的wait和notify
  2. 通过Condition的awiat和signal
  3. 通过一个阻塞队列
  4. 通过两个阻塞队列
  5. 通过SynchronousQueue
  6. 通过线程池的Callback回调
  7. 通过同步辅助类CountDownLatch
  8. 通过同步辅助类CyclicBarrier

maven的生命周期

Maven的生命周期就是对所有的构建过程进行抽象和统一。包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有的构建步骤。 Maven的生命周期是抽象的,即生命周期不做任何实际的工作,实际任务由插件完成,类似于设计模式中的模板方法。 clean、validate、complie、test、package、verify、install、site、deploy 主要把这个流程说清楚我觉得也挺好的了

开闭原则解锁一下

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。

单一指责解释一下

单一性原则一般指设计接口时,接口的功能单一不可分。目的还是提高接口的通用性,做到实现一个接口,其方法尽量都可以用的到。 单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。 在实际应用中,类的设计尽量做到只有一个原因引起变化,接口一定要做到单一职责原则,方法一定要做到单一职责原则,即一个方法只实现一个功能。 模板设计模式说一下 一个抽象类中,有一个主方法,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。 应用场景,抽象的上传文件接口,定义一个抽象的获取图片接口的地址,根据子类具体的实现获取不同上传地址。

hashmap 怎么根据key取得值,说一下流程

HashMap主要由数组和链表组成,他不是线程安全的。核心的点就是put插入数据的过程,get查询数据以及扩容的方式。JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入红黑树对性能有提升。

put插入数据流程

往map插入元素的时候首先通过对key hash然后与数组长度-1进行与运算((n-1)&hash),都是2的次幂所以等同于取模,但是位运算的效率更高。找到数组中的位置之后,如果数组中没有元素直接存入,反之则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部,如果链表的长度超过8,则会转换成红黑树,最后判断数组长度是否超过默认的长度*负载因子也就是12,超过则进行扩容。

get查询数据

查询数据相对来说就比较简单了,首先计算出hash值,然后去数组查询,是红黑树就去红黑树查,链表就遍历链表查询就可以了。

resize扩容过程

扩容的过程就是对key重新计算hash,然后把数据拷贝到新的数组。

CAS 聊一下

CAS叫做CompareAndSwap,比较并交换,主要是通过处理器的指令来保证操作的原子性,它包含三个操作数:

变量内存地址,V表示 旧的预期值,A表示 准备设置的新值,B表示 当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作。 那么CAS有什么缺点吗?

CAS的缺点主要有3点:

ABA问题:ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后又被改回了A,这个CAS更新的漏洞就叫做ABA。只是ABA的问题大部分场景下都不影响并发的最终效果。 Java中有AtomicStampedReference来解决这个问题,他加入了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。 循环时间长开销大:自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。 只能保证一个共享变量的原子操作:只对一个共享变量操作可以保证原子性,但是多个则不行,多个可以通过AtomicReference来处理或者使用锁synchronized实现。

git 和 svn 什么区别?

GIT是分布式的,SVN不是:

这是GIT和其它非分布式的版本控制系统,例如SVN,CVS等,最核心的区别。如果你能理解这个概念,那么你就已经上手一半了。需要做一点声明,GIT并不是目前第一个或唯一的分布式版本控制系统。还有一些系统,例如Bitkeeper, Mercurial等,也是运行在分布式模式上的。但GIT在这方面做的更好,而且有更多强大的功能特征。 GIT跟SVN一样有自己的集中式版本库或服务器。但,GIT更倾向于被使用于分布式模式,也就是每个开发人员从中心版本库/服务器上chect out代码后会在自己的机器上克隆一个自己的版本库。可以这样说,如果你被困在一个不能连接网络的地方时,就像在飞机上,地下室,电梯里等,你仍然能够提交文件,查看历史版本记录,创建项目分支,等。对一些人来说,这好像没多大用处,但当你突然遇到没有网络的环境时,这个将解决你的大麻烦。 GIT把内容按元数据方式存储,而SVN是按文件:

所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。如果你把.git目录的体积大小跟.svn比较,你会发现它们差距很大。因为,.git目录是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。

GIT分支和SVN的分支不同:

分支在SVN中一点不特别,就是版本库中的另外的一个目录。如果你想知道是否合并了一个分支,你需要手工运行像这样的命令svn propget svn:mergeinfo,来确认代码是否被合并。感谢Ben同学指出这个特征。所以,经常会发生有些分支被遗漏的情况。然而,处理GIT的分支却是相当的简单和有趣。你可以从同一个工作目录下快速的在几个分支间切换。你很容易发现未被合并的分支,你能简单而快捷的合并这些文件。

Http协议

Http特点

HTTP是无状态的。同一个客户第二次访问同一个服务器上的页面时。服务器的响应与第一次被访问时的相同,因为服务器并不记得曾经访问过的这个客户,也不记得为该客户曾经服务过多少次。这个特性简化了服务器的设计,使服务器更容易支持大量并发的HTTP请求。在实际应用中,通常使用Cookie加数据库的方式跟踪用户的活动。Cookie是存储在用户主机中的文本文件,里面有一串“识别码”,用于Web服务识别用户。Web服务器根据Cookie就能从数据库中查询到该用户的活动记录,进而执行一些个性化的工作,如根据用户之前浏览过的商品向其推荐新产品等。 HTTP采用TCP作为运输层协议,保证了数据的可靠传输。HTTP不必考虑数据在传输过程中被丢弃后又怎样被重传。但是HTTP协议本身是无连接的,虽然HTTP使用了TCP连接,但通信的双方在交换HTTP报文之前不需要先建立HTTP连接。 HTTP既可以使用非持久连接也可以使用持久连接。 对于非持久连接,每一个网页元素对象的传输都需要单独建立一个TCP连接。如图所示。第三次握手的报文段中捎带了客户对万维网文档的请求,请求一个万维网文档所需的时间是该文档的传输时间(与文档大小成正比)加上两倍往返时间RTT。 持久连接是万维网服务器在发送响应后仍然保持这条连接,使同一个客户和服务器可以继续在这条连接上传送后续的HTTP请求和响应报文。持久连接又分为非流水线和流水线两种方式。对于非流水线方式,客户在收到前一个响应后才能发出下一个请求。HTTP/1.1的默认模式是使用流水线的持久连接。这种情况下,客户每遇到一个对象引用就立即发出一个请求,因而客户可以一个接一个地连续发出对各个引用对象的请求。如果所有的请求和响应都是连续发送的,那么所有引用到的对象共计经历一个RTT延迟,而不是像非流水线版本那样,每个引用都必须有一个RTT延迟。 HTTP的报文结构

HTTP有两类报文:请求报文从客户向服务器发送的请求报文;响应报文从服务器到客户的应答。 HTTP两种报文都是由三个部分组成,即开始行、首部行、实体主体,从上图可以看出主要是开始行不同。 开始行用于区别是请求报文还是响应报文。在请求报文中的开始行叫请求行,响应报文的开始行叫状态行。开始行的三个字段之间都以空格分割开,最后回车换行。请求报文的“请求行”有三个内容:方法、请求资源的URL以及HTTP版本。常用的方法如下: GET:请求读取由URL所标志的信息; HEAD:请求读取URL所标志的信息的首部; POST:给服务器添加信息(如注释); CONNECT:用于代理服务器。 首部行用于说明浏览器、服务器或报文主体的一些信息。在每个首部行都有首部字段名和它的值,每一行在结束的地方都要有回车和换行。在整个首部结束时,还有一空行将首部行和实体主体分开。实体主体在请求报文中一般不使用这个字段,响应报文也可能没有

留下评论