1. 集合框架
Collection接口:
List接口:
ArrayList实现类
LinkList实现类
Vector实现类
Set接口:
HashSet实现类
TreeSet实现类
Queue接口:
问一:List、Set和Queue的区别:
List是有序的,可以重复的;
Set是无序的,不可以重复的;
Queue是一个单向的数据管道,这边进,另一边出。
问二:ArrayList、LinkList和Vector的区别:
ArrayList基于数组,存储慢(需要扩容和复制),访问快,需要分配连续的内存空间;
LinkList基于链表,存储快,访问慢,存储空间可以碎片化。
Vector也是基于数组,几乎和ArrayList一致,但是Vector是线程安全的,目测用Synchronized重写里面的方法了。
问三:HashSet和TreeSet的区别:
都是基于HashMap的key来实现的,但是后者支持排序;
问四:链表的实现原理:
有一个对象,它持有下一个对象的存储地址,可以根据该地址找到下一个对象时,就构成了单向链表。同理,如果该对象同时持有上一个对象的地址,可以根据该地址找到上一个对象时,就构成了双向链表。
问五:链表为什么增删快,查询慢:
因为链表中的对象不是连续存储的,需要一次遍历链表中的每个对象才能最终找到想要的结果对象;但是增删时,只要把要删除对象的前后对象指向的对象地址做修改就可以了。而数组需要重新计算容量来扩容并复制全部元素到新的数组里。
Map接口:
HashTable实现类(线程安全,但已弃用)
HashMap实现类(可用ConcurrentHashMap来实现线程安全,用ConcurrentSkipListMap(跳表Map)实现线程安全同时可排序)
TreeMap实现类(支持排序)
问一:Collection和Map的区别:
Collection是单列的;
Map是双链的;
问二:Map的实现原理:
用数组来存储经过计算后的key(即存储地址),用链表来存储value。查找时,根据该key重新计算,并在数组中找到对应的存储地址,然后根据存储地址找链表中的value。
2. io框架
BIO:阻塞式io,一个连接就需要分配一个线程去处理。
NIO:非阻塞式io,一个有效连接分配一个线程去处理。
AIO:异步io,一个有效请求分配一个线程去处理。
打个比方吧:幼儿园放学了,每个孩子都需要自己的家长来接就是BIO,放学后校车每隔一段时间出发一趟送孩子回家就是NIO。但是NIO带来的问题是如果每个孩子回家的时间不一致,校车一趟趟跑。AIO就是干这个的,有孩子要回家才发一辆车。
问一:TCP和UDP的区别:
TCP是面向连接的,需要三次握手,保证了数据的准确性。
UDP是无连接的,不关心对方有没有收到。
问二:TCP三次握手和四次挥手:
3. 多线程
问一:多线程的概念:
当一个任务进入阻塞时,为了不影响其他任务的进行,引入了多线程的概念。
问二:线程的生命周期:
创建->可执行->执行-(阻塞)->销毁。
问三:线程的创建方式:
(1)实现Runnable接口:比较通用
(2)继承Thread类:
好处:Thread类是Runnable的一个实现类,内存封装了很多东西,我们不需要再关注部分细节。
缺点:JAVA不支持多继承(可以用内部类来间接实现);
(3)用callable和future接口组合实现,再用一个线程去启动:
好处:可以有返回值,而且是异步的,已经大量用于AIO编程。
问四:Thread的start()方法和Runnable的run()方法有啥区别?
Thread重写了Runnable的run方法,线程不会再执行start()时就执行,而是等到CPU资源可用时才会启动此线程,比较友好。如果继承了Thread类后用run()方法启动,就没有这种效果了。
问五:怎么保证多个线程有序访问同一个对象?
加锁(Synchronized)或用lock手动实现。它俩的区别是:
Synchronized是JVM级别的锁,不需要我们关注细节;Lock锁是需要在代码中手动控制加锁和释放锁的,对开发人员的要求较高。不释放lock锁会带来一系列灾难性的问题。
问六:多线程中怎么保证多个任务都执行完成?
引入线程计数器(CountDownLatch) ,然后让主线程进入等待中。
4. 线程池
问一:线程池的类型:
固定容量线程池,可变容量线程池,单一线程线程池,定时任务线程池。
问二:线程池使用调优?
(1)合理分配线程数量(楼主的经验,windows下一百个线程同时不停歇的跑,系统很快就崩了);
(2)合理使用线程池策略,比如设置没有工作线程是线程池的存活时间和空闲线程的存活时间。
5. 缓存(Redis,Mongodb,Memcached)的比较
Redis:单线程的内存型数据库,支持丰富的数据类型(String,list,set,zset,hash),支持重启恢复,因为redis会在合适的时间将数据写入磁盘。
Mongodb:文档型数据库,支持丰富的数据类型,而且容易扩展。但是会受限于计算机io能力。理论上来说,如果不考虑io带来的性能瓶颈,Mongodb是最容易支持分布式扩展的。
Memcached:老牌内存型数据库,不支持数据恢复,而且数据类型单一,只支持键值对存储。随着计算机存储能力的提高,基本上现在都用直接在map中存储的方式替代了。
6. Aop、静态代理和动态代理
问一:什么是Aop?
Aop是指,在合适的时机,对业务进行横向切面的过程。基于Aop,我们可以完成日志统计,权限校验等功能,从而减少代码的冗余。
问二:静态代理和动态代理的区别是什么?
基于静态代理,我们要给每个被代理的对象编写代理类,非常不利于程序的开发,动态代理解决了这一问题。
问三:JDK动态代理和Cglib动态代理的区别:
JDK动态代理基于接口, 不支持对普通java类的代理。因为在底层实现的时候,JDK动态代理需要让代理对象继承Proxy类,而java不支持多继承。
Cglib动态代理重写了底层字节码,所以支持对普通java 类的动态代理。
Spring中大量使用了动态代理的模式,在默认情况下,使用JDK动态代理,在无法满足使用时,自动切换到Cglib动态代理。
问四:说说你对注解的理解:
注解让我们的开发变得标准化,我们需要按照注解要求的格式编程。对于java底层来说,注解就是一个接口。声明的注解自后会成为该类的父接口,而注解参数则成为常量值保存到内存中。
7. IOC
问一:为什么要引入ioc:
传统编程中,我们需要创建大量的对象,使用后又需要被销毁,这个过程是需要消耗CPU资源的。之所以引入ioc,就是为了解决这一问题,我们把创建对象的权力交给了框架,专注于业务开发。
问二:Spring加载bean的过程:
在系统启动过程中,扫描系统中所有文件,将xml中配置的bean,@Component注解和@Bean声明的bean都以BeanDefine的方式保存起来。到了合适的时机,再把他们统一转化为bean对象存储起来,按需加载。需要注意的是,有一部分bean是BeanFactory生产出来的。
8. JVM
问一:堆和栈
在jvm中,内存被分为堆和栈。堆是用来存放数据块的,栈是用来存储程序运行时的一些信息的(也会存放一些比较小的数据,比如基本数据类型)。程序运行期间,遇到函数调用需要传递参数的时候,传递的其实是堆中的对象地址。
问二:垃圾回收
在jvm中,存在四种不同的引用类型:强引用、软引用、弱引用和虚引用。垃圾回收其实就是对这些不同对象的回收。
强引用:只要程序依然运行,强引用对象永远不会回收掉。
软引用:会在内存溢出前回收掉。
弱引用:垃圾回收时主要回收的东西。
虚引用:它的存在与否对对象本身不会有任何影响,存在的意义只是在垃圾回收该对象时产生一个系统通知。
问三:垃圾回收算法:
标记:从根节点开始,对当前被引用的对象进行标记,然后全局扫描清除没有被标记的对象;
复制:将内存分为两块区域,内存溢出时就将当前被引用的对象复制到另一边,从而清除了没有被引用的对象。
标记-整理:先清理掉没有被标记的对象后,再进行复制。
分代回收:
9. mysql
1. mysql选型:
(1)MyISAM:超高插入和查询速度,不支持事务和外键;
(2)InnoDB:支持事务和外键;
(3)MEMORY:基于内存,访问极快,不可恢复;
(4)MERGE:MyISAM表的组合
2.sql优化:
加索引、分表、避免全表扫描