java面试题——高级篇

1、集合

Hashmap的原理

源码分析参考文章:http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.htmlhtml

题目参考文章:http://www.importnew.com/7099.htmljava

总结:mysql

HashMap基于hashing原理,咱们经过put()和get()方法储存和获取对象。当咱们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,经过键对象的equals()方法找到正确的键值对,而后返回值对象。程序员

HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每一个链表节点中储存键值对对象。web

当两个不一样的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。面试

面试时可能会问到的问题:redis

1.“你用过HashMap吗?” “什么是HashMap?你为何用到它?”算法

  用过,HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,惟一的区别是Hashtalbe中的方法是线程安全的,也就是同步的)。此实现提供全部可选的映射操做,并容许使用null键和null值。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。sql

2.“你知道HashMap的工做原理吗?” “你知道HashMap的get()方法的工做原理吗?数据库

  答案:HashMap是基于hashing的原理,咱们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当咱们给put()方法传递键和值时,咱们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。当获取对象时,经过键对象的equals()方法找到正确的键值对,而后返回值对象。

  解释:这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,做为Map.Entry。这一点有助于理解获取对象的逻辑。若是你没有意识到这一点,或者错误的认为仅仅只在bucket中存储值的话,你将不会回答如何从HashMap中获取对象的逻辑。这个答案至关的正确,也显示出面试者确实知道hashing以及HashMap的工做原理

3.当两个对象的hashcode相同会发生什么

  答案:由于hashcode相同,因此它们的bucket位置相同,‘碰撞’会发生。由于HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。

  解释:这个答案很是的合理,虽然有不少种处理碰撞的方法,这种方法是最简单的,也正是HashMap的处理方法。

4.若是两个键的hashcode相同,你如何获取值对象?

  答案:当咱们调用get(k)方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置以后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

  解释:许多状况下,面试者会在这个环节中出错,由于他们混淆了hashCode()和equals()方法。由于在此以前hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明做final的对象,而且采用合适的equals()和hashCode()方法的话,将会减小碰撞的发生,提升效率。不可变性使得可以缓存不一样键的hashcode,这将提升整个获取对象的速度,使用String,Interger这样的wrapper类做为键是很是好的选择。

5.若是HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

  答案:默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)同样,将会建立原来HashMap大小的两倍的bucket数组,来从新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫做rehashing,由于它调用hash方法找到新的bucket位置。

  解释:除非你真正知道HashMap的工做原理,不然你将回答不出这道题。

6.你了解从新调整HashMap大小存在什么问题吗?

  答案:当从新调整HashMap大小的时候,确实存在条件竞争,由于若是两个线程都发现HashMap须要从新调整大小了,它们会同时试着调整大小。在调整大小的过程当中,存储在链表中的元素的次序会反过来,由于移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了不尾部遍历(tail traversing)。若是条件竞争发生了,那么就死循环了。

  解释:你可能回答不上来,这时面试官会提醒你当多线程的状况下,可能产生条件竞争(race condition)。

7.为何String, Interger这样的wrapper类适合做为键?

  答案:String, Interger这样的wrapper类做为HashMap的键是再适合不过了,并且String最为经常使用。由于String是不可变的,也是final的,并且已经重写了equals()和hashCode()方法了。其余的wrapper类也有这个特色。不可变性是必要的,由于为了要计算hashCode(),就要防止键值改变,若是键值在放入时和获取时返回不一样的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其余的优势如线程安全。若是你能够仅仅经过将某个field声明成final就能保证hashCode是不变的,那么请这么作吧。由于获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是很是重要的。若是两个不相等的对象返回不一样的hashcode的话,那么碰撞的概率就会小些,这样就能提升HashMap的性能。

8.咱们可使用自定义的对象做为键吗?

  答案:这是前一个问题的延伸。固然你可能使用任何对象做为键,只要它遵照了equals()和hashCode()方法的定义规则,而且当对象插入到Map中以后将不会再改变了。若是这个自定义对象时不可变的,那么它已经知足了做为键的条件,由于当它建立以后就已经不能改变了。

9.咱们可使用CocurrentHashMap来代替Hashtable吗?

  答案:这是另一个很热门的面试题,由于ConcurrentHashMap愈来愈多人用了。咱们知道Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由于它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap固然能够代替HashTable,可是HashTable提供更强的线程安全性

10.Hashmap为何大小是2的幂次

  答案为了hash的平均分布,减小碰撞值

11.什么是HashMap的加载因子

  答案:加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减少了,但:空间浪费多了.

Arraylist的原理

源码分析参考文章:http://www.importnew.com/19867.html

问题参考文章:http://www.importnew.com/9928.html

ArrayList是基于数组实现的,是一个动态数组,其容量能自动增加,相似于C语言中的动态申请内存,动态增加内存。

ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下能够考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可使用concurrent并发包下的CopyOnWriteArrayList类。

ArrayList实现了Serializable接口,所以它支持序列化,可以经过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是经过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

一、ArrayList的大小是如何自动增长的?你能分享一下你的代码吗?

  答案:当有人试图在arraylist中增长一个对象的时候,Java会去检查arraylist,以确保已存在的数组中有足够的容量来存储这个新的对象。若是没有足够容量的话,那么就会新建一个长度更长的数组,旧的数组就会使用Arrays.copyOf方法被复制到新的数组中去,现有的数组引用指向了新的数组

  源码:

 1 //ArrayList Add方法:
 2 public boolean add(E e){
 3     ensureCapacity(size+1); //Increment modCount!!
 4     elementData[size++] = e;
 5     return true;
 6 }
 7  
 8 //ensureCapacity方法:处理ArrayList的大小
 9 public void ensureCapacity(int minCapacity) {
10     modCount++;
11     int oldCapacity = elementData.length;
12     if (minCapacity > oldCapacity) {
13     Object oldData[] = elementData;
14     int newCapacity = (oldCapacity * 3)/2 + 1;
15     if (newCapacity < minCapacity)
16         newCapacity = minCapacity;
17     // minCapacity is usually close to size, so this is a win:
18     elementData = Arrays.copyOf(elementData, newCapacity);
19     }
20 }

  解释:请注意这样一个状况:新建了一个数组;旧数组的对象被复制到了新的数组中,而且现有的数组指向新的数组。

二、什么状况下你会使用ArrayList?何时你会选择LinkedList?

  答案:访问元素比插入或者是删除元素更加频繁的时候,你应该使用ArrayList。当你在某个特别的索引中,插入或者是删除元素更加频繁,或者你压根就不须要访问元素的时候,你会选择LinkedList。

     这里的主要缘由是,在ArrayList中访问元素的最糟糕的时间复杂度是”1″,而在LinkedList中可能就是”n”了。在ArrayList中增长或者删除某个元素,一般会调用System.arraycopy方法,这是一种极为消耗资源的操做,所以,在频繁的插入或者是删除元素的状况下,LinkedList的性能会更加好一点。

三、当传递ArrayList到某个方法中,或者某个方法返回ArrayList,何时要考虑安全隐患?如何修复安全违规这个问题呢?

  答案:

当array被当作参数传递到某个方法中,若是array在没有被复制的状况下直接被分配给了成员变量,那么就可能发生这种状况,即当原始的数组被调用的方法改变的时候,传递到这个方法中的数组也会改变。下面的这段代码展现的就是安全违规以及如何修复这个问题。

ArrayList被直接赋给成员变量——安全隐患:

修复这个安全隐患:

四、如何复制某个ArrayList到另外一个ArrayList中去?写出你的代码?

  答案:下面就是把某个ArrayList复制到另外一个ArrayList中去的几种技术:

  1. 使用clone()方法,好比ArrayList newArray = oldArray.clone();
  2. 使用ArrayList构造方法,好比:ArrayList myObject = new ArrayList(myTempObject);
  3. 使用Collection的copy方法。

    注意1和2是浅拷贝(shallow copy)。

五、在索引中ArrayList的增长或者删除某个对象的运行过程?效率很低吗?解释一下为何?

  答案:在ArrayList中增长或者是删除元素,要调用System.arraycopy这种效率很低的操做,若是遇到了须要频繁插入或者是删除的时候,你能够选择其余的Java集合,好比LinkedList。

看一下下面的代码:

