0.我们的公众号:扶摇就业,欢迎前往,更多面经内推,祝您早日拿到offer奥~
1.hashmap中如何计算下标
从HashMap源码中,可以看到求容器下标值的方法,有两步,首先通过key值计算hash,然后用hash计算下标:
计算hash:
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
计算下标,其下标值为:(n-1) & hash
n = (tab = resize()).length;
p = tab[i = (n - 1) & hash
即,是通过key的hash值和容器的大小减1,两者进行与运算,获取容器数组下标。这里使用与运算,其实蕴含了一个隐藏条件,即数组的大小n,必须是2的n次方,否则,计算出来的下标值i是无法覆盖这个范围[0, n-1]的。
2.hashset的内容是什么
HashSet基于HashMap实现,将HashSet的数据作为HashMap的Key值保存,所以HashSet中元素不可重复,无序,允许null元素,线程不安全。
用HashMap的put方法完成HashSet的add操作,因为HashMap的key不可重复,所以HashSet中的元素不会重复。
没有索引,没有带索引的方法,也不能使用普通的for循环遍历
Set的子集合HashSet也继承了Set接口的特点,不过它也有自己的特点,是一个无序的集合,存储元素和取出元素的顺序有可能不一致,它的底层是一个哈希表结构,查询的速度很快。
3.线程池可以配哪些参数
corePollSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。
maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。
keepAliveTime:空闲的线程保留的时间。
TimeUnit:空闲线程的保留时间单位。
BlockingQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选。
ThreadFactory:线程工厂,用来创建线程
RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
4.核心线程数是什么
corePoolSize:
核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。
这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。
5.线程的状态
线程是一个动态执行的过程,它有一个从产生到死亡的过程,共五种状态:
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)。
6.block和wait的区别
BLOCKED状态
线程处于BLOCKED状态的场景。
当前线程在等待一个monitor lock,比如等待执行synchronized代码块或者使用synchronized标记的方法。
在synchronized块中循环调用Object类型的wait方法,如下是样例
synchronized(this)
{
while (flag)
{
obj.wait();
}
// some other code
}
WAITING状态
线程处于WAITING状态的场景。
调用Object对象的wait方法,但没有指定超时值。
调用Thread对象的join方法,但没有指定超时值。
调用LockSupport对象的park方法。
提到WAITING状态,顺便提一下TIMED_WAITING状态的场景。
TIMED_WAITING状态
线程处于TIMED_WAITING状态的场景。
调用Thread.sleep方法。
调用Object对象的wait方法,指定超时值。
调用Thread对象的join方法,指定超时值。
调用LockSupport对象的parkNanos方法。
调用LockSupport对象的parkUntil方法。
7.juc包下的常用类
java.util.concurrent.CountDownLatch;
CountDownLatch 又叫闭锁,可以让一个线程等待其他一组线程都执行结束之后再继续执行,如果在主方法中使用,就会将主线程阻塞,等待指定个数的线程都执行结束之后,主线程在恢复执行。
java.util.concurrent.CyclicBarrier 循环栅栏
CyclicBarrier 有叫循环栅栏,它的作用是等待一组线程都执行到某个状态后再继续执行
Exchanger线程交换器
Exchange主要用于再两个线程之间交换数据,就是当两个线程配对之后(调用Exchanger里面的exchange方法),将两个线程间的数据交换,然后再一起执行,如果只有一个线程调用exchange方法,这个线程将阻塞,直到有另一个线程和他配对
8.原子类的实现
Java提供的原子类是靠 sun 基于 CAS 实现的,CAS 是一种乐观锁。关于乐观锁与悲观锁。
原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
AtomicInteger 是一个支持原子操作的 Integer 类,就是保证对 AtomicInteger 类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。如果不使用 AtomicInteger,要实现一个按顺序获取的 ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的 ID 的现象。
CAS(Compare-And-Swap)算法保证数据操作的原子性。
CAS 算法是硬件对于并发操作共享数据的支持。
CAS 包含了三个操作数:
内存值 V
预估值 A
更新值 B
当且仅当 V == A 时,V 将被赋值为 B,否则循环着不断进行判断 V 与 A 是否相等。
9.CAS为什么是原子性的
处理器自动保证基本内存操作的原子性
首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
使用总线锁保证原子性
第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。
使用缓存锁保证原子性
第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。
10.http和https,https加密过程,加密密钥怎么来,对称加密密钥哪里生成
HTTP和HTTPS的区别
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
对称加密和非对称加密的工作方式
所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。
非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥
先通过公开密钥加密获取到对称密钥加密的密钥,再通过对称密钥加密传输数据。
HTTPS采用的是处理信息的方式是:结合对称加密+非对称加密这两种方式,我们可以用非对称加密的方式来传输对称加密过程中的密钥,之后我们就可以采取对称加密的方式来传输数据了。具体是这样子的:
服务器用明文的方式给客户端发送自己的公钥,客户端收到公钥之后,会生成一把密钥(对称加密用的),然后用服务器的公钥对这把密钥进行加密,之后再把密钥传输给服务器,服务器收到之后进行解密,最后服务器就可以安全得到这把密钥了,而客户端也有同样一把密钥,他们就可以进行对称加密了
11.cookie和session区别和联系,session用法
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、Session容易丢失,导致数据的不确定性,而Cache不会出现这种情况
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
当服务器创建完session对象后,会把session对象的id以cookie形式返回给客户端。这样,当用户保持当前浏览器的情况下再去访问服务器时,会把session的id传给服务器,服务器根据session的id来为用户提供相应的服务。
12.session共享
1. 基于NFS的Session共享
NFS是Net FileSystem的简称,最早由Sun公司为解决Unix网络主机间的目录共享而研发。
这个方案实现最为简单,无需做过多的二次开发,仅需将共享目录服务器mount到各频道服务器的本地session目录即可,缺点是NFS依托 于复 杂的安全机制和文件系统,因此并发效率不高,尤其对于session这类高并发读写的小文件, 会由于共享目录服务器的io-wait过高,最终拖累前端WEB应用程序的执行效率。
2. 基于数据库的Session共享
首选是Mysql数据库,并且建议使用内存表Heap,提高session操作的读写效率。它的缺点在于session的并发读写能力取决于Mysql数据库的性能,同时需要自己实现session淘汰逻辑,以便定时从数据表中更新、删除 session记录,当并发过高时容易出现表锁,虽然我们可以选择行级锁的表引擎,但不得不否认使用数据库存储Session还是有些杀鸡用牛刀的架势。
3. 基于Cookie的Session共享
这个方案我们可能比较陌生,但它在大型网站中还是比较普遍被使用。原理是将全站用户的Session信息加密、序列化后以Cookie的方式, 统一 种植在根域名下(如:. http://host.com),利用浏览器访问该根域名下的所有二级域名站点时,会传递与之域名对应的所有Cookie内容的特性,从而实现 用户的Cookie化Session 在多服务间的共享访问。
这个方案的优点无需额外的服务器资源;缺点是由于受http协议头信心长度的限制,仅能够存储小部分的用户信息,同时Cookie化的 Session内容需要进行安全加解密(如:采用DES、RSA等进行明文加解密;再由MD5、SHA-1等算法进行防伪认证),另外它也会占用一定的带 宽资源,因为浏览器会在请求当前域名下任何资源时将本地Cookie附加在http头中传递到服务器。
4. 基于Memcache的Session共享
Memcache由于是一款基于Libevent多路异步I/O技术的内存共享系统,简单的Key + Value数据存储模式使得代码逻辑小巧高效,因此在并发处理能力上占据了绝对优势,目前本人所经历的项目达到2000/秒 平均查询,并且服务器CPU消耗依然不到10%。
13.http状态码
100|Continue |客户必须继续发出请求
101|Switching Protocols |客户要求服务器根据请求转换HTTP协议版本
200|OK|请求成功
201|Created|服务器已经创建了文档,Location头给出了它的URL
202|Accepted|接受和处理、但处理未完成
203|Non-Authoriative Information |返回信息不确定或不完整
300|Multiple Choices |请求的资源可在多处得到
301|Moved Permanently |删除请求数据
302|Found|在其他地址发现了请求数据
303|See Other |建议客户访问其他URL或访问方式
400|Not Found|客服端请求的语法错误,服务器无法解析
401|Unauthorized |请求授权失败
402|Payment Granted |保留有效ChargeTo头响应
403|Forbidden|请求不允许
404|Not Found|服务器无法根据客户端的请求找到资源
405|Method Not Allowed |用户在Request-Line字段定义的方法不允许
500|Internal Server Error|服务器内部错误,无法完成请求
501|Not Implemented |服务器不支持请求的函数
502|Bad Gateway |服务器暂时不可用,有时是为了防止发生系统过载
503|Service Unavailable |服务器过载或暂停维修
504|Gateway Timeout |关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长
505|HTTP Version Not Supported |服务器不支持或拒绝支请求头中指定的HTTP版本
14.事务是什么,隔离级别,每种隔离级别解决了什么问题
事务是逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功 ,为了解决并发情况下保持数据一致性的问题。
隔离级别 脏读 丢失更新 不可重复读 幻读 并发模型 更新冲突检测
未提交读:Read Uncommited 是 是 是 是 悲观 否
已提交读:Read commited 否 是 是 是 悲观 否
可重复读:Repeatable Read 否 否 否 是 悲观 否
可串行读:Serializable 否 否 否 否 悲观 否
15.不可重复读是什么问题
不可重复读 就是一个事务读到另一个事务修改后并提交的数据(update)。在同一个事务中,对于同一组数据读取到的结果不一致。比如,事务B 在 事务A 提交前读到的结果,和在 事务A 提交后读到的结果可能不同。不可重复读出现的原因就是由于事务并发修改记录而导致的。
16.悲观锁,乐观锁,读锁,写锁(数据库中的)
悲观锁(Pessimistic Lock),每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。(实现:在sql语句后面加上for update)
乐观锁(Optimistic Lock), 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。(实现:在表上加version)
读锁(共享锁)是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
写锁(排他锁)如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
事务可以通过以下语句给sql加共享锁和排他锁:
共享锁:select …… lock in share mode;
排他锁:select …… for update;
17.索引有哪些,数据结构是什么,区别是什么
普通索引
唯一索引:唯一索引是不允许其中任何两行具有相同索引值的索引
主键索引:数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。
聚集索引:表中行的物理顺序与键值的逻辑(索引)顺序相同
非聚集索引:数据库表中记录的物理顺序与索引顺序可以不相同
索引的实现通常使用B树及其变种B+树。
1)B树
B树中每个节点包含了键值和键值对于的数据对象存放地址指针,所以成功搜索一个对象可以不用到达树的叶节点。
成功搜索包括节点内搜索和沿某一路径的搜索,成功搜索时间取决于关键码所在的层次以及节点内关键码的数量。
在B树中查找给定关键字的方法是:首先把根结点取来,在根结点所包含的关键字K1,…,kj查找给定的关键字(可用顺序查找或二分查找法),若找到等于给定值的关键字,则查找成功;否则,一定可以确定要查的关键字在某个Ki或Ki+1之间,于是取Pi所指的下一层索引节点块继续查找,直到找到,或指针Pi为空时查找失败。
2)B+树
B+树非叶节点中存放的关键码并不指示数据对象的地址指针,非也节点只是索引部分。所有的叶节点在同一层上,包含了全部关键码和相应数据对象的存放地址指针,且叶节点按关键码从小到大顺序链接。如果实际数据对象按加入的顺序存储而不是按关键码次数存储的话,叶节点的索引必须是稠密索引,若实际数据存储按关键码次序存放的话,叶节点索引时稀疏索引。
B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。
所以 B+树有两种搜索方法:
一种是按叶节点自己拉起的链表顺序搜索。
一种是从根节点开始搜索,和B树类似,不过如果非叶节点的关键码等于给定值,搜索并不停止,而是继续沿右指针,一直查到叶节点上的关键码。所以无论搜索是否成功,都将走完树的所有层。
B+ 树中,数据对象的插入和删除仅在叶节点上进行。
这两种处理索引的数据结构的不同之处:
a,B树中同一键值不会出现多次,并且它有可能出现在叶结点,也有可能出现在非叶结点中。而B+树的键一定会出现在叶结点中,并且有可能在非叶结点中也有可能重复出现,以维持B+树的平衡。
b,因为B树键位置不定,且在整个树结构中只出现一次,虽然可以节省存储空间,但使得在插入、删除操作复杂度明显增加。B+树相比来说是一种较好的折中。
c,B树的查询效率与键在树中的位置有关,最大时间复杂度与B+树相同(在叶结点的时候),最小时间复杂度为1(在根结点的时候)。而B+树的时候复杂度对某建成的树是固定的。
18.springboot中用过什么注解
@SpringBootApplication:
包含@Configuration、@EnableAutoConfiguration、@ComponentScan
通常用在主类上。
@Repository:
用于标注数据访问组件,即DAO组件。
@Service:
用于标注业务层组件。
@RestController:
用于标注控制层组件(如struts中的action),包含@Controller和@ResponseBody。
@EnableAutoConfiguration:
让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,一般加在主类上。
@AutoWired:
byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
当加上(required=false)时,就算找不到bean也不报错。
@Qualifier:
当有多个同一类型的Bean时,可以用@Qualifier("name")来指定。与@Autowired配合使用
@Resource(name="name",type="type"):
没有括号内内容的话,默认byName。
5650





