Java interviewee-基础知识《一》

说明:所有面试题都是从网上查找到的,对应的答案是我自己从度娘上找的,并融合了自己的理解,如有侵权,请告知。

一.基础知识:

1)集合类:List和Set比较,各自的子类比较(ArrayList,Vector,LinkedList;HashSet,TreeSet);

1.List和Set都是接口继承于Collection接口。
2. 最大的不同就是List是可以重复的。而Set是不能重复的。(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的) 
3.
List接口有三个实现类:LinkedList,ArrayList,Vector ;
Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;

ArrayList : 由数组实现的List。允许 对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList, 而不是用来插入和移除元素。因为那比LinkedList开销要大很多。

LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

HashSet : 为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
说白了,HashSet就是限制了功能的HashMap
TreeSet : 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

HashMap : Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。

[注]散列表:就是hash表,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
LinkedHashMap : 类似于HashMap,但是迭代遍历它时, 取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。

2)HashMap的底层实现,之后会问ConcurrentHashMap的底层实现;

答: HashMap 使用数组+链表实现的;解决冲突的方式是什么?数组+链表 不会有冲突
HashMap的数据结构是数组和链表的结合
当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。


跟hbase一样是分段锁。
ConcurrentHashMap 是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的 写并发 能力,但同时降低了对读一致性的要求
ConcurrentHashMap采用了 分段锁 的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。
ConcurrentHashMap是弱一致性的。
ConcurrentHashMap中的分段锁称为 Segment ,它即类似于HashMap( JDK7与JDK8中HashMap的实现 )的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。ConcurrentHashMap中的HashEntry相对于HashMap中的Entry有一定的差异性:HashEntry中的value以及next都被volatile修饰,这样在多线程读写过程中能够保持它们的可见性。

不变(Immutable)和易变(Volatile)
    ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:
static final class HashEntry<K,V> { final K key; final int hash; volatile V value; final HashEntry<K,V> next; }
  可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

 所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。

2.hashtable concurrenthashtable 有什么区别,底层实现是什么样的?性能差别是怎么体现的。
 
它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。
因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。 简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。


3)如何实现HashMap顺序存储:可以参考LinkedHashMap的底层实现;

如何实现迭代有序?
重新定义了数组中保存的元素Entry(继承于HashMap.Entry),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了 双向链接列表 。仍然保留next属性,所以既可像HashMap一样快速查找,用next获取该链表下一个Entry,也可以通过双向链接,通过after完成所有数据的有序迭代。

简单来说:就是重写entry

5)String,StringBuffer和StringBuilder的区别;

答:String 是final的;StringBuffer是线程安全的;StringBuilder不是线程安全的。

6)Object的方法有哪些:比如有wait方法,为什么会有;

registerNatives()  
getClass()  
hashCode()  
equals(Object obj)   
clone()  
toString()   
notify()  
notifyAll()  
wait(long timeout)  
wait(long timeout, int nanos)  
wait()  
finalize()  
wait和notify的本质是基于条件对象的,而且只能由已经获得锁的线程调用。j ava的每个Object都有一个隐式锁,这个隐式锁关联一个Condition条件对象,线程拿到这个隐式锁(比如进入synchronized代码区域),就可以调用wait,语义是在Condition条件对象上等待,其他的线程可以在这个Condition条件对象上等待,等满足条件之后,就可以调用notify或者notifyAll来唤醒所有在此条件对象上等待的线程。
建议你参考一下ReentrantLock类,上面说的东西它都能反映出来。

7)wait和sleep的区别,必须理解;

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
在调用sleep()方法的过程中,线程不会释放对象锁。

8)JVM的内存结构,JVM的算法;

JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;
方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
控制参数
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小。

2、JVM垃圾收集算法:
内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.
判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
垃圾收集算法
“标记-清除”(Mark-Sweep)算法:标记可以回收的对象,然后清楚;
复制算法:非常大小相同的两部分,复制不回收的到空闲区域;
老年代:标记-压缩算法:标记不回收的对象,往一边移动;
分代收集算法:
“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
垃圾收集器
Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
Parallel收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

9)强引用,软引用和弱引用的区别;

强引用:  
String str = “abc”; 
list.add(str); 
软引用: 
如果弱引用对象回收完之后,内存还是报警,继续回收软引用对象 
弱引用: 
如果虚引用对象回收完之后,内存还是报警,继续回收弱引用对象 
虚引用: 
虚拟机的内存不够使用,开始报警,这时候垃圾回收机制开始执行System.gc(); String s = “abc”;如果没有对象回收了, 就回收没虚引用的对象

10)数组在内存中如何分配;

对于Java数组的初始化,有以下两种方式,这也是面试中经常考到的经典题目:
  1. 静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度,如:
1 //只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为4 2 String[] computers = {"Dell", "Lenovo", "Apple", "Acer"};  //① 3 //只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为3 4 String[] names = new String[]{"多啦A梦", "大雄", "静香"};  //②
  1. 动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值,如:
1 //只是指定了数组的长度,并没有显示的为数组指定初始值,但是系统会默认给数组数组元素分配初始值为null 2 String[] cars = new String[4];  //③
 
前面提到,因为Java数组变量是引用类型的变量,所以上述几行初始化语句执行后,三个数组在内存中的分配情况如下图所示:

11)用过哪些设计模式,手写一个(除单例);

单例模式;工厂模式;


12)springmvc的核心是什么,请求的流程是怎么处理的,控制反转怎么实现的;

