导读 :
作者: 瞿云康 , 英文名 jacksonKang ,是一名努力成长中的 Java 爱好者 。
原文: http://mayiyk.cn/article/6
本文为一个java面试题集锦系列首篇,主要把一些常见的java面试题目整理发给大家,希望能给大家以后面试中提供一些帮助。
一、String stringBuffer 和 stringBuilder
String :适用于少量字符串操作的情况,为字符串常量,即对象一旦创建之后对象是不可更改的。
StringBuffer :适用多线程下字符缓冲区进行大量操作的情况。属于线程安全。
StringBuilder :适用于单线程下在字符缓冲区进行大量操作的情况。属于线程不安全。
StringBuffer 与 StringBuilder 均为字符串变量,对象是变量,即可以更改。 StringBuilder 所有方面都没有被 synchronized 修饰,它的效率比 StringBuffer 要高。
二、HashMap 的底层实现原理
HashMap 底层是数组 + 链表实现的,它是一个 entry 类的数组, entry 中包含 key 和 value 的值,允许 key 、 value 可以为 null ,通过 key 的 hashcode 计算在这个数组所在位置,遍历这个链表从而查询到值, hashMap 默认的初始化容器大小为 16 ,之后每次扩充为原来的 2 倍。属于线程不安全的。
在 JDK1.7 及以前, HashMap 中维护着 Entry , Entry 中维护着 key , value 以及 hash 和 next 指针,而整个 HashMap 实际就是一个 Entry 数组
当向 HashMap 中 put 一对键值时,它会根据 key 的 hashCode 值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找 ( 为了判断是否是否值相同, map 不允许 <key,value> 键值对重复 ) ,如果此链上有对象的话,再去使用 equals 方法进行比较,如果对此链上的每个对象的 equals 方法比较为 false ,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。
get 方法类似,通过 key 取 hash 找到数组的某个位置,然后遍历这个数组上的每个 Entry ,直到 key 值 equals 则返回。
如果 Hash 碰撞严重,那么 JDK1.7 中的实现性能就很差,因为每次插入都要遍历完整条链去查看 key 值是否重复,每次 get 也要遍历整个链,在 JDK1.8 中,由于链表的查找复杂度为 O(n) ,而红黑树的查找复杂度为 O(logn) , JDK1.8 中采用链表 / 红黑树的方式实现 HashMap ,达到某个阀值时,链表转成了红黑树。
三、HashMap 和 Concurrent HashMap 区别, Concurrent HashMap 线程安全吗, ConcurrentHashMap 如何保证 线程安全?
HashMap 不是线程安全的, ConcurrentHashMap 是线程安全的, HashMap 内部维护着一个 Entry 数组,而 ConcurrentHashMap 内部有一个 Segment 段,它将大的 HashMap 切分成若干个段(小的 HashMap ),然后让数据在每一段上 Hash ,这样多个线程在不同段上的 Hash 操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。 ConcurrentHashMap 的实现中还使用了不变模式 final 和 volatile 来保障线程安全
四、HashMap 的 put 方法做了哪些操作
他会根据 key 的 hashcode 重新计算 hash 值,根据 hash 值得到这个元素在数据中的位置,如果数组在该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
五、HashMap 的缺点
HashMap 不支持并发操作,所以不适用于多线程环境,在高并发状态下,如果产生同时 put 操作,并且在 put 时刚好遇上要扩容,可能会形成链环,如果 get 的 key 的 hashcode 值刚好在链环的位置,而这个 key 对应的值为 null 或不存在就会进入死循环,耗尽 cpu 内存。
六、ConcurrentHashMap
底层是数组 + 红黑树 + 链表实现,可以替代 HashTable ,因为使用了多个锁代替 hashTable 中的单个锁,也就是锁分离技术, hashTable 是锁住了整个数组导致效率特别低,属于线程安全。
七、Springmvc 的工作原理
用户发送请求至前端控制器 DispatcherServlet , DispatcherServlet 收到请求调用 handlerMapping 处理器映射器,解析请求对应的 handler ,开始由 handlerAdapter 适配器处理请求,并处理相应的业务逻辑并返回一个 ModelAndView 对象,根据返回的 modelAndView 选择一个合适视图解析器返回给 DispatcherServlet ,视图解析器根据 view 和 model 渲染页面。
八、集合的父类及各子类的区别
Collection
├List 允许重复值、有序容器,保持了每个元素的插入顺序
│├LinkedList 线程不安全增删改速度快,基于链表数据结构
│├ArrayList 线程不安全查询速度快,基于动态数组结构
│└Vector 线程安全
│└Stack
├set 不允许重复值、无序容器,无法保证元素的存储顺序,可以通过 TreeSet 的 comparator 或者 comparable 维护一个排序顺序
│├HashSet 线程不安全无序(存入与取出时顺序不同)不重复,无索引,底层 hash 表结构查询删除快,增改慢。
│├TreeSet 是 sortedSet 接口的唯一实现,可以进行排序
九、什么是线程安全与不安全
线程安全是多个线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用,不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程后更改数据造成所得到数据时脏数据。
十、为什么使用 reids ,用 redis 都做了些什么
Redis 是一个开源的 key-value 型数据库,运行在内存,速度快,同时支持持久化,支持的数据结构丰富( String,list,set,sorted set,hash ) , 支持订阅发布功能。
String :(常用命令 get,set,incr,decr,mget )常规的 key-value 缓存应用。统计访问次数,关注人数。
Hash (常用命令 hget , hset , hgetall )省市区联动,用户信息
List (常用命令 Ipush , rpush , Ipop )用户关注列表
Set 注册用户用户名不能重复,使用 set 记录注册用户
十一、Jvm 如何调优
VisualVM : jdk 自带,功能强大
堆信息查看:观察内存释放情况、集合类检查、对象树 ----- (查看堆空间的大小分配【年轻代、年老代、持久代分配】、提供即时的垃圾回收功能、垃圾监控),可以解决年老代年轻代大小划分是否合理、内存泄漏、垃圾回收算法设置是否合理。
线程监控:可以查看线程在系统中的数量,各个线程都处在什么状态下、死锁检查
热点分析: cpu 热点 --- 检查系统哪些方面占用大量 cpu 时间,内存热点 ------- 检查哪些对象在系统中的数量最大。
内存泄漏检查:在错误的使用下导致使用完毕的资源无法回收,引起系统错误。常表现年老代空间被占满( java heap space )一般根据垃圾回收前后情况对比,同时根据对象引用情况分析,基本可以找到泄漏点。
持久代被沾满:无法为新的 class 分配存储空间而引发的异常,在 java 大量的反射的使用会造成,大量动态反射生成的类不断被加载。解决: -XX:MaxPermSize=16m
堆栈溢出:一般是递归没返回,或者循环调用造成
线程池栈满: java 中一个线程空间大小是有限的,在 jdk5 以后这个值是 1m 。解决:增加线程栈大小, -Xss2m
十二、 GC 垃圾回收机制
什么时候 : eden 满了 minor gc ,升到老年代的对象大于老年代剩余空间 full gc ,或者小于时被 HandlePromotionFailure 参数强制 full gc
对什么东西 :从 root 搜索不到,而且经过第一次标记,清理后仍然没有复活的对象。
做什么事情 :删除不使用的对象,腾出内存空间。
算法 :
1 、 标记 - 清除 :分为标记和清除两个阶段,首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。
2 、 复制算法 :它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活的对象复制到另一块上,然后再把已经使用过的内存空间一次性清理掉。
3 、 标记 - 整理算法 :是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
十三、 JVM 如何 GC ,新生代,老年代,持久代,都存储哪些东西?
JVM 通过可达性(可触及性)分析算法标记出哪些对象是垃圾对象,然后将垃圾对象进行回收,在新生代中采用复制算法,在老年代中采用标记清理或标记压缩算法。新生代存储了新 new 出的对象,老年代存储了大的对象和多次 GC 后仍然存在的老年对象,持久代存储了类信息,常量( JDK7 中 String 常量池被移到堆中),静态变量( JDK7 中被移到了 Java 堆),类方法
十四、 强引用、软引用、弱引用、虚引用的区别
强引用 :是最难被 GC 回收的,宁可虚拟机抛出异常,中断程序,也不会去回收该对象。( Object o=new Object() )
软引用 :非必须引用,内存溢出之前进行回收。软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
弱引用 :第二次垃圾回收时回收。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的 isEnQueued 方法返回对象是否被垃圾回收器标记。
虚引用 :垃圾回收时回收,无法通过引用取到对象值。虚引用主要用于检测对象是否已经从内存中删除。
十五、Spring 的原理
内部核心是 ioc ,动态注入,让一个对象的创建不用 new 了,可以自动生成,这其实就是利用了 java 里的反射,反射其实就是在运行时动态的去创建、调用对象, spring 就是在运行时,跟 xml spring 的配置文件来动态的创建对象,和调用对象的方法。
Spring 还有一个核心就是 AOP 面向切面编程,可以为某一类对象进行监督和控制从而达到堆一个模块扩充的功能。这些都是通过配置类达到的。
十六、 Aop 如何实现
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行时,代理类 .class 文件就已经存在了。
动态代理:在程序运行时,运用发射机制动态创建而成。
Cglib 动态代理:针对类实现的代理。它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。
JDK 的动态代理依靠接口实现,如果有些类没有实现接口,则不能使用 jdk 代理,就要使用 cglib 代理了。
十七、 MySQL 如何优化
使用查询缓存优化查询。(例如 NOW(),RAND() 或其他 SQL 函数都不会开启查询缓存,因为返回是会不定的易变得,所以你需要的就是一个变量来替代 mysql 的函数,从而开启缓存)
使用 EXPLANIN 关键字检测查询(可以使我们知道 MYSQL 是如何处理 SQL 语句的,帮助分析查询语句或是表结构性能瓶颈;索引主键是如何被利用的,数据表是如何被搜索或排序的,语句格式: EXPLAIN+SELECT 语句)
当只有一行数据时使用 LIMIT 1 (可以增加性能,会查到第一条数据后停止搜索)
为搜索字段建立索引(普通索引 INDEX :适用于 name 、 email 等一般属性,唯一索引 UNIQUE :要求索引字段值在表中是唯一的,唯一索引允许有空值。适用于身份证号码、用户账户等,全文索引:适用于 VARCHAR 和 TEXT 类型字段)
在 jion 表的时候使用相当类型的列,并将其索引(存在很多 jion 查询时,保证两个表中 jion 的字段时被建立索引的,这样 mysql 会启动优化 JION 的 sql 语句机制)
避免使用 select *
永远为每一张表设置一个 ID 主键
尽可能的不要赋值为 NULL (会占用存储空间,程序判断更加复杂,索引不存储 null 值,使用 not null 约束以及默认值。)
固定长度的表会更快(容易计算下一个数据的偏移量,容易被缓存和重建。)
垂直分割:是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段数目
拆分大的 delete 或 insert (这两个大操作会锁表,这样别的操作就进不来了,可以使用 LIMIT 控制操作记录的数量)
知数堂面试宝典——《叶问》专辑: https://zhishutang.com/Z4z
END
扫码加入MySQL技术Q群
(群号: 650149401)