在ArrayList的某个索引i处添加元素:

删除ArrayList的某个索引i处的元素:

 2、java虚拟机

native关键字

  修饰方法表示这个方法是本地方法,即虚拟机的底层C程序,如线程Thread的start方法调用的start0()方法就是一个本地方法

java内存模型

什么是类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,而且向Java程序员提供了访问方法区内的数据结构的接口。

 

 

类加载器并不须要等到某个类被“首次主动使用”时再加载它,JVM规范容许类加载器在预料某个类将要被使用时就预先加载它,若是在预先加载的过程当中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)若是这个类一直没有被程序主动使用,那么类加载器就不会报告错误

从更高的一个维度再次来看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设置每一个线程的堆栈大小。

没有直接设置老年代的参数,可是能够设置堆空间大小和新生代空间大小两个参数来间接控制。老年代空间大小=堆空间大小-年轻代大空间大小

何时触发垃圾回收

1.当程序在新生代申请内存失败时会触发垃圾回收,回收的过程:把eden区还存活的对象放到S1(幸存区1),若是S1已结满了就把S1还存活的对象放到S2(幸存区2),S2满了就把幸存的对象放到老年代,老年代满了就把幸存的对象放到持久代

2.当老年代或者持久代内存满了就触发full gc,在代码里面显示的使用System.gc()也会触发full gc,full gc会使应用程序变得很慢,因此要尽可能避免full gc,jvm调优的本质就是减小full gc

3、BIO、NIO、AIO

BIO:blocking IO,阻塞IO

NIO:non-blocking IO,非阻塞IO,为全部的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它能够提供非阻塞式的高伸缩性网络。

AIO:asynchronous-non-blocking IO:异步非阻塞IO

1.基本概念引入

 

同步:用户触发IO操做,你发起了请求就得等着对方给你返回结果,你不能走,针对调用方的,你发起了请求你等

异步:触发触发了IO操做,即发起了请求之后能够作本身的事,等处理完之后会给你返回处理完成的标志,针对调用方的,你发起了请求你不等

阻塞:你调用我,我试图对文件进行读写的时候发现没有可读写的文件,个人程序就会进入等待状态,等能够读写了,我处理完给你返回结果,这里的等待和同步的等待有很大的区别,针对服务提供方的,你调用我我发现服务不可用我等

非阻塞:你调用我,我试图对文件读写的时候发现没有读写的文件,不等待直接返回,等我发现能够读写文件处理完了再给你返回成功标志,针对服务提供方的,你调用我我不等,我处理完了给你返回结果

二、Java对BIO、NIO、AIO的支持:
Java BIO :  同步阻塞:你调用我,你等待我给你返回结果,我发现没有可读写的资源我也等待,两个一块儿等,JDK1.4之前的惟一选择, 适用于数目比较少而且比较固定的架构,对服务器资源要求比较高,你们都在等资源,等服务提供方处理完了再给你返回结果
Java NIO : 同步非阻塞: 你调用我,你等待我给你返回结果,我发现没有能够读写的资源,我不等待先直接返回,等我发现有能够读写的资源之后处理完给你返回结果, 适用于链接数目多且链接时间比较短(轻操做)的架构,好比聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
Java AIO(NIO.2) :  异步非阻塞:你调用我,你不等待继续作本身的事,我发现没有能够读写的资源,我也不等待继续作我本身的事,等有能够读写的资源的时候我处理完给你返回结果, 适用于链接数目多且链接时间比较长(重操做)的架构,好比相册服务器,充分调用OS参与并发操做,编程比较复杂,JDK7开始支持。
三、BIO、NIO、AIO适用场景分析:
BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的惟一选择,但程序直观简单易理解。
NIO方式适用于链接数目多且链接比较短(轻操做)的架构,好比聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于链接数目多且链接比较长(重操做)的架构,好比相册服务器,充分调用OS参与并发操做,编程比较复杂,JDK7开始支持。
 
另外,I/O属于底层操做,须要操做系统支持,并发也须要操做系统的支持,因此性能方面不一样操做系统差别会比较明显。

4、数据库

乐观锁和悲观锁