答:核心是DispatherServlet。

13)spring里面的aop的原理是什么;

OOP解决纵向问题;AOP解决横向问题。
spring里面的ioc原理是什么?
控制反转:
对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

14)mybatis如何处理结果集

反射,建议看看源码;

15)java的多态表现在哪里;

16)接口有什么用;

举个例子:
接口就是个招牌。
比如说你今年放假出去杭州旅游,玩了一上午,你也有点饿了,突然看到前面有个店子,上面挂着KFC,然后你就知道今天中饭有着落了。
KFC就是接口,我们看到了这个接口,就知道这个店会卖炸鸡腿(实现接口)。
那么为神马我们要去定义一个接口涅,这个店可以直接卖炸鸡腿啊(直接写实现方法),是的,这个店可以直接卖炸鸡腿,但没有挂KFC的招牌,我们就不能直接简单粗暴的冲进去叫服务员给两个炸鸡腿了。
要么,我们就要进去问,你这里卖不卖炸鸡腿啊,卖不卖汉堡啊,卖不卖圣代啊(这就是反射)。很显然,这样一家家的问实在是非常麻烦(反射性能很差)。
要么,我们就要记住,中山路108号卖炸鸡,黄山路45号卖炸鸡(硬编码),很显然这样我们要记住的很多很多东西(代码量剧增),而且,如果有新的店卖炸鸡腿,我们也不可能知道(不利于扩展)。


作者:Ivony
链接:https://www.zhihu.com/question/20111251/answer/16585393
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

17)说说http, https协议;


18)tcp/ip协议簇;


19)osi五层网络协议;


20)tcp,udp区别;


21)用过哪些加密算法:

对称加密,非对称加密算法;
答:MD5

22)说说tcp三次握手,四次挥手;


23)cookie和session的区别,分布式环境怎么保存用户状态;

cookie主要作用于前端;session主要用于后端。
1、session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息。
2、session中保存的是对象,cookie中保存的是字符串。
3、session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到。而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。
4、session需要借助cookie才能正常访问
一。分布式Session的几种实现方式
1.基于数据库的Session共享
2.基于NFS共享文件系统
3.基于memcached 的session,如何保证 memcached 本身的高可用性?
4. 基于resin/tomcat web容器本身的session复制机制
5. 基于TT/Redis 或 jbosscache 进行 session 共享。
6. 基于cookie 进行session共享
或者是:
一、Session Replication 方式管理 (即session复制)
简介:将一台机器上的Session数据广播复制到集群中其余机器上
使用场景:机器较少,网络流量较小
优点:实现简单、配置较少、当网络中有机器Down掉时不影响用户访问
缺点:广播式复制到其余机器有一定廷时,带来一定网络开销
二、Session Sticky 方式管理
简介:即粘性Session、当用户访问集群中某台机器后,强制指定后续所有请求均落到此机器上
使用场景:机器数适中、对稳定性要求不是非常苛刻
优点:实现简单、配置方便、没有额外网络开销
缺点:网络中有机器Down掉时、用户Session会丢失、容易造成单点故障
三、缓存集中式管理
简介:将Session存入分布式缓存集群中的某台机器上,当用户访问不同节点时先从缓存中拿Session信息
使用场景:集群中机器数多、网络环境复杂
优点:可靠性好
缺点:实现复杂、稳定性依赖于缓存的稳定性、Session信息放入缓存时要有合理的策略写入

24)git,svn区别;

1.git是分布式的scm,svn是集中式的。(最核心)
2.git是每个历史版本都存储完整的文件,便于恢复,svn是存储差异文件,历史版本不可恢复。(核心)
3.git可离线完成大部分操作,svn则不能。
4.git有着更优雅的分支和合并实现。
5.git有着更强的撤销修改和修改历史版本的能力
6.git速度更快,效率更高。
基于以上区别,git有了很明显的优势,特别在于它具有的本地仓库。

25)请写一段栈溢出、堆溢出的代码;

设置eclipse执行内存:
java project 可以右击工程 Run AS
-->选最下面Run...-->
Arguments-->在 VM arguments里面填
-Xmx256m。这样就可以设置它运行时最大内存为256m
例子: -Xmx128m -Xms64m -Xmn32m -Xss16m

堆溢出:内存超出

public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
int i=0;
while(true){
list.add(new byte[5*1024*1024]);
System.out.println("分配次数:"+(++i));
}
}

栈溢出:深度超出

public class StackSOFTest { int depth = 0 ;
//递归调用 public void sofMethod(){ depth ++ ; sofMethod(); } public static void main(String[] args) { StackSOFTest test = null; try { test = new StackSOFTest(); test.sofMethod(); } finally { System.out.println("递归次数:"+test.depth); } }}

26)ThreadLocal可以用来共享数据吗;

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
ThreadLocal的确是和java线程有关,不过它并不是java线程的一个实现,它只是用来维护本地变量。针对每个线程,提供自己的变量版本,主要是为了避免线程冲突,每个线程维护自己的版本。彼此独立,修改不会影响到对方。
理论上来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。


推荐博客:
参考:
代码实现溢出:
JVM类加载器:
zookeeper选举:
redis和memecache的区别:
redis持久化:
强弱引用:


【ps】本文原创发布于微信公众号「prepared」,关注微信公众号,及时获取最新博主消息!

猜你喜欢

转载自blog.csdn.net/prepared/article/details/80701344