面试准备17-模拟面试
被问过的面试题整理,以及自己想到的模拟题目
介绍项目
介绍你的一个项目,在纸上画出架构图,并重点说明以下几点:(58到家二面)
()你在项目中做了什么?
负责xx模块的开发,比如多个监控模块、预警模块,到后期参与一定的开发管理工作
()为什么这么设计?
()架构设计中对高并发、高可用的考虑?(58同城业务部二面)
负载均衡器做了双机备份,后台服务做了按业务垂直拆分,每个服务也都有水平扩展的集群,关系库做了partition分区,后续考虑做分库分表,关系库前面有一层redis缓存。
()项目中遇到什么难题,怎么解决的?(多次被问到)
参见MQ使用过程中遇到过什么问题
()你在项目中的工作亮点?(多次被问到)
xx模块的定时计算总是出问题,经常挂掉,对此块功能做了服务的垂直拆分,将不同业务模块分离到不同服务器,并且重构代码改为dubbo微服务架构,到目前为止运行良好。
()之前为什么宕机?分析过吗?(多次被问到)
每条xx的计算,还需要去调其他服务,或者去库中进行条件查询,由于IO性能瓶颈导致计算很慢,积压了很多xx实例对象无法被gc回收,最终导致内存溢出而宕机。
()你说的这个好像是垂直拆分起的作用更大吧?dubbo架构起作用了吗?(多次被问到)
确实最主要的性能提升是垂直拆分带来的。但我们以此为契机,对架构进行了升级,为之后的弹性扩展打下基础。
()你们业务中的定时任务、数据分析,有没考虑过更好的框架?(58同城业务部一面)
用quartz做定时任务,尝试过用jboss的drools规则引擎做规则计算。
()让你做软件选型,你会考虑哪些因素? (58同城业务部二面)
高性能,开发容易度(高性能的组件可能上手难度也高),上线后的扩展性
STAR原则
使用 STAR 原则来介绍项目
Situation 情况
Task 任务
Action 行动
Result 结果
如果过多的提到 “我们”,“通常来说”,“如果。。。我会”,会让面试官认为不是你自己做的,或者是你在做假设,应该说“我是怎样怎样做的”
人才测评答题
1、“我从来没说过谎” 这个肯定要选“非常不符合”,因为每个人都说过谎,这个题目是为了测试候选人是否过分包装自己。
2、前后作答一致性。一定要注意,一些题目前后会重复出现,或者以不同的问法出现,一定要注意自己的作答要保持前后一致。否则会被认为是特意作假回答。
3、选项分布。不能全都选C,也就是不能全都选“基本符合”,最后统计结果有选项分布统计,如果都选“基本符合”,会被认为是为节省时间故意随意作答。
Java基础和其他
()java中int的范围?(百度一面)
java中int是有符号的,占4个字节32位,最左边是1位符号位,表示范围是-(2^31)到(2^31)-1
()java8和java9的新特性有哪些?(滴滴二面)
java8:lambda表达式,函数式接口,流,接口的默认方法和静态方法,对HashMap和ConcurrentHashMap的改进
java9:模块化
()为什么流可以使更方便的处理集合?(滴滴二面)
涉及到lambda表达式和函数式接口
异常
()Error和Exception的区别?(哗啦啦一面)
1、Error都是不可被捕获不可处理的,Exception分为受检异常和不受检异常,受检异常可被捕获处理。
2、Error一般是系统级、虚拟机级错误,Exception一般是代码错误。
()举个非检查型异常的例子?(哗啦啦一面)
NullPointerException空指针异常,IndexOutOfBoundsException下标越界异常。
()举个Error的例子?(哗啦啦一面)
OutOfMemoryError内存溢出错误,StackOverflowError栈溢出错误
()Throwable/Exception/Error之间是什么关系?(高德一面)
所有 Exception 和 Error 都是 Throwable 的子类,注意 Throwable 是个类不是接口。
Java 中的异常分为受检异常和非受检异常:
受检异常 必须在代码中进行 catch 或向上 throw ,常见的比如打开File,IO操作,Socket操作,建立Connection, Session等相关的异常
非受检异常包括 不需要在代码中进行手动处理,包括 运行时异常 和 Error ,常见的运行时异常比如 NPE ,数组下标越界,零除,常见的 Error 比如 OOM, StackOverflowError
()ClassNotFoundException 和 NoClassDefFoundError 有什么区别?分别在什么情况下产生?(方正证券)
正如它们的名字所说明的:NoClassDefFoundError
是一个错误 Error ,而 ClassNOtFoundException
是一个异常 Exception,在Java中错误和异常是有区别的,我们可以从异常中恢复程序但却不应该尝试从错误中恢复程序。
在根据类名加载类的 class 文件时如果在 classpath 下找不到会抛出 ClassNOtFoundException
某个类在编译时没问题,但在运行时初始化失败会抛出 NoClassDefFoundError
equals()和hashcode()
()重写equals方法需要注意什么?(老虎证券)
equals方法需要满足自反性、对称性、传递性。
重写equals方法的一般流程:1、比较引用地址。2、比较类型。3、转型后比较每个数据域。
重写equals方法后必须重写hashcode方法。
equals相等hashcode必须相等,equals不等hashcode最好不等(在使用HashMap时无碰撞,效率高)
hashcode相等equals可等可不等,hashcode不等equals必须不等
()hashcode()和equals()方法的关系?(哗啦啦一面)
重写equals方法后必须重写hashcode方法。
equals相等hashcode必须相等,equals不等hashcode最好不等(在使用HashMap时无碰撞,效率高)
hashcode相等equals可等可不等,hashcode不等equals必须不等
()为什么equals不等时最好hashcode也不等?(哗啦啦一面)
在使用HashMap时无碰撞,效率高。往HashMap中put时,先根据hashcode决定散列到哪个桶,若桶中有数据(即发生冲突)再根据equals对比key。如果直接hashcode就不等的话就可以避免冲突了。
重载/重写/动态绑定
()方法重载和重写分别是在什么阶段确定的?(哗啦啦一面)
方法重载是在编译阶段确定的,编译器根据参数列表的不同(个数、类型、顺序)来决定具体调用哪个方法。
父类引用指向子类对象,调用的是子类中重写(覆盖)的方法,这是在运行时确定的。
()动态绑定是如何实现的?(哗啦啦一面)
通过方法区中的方法表来实现。类加载完成后,会在方法区生成方法表,方法表中保存类的所有方法的内存地址(指向方法字节码的指针)。
父类引用指向子类对象,在调用时将子类对象实例压入操作数栈,根据实例对象所属的类型去查找它的方法表,所以最终调用的是子类中重写的方法,实现了动态绑定。
()下面代码运行结果是什么?里面涉及到java的哪些特性?(哗啦啦一面)
public class PolyTest {
public static void main(String[] args){
Father father = new Son();
testFather(father);
testSon((Son)father);
}
public static void testFather(Father f){
System.out.println("PolyTest.testFather()");
f.whoami();
}
public static void testSon(Son s){
System.out.println("PolyTest.testSon()");
s.whoami();
}
}
class Father{
public void whoami(){ System.out.println("PolyTest.Father.whoami()"); }
}
class Son extends Father{
public void whoami(){ System.out.println("PolyTest.Son.whoami()"); }
}
结果是:
“PolyTest.testFather()”
“PolyTest.Son.whoami()”
“PolyTest.testSon()”
“PolyTest.Son.whoami()”
涉及的java特性:
1、两个testFather()方法重载
2、Son类覆盖(重写)了父类whoami()方法
3、执行testFather(Father f)方法时涉及到运行时动态绑定,父类引用指向子类对象
NIO
()nio中包括哪些类?(58到家二面)
Channel通道,Buffer缓冲区,Selector选择器
数据从通道读取到缓冲区中,或者从缓冲区写入到通道中,Selector能够检测多个注册的通道上是否有事件发生。
()nio中的选择器是怎么工作的?(58到家二面)
selector是一种多路复用io模型,可同时监控多个channel上的事件。将Channel注册到selector上,指定感兴趣的事件,调用选择器的select()方法判断事件是否发生。
Java集合框架
()你用过哪些集合类?在纸上写出来,并画出他们之间的继承关系(百度一面,滴滴一面)
要注意Map和Collection是并列的
Collection:List(ArrayList,LinkedList),Set(HashSet,TreSet), Map:HashMap,LinkedHashMap,TreeMap
HashMap
内部实现
()HashMap实现原理?(每次必问)
HashMap内部采用数组+链表来实现,有个 Node<K,V>[] table
数组,数组中每个元素(也就是哈希中的桶)中存放冲突链表的首节点,冲突链表的每个结点包括下列元素:{hash值,key,value,下一结点指针next}
往HashMap中put时,先根据hashcode决定散列到哪个桶,若桶中有数据(即发生冲突)再根据equals对比key值,决定覆盖还是挂链表上。
()HashMap数组中存的是什么?(多次被问)
HashMap中有个 Node<K,V>[] table
数组,数组中每个元素(也就是哈希中的桶)中存放冲突链表的首节点,冲突链表的每个结点包括下列元素:{hash值,key,value,下一结点指针next}
()HashMap的put过程是怎样的?(哗啦啦一面)
往HashMap中put时,先根据hashcode()决定散列到哪个桶,若桶中有数据(即发生冲突)再遍历冲突链表,调用equals方法对比key值,决定覆盖还是挂链表上。
此外还涉及到 再哈希计算,元素个数大于table.length*loadfactor时resize为2倍长度,jdk8之后冲突链表长度大于8时改为红黑树。
()HashMap中为什么要进行rehash计算?(哗啦啦一面)
在jdk1.8中,将hashCode()的高16位与低16位异或来进行rehash计算,(h = k.hashCode()) ^ (h >>> 16),然后再与table长度n-1进行按位与来确定数组下标 (n - 1) & hash
这样是为了在table数组长度较短时,hashcode的高位也能参与数组下标的运算,从而减少碰撞的发生。
初始化与扩容
()HashMap如何扩容?(多次被问)
当元素个数大于 当前容量×loadfactor时,需要扩容。新申请一个2倍当前大小的table数组,遍历旧数组,重新计算每个key的数组下标,放到新数组中(当然要考虑碰撞),把旧数组空间释放掉(设为null)
()给一组key{1,2,…,10},创建一个HashMap,指定初始容量为5,hash函数是key左移1位(<<1),在纸上画出来是怎么存储的?(滴滴一面)
1、考虑初始容量必须是2的幂值,所以new的时候传入5但其实初始容量为8
2、考虑负载因子默认0.75,所以元素个数大于6的时候会进行resize扩容
3、考虑怎么扩容,默认每次扩容为2倍当前大小,容量变为2倍即16
key的hash值发生碰撞后挂到链表上
性能分析
()HashMap get的时间复杂度?什么情况下性能降为O(n)?(蚂蚁金服电话面试)
最好情况下O(1),最坏情况下,所有key散列到同一个桶中,性能降为O(n)
()java8对HashMap的优化?(58同城架构组一面,哗啦啦一面)
如果某个桶中的链表长度过大的话(默认是TREEIFY_THRESHOLD = 8),就会把这个链动态变成红黑二叉树,使查询最差复杂度由O(N)变成了O(logN)。
()LinkedHashMap的时间复杂度是怎样的?(58同城架构组三面)
碰撞少的情况下,put和get的时间复杂度都是O(1),按访问顺序调整也可以在O(1)时间内完成,因为定位到访问的元素后,将其从双向链表中摘下再插入到末尾的操作可在O(1)时间复杂度内完成。
有序
()HashMap是有序的吗?哪些集合类是有序的?为什么?(58同城架构组一面)
HashMap不是有序的。LinkedHashMap和TreeMap是有序的。
LinkedHashMap默认按插入有序,可构造时设置为按访问有序,内部有个双向链表,每个数组有个before和after指针,指向前、后节点。
TreeMap根据key来对key-value键值对进行排序,内部用红黑树实现按key有序。
()LinkedHashMap怎么实现有序的?(58同城架构组三面)
LinkedHashMap的构造方法LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder) 的第三个参数accessOrder默认为false按插入顺序,设为true则按访问顺序排序。
LinkedHashMap重写了HashMap中保存的元素Entry,增加了上一个元素的引用before和下一个元素的引用after,从而在哈希表的基础上又构成了双向循环链表。若指定按访问顺序排序的话,每次访问(get和put都算访问)时都会把刚访问的元素移到双向链表的尾部,所以循环双向链表的头部存放的是最久访问的节点或最先插入的节点,尾部为最近访问的或最近插入的节点。具体实现是在put和get中都会调用recordAccess方法(jdk1.8中是afterNodeAccess方法)来将刚访问的元素移到双向链表末尾。
线程安全
()HashMap是线程安全的吗?什么情况下出现不同步?(几乎每次必问)
不是
HashMap可能发生线程不同步的情况:
1、多个线程同时put一个HashMap,key的hashcode相同发生碰撞时,都往链表末尾挂,第一个线程挂上去的结点会被覆盖
2、多个线程put的key已存在进行value覆盖时,第一个线程的value会被覆盖(比如原来key的value是1,第一个线程设为2,第二线程设为3,则最终value为3);
3、多个线程同时对一个HashMap进行扩容,可能导致某个线程的数据丢失
()如何线程安全的使用HashMap?(几乎每次必问)
1、改用Hashtable
2、用Collections工具类进行线程安全包装
3、用ConcurrentHashMap
4、代码中手动对修改HashMap的方法加synchronized锁
ConcurrentHashMap
()Hashtable, HashMap, ConcurrentHashMap以及Collections包装的区别?(几乎每次必问)
HashMap线程不安全,单线程中使用效率高
Hashtable线程安全,因为它对put,get等方法加了synchronized关键字,但效率低,同时只有一个线程能访问Hashtable
Collections线程安全包装也是加synchronized,效率也低
ConcurrentHashMap内部对数据进行分段加锁,效率高,不同线程可并发访问不同的段内数据(包括put,remove操作都可并发操作不同段),并且get操作不加锁可与任意段的其他操作并发
()ConcurrentHashMap的get过程是怎样的?(哗啦啦一面)
jdk8之前,ConcurrentHashMap采用分段加锁实现,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,定位key的过程进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,不同segment之间可线程并发访问。
jdk8及之后,不再使用segments分段加锁,改为更小粒度的锁,即对table数组的每个元素加锁,所以只要hashcode不冲突就可以并发访问,进一步减少并发冲突的概率。
()ConcurrentHashMap的get方法加锁了吗?为什么(阿里健康电话面试)
get不需要加锁。但当读到的value为null时(极少情况下),需要调用内部的加锁方法来读取。
因为get方法中读的变量count和value都加了volatile关键字,根据happens-before原则,其他线程对这些变量的写操作优先于读操作,所以不会读到过期值。
这个问题详细说的话,还得解释发生冲突时遍历的hash链的链指针next是final的,只能在头部插入,以及remove时只能把链表前面的元素克隆后进行连接等。
此外,get方法只能保证读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。
()ConcurrentHashMap中用到了Unsafe类的CAS操作,你了解吗?(哗啦啦一面)
ConcurrentHashMap中设置table数组中元素i的值时使用了Unsafe.compareAndSwapObject进行CAS原子操作,比较原值和预期原值是否相等,相等时才执行更新操作。
HashSet/TreeMap
()HashSet内部如何实现?如何保证元素不重复?(百度一面,滴滴一面)
基于HashMap实现,完全调用HashMap的方法,key是元素值,value是统一的一个对象
HashSet的add方法直接调用HashMap的put方法,key相同时覆盖,由于value是固定对象也无所谓覆盖,key不相同时可put进HashMap
()TreeMap内部实现?介绍下红黑树?(蚂蚁金服电话面试)
红黑树,红黑树是近似平衡的一种二叉搜索树,相对于AVL树来说调整开销更低
List
()ArrayList和普通数组的区别?(58到家电话面试)
ArrayList是动态数组,容量不够时可自动扩容。普通数组(int[])大小是固定的,在创建时指定。ArrayList使用起来更加方便,但由于多了一层封装和方法调用,效率不如普通数组。
()ArrayList如何进行扩容?(58到家电话面试)
向ArrayList中add对象时,如果添加后元素个数大于底层数组长度,则以适当长度(添加1个元素时是扩容1.5倍,添加多个元素时扩容至足够放下所有元素)新建一个原数组的拷贝(使用Arrays.copyOf()方法),并使数组引用elementData指向这个新建数组。原数组由于没有引用指向会被java垃圾回收机制会自动回收。
简单点说:放不下时新申请一个足够大小的数组,将原数组元素拷贝过去,使elementData指向这个新数组,原数组等待被垃圾回收。
()ArrayList和LinkedList区别?(58同城业务部一面)
ArrayList是动态数组,可自动扩容,随机访问效率高,插入删除效率低。
LinkedList是双向链表,插入删除效率高,随机访问效率低。
()对于ArrayList和LinkedList,在for循环中通过get(i)遍历哪个效率更高?(58同城业务部一面)
for循环中使用get(i)访问,采用的即是随机访问的方法,对于ArrayList效率高,可直接定位到元素i,对于LinkedList效率低,因为每次get(i)都需要从头开始数到i
()通过哪种方式遍历LinkedList效率更高?(58同城业务部一面)
通过foreach方式(也就是for(a:linkedList))或者使用迭代器遍历效率高,因为Iterator中的next()方法采用的是顺序访问,其实foreach内部也是通过迭代器访问。
()java中Arrays类的sort()方法内部是怎么实现的?(58同城业务部一面)
Arrays.sort()对基本类型数组和对象数组使用了不同的排序算法,在jdk8中:
1、如果是基本类型数组,例如int数组,长度小于47时使用插入排序,长度大于47小于286时使用双枢轴快速排序,长度大于286时对数组的顺序性进行判断,如果顺序性好使用归并排序的变种TimSort,如果顺序性不好使用双枢轴快速排序。
2、如果是对象数组,长度小于32时使用折半插入排序,长度大于32,使用归并排序的变种TimSort
JVM 虚拟机
jvm内存模型
()说下jvm内存模型?堆空间如何划分?何时发生gc?(每次必问)
1、内存划分:jvm内存分为线程独享的虚拟机栈(一个栈帧是一个方法,用于方法调用)、PC(程序计数器记住执行到哪条指令)、native方法栈(用于native方法调用),以及线程共享的堆(最大的一块,new对象用)、方法区(存放类加载器加载的class类库、静态变量、常量池、JIT代码)
2、分代:堆空间从分代gc角度分为:新生代、老年代、永久带(对应方法区)。新生代又分为eden和两个survivor。
3、内存分配与回收:新对象在eden分配(大对象直接在老年代分配),eden满了触发young gc,幸存对象放到一个survivor中,放不下放老年代,下次eden再满了young gc对eden和一个survivor区回收,幸存对象放另一个survivor。每经过一次young gc幸存对象年龄加1,达到阈值(默认15)后进入老年代。老年代满了触发full gc
()堆内存、栈内存分别干什么用的?(58同城业务部一面)
堆内存用于存放生成的对象,方法区也可以看做是堆空间的一部分,存放加载的类库、静态变量、常量、JIT代码。
栈内存主要指虚拟机栈和本地方法栈,栈中每个栈帧代表一个调用过程中的方法,用于方法调用。
()非堆内存中存放什么?(老虎证券)
非堆内存中有调用方法用的虚拟机栈、PC程序计数器、native方法栈。
此外,java还能直接申请堆外内存使用(Unsafe,NIO的ByteBuffer)。
()java中能操作非堆内存吗?(58同城业务部一面)
java中可以操作非堆内存,有两种方式:
1、通过sun.misc.Unsafe类使用堆外内存,首先需要通过反射获取Unsafe类实例,然后unsafe.allocateMemory(1024)直接申请堆外内存,使用完后需要手动unsafe.freeMemory释放内存。
2、通过NIO中的ByteBuffer使用堆外内存,ByteBuffer.allocateDirect(1024)申请内存,ByteBuffer.allocateDirect分配的堆外内存不需要我们手动释放,可被gc自动回收。其内部也是调用sun.misc.Unsafe类实现直接在堆外分配内存。
内存分代与gc
()什么时候full gc?什么时候young gc?(几乎每次必问)
eden满了之后触发young gc,幸存对象拷贝到survivor,放不下时放到老年代,老年代满了触发full gc
()新生代分为哪几个区?比例是多少?(几乎每次必问)
新生代分为1个eden区和2个survivor区,默认比例为8:1:1,可设置
()survivor区是干什么的?为什么需要survivor区?没有会怎样?(雪球二面)
有两个survivor区,用来保存young gc后幸存的对象,每次young gc时eden和有对象的survivor区中的对象一起被回收,幸存的拷贝到另一个survivor区内
()为什么两个survivor区大小是相等的?(滴滴二面)
因为两个survivor区一个做from,一个做to,在young gc过程中会不断互换(当前是from的survivor,young gc后成为to)
两个survivor区的大小不一定总是相等的,可能配置在jvm运行过程中被动态调节。
()如何设置年轻代大小?(滴滴二面)
-Xmn设定年轻代大小,即eden加2个survivor的总和
如果不设置-Xmn,可以通过-XX:NewRatio指定年轻代与老年代比值(其实是反的)
()垃圾回收算法中,标记-清除算法和复制算法的区别?(58同城业务部一面)
标记-清除算法就是先标记出哪些对象可回收,然后再回收,容易产生内存碎片
复制算法将内存空间分为两部分,每次只使用一部分,满了回收时将幸存对象都拷贝到另一空间中,不会产生内存碎片
()jvm如何确定哪些对象需要回收?(多次被问)
可达性分析法(根搜索法),从GC Root开始,按他引用的对象一层一层往下找,如果某个对象没在任意引用链中,则此对象是不可达的。
不可达对象是需要被回收的对象。
()哪些对象可以作为GC Root?(多次被问)
虚拟机栈中引用的对象、本地方法栈中引用的对象、方法区中的常量和静态变量
()为什么虚拟机栈中的对象可以做GC Root?(58同城架构组一面)
虚拟机栈中的栈帧代表还未执行完的方法,所以其中引用的变量不能被回收,可以做GC Root
()什么对象生存周期长?什么对象生存周期短?分别举个例子(雪球二面)
数据库连接、网络连接、session、静态变量、final常量生存周期长
方法中的局部变量生存周期短
内存泄漏/jdk工具
()虚拟机堆内存持续增长,可能的原因是什么?如何分析?(58同城业务部一面,58到家一面)
堆内存持续增长可能是有内存泄漏
内存泄露就是用不到的对象无法被gc回收,根本原因是长生命周期的对象持有短生命周期对象的引用,常见的有:
1、集合类中引用不需要的对象。
2、打开的文件、数据库或Socket连接、session等用完了不手动释放。
3、单例对象持有不需要的对象的引用。
4、HashMap放进去对象后修改对象内容导致hashcode改变而无法取出
堆内存持续增长分析步骤:
1、jps查看java进程号
2、导出堆内存转储文件。jmap -dump:live,format=b,file=dump.hprof pid
3、打开dump文件。jhat -port 7000 file 分析dump文件并启动一个http服务器,然后在浏览器查看分析。也可以用MAT或VisualVM打开dump文件。
4、分析dump文件。看哪个类的对象个数过多、占用内存过大。
5、也可以不导出dump文件,直接jmap -histo:live pid 打印堆的对象统计信息,看哪个类的对象个数过多、占用内存过大。
()什么时候会发生内存溢出OOM?举个例子(雪球二面)
1、堆溢出:无限循环new很多对象,堆内存会溢出。或者有内存泄露,比如用不到的对象还有引用而没被gc回收,不断累积也会导致堆内存溢出
2、栈溢出:写个递归方法,无限循环递归调用自己,栈会溢出。注意栈溢出可能抛出两种错误,抛StackOverflowError是递归太深栈满了,抛OutOfMemoryError是jvm扩展栈空间时无法申请足够内存。
3、方法区(永久代)溢出:加载的类库过大,或者常量太多太大,可能导致方法区溢出。
()java中会有内存泄露吗?什么情况下发生?举个例子(雪球二面,58同城业务部一面)
java中会有内存泄露。
内存泄露就是用不到的对象无法被gc回收,根本原因是长生命周期的对象持有短生命周期对象的引用,常见的有:
1、集合类中引用不需要的对象。
2、打开的文件、数据库或Socket连接、session等用完了不手动释放。
3、单例对象持有不需要的对象的引用。
4、HashMap放进去对象后修改对象内容导致hashcode改变而无法取出
()如何排查内存泄露?(雪球二面)
内存泄露的表现有:1、堆内存持续增长。2、频繁full gc(长生命周期对象进入老年代,老年代满后触发full gc)
内存泄露排查步骤:
1、jps找到java进程号
2、jstat -gc pid 2s,以一定频次查看gc次数,其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
3、jstat -gcutil pid 2s,以一定频次查看堆空间(新生代、老年代、持久代)占用百分比,看是否内存持续增长。
4、jmap -histo:live pid 打印堆的对象统计信息,看哪个类的对象个数过多、占用内存过大。
5、导出堆内存转储文件。jmap -dump:live,format=b,file=dump.hprof pid
6、打开dump文件。jhat -port 7000 file 分析dump文件并启动一个http服务器,然后在浏览器查看分析。也可以用MAT或VisualVM打开dump文件。
7、分析dump文件。看哪个类的对象个数过多、占用内存过大。再具体分析可能是哪些代码产生的这些对象。
()用过哪些jdk工具?(滴滴二面)
jvisualvm/jconsole连接远程jvm查看性能参数,jps看java进程号,jinfo看jvm参数,jstack查死锁,jstat看gc次数、原因及堆空间占用,jmap导出dump文件或分析哪类对象过多,jhat分析dump文件
()如何分析服务器上是否有死锁?(滴滴二面,58同城业务部一面)
发生死锁可能伴有cpu使用过高,top查看服务器运行参数,输入P按cpu占用百分比排序,找到cpu使用过高的进程号。
jstack -l pid,-l参数表示 long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
()如何排查是否有死循环?(58同城业务部一面,58到家一面)
1、有死循环会伴有cpu使用过高,top查看服务器运行参数,输入P按cpu占用百分比排序,找到cpu使用过高的进程号。
2、jstack -l pid 查看线程堆栈,找到处于Runnable状态的线程,定位到具体的代码行。
()如何配置产生coredump文件?(滴滴一面)
加jvm参数-XX:HeapDumpOnOutOfMemoryError,可在内存溢出时自动产生dump文件。
或者使用jmap工具手动导出dump文件:jmap -dump:live,format=b,file=dump.hprof pid
()在命令行中如何分析dump文件?(滴滴二面)
用jmap -dump:live,format=b,file=dump.hprof pid把进程内存使用情况dump到文件中,再用jhat -port 7000 file分析dump文件并启动一个http服务器在浏览器中查看。
()如何查看young gc和full gc的次数?(滴滴二面)
用jstat -gc pid看看gc次数及时间,其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
()如何查看gc的原因?(58到家一面)
用jstat -gccause pid 查看最近2次gc的原因,或者jstat -gccause pid 2s 以2秒的频率持续查看gc原因。
()top结果中load过高可能的原因?怎么分析?(58同城架构组一面)
load average后面的三个数分别是最近1分钟、5分钟和15分钟的系统平均负载,即活跃进程数。如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。
cpu load的飙升,一方面可能和full gc的次数增大有关,一方面可能和死循环有关系。
可以通过vmstat查看内存、IO、CPU使用情况,或者top后输入P按cpu占用量排序,看看哪个进程占用cpu过高,得到进程号。
如果是java进程的话,用jstat -gc pid看看gc次数及时间,jstat -gccause pid查看jc原因,如果gc过多具体分析原因。用jstack -l pid看看是否有死锁,找到死锁代码分析。
()你们的服务器怎么配置的堆大小?为什么这么配?(滴滴二面)
12g内存的机器,上面只跑一个jvm,-Xms和-Xmx相等,都是10g,防止jvm频繁扩大或收缩堆大小
()做过jvm调优吗?如何调的?为什么这么调?(58同城架构组一面)
做过简单的参数调优。把-Xms和-Xmx配成一样大的,防止堆内存经常扩大、缩小
类加载
()ClassNotFoundException 和 NoClassDefFoundError 有什么区别?分别在什么情况下产生?(方正证券)
正如它们的名字所说明的:NoClassDefFoundError
是一个错误 Error ,而 ClassNOtFoundException
是一个异常 Exception,在Java中错误和异常是有区别的,我们可以从异常中恢复程序但却不应该尝试从错误中恢复程序。
在根据类名加载类的 class 文件时如果在 classpath 下找不到会抛出 ClassNOtFoundException
某个类在编译时没问题,但在运行时初始化失败会抛出 NoClassDefFoundError
Java 多线程和并发
()用过concurrent包下的哪些类?(蚂蚁金服电话面试,58同城架构组二面)
线程安全的集合类(ConcurrentHashMap,CopyOnWriteArrayList),线程池ThreadPoolExecutor,Lock,Future,Callable,ThreadLocal,原子变量,并发控制工具如CountDownLatch,CyclicBarrier,Semaphore
()java中线程间同步方法有哪些?(哗啦啦一面)
1、synchronized(同步代码块、同步实例方法、同步类方法)
2、Lock(ReentrantLock、ReentrantReadWriteLock)
3、Object的wait和notify
4、Condition的await和signal
5、concurrent包中提供的阻塞队列
6、atomic包中的原子操作类
7、ThreadLocal本地变量
8、volatile变量
synchronized和Lock
()synchronized和Lock的区别?(每次必问)
synchronized是关键字,Lock是一组类和接口
synchronized无法判断锁状态,会阻塞线程。Lock可判断锁状态,提供tryLock方法尝试获取锁
synchronized不需要手动释放锁。Lock需要释放锁。
synchronized在线程异常时会被系统自动释放掉,不会死锁。Lock必须在finally中手动释放锁,否则可能死锁。
synchronized和Lock都是可重入锁。
synchronized是非公平锁。Lock默认非公平,可设置为公平锁。
synchronized无法被中断。Lock提供可被中断的加锁方法。
()什么是公平锁?synchronized和lock是公平锁吗?(58同城架构组一面)
按申请锁的顺序分配的锁是公平锁。synchronized不是公平锁,无法保证按申请顺序分配,locl默认是非公平锁,创建ReentrantLock对象时可设置参数true使之成为公平锁。
()什么是可重入锁?synchronized和lock是可重入锁吗?可重入时机是什么?(58同城架构组一面)
可重入锁指的是在一个线程中可以多次获取同一把锁。synchronized和lock都是可重入锁。比如获取了一个方法的锁,此方法中再调用另一个加了锁的方法时不需要重新申请锁。
()java中对乐观锁的实现是什么?(58同城架构组一面)
java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,而CAS就是一种乐观锁。
()一个线程在写的时候另一个线程能读吗?(58到家一面)
如果是排他锁的话,一个线程加锁后(不论读还是写)另一个线程都不能再加锁(不论读还是写)
如果是读写锁(共享锁)的话,一个线程加读锁后其他线程可加读锁但不能加写锁,一个线程加写锁的话其他线程不能再加任何锁。即读读可并发,读写不可并发。
上面的描述既是Java并发中ReentrantLock和ReadWriteLock的区别,又是数据库中的共享锁和排它锁的区别。
()synchronized的锁是加在哪的?(雪球一面)
synchronized锁是加在对象上的。
1、synchronized修饰非静态方法,锁加在调用此方法的实例对象上,相当于synchronized(this)
2、synchronized修饰静态方法,锁加在方法所在的类Class对象上
3、synchronized(obj){}修饰代码段,锁加在参数obj对象上
()synchronized的实现原理是什么?(哗啦啦一面)
这道题的回答可深可浅,往深了回答要涉及java对象头里的锁标志位,往浅了回答就说出锁是加在对象上的即可。
一般来说,面试官并没想着问那么深,就是问synchronized锁是加在什么对象上的?
synchronized锁是加在对象上的。
1、synchronized修饰非静态方法,锁加在调用此方法的实例对象上,相当于synchronized(this)
2、synchronized修饰静态方法,锁加在方法所在的类Class对象上
3、synchronized(obj){}修饰代码段,锁加在参数obj对象上
()一个类中两个方法上分别有synchronized,两个线程中通过同一个对象分别调用这两个方法,会发生竞争吗?(几乎每次必问)
普通方法的锁是实例对象,那这个问题得先确认下是不是通过同一个对象调用的,如果通过同一个对象调用,会阻塞;如果通过不同对象调用,不会阻塞。
比如一个自定义Runnable对象,里面有两个synchronized方法,把同一个Runnable对象分别传给两个Thread,两个线程中分别调用这两个方法,则他们之间会有竞争关系,因为申请的锁是同一个。但如果把两个不同的Runnable实例分别传给两个thread,则不会有竞争。
()类中有个synchronized静态方法和一个synchronized非静态方法,两个线程同时访问,会存在竞争问题吗?(几乎每次必问)
不会,占用的锁不同(这种题就首先分析他需要获取什么锁)
如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。
()类中有个synchronized静态方法,在两个线程中分别通过两个不同的对象调用此静态方法,会阻塞吗?(几乎每次必问)
会,因为占用的锁是同一个锁,都是类对象本身。
volatile
()volatile关键字有什么用?(每次必问)
1、volatile修饰的变量保证线程间内存可见性,读写都直接从主内存中(说下主内存和线程本地内存)
2、访问volatile变量的语句不会被jvm指令重排,就是不会被放到他前面的语句之前,也不会被放到他后面的语句之后
3、volatile修饰的变量符合happens before原则,即对这个变量的写操作发生于读操作之前
()线程中有个volatile变量:volatile int i=0; 有10个线程同时做i++操作,最终结果是几?为什么?如何解决?(老虎证券)
小于等于10
自增操作不是备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。volatile只能保证可见性,不能保证对其操作的原子性。
比如,线程1先读取了变量i的原始值,然后线程1被阻塞了;线程2也去读取变量i的原始值,然后进行加1操作,并把+1后的值写入工作内存,最后写入主存,然后线程1接着进行加1操作,由于已经读取了i的值,此时在线程1的工作内存中i的值仍然是之前的值,所以线程1对i进行加1操作后的值和刚才一样,然后将这个值写入工作内存,最后写入主存。这样就出现了两个线程自增完后其实只加了一次。
解决方法:
1、对i自加操作加synchronized锁。效率低。
2、使用java.util.concurrent.atomic包下AtomicInteger原子类的自加操作方法,其内部使用Unsafe类的CAS机制保证原子性。
多线程和线程池
()用过多线程吗?干什么用的?(老虎证券)
用过,用线程池计算xx信息,每个xx的计算是一个线程任务,提交到线程池中执行。
()java中如何自定义创建线程池?有哪些参数?(百度二面,阿里健康电话面试,58到家一面)
通过ThreadPoolExecutor自定义创建线程池,参数包括:core大小,max大小,线程生存时间,时间单位,线程工厂,阻塞队列,拒绝策略
还可以通过Executors获取java给配置好的线程池
()java中有哪几种线程池?分别在什么情况下使用?(滴滴二面)
newCachedThreadPool,无界线程池,可以用于网络请求,并发量很多时可缓存
newFixedThreadPool,固定大小线程池,用于预知任务个数时
newSingleThreadExecutor,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newScheduleThreadPool,相当于定时任务
()拒绝策略(饱和策略)有哪几种?(阿里健康电话面试)
当队列和线程池都满了,放不下新提交的线程任务时需要用到拒绝策略。包括:抛出异常(默认)、丢弃、丢弃队列中最老的一个任务、在调用线程中执行。
()阻塞队列有哪几种?分别什么时候用?(滴滴二面)
ArrayBlockingQueue,基于数组的阻塞队列,FIFO
LinkedBlockingQueue,基于链表的阻塞队列,FIFO
PriorityBlockingQueue,优先级队列,自动按优先级排序,每次出队的是优先级最高的元素
SynchronousQueue,同步队列,相当于队列长度为1的同步器
DelayQueue,延时阻塞队列,到一定时间后才能获取元素
()你们线程池大小配置为多少?配置线程池的大小需要考虑哪些因素?(百度二面)
我们线程池大小默认是20,4核服务器,io较为密集(经常查库,调远程服务)
考虑cpu个数,以及任务是cpu密集型还是IO密集型:cpu密集型任务不建议把线程池配置过大,一般配1倍cpu个数,否则增加切换开销;io密集型任务由于有很多时间在io读写不需要cpu,可配置较大线程池,比如2倍cpu个数。混合型任务尽量分开到不同的线程池。
()ThreadLocal做什么用的?不同线程中操作的ThreadLocal变量是不同的吗?(雪球一面,58同城架构组二面)
ThreadLocal就是线程本地变量,Thread类中有个ThreadLocalMap,里面存放每个线程的ThreadLocal变量副本,对ThreadLocal进行get()和set()的时候,都是先获取当前线程的ThreadLocalMap,再get或set对应的变量值。所以每个线程用的是自己线程中的那一份,互不影响
是不同的,ThreadLocal变量在每个线程中都有一个副本,线程之间互不影响。
()有多个子线程并发执行,主线程想等所有子线程都执行完后再执行,怎么做?(58同城架构组二面)
1、使用并发包中的CountDownLatch计数器:有n个子线程就创建一个计数值为n的CountDownLatch latch,每个子线程执行完成后调用latch.countDown(),主线程中调用latch.await()阻塞等待计数值减为0,即所有子线程执行完毕后再继续执行。
2、或者使用并发包中的CyclicBarrier循环栅栏,有n个子线程就创建一个数量为n的CyclicBarrier barrier,每个子线程执行完成后调用barrier.await()等待其他线程执行完毕,主线程中也调用barrier.await()阻塞等待所有子线程执行完毕,然后再继续执行。
数据库
事务
()脏读是如何产生的,举个例子?如何避免脏读?(蚂蚁金服电话面试)
并发访问时产生,A事务修改了数据还未提交,B事务读了,之后A事务失败回滚,则B读的是脏数据。
事务隔离,读已提交及更严格的隔离级别可避免脏读
()表中存着A、B两个人的账户余额,A是0,B是100,B给A打了100块钱,A把100块钱取出来,这期间可能发生什么问题?怎么解决?(雪球二面)
可能会发生脏读,比如B余额减100,A余额加100后,A的事务读取了账户余额,B事务还未提交时出了问题回滚,B余额回滚到100,这时A的事务读到的就是脏数据。
设置数据库的隔离级别,比如设置“读已提交”及以上的隔离级别可解决脏读问题。
()数据库事务的隔离级别有哪些?(蚂蚁金服电话面试,百度二面)
读未提交、读已提交、可重复读、串行化
()事务的传播属性有哪些?(蚂蚁金服电话面试)
事务的传播属性是为了规定什么时候应该创建事务,包括…
()说下分布式事务如何实现?(58同城架构组一面)
分布式事务的实现方案有:
1、基于XA协议的两阶段提交(2pc)
2、基于消息的分布式事务
3、TCC编程模型
()了解两阶段提交吗?(58同城架构组一面)
两阶段提交时一种经典的分布式事务解决方案,分为准备阶段和提交阶段。在准备阶段,协调者询问所有参与者是否准备好提交,参与者如果已经准备好提交则回复Prepared,否则回复Non-Prepared。在提交阶段,协调者根据所有参与者的反馈情况决定向所有参与者发送commit或者rollback指令。
zookeeper可以作为协调者。
数据库锁
()有哪些类型的数据库锁?(蚂蚁金服电话面试)
共享锁(读锁)、排它锁(写锁)、还可以分为乐观锁、悲观锁
()说下数据库的乐观锁和悲观锁?(58到家一面)
乐观锁总是认为他操作的数据不会被外界修改,所以在提交的时候才判断是否冲突,数据库本身并不提供乐观锁的实现,一般通过对比版本号来判断,并发效率较高。
悲观锁总是认为他操作的数据会被外界修改,所以在操作数据之前就对其加锁,数据库的排它锁就是一种悲观锁,悲观锁并发效率较低,但数据安全性高。
()mysql或pg中怎么实现乐观锁?(58到家一面)
数据库本身并不提供乐观锁的实现,一般通过对比版本号来判断,也就是给数据加版本号,写入时对比是否和读的版本号一致,一致时可写入,不一致时则放弃。
()数据库分布式锁如何实现?(58同城架构组一面)
分布式锁的实现有以下几种方案:
1、基于数据库实现分布式锁,创建一个锁表,需要加锁时在表中插入一条记录。
2、基于缓存(redis,memcached,tair)实现分布式锁,比如使用redis的SETNX key value设置一个key,set成功表示获得锁。
3、基于Zookeeper实现分布式锁,加锁时在zk对应的目录下创建一个节点。
索引
()索引应该建在哪些字段上?(58到家一面)
索引应该建在经常做where条件的字段上,以及join,order by,group by字段上。
()比如表中有1亿数据,而id只有1,2,3这三个值,需要建索引吗?(58到家一面)
不需要。要考虑索引的区分度(选择性),索引的区分度是指索引列中不同值的数目与表中记录总数的比值,索引的区分度越高性能越好,区分度为1的索引就是唯一索引。一般像性别(F/M)、状态(yes/no)等字段上不需要建索引,建了也没什么用,因为无法根据这个字段将数据行区分开。
这种情况下怎么高效查询?(58到家一面)
按id分表,或者在区分度高的字段上加索引。
()数据库索引的内部实现?为什么加索引查询就快?(58同城架构组一面,58到家一面)
索引一般通过哈希表(哈希索引)或B树/B+树(B树索引)实现。
哈希索引可直接根据索引列(key)定位到数据行(无冲突时),适合等值或不等查询,不支持范围查询和排序操作,也不支持部分索引。
B树索引内部通过B树或B+树实现,可在O(logn)复杂度内定位到数据行,支持范围查询和排序操作,也支持部分索引。
加了索引后可根据索引列快速定位到数据行,不需要顺序遍历,所以查询快。
()如何看一个sql是否使用了索引?(雪球一面)
explain sql分析
()怎么分析一个sql执行过慢? (爱奇艺一面)
查数据库的系统表,比如pg_stat_activity,看哪条sql执行时间过长,然后用explain分析sql,看是否使用了索引
数据库优化
()为了应对大量数据和高并发访问,数据库如何优化?(爱奇艺一面)
按业务垂直拆分,读写分离(主库写,从库读),单表过大时进行分库分表,热点数据放redis缓存中
()单个表过大时,怎么高效存储?(58同城业务部一面,58到家一面)
()有10亿用户数据存储在一个表中,id,username,mail,怎么在数据库中高效的存储?(58同城架构组一面)
单表过大,需要进行sharding分库分表。
()怎么分表?分表依据字段是哪个?(58同城架构组一面,58同城业务部一面)
根据业务类型来具体考虑按哪个字段分表,按id查的多就按id分表
()按id分库分表后,涉及到多个id的范围查询怎么办?如果id和name都查询的很多怎么办?(58同城架构组一面,58同城业务部一面,58到家一面)
在多个表中查询,最后结果做union
优化方法:
1、双写,按name再做一次分表,这样会有数据冗余,空间换时间
2、还是按id分表,然后把id和name做一个映射放到缓存中,按name查询时先找到对应的id,再根据id去分表中查询。
()如何不停机分库分表?(58到家一面)
可以借鉴redis集群的presharding方案来解决动态扩容和数据分区的问题,即预先部署多个轻量级的Redis实例,新加入的redis结点作为这些实例的slave,主从同步后再替换原有轻量级实例。
Redis和缓存
redis应用
()你们系统中用Redis来做什么,或者说怎么用redis的?(多次被问到)
做数据库的缓存,缓存xx信息,提高查询性能
()自己写的内存缓存和redis有什么区别?(58同城业务部一面)
redis提供主从集群部署、sentinel哨兵监控、持久化机制、过期机制、内存淘汰策略等高级特性,使用起来更加方便。
()get的过程是怎样的?(58同城架构组三面)
先尝试从缓存中get,没有的话从数据库get,返回后put到缓存中
redis数据结构
()你们redis用的哪种数据结构?(多次被问到)
用的哈希,航班号+航班日期作为key,value是多个字段,包括客座率,出票数,航线等。
当字段个数超过10时,有个continue key字段,保存一下k-v对的key,可通过continue key继续读取,用来避免value过大。
()你们按航班号模糊查询是怎么做的?比如查询所有CA12开头的航班(腾讯一面)
redis只支持模糊匹配keys,所以我们有个航司及其所有航班号的表,也在redis中有缓存,先查出此航司的所有航班号,后端筛选出模糊匹配出来的具体航班号,用具体航班号再次查询redis
()失效时间怎么配的?(雪球二面)
写入时通过expire key second 为key设置过期时间,设置为入库时间与航班起飞时间之差,即航班起飞之前一直是有效的。
定期删除频率配置为默认的10,即hz 10,即每秒刷新10次,航班起飞后缓存数据可以马上被清楚。
最大内存配置为7g,即maxmemory 75663588kb,超过最大内存后的淘汰策略配置为maxmemory-policy allkeys-lru,即最长时间没被访问的会优先被淘汰。
()哪些redis命令是O(n)时间复杂度的?(跟谁学一面)
lindex, lrange, lset 这种按下标在 list 中间操作的指令,必须 O(n) 遍历 list
hkeys hvals 这种获取哈希全部 key 和 value 的操作
redis部署
()你们的redis怎么部署的?(多次被问到)
一主两从,主从结构,向master写入,从slave读出。
每台redis服务器上起一个Sentinel哨兵进程,组成sentinel哨兵集群,负责监视master是否挂掉,挂掉后将slave作为master,再将master启动后成为slave,自动主从切换。
()redis主从如何复制?(58到家一面)
slave刚启动时进行全量同步,向master发送sync命令,master收到后fork一个进程进行bgsave保存快照文件并发给slave,slave收到后载入内存。
slave运行过程中,master上的所有写命令都会发送给slave,slave收到后同步执行来保证与master的数据一致。
()怎么保证redis的高可用?怎么防止redis挂掉?(多次被问到)
每台Redis上启动Sentinel哨兵进程,组成sentinel哨兵集群,哨兵负责自动主从切换,即master挂掉后用一个slave做master,再把挂掉的master起来作为slave
()主从切换后,master的ip变了,如何知道往哪个里面写入?(58同城业务部一面)
java后台使用jedis来操作redis,配置一个JedisSentinelPool,传入sentinel哨兵集群中每个节点的地址,JedisSentinelPool初始化时会与所有sentinel沟通,确定当前sentinel集群所监视的master是哪一个。主从切换后通过sentinel集群还能读出新的master的地址,所以写入不会出错。
()redis集群如何扩容?(爱奇艺一面,58同城架构组一面)
采用客户端sharding方式来做redis集群的话,可以使用presharding来扩容,即预先部署多个轻量级的Redis实例,新加入的redis结点作为这些实例的slave,主从同步后再替换原有轻量级实例。
()说说一致性哈希算法?(爱奇艺一面,58同城架构组一面)
根据服务器结点的ip求一个哈希值,然后将其分布到0~2^32的一个圆环上。对于kv数据,也对key求一个哈希值hash(k),hash(k)在圆环上顺时针找到的第一个结点就是key所在的服务器结点。
为了解决服务器结点太少时的分配不平衡问题,可以给每个物理结点虚拟出多个虚拟结点,比如ip#1,ip#2,ip#3,增加一层虚拟结点到物理结点的映射。
持久化
()redis的持久化策略有哪些?(58到家一面)
redis的持久化方法有RDB磁盘快照和AOF增量日志。
RDB方式在指定的时间以及多少key被修改后将内存写入磁盘快照(二进制),通过bgsave异步方式fork一个进程,写入文件结束后替换之前的文件。
AOF方式将所有写操作存入缓冲区中,再将缓冲区内容写入文件,可配置在何时进行aof持久化:always每次写操作、everysec每秒、no由系统决定。
()AOF持久化方式多长时间备份一次?(58到家一面)
AOF方式将所有写操作存入缓冲区中,再将缓冲区内容写入文件,可配置在何时进行aof持久化:always每次写操作、everysec每秒、no由系统决定。
数据一致性
()你们的Redis写入策略是怎样的?(多次被问到)
先使redis key失效(删除key),再写入数据库,等redis查询时自动去数据库更新。也就是缓存只做失效,不做更新。
()能不能先写数据库,再删除redis key?为什么?(老虎证券)
不能,这样会有数据不一致发生。例如当前值为100,库中更新为200,然后删除缓存前应用挂了或者某种原因删除缓存失败,这时缓存中还是100,与数据库不一致。
()如何保证redis和数据库的数据一致性?(每次必问)
为保证redis和数据库的数据一致性,写入策略应该是:先使redis key失效(删除key),再写入数据库,等redis查询时自动去数据库更新。也就是缓存只做失效,不做更新。
此外,假如有数据不一致,可以利用数据库的binlog,使用一个额外进程监控binlog,发生改动时更新redis。
此外,redis适用于读多写少的情况,如果要求绝对的强一致性,会降低redis带来的性能优势,所以不建议使用redis,直接查库。redis更适合于一致性要求不高的数据,比如Top10排行榜。
()什么情况下Redis和数据库不一致?如何解决?(老虎证券)
写入时先写数据库,再删除redis key,删除redis失败则会有数据不一致。例如当前值为100,库中更新为200,然后删除缓存前应用挂了或者某种原因删除缓存失败,这时缓存中还是100,与数据库不一致。
解决方法:
写入顺序改为先删除redis key,再写入数据库。如果删除缓存失败,那就不要更新数据库,如果说删除缓存成功,而更新数据库失败,那查询的时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一致性。
或者
可以利用数据库的binlog,使用一个额外进程监控binlog,发生改动时更新redis
()同时有大量并发的request到达redis,都没命中缓存,都需要去请求数据库,而数据库无法应对这么高的并发,如何解决?(58同城业务部一面)
大量请求绕过redis到达数据库可能的原因有:1、缓存穿透,即请求大量不可能存在的key。2、缓存雪崩,即要访问的key同时间大量过期
对于缓存穿透,可以加一层布隆过滤器来过滤对不存在key的访问,或者将不存在的key也缓存起来,value为空值。这两种方法都有问题,布隆过滤器有误差,缓存空值会导致key过多,可以给空值设置较短的过期时间。
对于缓存雪崩,解决方法有给不同的key设置不同的过期时间来避免集中失效,或者将访问同一key的请求放到mq中缓存避免都到数据库中查询。
redis性能
()redis是单线程的还是多线程的?为什么?(58同城业务部一面)
redis是单进程单线程的。
redis主要是内存操作,不是cpu密集型的,使用单线程模型更简单。内部采用多路复用io模型,自己封装的epoll模型,可实现高效io
Spring
()介绍下依赖注入DI,或控制反转IOC?(蚂蚁金服电话面试)
使用一个依赖对象的时候不用手动new,而是系统直接构造好后注入给使用者,控制权交给spring
依赖注入是一种解耦手段,代码中只通过接口表明依赖关系。
()依赖注入内部是如何实现的?(蚂蚁金服电话面试)
通过反射实现。分为两个步骤:
1、手动配置bean,或自动扫描bean,通过反射构造出每个bean的实例。
2、spring把要注入的属性用HashMap保存,再通过反射调用类的set方法注入。
()介绍下AOP?(蚂蚁金服电话面试)
把通用功能抽取出来,以对Service透明的方式集中管理。
其实AOP就是代理,把通用功能抽取出来,编译(静态代理)或运行(动态代理)时自动织入到业务代码中。
()AOP和手动抽取通用代码的区别?(蚂蚁金服电话面试)
AOP对业务类是透明的,业务类完全不知道AOP的存在。
()AOP内部时如何实现的?(蚂蚁金服电话面试)
通过代理实现,分为编译时织入的静态代理,和运行时织入的动态代理。
()动态代理和静态代理的区别?(蚂蚁金服电话面试)
静态代理是在编译时将切面织入业务代码中,即修改class字节码文件,扩展性低。比如AspectJ是静态代理。
动态代理不修改字节码,是在内存中动态生成代理对象,代理对象再回调原业务类。JDK动态代理或CGLib是动态代理。
()用的哪种Spring框架?用来做什么?(滴滴一面,滴滴二面)
最基本的Spring
主要用来做组件扫描和自动注入
()用过Spring boot或Spring cloud吗?他们之间有什么区别?(滴滴一面,滴滴二面)
没
spring boot是基于spring的一种微框架,简化spring开发
spring cloud是基于spring boot的一种微服务框架。
()@Autowired注解是干什么用的?根据什么扫描?如何注入id不同类型相同的两个bean?(滴滴二面)
自动注入
根据类型扫描
使用Spring的@Autowired@Qualifier(“beanid”)指定bean id注入。或者使用java的@Resource(name=”beanid”)注入
MQ
()为什么使用MQ?使用MQ有哪些好处?(58同城架构组一面)
缓冲生产者、消费者执行速度的不协调,屏蔽不同系统的差异
()MQ消息丢失怎么办?MQ的持久化方法有哪些?(蚂蚁金服电话面试)
配置消息持久化
AMQ可配置基于数据库的JDBC方式,基于日志的AMQ方式,基于日志的kahaDB方式,基于文件的LevelDB方式
()mq使用过程中出现过哪些问题?什么原因造成的?怎么分析的?怎么解决的?(爱奇艺一面)
航班漏洞监控是通过处理MQ队列过来的消息实现的,发生过消费者处理过慢,队列积压,导致消息丢失的问题。
原因是sql执行过慢,数据库连接池被耗尽,后来的sql无法执行,导致消费者处理过慢。
查pg库的pg_stat_activity表,看哪些sql执行时间过长,再用explain分析这条sql,看是什么原因,是不是没用索引。
发生MQ报警后,应急方法是重启处理服务器,重启后数据库连接都被释放了,可继续处理消息,然后再具体分析执行慢的原因。
()为了应对高并发,mq如何扩展?(爱奇艺一面)
()AMQ的消息确认机制(ACK模式)有哪些?(58同城架构组一面)
AMQ的消息确认机制包括:
1、JMS规范中定义的消息确认机制:
AUTO_ACKNOWLEDGE = 1 自动确认。同步接受时,receive()方法返回之前自动确认。异步接收时,listener.onMessage(message)返回成功后自动确认。
CLIENT_ACKNOWLEDGE = 2 客户端手动确认。需要客户端手动调用Message.acknowledge()方法完成确认。
DUPS_OK_ACKNOWLEDGE = 3 自动批量确认。一种延时/批量确认机制,也是自动的,在收到多个消息之后一次完成确认。
2、使用事务session时的确认机制
SESSION_TRANSACTED = 0 事务提交并确认。要么全部正常确认,要么全部redelivery,调用session.commit()方法提交事务时确认。
3、AMQ独有的单条消息确认
INDIVIDUAL_ACKNOWLEDGE = 4 单条消息确认,AMQ独有的消息确认机制。需要调用message.acknowledege来确认此消息
()mq中,对同一个用户id有update,delete,add三个操作消息,我们需要顺序执行,但mq另一端有多个服务器并发处理消息,怎么实现这3个消息的顺序处理?(58同城架构组二面)
在发送方保证同一用户id的消息都发送到同一个MQ中,在MQ中保证同一用户id的消息都发送到同一消费者中。
具体实现上,发送方通过消息中的用户OrderID选择MQ,比如id对mq个数求余,余数为i则选择第i个mq。MQ中把所有消费者保存在一个队列中,通过id对队列长度求余,余数为i则选择队列中的第i个消费者。
这种方案的问题是高并发时会有瓶颈,可能大量消息都发送到同一mq或同一消费者中。
写SQL
()学生成绩表student,字段stu_id,class_id,class_name,score,求:1、每个班多少人?2、每人的平均分?3、所有学生的平均分都大于80的班?(百度一面)
group by用法、聚集函数用法、exists用法
()举个sql注入的例子?如何预防sql注入?(百度二面、滴滴二面)
sql注入的原因是把用户输入的数据当做了执行语句或代码,例如select * frome table where id=?,传入123 or 1=1,可查出表中所有内容
预防方法包括对用户输入内容进行语法分析,或者更常用的采用预编译的PreparedStatement,sql语句被编译好了,只留下数据段的占位符,用户输入的内容只能被当做数据。在mybatis中使用井号可避免sql注入,使用$容易被sql注入。
()stu_cls表有字段name class score,用一条sql查询每个class里及格和不及格的学生人数,结果形式class num(>=60),num(<60)
用case when
select class,
sum(case when score>=60 then 1 else 0 end) '及格人数' ,
sum(case when score<60 then 1 else 0 end) '不及格人数',
count(*)
from stu_cls
group by class;
架构/RPC
dubbo/微服务
()dubbo是如何架构的?(雪球二面,58同城架构组二面)
用zookeeper做注册中心,有多个服务的Provider,把服务注册到zk,消费者从zk上获取有哪些可用的服务。还有monitor监控调用次数等。
()dubbo消费者每次都要从zk获取提供者吗?(58同城架构组二面)
不用。启动dubbo时,消费者会从zk拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。
()注册中心怎么知道哪些服务挂了?(58同城架构组二面)
注册中心和消费者、提供者之间都会建立长连接,通过心跳检测机制感知提供者是否宕机。
()Provider发生变化后,注册中心会主动通知消费者吗?还是等消费者定时拉取更新?(雪球二面,58同城架构组二面)
会主动通知消费者,注册中心和消费者、提供者之间都会建立长连接,如果Provider有变更,注册中心将基于长连接推送变更数据给消费者。
()假如zk挂掉,影响服务的使用吗?是所有服务都没法用还是一部分无法用?(雪球二面,58同城架构组二面)
由于消费者本地会缓存可用的服务列表,所以如果Provider都没有变化,可以根据缓存继续调用服务。
但是无法从注册中心去同步最新的服务列表,所以如果期间某个服务挂掉则调用失败,此外也无法访问新增的服务。
()说说你对微服务的理解? (58同城业务部二面)
传统的服务一个Service太大,不同业务功能耦合在一起,一个出问题都出问题,调用起来也耗时,重构或修改也很麻烦。微服务将Service细分,解耦,解决了上述问题,并且有更好的扩展性,伸缩性。
负载均衡
()你们的jboss集群怎么配置的?(腾讯一面,58同城业务部一面)
每个Service有多台jboss应用服务器组成集群,前面用Apache httpd做负载均衡器。
jboss集群使用mod_cluster(jboss的一个开源集群模块)来配置,mod_cluster使用多播技术来自动发现集群成员,并可动态增减。具体配置上,同一集群中的成员节点使用-g参数指定相同的HA名称,使用-u参数指定相同的多播地址,使用-Djboss.mod_cluster.proxyList参数配置Apache httpd负载均衡器的地址,从而使负载均衡器知道有哪些后端节点。
()你们的负载均衡如何配置的?使用了什么负载均衡算法?(腾讯一面,58同城业务部一面)
用Apache httpd做负载均衡器,后端是jboss集群。httpd中使用了jboss的mod_cluster模块,mod_cluster是jboss提供的一个集群模块,提供集群自动发现和负载均衡功能。
使用了默认的按请求次数均衡(也就是不需要配置,一切都默认),lbmethod使用默认的byrequests,每个后台jboss服务器的loadfactor也是默认的1,也就是轮询转发,一次到A,一次到B
httpd上可配置的负载均衡策略有:
byrequests 按照请求次数均衡(默认)
bytraffic 按照流量均衡
bybusyness 按照繁忙程度均衡(总是分配给活跃请求数最少的服务器)
()常用的负载均衡算法有哪些?(腾讯一面,58同城业务部一面)
随机、按请求次数轮询及加权轮询、按相应时间均衡、按负载流量均衡
()你们有几台负载均衡器?域名解析时转发到哪台服务器上?(58同城业务部一面)
有两台Apache httpd负载均衡器,在httpd-vhosts.conf中配置基于IP的虚拟主机(虚拟IP,我们习惯叫服务IP),两台Apache的虚拟IP是相同的,比如都是122.119.160.87,DNS解析到此虚拟IP上。
()负载均衡器如何做双机热备?(58同城业务部二面)
使用keepalived+两台Apache实现负载均衡的高可用,keepalived上配置虚拟ip
()你么的session同步怎么做的?为什么?(58同城业务部一面)
使用了session复制,后台是基于mod_cluster的jboss集群,直接在web.xml中加上<distributable />
即可开启session复制。
粘着session会影响负载均衡,导致后端负载不平均,而且一旦某个应用服务器挂掉,上面的session全部失效,所以较少使用。
()有哪些session同步方法?(58同城业务部一面)
session同步方法有:
1、sticky粘着session
2、session复制
3、以及使用第三方缓存session数据(比如memcached或redis)
()粘着session怎么配置?在Apache上还是jboss上?(58同城业务部一面)
粘着session在Apache负载均衡器上配置,ProxyPass /test balancer://mycluster/ stickysession=JSESSIONID
session复制在应用服务器集群上配置,比如在tomcat集群或jboss集群上配置。
()粘着session会影响负载均衡吗?(58同城业务部一面)
粘着session会影响负载均衡,导致后端负载不平均。
RPC
()你们后端服务的互相调用怎么做的?(58同城架构组三面)
使用Hessian将后端服务发布为http接口供后台调用,比如redis服务就是通过Hessian发布的。
最近也在做dubbo微服务改造,将一些预警Service改造为dubbo架构。
()你们用Hessian来做什么?(58同城业务部一面)
使用Hessian将后端服务发布为http接口供后台调用,比如redis服务就是通过Hessian发布的。
()为什么用Hessian?使用了什么协议?如何序列化的?(腾讯一面,58同城架构组三面)
Hessian是一种轻量级的RPC框架,基于http传输,二进制序列化。
Hessian简单易用,可方便的将服务发布为http接口
()Hessian服务挂了怎么办?(58同城架构组三面)
每个Service配置双机互备,调用Hessian服务的地方配置failover两个地址,挂掉一个自动用另一个
()那平常岂不是都调用第一个了,如何做负载均衡的?并且第一个挂了后岂不是每次都要重试?(58同城架构组三面)
这些后端服务都很小,我们生产上没做负载均衡
如果要做的话,可以在调用端做负载均衡,比如随机、轮询、按流量或响应时间等。或者也可以添加一个负载均衡器,服务地址处配置lb的地址,lb再做负载均衡转发
为了解决每次都要重试已经宕机的服务,可以用zookeeper做个注册中心,去注册中心取可用的服务,调用端做负载均衡策略
()你们用RMI来做什么?(58同城业务部一面)
分布式缓存ehcache的集群间数据共享是通过RMI方式实现的。
()cxf是什么?具体说说怎么发布webservice的?(58同城架构组三面)
cxf是一个用于发布webservice的第三方框架。
在服务端,使用原生java发布ws时需要定义接口、实现类、Endpoint发布,cxf提供了一个代理工厂类JaxWsProxyFactoryBean来帮我们做这些事,结合spring使用时可直接将bean发布为webservice,此外还提供Interceptor拦截器对服务进行扩展,可以在服务被调用前或调用后增加一些额外的处理,比如安全验证等。
在客户端,提供一个JaxWsProxyFactoryBean工厂类,能够直接以接口的方式来调用远程的WebService,不需要手动通过wsimport工具生成客户端代码,简化了调用WebService调用的复杂性。
()webservice和restful接口的区别?(58同城架构组三面)
rest是一种面向资源的web服务架构风格,用HTTP动词(GET,POST,DELETE,DETC)描述对资源的操作。适合简单的CRUD业务操作,特别适合对于效率要求很高,但是对于安全要求不高的场景。
WebService是一种跨平台的web服务远程调用技术,使用WSDL描述接口(方法、参数、返回值),使用SOAP协议传输内容,此外还提供WS-Security等安全验证。
rest更加简洁、轻量级,webservice安全性高但更复杂。
web
()你们的服务是如何做认证的?(百度二面)
通过cxf的拦截器,读取soap头中的wss认证信息,判断是否可访问
()同一session中的多个请求,第一个通过验证后,后续请求还需要再次验证码?(百度二面)
查基于session的验证和基于cookie的验证
()ssl协议是怎么加密的?(百度二面)
()get和post有什么区别?(百度二面)
get直接通过url地址的参数请求资源,post通过报文中的header请求资源,相对安全
()说下常见的http返回代码?(百度二面)
2开头的表示成功(200成功),3开头的表示重定向,4开头的表示客户端错误(404找不到,403禁止),5开头的表示服务器错误(500服务器内部错误,502网关错误)
servlet
()servlet是单例的吗?(老虎证券)
是单例的,严格地说是一个ServletMapping对应一个单例实例(如果一个Servlet被映射了两个URL地址,会生成两个实例)。
()浏览器地址栏中输入url回车到页面显示,都发生了什么?(雪球二面)
DNS解析域名,http协议封装,到TCP,请求路由到服务器,http服务器解析转发给后台应用服务器,应用服务器准备数据返回给http服务器,再不断路由返回给浏览器,浏览器解析文档内容显示页面。
()浏览器输入url回车后等了会儿发生超时,可能原因有哪些?分别如何判断?如何处理?(雪球二面)
一、可能原因:
请求超时:比如网络不通,或者网络状况非常差,无法建立tcp连接
响应超时:服务端响应过慢,超过设定的超时时间,可能原因有服务器挂了,或者并发请求太多服务器来不及响应导致请求丢失(可能发生在http服务器上,也可能发生在应用服务器上),或者能接收请求但处理超时(比如查库超时,内部调远程服务超时,或者服务内部发生死锁、阻塞等异常),或者请求处理完后由于网络阻塞等原因无法及时返回给客户端。
二、判断和处理方法
网络不通:ping ip/host 看通不通,或者tracert ip/host 看ttl各结点响应时间
服务端:加断点或看日志,看是否能收到请求,看请求数量(太多的话考虑加mq缓存请求,或做服务器水平、垂直拆分提高并发响应能力),断点跟踪看是否有内部调用超时,用jstack -l pid看是否有死锁,分析代码解决
数据库:超时可能是连接池用完,或者sql执行过慢,可以看日志或在库上查系统表,比如pg_stat_activity,看连接数或哪条sql执行时间过长,用explain分析sql,加索引、加缓存、垂直拆分、或分库分表
()Apache和Nginx有什么区别?(饿了么一面)
Apache和Nginx都是web服务器,作用就是接收用户请求,然后处理请求,最后将处理结果返回给用户。
区别:
1、进程模型。apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程。
2、IO模型。两者性能差别的主要原因在于网络IO模型选择不同,apache使用了select,而nginx使用了epoll模型!
3、静态与动态内容的处理。nginx的优势是处理静态请求,apache适合处理动态请求,所以现在一般前端用nginx作为反向代理抗住压力,apache作为后端处理动态请求。
4、Nginx轻量级,配置简洁,支持热配,占用资源少,性能高,支持高并发。
Apache 配置复杂,组件丰富,bug少,更稳定。
5、Apache 对 PHP 支持比较简单,Nginx 需要配合其他后端来使用。
()为什么Nginx性能高?(饿了么一面)
两者性能差别的主要原因在于网络IO模型选择不同,apache使用了select,而nginx使用了epoll模型!
()了解Apache的MPM吗?(饿了么一面)
类设计
()用java实现一个生产者、消费者队列 (百度二面)
()给你10个文件,每个文件中有姓名、性别、年龄,求所有文件中的性别的平均年龄,要求尽量快,在纸上大概写写吧?(老虎证券)
1、用线程池,Callable任务
写了个自定义的MyCallable类,继承自Callable类,构造函数有个参数File:MyCallable(File file),在call()方法中打开file,计算此文件的性别平均年龄,返回值放到一个Map(String,Integer)中,就是{“男”,”25”}{“女”,”23”}
主函数中,创建一个容量为10的固定大小线程池,ExecutorService es = Executors.newFixedThreadPool(10); 写个for循环,依次构造MyCallable类并提交到线程池,es.submit(new MyCallable(new File(filename+i))),用一个List(Future)接收返回结果。
最后遍历遍历Future的list,依次取出返回结果,计算10个结果的性别平均值
2、Fork/Join框架
后来学习过Fork/Join框架后,我感觉面试官出这道题的目的是考察是否了解Fork/Join框架。
继承RecursiveTask写一个任务类MyRecursiveTask,重写compute()方法:当只有一个文件时,解决此问题,即打开文件计算平均年龄,放到HashMap中。当文件个数大于1时,比如为n,拆分为文件个数为n/2,n/2的两个任务,invokeAll()执行这两个任务,最后合并子任务计算结果。
main方法中,创建一个文件个数为10的MyRecursiveTask任务,ForkJoinPool.submit()提交到pool中执行,用Future接收计算结果。
()实现一个内存缓存,可以实现LRU淘汰算法,提供put,get服务,设计成一个类(58同城架构组三面)
缓存需要高效存取,所以选择HashMap作为存储结构。为了实现LRU,需要能按访问顺序排序,所以选择可按访问顺序排序的LinkedHashMap存储数据,构造时指定访问顺序排序LinkedHashMap(16, 0.75, true),第3个参数true表示按访问顺序排序。
我当时回答时只说了这么个思想,其实利用LinkedHashMap实现LRU缓存是一种非常经典的用法。具体如下:
LinkedHashMap的put方法最后会调用addEntry,addEntry中先通过removeEldestEntry()方法判断是否需要删除元素(默认直接返回false即不删除),如果需要的话通过removeEntryForKey()删除最久未使用元素(也就是头结点后的第一个元素,刚被访问的元素会被摘下来插入到末尾),所以只要重写removeEldestEntry()方法,在其中判断size大于缓存容量时返回true,即可快速利用LinkedHashMap实现一个LRU缓存。
()实现一个单例模式(哗啦啦一面)
写单例模式时一定要考虑线程安全,为了简便,直接写饿汉模式:
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
但面试官看过后,肯定会让写个lady-load的懒汉模式,所以必须会写double-check形式的单例,注意必须volatile变量
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
()有个很大的日志文件,里面有时间戳2018-03-21 17:30:21,如何高效的实现按时间戳排序,以及快速选出指定时间范围内的日志?(哗啦啦一面)
按时间戳拆分成小文件,比如一天一个,先在小文件内排序,再整体排序。
按范围查询时也是直接定位到多个小文件,第一个和最后一个小文件要在文件内查询,得到起始行和结束行。
算法
排序
()你知道哪些排序算法?(58同城业务部一面)
选择排序O(n^2),插入排序O(n^2),折半插入排序O(n^2),冒泡排序O(n^2),快速排序O(nlogn),希尔排序,归并排序O(nlogn),堆排序O(nlogn)
()快速排序的思想?时间复杂度?(百度二面,58同城业务部一面)
选取一个枢轴,把小于枢轴的元素交换到左边,大于枢轴的换到右边,采用二分的思想,递归对左右两部分执行此操作直到有序。
快速排序对数组中的数据分布很敏感,当数据分布较随机时,快速排序性能最好,复杂度为O(nlogn),如果数组已基本有序,快排性能最差,复杂度为O(N^2),退化为冒泡排序。
()快速排序和冒泡排序的区别?(58同城业务部一面)
快排和冒泡都是基于交换的排序,冒泡排序是n趟两两比较并交换,快速排序是每次和枢轴比较后分为左右两部分,递归进行logn次。
冒泡排序的时间复杂度为O(n^2)
快速排序对数组中的数据分布很敏感,当数据分布较随机时,快速排序性能最好,复杂度为O(nlogn),如果数组已基本有序,快排性能最差,复杂度为O(N^2),退化为冒泡排序
平衡二叉树/红黑树/B树
()介绍下红黑树?红黑树和普通平衡二叉树的区别?(蚂蚁金服电话面试,58同城架构组一面,哗啦啦一面)
红黑树是近似平衡的一种二叉搜索树,相对于AVL树来说调整开销更低,avl树适用于查询多修改少的情况,如果修改也很多,红黑树效率更高
()介绍下B-Tree?(58同城架构组一面,58到家一面)
B树是一种多叉平衡树,每个节点上有多个key,有k个关键字的结点有k+1个孩子,可在O(h)(h为树的高度)时间复杂度内查询到数据。
由于B树是多叉的,对于同样的结点个数n,B树比普通平衡二叉树的高度更小,非常适合用于文件系统或数据库索引,可减少磁盘IO次数。
()B-Tree是二叉的吗?(58到家一面)
不是,B树是多叉的,有k个关键字的结点有k+1个孩子。
图
()图的连通性?赋权图单源最短路径问题?(腾讯一面)
给定一个有向图,有n1,…,nn个结点,每条边上有权重值,问(1)给定一个起始点,看能否由起始点到达图上所有结点?(2)如果能到达的话,求所有边的权重和的最小值。
第1问判断图的连通性,从起始点开始深度或广度遍历,能遍历到所有的结点就是连通的。使用邻接矩阵存储图时,时间复杂度是O(n^2)
第2问带权单源最短路径问题,即迪杰斯特拉算法。
()了解最短路径算法吗?(58同城业务部一面)
无权图的最短路径只要做广度优先遍历第一次遍历到结点时的路径就是最短路径,赋权图的单源最短路径用地杰斯特拉算法求,多源最短路径用弗洛伊德算法求
()说下迪杰斯特拉算法的思想?它是用于有向图的还是无向图的?(58同城业务部一面)
迪杰斯特拉算法用于求从一个起点开始的单源最短路径问题。采用贪心的策略,用一个集合S保存已找到最短路径的结点的集合,用一个数组dist[]保存当前源点到各结点的最短距离,每次从dist中找一个最小值对应的结点加入集合S,然后更新dist看经过刚加入S的结点是否有更短的路径,不断循环直到S中包含所有结点。
迪杰斯特拉算法既能用于有向图,也能用于无向图,一般用邻接矩阵作为图的存储结构。
手写代码
字符串处理
()给一个字符串,判断其是否合法IPv4地址?返回true false (百度一面)
用split分割后判断是否是4个元素,以及每个元素是否0-255,这道题关键是case考虑是否全面,考点就在这
栈/队列
()实现一个栈,增加getMaxValue()方法(最大栈问题)(滴滴二面)
把两个栈封装在一个栈中,其中的一个栈存放正常的元素,另一个栈max存放当前栈的最大元素
()输入是给定的一个linux目录,比如”/usr/local/bin/./..”,输出处理了.和..的最终目录。可以假定输入肯定是合法的目录(腾讯一面)
用栈实现,先用String.split()分割成一个String数组,依次遍历,遇到.忽略,遇到..出栈,其他则入栈。最后将栈内元素逆序拼接,中间加斜线。
需要考虑/usr//local这种连着两个斜线的情况
数组/链表
()判断两个单链表是否相交,找出交点?(哗啦啦一面)
分别遍历两个链表,最后一个结点相同(用==比较引用相同)则说明有交点。
同时开始遍历两个链表,其中一个结束后开始计数,得到两个链表长度查m,此时也知道了哪个链表更长。
较长的链表先走m步,然后较短的链表开始,不断比较结点是否相等,遇到的第一个相等结点就是交点。
递归
()写一个递归求阶乘(58到家一面)
int getValue(int n){
if(n<1) return 0;
if(n==1) return 1;
else return n*getValue(n-1);
}
二分
()给个数组{1,1,1,2,2,2,3,3,4,4,4,5,8,9,9},有序,有重复,根据输入参数返回值,返回比输入值大的下一个数,例如输入1返回2,3返回4,6返回-1(不存在),9返回0。(58到家二面)
O(n)顺序遍历 。嗯,怎么优化?
用一个map存储 。建立map过程已经O(n)了,还有吗?
因为数组是有序的,用二分搜索的思想。
利用HashMap
()去除String字符串中的重复字符,字符串中字符无序,在白板上写代码(雪球一面)
用HashMap判断去重
()数据库表中有3个字段,(id,pid,name),pid字段是id的父亲,遍历形成一个树形结构。(58同城架构组二面)
比如
id pid name
1 0 ..
5 4 ..
2 1 ..
3 1 ..
表示1的父亲是0,2和3都是1的孩子,表中内容是无序的,把他转化为这样一个类:
Class C{
int id;
List(C) subs; //id的孩子列表
}
返回值是根节点的class对象,通过这个对象可以找到表中所有数据行。
伪代码:
C getClassC(Set S){
//申请个map,key是id,value是C对象
Map(Integer,C) map = new Map;
//先建立根节点结构
C root = new C;
root.id=0;
root.subs=null;
map.put(0,root);//把根放进去
//遍历集合S,把所有元素生成C对象放进map中
for(s:S){
C c = new C;
c.id=s.id;
c.subs=null;
map.put(s.id,c);
}
for(s:S){ //遍历集合s中的每行数据
//建立当前遍历元素s结构
C sc = new C;
sc.id=s.id;
sc.subs=null;
C father = map.get(s.pid);//通过s父亲的id(pid)直接从map中拿到父亲对象
father.subs.add(sc);//把s挂到他父亲的subs列表上
}
}
Docker/k8s/容器
()image container 的区别?从 layer 层来说说区别?
镜像image运行起来就是容器。
image是分层构建的,启动为容器后,再最上面覆盖一个读写层,容器中对文件的修改是先复制到读写层再修改,容器停止时改动会消失。
()如何持久化容器里的数据?
1、容器和宿主机共享 volume,有分为两种方式,一种是 docker run 启动时 -v 进行目录映射,另一种是先创建好一个 volume,启动时指定。
2、容器修改完后 docker commit
成为一个新的镜像
3、使用外部持久化服务,例如 aws s3 等
()k8s的服务发现机制?
k8s 有两种服务发现方式:
1、Pod创建的时候,集群中所有当前可用的 service 的 ip 和 port 会以环境变量的形式注入到pod里,在pod中的服务可以通过环境变量来知晓当前集群中的其他服务地址
2、K8s集群会内置一个dns服务器,service创建成功后,会在dns服务器里导入一些记录,想要访问某个服务,通过dns服务器解析出对应的ip和port,从而实现服务访问
()主机网络和非主机网络的区别?如何看一个Pod是否主机网络?
()k8s的service类型有哪几种?分别有什么区别?
()如何从集群外访问k8s内的服务?
计算机基础
网络
()TCP建立连接三次握手过程?期间client和server的状态?为什么需要3次握手?(百度二面,哗啦啦一面)
client发送syn,进入同步发送状态;
server发送syn,ack,进入连接等待状态;
client发送ack,seq,开始传输数据,双方进入连接建立状态。
为了防止失效的连接请求再次到达server,server直接建立连接而浪费资源
()TCP断开连接四次挥手过程?为什么需要4次而不是3次?(百度二面)
client传输完数据发送fin
server收到请求,返回给client知道了ack
server等待数据传输完成,发送fin
client收到,发送确认ack
因为server收到fin后,数据可能还未传输完,不能直接返回fin来关闭连接,只能先告诉client我知道你不发送了,等数据传输完再给client发送fin
()MTU是哪一层的属性?MTU由谁决定?通常值是多少?
操作系统
()进程和线程的区别?(百度二面)
进程是资源分配的单位,线程是调度的单位。进程中可开多个线程,线程共享进程的资源(如内存,这就涉及到线程并发问题)。比如java中,启动一个jvm是一个进程,我们在jvm上可以自己创建线程。
()进程cpu占用高如何排查?
1、top查看进程的cpu占用,默认按cpu占用降序排序,按 c 可显示进程的启动命令,可大概看出是哪个进程。
2、top -Hp pid 查看指定进程号的线程,能看到每个线程的cpu占用,大概能看出是什么线程
如果是java进程的话,继续
3、可以通过 jstack pid
查看 jvm 线程信息,其中的 nid
就是线程号,是十六进制的。
可以将 top -Hp
结果中最耗cpu的线程号转为十六进制后(比如 d4c)在 jstack 结果中搜索 0xd4c
,从而看到指定线程号的堆栈。
4、或者有 arthas 的话,通过 thread -n 3 能看到最耗cpu的3个线程及其堆栈,相当于直接将 top -H + jstack 结合到一起了,很方便。
Linux和Shell
()如何查看某进程的tcp连接?(百度二面)
netstat -anp | grep pid
()如何查看一个文件的属性?(百度二面)
ll file(ls -l file)
()说一下文件的权限?(百度二面)
最左边表示文件类型,-是文件,l是连接,d是目录
然后是三组属性,分别表示文件拥有着、拥有着所在组、其他用户的权限。每组属性包含三个字母r读,w写,x执行,-无此属性。
()如何将一个不可执行的文件变为可执行的?(百度二面,滴滴一面)
chmod u+x file
()如何看一个进程打开的文件?(滴滴一面)
lsof -p pid(lsof | grep pid)
()top命令结果是如何排序的?(滴滴一面)
默认进入top时,各进程是按照CPU的占用量来排序的
输入top命令后,输入大写P按cpu占用排序,输入大写M按内存占用排序
()kill命令的-2,-9,-15有什么区别?实现原理是什么?(滴滴一面)
-15,默认值,发送SIGTERM信号,可以被进程忽略或处理
-9,发送SIGKILL信号,不能被忽略或处理,强行终止进程
-2,发送SIGINT信号,等同于ctrl+c
实现原理是向进程发送信号,每个信号都关联一个默认行为,如果进程没有捕获并处理信号则会执行默认行为。
()linux上从文件中筛选一个关键字,用哪个命令?(58到家电话面试)
grep keyword file
()Nginx日志,里面有ip字段,写shell脚本找出出现次数最多的10个ip ?(哗啦啦一面)
()你了解过或者说改过哪些 linux 内核参数?做什么用的?为什么要改?
智力题
()经典的村里有几只病狗问题?(腾讯二面)
()估算问题,估算下中国现在有多少只用来吃肉的羊?(腾讯二面)
下一篇 面试准备16-智力题
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: