JAVA高级面试题总结

FILEcbfe0e33-4675-4a20-989a-911b03974949.jpg.jpg


1、HashMap的实现原理

(1)、 HashMap的数据结构

答:数据结构中有数组和链表来实现对数据的存储,

数组存储区间是连续的,占用内存严重,故空间复杂度大。但数组的二分查找时间复杂度小;特点是:寻址容易,插入和删除困难

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大。特点是:寻址困难,插入和删除容易

哈希表(Hash table)寻址容易,插入删除也容易。既满足了数据的查找方便,也不占用太多空间。

哈希表有多种不同的实现方法,最常用的方法—— 拉链法,我们可以理解为链表的数组 ,如图:

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap存储数据的容器是个线性的数组。一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,Entry就是HashMap键值对实现的一个基础bean,HashMap的基础就是一个线性数组,就是Entry[]Map里面的内容都保存在Entry[]里面


(2)、HashMap的存取实现

既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现:

// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;
// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

1)put

疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?

  这里HashMap里面用到链式数据结构的一个概念。Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。又进来一个键值对B,通过计算其index也等于0,HashMap会这样做:B.next = A,Entry[0] = B,又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起数组中存储的是最后插入的元素


public V put(K key, V value) {

if (key null)

return putForNullKey(value); //null总是放在数组的第一个链表中

int hash = hash(key.hashCode());

int i = indexFor(hash, table.length);

for (Entrye = table; e != null; e = e.next) { //遍历链表

Object k;

//如果key在链表中已存在,则替换为新value

if (e.hash hash && ((k = e.key) key || key.equals(k))) {

V oldValue = e.value;

e.value = value;

e.recordAccess(this);

return oldValue;

}

}

modCount++;

addEntry(hash, key, value, i);

return null;

}

void addEntry(int hash, K key, V value, int bucketIndex) {

Entrye = table[bucketIndex];

table[bucketIndex] = new Entry(hash, key, value, e); //参数e, 是Entry.next

//如果size超过threshold,则扩充table大小。再散列

if (size++ >= threshold)

resize(2 * table.length);

}

2)get

public V get(Object key) {

if (key null)

return getForNullKey();

int hash = hash(key.hashCode());

//先定位到数组元素,再遍历该元素处的链表

for (Entrye = table[indexFor(hash, table.length)];

e != null;

e = e.next) {

Object k;

if (e.hash hash && ((k = e.key) key || key.equals(k)))

return e.value;

}

return null;

}


参考地址:http://blog.csdn.net/vking_wang/article/details/14166593

2、解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)

  2. 再哈希法

  3. 链地址法

  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

3、Spring注解原理的详细剖析与实现


* 元注解@Target,@Retention,@Documented,@Inherited
*
@Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
* ElemenetType.CONSTRUCTOR 构造器声明
* ElemenetType.FIELD 域声明(包括 enum 实例)
* ElemenetType.LOCAL_VARIABLE 局部变量声明
* ElemenetType.METHOD 方法声明
* ElemenetType.PACKAGE 包声明
* ElemenetType.PARAMETER 参数声明
* ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
*
@Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
* RetentionPolicy.SOURCE 注解将被编译器丢弃
* RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
* RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。


//定义注解Test
@Target(ElementType.METHOD) //该注解用于方法声明

@Retention(RetentionPolicy.RUNTIME) //VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息

@Documented //将此注解包含在javadoc中

@Inherited //允许子类继承父类中的注解

public@interfaceTest {

public int id();

public String description() default "no description";

}

public class Test_1{

@Test(id=1,description="hello method1")


public void method1(){}

@Test(id=2)

public void method2(){}





//解析注释,将Test_1类所有被注解方法的信息打印出来

public static void main(String[] args) {

Method[] methods = Test_1.class.getDeclaredMethods();

for (Method method : methods) {

//判断方法中是否有指定注解类型的注解

boolean hasAnnotation = method.isAnnotationPresent(Test.class);

if(hasAnnotation) {

//根据注解类型返回方法的指定类型注解

Test annotation = method.getAnnotation(Test.class);

System.out.println("Test(method=" + method.getName() + ",id=" + annotation.id()

+ ",description=" + annotation.description() + ")");

}

}

}

}

参考地址:http://freewxy.iteye.com/blog/1149128/?from=singlemessage&isappinstalled=1

4、Spring中Bean的生命周期

1.容器寻找Bean的定义信息并且将其实例化。

2.使用依赖注入,Spring按照Bean定义信息配置Bean的所有属性。

3.如果Bean实现了BeanNameAware接口,工厂调用Bean的setBeanName()方法传递Bean的ID。

4.如果Bean实现了BeanFactoryAware接口,工厂调用setBeanFactory()方法传入工厂自身。

5.如果BeanPostProcessor和Bean关联,那么它们的postProcessBeforeInitialzation()方法将被调用。

6.如果Bean指定了init-method方法,它将被调用。

7.最后,如果有BeanPsotProcessor和Bean关联,那么它们的postProcessAfterInitialization()方法将被调用。



有两种方法可以把它从Bean Factory中删除掉。

1.如果Bean实现了DisposableBean接口,destory()方法被调用。

2.如果指定了订制的销毁方法,就调用这个方法。

Bean在Spring应用上下文的生命周期与在Bean工厂中的生命周期只有一点不同, 唯一不同的是,如果Bean实现了ApplicationContextAwre接口,setApplicationContext()方法被调用。


只有singleton行为的bean接受容器管理生命周期。 non-singleton行为的bean,Spring容器仅仅是new的替代,容器只负责创建。对于singleton bean,Spring容器知道bean何时实例化结束,何时销毁, Spring可以管理实例化结束之后,和销毁之前的行为,管理bean的生命周期行为主要未如下两个时机:

Bean全部依赖注入之后
Bean即将销毁之前

1)依赖关系注入后的行为实现:
有两种方法:A.编写init方法 B.实现InitializingBean接口
afterPropertiesSet和init同时出现,前者先于后者执行,使用init方法,需要对配置文件加入init-method属性

2)bean销毁之前的行为
有两种方法:A.编写close方法 B.实现DisposableBean接口
destroy和close同时出现,前者先于后者执行,使用close方法,需要对配置文件加入destroy-method属性

参考地址:http://blog.csdn.net/java958199586/article/details/7469147

5、实现单例的几种方

第一种(懒汉,线程不安全)

public class Singleton {

private static Singleton instance;

private Singleton (){}

public static Singleton getInstance() {

if (instance null) {

instance = new Singleton();

}

return instance;

}

}

种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

二种(懒汉,线程安全):


public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance null) {
instance = new Singleton();
}
return instance;
}

}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。





第三种(饿汉):

public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第四种(饿汉,变种):

public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}

private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}

跟第三种方式差不多,都是在类初始化即实例化instance。

第五种(静态内部类):
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第六种(枚举):
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,用这种方式写不免让人感觉生疏,在实际工作中很少见有人这么写。

第七种(双重校验锁):