乐观锁:

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据通常状况下不会形成冲突,因此在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,若是发现冲突了,则让返回用户错误的信息,让用户决定如何去作

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。通常的实现乐观锁的方式就是记录数据版本

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳

备注:

数据版本,为数据增长的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,若是数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,不然认为是过时数据。

使用版本号实现乐观锁

使用版本号时,能够在数据初始化时指定一个版本号,每次对数据的更新操做都对版本号执行+1操做。并判断当前版本号是否是该数据的最新的版本号。

1 1.查询出商品信息
2 select (status,status,version) from t_goods where id=#{id}
3 2.根据商品信息生成订单
4 3.修改商品status为2
5 update t_goods 
6 set status=2,version=version+1
7 where id=#{id} and version=#{version};

 

乐观锁优势与不足

乐观并发控制相信事务之间的数据竞争(data race)的几率是比较小的,所以尽量直接作下去,直到提交的时候才去锁定,因此不会产生任何锁和死锁。但若是直接简单这么作,仍是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,通过修改之后写回数据库,这时就遇到了问题。

悲观锁:

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度(悲观),所以,在整个数据处理过程当中,将数据处于锁定状态。 悲观锁的实现,每每依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)

在数据库中,悲观锁的流程以下:

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

若是加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际须要决定。

若是成功加锁,那么就能够对记录作修改,事务完成后就会解锁了。

其间若是有其余对该记录作修改或加排他锁的操做,都会等待咱们解锁或直接抛出异常。

在查询语句后面增长LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其余线程对查询结果集中的任何一行使用排他锁时,能够成功申请共享锁,不然会被阻塞。其余线程也能够读取使用了共享锁的表,并且这些线程读取的是同一个版本的数据。

MySQL InnoDB中使用悲观锁

要使用悲观锁,咱们必须关闭mysql数据库的自动提交属性,由于MySQL默认使用autocommit模式,也就是说,当你执行一个更新操做后,MySQL会马上将结果进行提交。set autocommit=0;

 1 //0.开始事务
 2 begin;/begin work;/start transaction; (三者选一就能够)
 3 //1.查询出商品信息
 4 select status from t_goods where id=1 for update;
 5 //2.根据商品信息生成订单
 6 insert into t_orders (id,goods_id) values (null,1);
 7 //3.修改商品status为2
 8 update t_goods set status=2;
 9 //4.提交事务
10 commit;/commit work;

 

 

悲观锁优势与不足

悲观并发控制其实是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。可是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增长产生死锁的机会;另外,在只读型事务处理中因为不会产生冲突,也不必使用锁,这样作只能增长系统负载;还有会下降了并行性,一个事务若是锁定了某行数据,其余事务就必须等待该事务处理完才能够处理那行数

知识拓展:

1.排他锁

排他锁又称写锁,若是事务T对数据A加上排他锁后,则其余事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

用法:

SELECT ... FOR UPDATE;

在查询语句后面增长FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其余线程对查询结果集中的任何一行使用排他锁时,能够成功申请排他锁,不然会被阻塞。

2. 共享锁

共享锁又称读锁,是读取操做建立的锁。其余用户能够并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放全部共享锁。

若是事务T对数据A加上共享锁后,则其余事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

用法:

SELECT ... LOCK IN SHARE MODE;

在查询语句后面增长LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其余线程对查询结果集中的任何一行使用排他锁时,能够成功申请共享锁,不然会被阻塞。其余线程也能够读取使用了共享锁的表,并且这些线程读取的是同一个版本的数据。

乐观锁:修改数据库记录前,给数据库的记录加上一个版本号version,修改时把版本号加1,两我的若是同时修改数据,第一我的已经把version加1了,第二我的就不能再修改了,并发数比较少时用数据库的乐观锁和悲观锁便可知足需求,并发数比较大时就redis的队列来解决

5、服务间的通讯

webservice对应的wsdl返回的数据是xml格式的,

restfull风格的对应的wdl,返回的数据是json的,轻量级的,如今比较经常使用,去wsdl留wdl

webservice和restfull的底层实现都是socket只是实现方式不同而已

六,数据结构和算法

数据结构:hashmap和list底层原理

算法:堆栈,快速排序,递归算法

相关文章
相关标签/搜索