杭州余杭区办公环境一般,两层一栋的办公楼,但是开发工位是连成一片的没有卡位一说。比较拥挤。
面试两轮技术面,总共耗时3小时,各种等待就有1小时,最后人事说三天答复,本人未通过。
一轮技术面试:
面试官比较和蔼,整体仪态大气正式,绅士风度,估计三十几岁是管理层次,问的问题有深度,直接就问底层的实现等。
1. Map的底层实现:http://www.importnew.com/7099.html
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
Hanshcode 然后根据算法 mod 算出位置
2. hashMap重写equals 方法为什么一定要重写hashcode
如果不重写,两个对象明明是“相等”,而hashCode却不一样。
1. 同一个对象,无论何时调用hashCode()得到的返回值必须一样。
如果一个key对象在put的时候调用hashCode()决定了存放的位置,而在get的时候调用hashCode()得到了不一样的返回值,这个值映射到了一个和原来不一样的地方,那么肯定就找不到原来那个键值对了。
2. hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象
不相等的对象的hashCode()的结果可以相等。hashCode()在注意关注碰撞问题的时候,也要关注生成速度问题,完美hash不现实
3. 一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段
3. Listt删除,添加用for(i=0;i
查询的时候可用:
普通for
List
For(int I =0 ;I
System.out.println(list.get(i));
}
迭代器
Iterator
While(iter.hasNext){
System.out.println(iter.next()));
}
remove的时候可用:
/* 普通for
List
For(int I =0 ;I
List.remove(i) //只有删除第一个元素的时候不会报错,其他都会报错,应为没删除一个size都会从新算。
} */
迭代器
Iterator
While(iter.hasNext){
String i = iter.hasNext;
i.remover();
}
4. 类装载的时候classload机制参考http://blog.csdn.net/briblue/article/details/54973413
ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。
加载顺序:
1. Bootstrap CLassloder
2. Extention ClassLoader
3. AppClassLoader
Test.class appClaaLoader加载,int.class BootsrapClassloader加载
每个类加载器都有一个父加载器,AppClassLoader的父加载器是ExtClassLoader
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用。
5. Volatile的作用,解决的什么问题:
参考http://www.cnblogs.com/sunrunzhi/p/3930297.html
volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。
volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。
也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。
可见性:
(1)修改volatile变量时会强制将修改后的值刷新的主内存中。
(2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
6. 内存溢出错误什么时候出现?
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。
1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3. 代码中存在死循环或循环产生过多重复的对象实体;
4. 启动参数内存值设定的过小;例如设置-Xmx128m-Xms64m-Xmn32m-Xss16m
5. 尽量少用静态变量,因为静态变量是全局的, GC 不会回收的;
7. Dubbo框架的运行步骤,zookepper的作用
消费者订阅服务,提供者注册服务,Zookeeper是dubbo的注册中心
消费订阅subscribe服务,如果没有订阅到自己想获得的服务,它会不断的尝试订阅。新的服务注册到注册中心以后,注册中心会将这些服务通过notify到消费者。
8. Spring和mysql事务的差异
Spring事务直接加@Transactional注解,其他还不知道怎么回答。。。。
9. Session怎么防止多次提交 参考http://www.cnblogs.com/xdp-gacl/p/3859416.html
Session存储在服务器端,一般放置在服务器的内存中(为了高速存取),Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。
服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为20分钟。
具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
10. 熟悉那种设计模式,IO中用到的设计模式:装饰者模式
参考http://blog.csdn.net/caihuangshi/article/details/51334097
InputStreamoutputstream read write 都是抽象构建类(component)
优点:
A、装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
B、装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。
C、装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
D、使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
11. 数据库使用中如何优化:建立索引,满足范式规范,索引有几种
1、聚集索引:
聚集索引的意思可以理解为顺序排列,比如一个主键自增的表即为聚集索引,即id为1的存在于第一条,id为2的存在于第二条...假使数据库中是使用数组来存放的这张表中的数据,那么如果我需要查找第100条,那么直接第一条数据的地址加上100即为第一百条的地址,一次就能查询出来。
因为数据库中的数据只能按照一个顺序进行排列,所以聚集索引一个数据库只能有一个。在mysql中,不能自己创建聚集索引,主键即为聚集索引,如果没有创建主键,那么默认非空的列为聚集索引,如果没有非空的列那么会自动生成一个隐藏列为聚集索引。
所以一般在mysql中,我们创建的主键即为聚集索引,数据是按照我们的主键顺序进行排列。所以在根据主键进行查询时会非常快。
2、非聚集索引:
非聚集索引可以简单理解为有序目录,是一种以空间换取时间的方法。举个例子,在一个user表中,有一个id_num,即身份号,此不为主键id,那么这些数据在存储的时候都是无序的,比如
id为1的id_num为100,id为2的id_num为97,id为3的id_num为98,id为4的id_num为99,id为5的id_num为96。。。id为67的id_num为56。。。
那么如果我要查找id_num为56的人,那么只能一条一条的遍历,n条就需要查询n次,时间复杂度为O(n),这是非常耗费性能的。
所以,现在就需要为id_num增加非聚集索引,添加了非聚集索引后,会给id_num进行排序(内部使用结构为B+树),并且排序后,只需要查询此目录(即查询B+树),很快就知道为id为56的在数据库中的第67条,而不需要在去遍历表中的所有数据。
所以,在非聚集索引中,不重复的数据越多,那么索引的效率越高。
CREATE INDEX 索引名 ON 表名(列名1,列名2,...);
DROP INDEX index_name ON talbe_name
12. 索引什么情况下不被调用:like “%xx”
1、如果条件中有or,即使其中有条件带索引也不会使用
注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
2.like查询是以%开头
3.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
4.如果mysql估计使用全表扫描要比使用索引快,则不使用索引
13. 平时看的书 think in java (考官说他出的题一般都是从这里来的)
八百多页呀
14. 存储过程的优缺点
优点
1.存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般 SQL 语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。
2.当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete 时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。存储过程,只需要连接一次数据库就可以了。
3.存储过程可以重复使用,可减少数据库开发人员的工作量。
4.安全性高,可设定只有某此用户才具有对指定存储过程的使用权。
缺点
1. SQL本身是一种结构化查询语言,但不是面向对象的的,本质上还是过程化的语言,面对复杂的业务逻辑,过程化的处理会很吃力。同时SQL擅长的是数据查询而非业务逻辑的处理,如果如果把业务逻辑全放在存储过程里面,违背了这一原则。
2.调试不方便
3. 没办法应用缓存。虽然有全局临时表( create temporary tabl)之类的方法可以做缓存,但同样加重了数据库的负担。如果缓存并发严重,经常要加锁,那效率实在堪忧。
4、不支持集群,数据库服务器无法水平扩展,或者数据库的切割(水平或垂直切割)。数据库切割之后,存储过程并不清楚数据存储在哪个数据库中。
二轮技术面试:
面试官比较邋遢,全程绷脸严肃,说话生硬,有口臭,估计28 9的样子。
15. 对spring的理解,面向切面编程体现在哪里
参考:http://www.cnblogs.com/xuejupo/p/5206087.html
反转控制,由代码控制依赖,转为外部容器控制依赖,依赖注入由容器负责注入组件。
面向切面编程的场景,最重要的一点还是解耦,面相切面编程是将与业务逻辑无关的公共部分抽离开,形成一个横切的关注点(比如权限问题,比如日志问题)。一般情况下,这个横切的关注点在公司里有专人负责,业务逻辑部分的编码人员不需要关心他,公司需要的时候只是配置一下xml文件即可,方便快捷,而且出问题也是切面的负责人负责调试测试,与主逻辑无关。
在系统开发中,将系统的共性的公共的功能独立实现,在程序运行的过程中,将共性功能和核心的业务功能,进行整合。
好处:
1 完成共性功能和核心业务功能的解耦合
2 提供共性功能的复用性。
16. Spring MVC 流程参考http://blog.csdn.net/u014010769/article/details/47354529
Request请求找到controller里对应代码运行,@@ResponseBody返回json,return redirect:index.html 页面跳转。
1.从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2.从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3.从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
4.从效率来说
forward:高.redirect:低.
17. 直接手写代码:b b a c a b a a 找出第一次出现次数最多的字符
相关扩展:
Wrapper class 包装类
在Java里一切都是对象,除了Java中的基本数据类型(byte,short,int,long,char,float,double,boolean)不是面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class),有些地方也翻译为外覆类或数据类型类。
包装类的用途
a、作为和基本数据类型对应的类类型存在,方便涉及到对象的操作。
b、包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。
为什么String, Interger这样的wrapper类适合作为键?
String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
Integer与int的区别
Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。
① 无论如何,Integer与new Integer不会相等。他们的内存地址不一样,所以为false
Integer i1 = 127;
Integer i2 = new Integer(127); i1 == i2false
② 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
Integer i1= 127
Integer i2 = 127 i1 == i2 true
Integer i3= 128
Integer i4 = 128 i1 == i2 false
java在编译Integer i2 = 128的时候,被翻译成->Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存
③ 两个都是new出来的,都为false
Integer i1= new Integer(127)
Integer i2 = new Integer(127) i1 == i2 false
④ int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比
int i1 = 127
Integer i2 =127/new Integer(127) i1 == i2 true
Ps:
size()是集合的方法,可以返回集合中对象的数量
length()是数组的方法,返回数组的长度
hashmap是线程不安全的,加synchronized
或者用concurrentHashmap