public class Singleton {
private
volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton null) {
synchronized (Singleton.class) {
if (singleton null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

在JDK1.5之后,双重检查锁定才能够正常达到单例效果。




1、有哪两钟锁?悲观锁和乐观锁的用处和区别?

答:(1)悲观锁(Pessimistic Lock),每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。悲观锁适用于写比较多的情况。

(2)乐观锁(Optimistic Lock), 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

2、数据库的四种隔离级别?

答:(1)、READ UNCOMMITTED(读未提交数据) 允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现。

(2)、READ COMMITED(读已提交数据) 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然会出现。

(3)、REPEATABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题依然存在。

(4)、SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下。

3、什么情况下索引会失效?

答:(1)使用OR时,有一个列没有索引,那么其它列的索引将不起作用。

(2)对于多列索引只有在它的第一个列被where子句引用时,优化器才会选择使用该索引

(3)like查询是以%开头

(4)索引列有隐式转换,如 列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。

(5)如果mysql估计使用全表扫描要比使用索引快,则不使用索引。

(6)索引列有函数处理。

(7)对列进行计算,如:aa+10=20。

(8)某些情况使用反向操作,如:<> 、not in 、not exist、!= 。

4、Spring IOC 实现原理?

利用Java的反射机制并充当工厂的角色完成对象的装配和注入。实现对象之间的解耦

5、Spring AOP 实现原理?及应用场景?

(1)、目的:将应用程序中的业务逻辑同对其提供支持的通用服务进行分离

(2)、实现AOP的技术,主要分为两大类:

一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;

二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

(3)、应用场景:日志、权限、缓存、内容传递、错误处理、懒加载、调试、记录跟踪 优化 校准、性能优化、持久化、资源池、同步、事务。

6、Spring处理请求的完整过程?

(1)、首先用户 发送请求—— >DispatcherServlet , 前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

(2)、DispatcherServlet —— >HandlerMapping , HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

(3)、DispatcherServlet —— >HandlerAdapter , HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

(4)、HandlerAdapter —— > 处理器功能处理方法的调用, HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);

(5)、ModelAndView 的逻辑视图名—— > ViewResolver , ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其他视图技术;

(6)、View —— > 渲染 ,View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构,因此很容易支持其他视图技术;

(7)、返回控制权给 DispatcherServlet , 由 DispatcherServlet 返回响应给用户,到此一个流程结束。

7、Redis和MemCache的区别?

(1)、存储方式

memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小

redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(有快照和AOF日志两种持久化方式)。

(2)数据支持类型

Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash,Sort Set 数据结构的存储。

(3)、使用底层模型不同

redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

(4)、运行环境不同

redis目前官方只支持LINUX 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁,但是没有放到主干上。

8、介绍下集合,ArrayList和LinkedList的区别?ArrayList的底层存储方式?应用场景?

ArrayList 是数组,将对象放在连续的位置中,访问效率高插入删除效率低

LinkedList 是双向链表,可以被当作堆栈、队列或双端队列,将对象存放在独立的空间中,在每个空间中保存下一个链接的索引。访问效率低插入、删除效率高

9、HashTable和HashMap的区别?应用场景?

(1)、父类

Hashtable:基于陈旧的Dictionary类的。

HashMap:Java 1.2引进的Map接口的一个实现。

(2)、同步

Hashtable:使用同步机制,实际应用程序中,仅仅是Hashtable本身的同步并不能保证程序在并发操作下的正确性,需要高层次的并发保 护。

HashMap:没有同步机制,需要使用者自己进行并发访问控制。

(3)、是否接受值为null的Key 或Value:

Hashtable:不接受。

HashMap:接受。

(4)、缺省初始长度

Hashtable:缺省初始长度为11,初始化时可以指定initial capacity。

HashMap:缺省初始长度为16,长度始终保持2的n次方,初始化时可以指定initial capacity,若不是2的次方,HashMap将选取第一个大 于initial capacity 的2n次方值作为其初始长度。

(5)、负荷超过一定数组长度时,增长方式

Hashtable:扩展数组:2*原数组长度+1

HashMap:扩展数组: 原数组长度 * 2

详情参考:http://www.cnblogs.com/carbs/archive/2012/07/04/2576995.html











原文地址:http://qianxunclub.com/javagao-ji-mian-shi-ti-zong-jie/
本文由 千寻啊千寻创作。可自由转载、引用,但需署名作者且注明文章出处。

上一篇 :   终于知道了酒干倘卖无暗示什么意思了!

下一篇 :   Redis与Hazelcast,在重负荷下的表现孰优孰劣?

热门评论