面试知识点2
目录
5.try catch的catch中有return,那之后的finally还会不会执行
7.String、StringBuffer、StringBuilder区别。
11、HashMap底层源码一问到底,数据结构,put,扩容等等,为啥用红黑树?二叉搜索树不行么
12、ConcurrentHashMap在1.8中的底层,CAS原理,用啥锁,以及它在JDK1.7中的底层
1.get和post的区别
2.两个栈实现一个队列
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
3.接口和抽象类
1, 抽象类里可以有构造方法, 而接口内不能有构造方法。
2, 抽象类中可以有普通成员变量, 而接口中不能有普通成员变量。
3, 抽象类中可以包含非抽象的普通方法, 而接口中所有的方法必须是抽象
的, 不能有非抽象的普通方法。
4, 抽象类中的抽象方法的访问类型可以是public , protected和private,
但接口中的抽象方法只能是public类型的, 并且默认即为public abstract
类型。
5, 抽象类中可以包含静态方法, 接口内不能包含静态方法。
6, 抽象类和接口中都可以包含静态成员变量, 抽象类中的静态成员变量的
访问类型可以任意, 但接口中定义的变量只能是public static类型, 并且
默认为public static final类型。
7, 一个类可以实现多个接口, 但只能继承一个抽象类。
4.死锁的四个必要条件
互斥条件: 进程要求对所分配的资源(如打印机) 进行排他性控制, 即在一
段时间内某 资源仅为一个进程所占有。 此时若有其他进程请求该资源, 则请求进程
只能等待。
不剥夺条件: 进程所获得的资源在未使用完毕之前, 不能被其他进程强行夺
走, 即只能 由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件: 进程已经保持了至少一个资源, 但又提出了新的资源请求,
而该资源 已被其他进程占有, 此时请求进程被阻塞, 但对自己已获得的资源保持不
放。
循环等待条件: 存在一种进程资源的循环等待链, 链中每一个进程已获得的
资源同时被 链中下一个进程所请求。
5.try catch的catch中有return,那之后的finally还会不会执行
finally 一定会被执行, 如果 finally 里有 return 语句, 则覆盖 try/catch 里的 return ,
比较爱考的是 finally 里没有 return 语句, 这时虽然 finally 里对 return 的值进行了
修改, 但 return 的值并不改变这种情况
6.重定向和请求转发
一句话, 转发是服务器行为, 重定向是客户端行为。 为什么这样说呢, 这就要看两个动作
的工作流程:
转发过程: 客户浏览器发送 http 请求----》 web 服务器接受此请求--》 调用内部的一个方法在容
器内部完成请求处理和转发动作----》 将目标资源发送给客户; 在这里, 转发的路径必须是同一
个 web 容器下的 url, 其不能转向到其他的 web 路径上去, 中间传递的是自己的容器内的 requ
est。 在客户浏览器路径栏显示的仍然是其第一次访问的路径, 也就是说客户是感觉不到服务器
做了转发的。 转发行为是浏览器只做了一次访问请求。
重定向过程: 客户浏览器发送 http 请求----》 web 服务器接受后发送 302 状态码响应及对应新的
location 给客户浏览器--》 客户浏览器发现是 302 响应, 则自动再发送一个新的 http 请求, 请求
url 是新的 location 地址----》 服务器根据此请求寻找资源并发送给客户。 在这里 location 可以重
定向到任意 URL, 既然是浏览器重新发出了请求, 则就没有什么 request 传递的概念了。
在客户浏览器路径栏显示的是其重定向的路径, 客户可以观察到地址的变化的。 重定向行为
是浏览器做了至少两次的访问请求的。
7.String、StringBuffer、StringBuilder区别。
8.普通类和抽象类区别
1.抽象类不能被实例化。
2.抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。
3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
4.含有抽象方法的类必须申明为抽象类
5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。
9.session和cookie区别。
10.集合体系
所有集合类都位于 java.util 包下。Java 的集合类主要由两个接口派生而出:Collection 和 Map,Collection 和 Map 是 Java 集合框架的根接口,这两个接口又包含了一些子接口或实现类。
Set、List 和 Map 可以看做集合的三大类:
List 集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
Set 集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
Map 集合中保存 Key-value 对形式的元素,访问时只能根据每项元素的 key 来访问其 value
1、Collection 是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。Collection 包含了 List 和 Set 两大分支。
(1)List 是一个 有序的队列,每一个元素都有它的索引。第一个元素的索引值是 0。List 的实现类有 LinkedList, ArrayList, Vector, Stack。
(2)Set 是一个不允许有重复元素的集合。Set 的实现类有 HastSet 和 TreeSet。 HashSet 依赖于 HashMap,它实际上是通过 HashMap 实现的;TreeSet 依赖于 TreeMap,它实际上是通过 TreeMap 实现的。
2、Map 是一个映射接口,即 key-value 键值对。Map 中的每一个元素包含 “一个 key” 和 “key 对应的 value”。AbstractMap 是个抽象类,它实现了 Map 接口中的大部分 API。而 HashMap,TreeMap,WeakHashMap 都是继承于 AbstractMap。Hashtable 虽然继承于 Dictionary,但它实现了 Map 接口。
3、接下来,再看 Iterator。它是遍历集合的工具,即我们通常通过 Iterator 迭代器来遍历集合。我们说 Collection 依赖于 Iterator,是因为 Collection 的实现类都要实现 iterator () 函数,返回一个 Iterator 对象。ListIterator 是专门为遍历 List 而存在的。
4、再看 Enumeration,它是 JDK 1.0 引入的抽象类。作用和 Iterator 一样,也是遍历集合;但是 Enumeration 的功能要比 Iterator 少。在上面的框图中, Enumeration 只能在 Hashtable, Vector, Stack 中使用。
5、最后,看 Arrays 和 Collections。它们是操作数组、集合的两个工具类。
11、HashMap底层源码一问到底,数据结构,put,扩容等等,为啥用红黑树?二叉搜索树不行么
我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对
当两个对象的hashcode相同会发生什么?hashcode相同,但是它们可能并不相等。因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。如果有两个值对象储存在同一个bucket,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
你了解重新调整HashMap大小存在什么问题吗?”当多线程的情况下,可能产生条件竞争(race condition)。
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。
12、ConcurrentHashMap在1.8中的底层,CAS原理,用啥锁,以及它在JDK1.7中的底层
13、事务的特性。
原子性(Atomicity):事务包含的操作全部成功或者全部失败
一致性(Consistency):数据库从一个一致性状态变到另一个一致性状态
(一系列操作后,所有的操作和更新全部提交成功,数据库只包含全部成功后的数据就是数据的一致性)
(由于系统异常或数据库系统出现故障导致只有部分数据更新成功,但是这不是我们需要的最终数据,这就是数据的不一致)
隔离性(Isolation):事务互相隔离互不干扰
(事务内部操作的数据对其它事务是隔离的,在一个事务执行完之前不会被其他事务影响和操作)
持久性(Durability):事务提交后数据应该被永久的保存下来,出现宕机等故障后可以恢复数据
数据库事务的隔离级别有4种,由低到高分别为Readuncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
14、为什么需要3次握手,4次挥手
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
建立TCP连接时会发生:三次握手(three-way handshake)
firefox > nginx [SYN] 在么
nginx > firefox [SYN, ACK] 在
firefox > nginx [ACK] 知道了
关闭TCP连接时会发生:四次挥手(four-way handshake)
firefox > nginx [FIN] 我要关闭连接了
nginx > firefox [ACK] 知道了,等我发完包先
nginx > firefox [FIN] 我也关闭连接了
firefox > nginx [ACK] 好的,知道了
几个报文的标识的解释:
SYN: synchronization(同步)
ACK: acknowledgement(确认:告知已收到)
FIN: finish(结束)
在HTTP/1.1中,keep-alive能够复用TCP连接,减少TCP三次握手的次数,从而提升性能.
结合到PHP编程中,拿Swoole引擎内置的异步HTTP服务器来说说:
调用 $res->end() 将结束HTTP请求,但不会关闭HTTP连接,因为Swoole支持keep-alive.
调用 $serv->close($res->fd) 将关闭HTTP连接.
15、线程池的几个状态
线程状态有 5 种,新建,就绪,运行,阻塞,死亡。
1. 新建(NEW):新创建了一个线程对象。
2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
16. 内存泄漏和内存溢出的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
17. 垃圾收集算法
堆内存对象空间内有任何栈内存空间的引用,形成了垃圾空间,等待垃圾回收机制进行回收
Java的一个重要优点就是通过垃圾收集器GC (Garbage Collection)自动管理内存的回收,程序员不需要通过调用函数来释放内存。
Java 的内存管理就是对象的分配和释放问题。分配内存的方式多种多样,取决于该种语言的语法结构。但不论是哪一种语言的内存分配方式,最后都要返回所分配的内存块的起始地址,即返回一个指针到内存块的首地址。在Java 中所有对象都是在堆(Heap)中分配的,对象的创建通常都是采用new或者是反射的方式,但对象释放却有直接的手段,所以对象的回收都是由Java虚拟机通过垃圾收集器去完成的。这种收支两条线的方法确实简化了程序员的工作,但同时也加重了JVM的工作,这也是Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
Java 使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达,那么GC 也是可以回收它们的。
在Java 语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个:
一个是给对象赋予了空值null,以下再没有调用过,
1、标记清除算法
标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收,另一个是给对象赋予了新值,即 重新分配了内存空间。
标记-清除算法的主要不足有两个:
效率问题:标记和清除两个过程的效率都不高;
空间问题:标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,因此标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2、复制算法
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。实践中会将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间 (如下图所示),每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用内存空间为整个新生代容量的90% ( 80%+10% ),只有10% 的内存会被“浪费”。
3、标记整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)。标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,并且仅对不存活的对象进行处理;而标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片。
4、分代收集算法
对于一个大型的系统,当创建的对象和方法变量比较多时,堆内存中的对象也会比较多,如果逐一分析对象是否该回收,那么势必造成效率低下。分代收集算法是基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块。
新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区,大部分对象在Eden区中生成。在进行垃圾回收时,先将eden区存活对象复制到survivor0区,然后清空eden区,当这个survivor0区也满了时,则将eden区和survivor0区存活对象复制到survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后交换survivor0区和survivor1区的角色(即下次垃圾回收时会扫描Eden区和survivor1区),即保持survivor0区为空,如此往复。特别地,当survivor1区也不足以存放eden区和survivor0区的存活对象时,就将存活对象直接存放到老年代。如果老年代也满了,就会触发一次FullGC,也就是新生代、老年代都进行回收。
永久代主要用于存放静态文件,如Java类、方法等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如使用反射、动态代理、CGLib等bytecode框架时,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。
18. 深拷贝和浅拷贝
| 深拷贝:引用对象的值等信息,复制一份一样的。 对一个对象所有的层次递归的拷贝,深拷贝可以通过copy.deepcopy()来实现
| 浅拷贝:只复制引用,另一处修改,你当下的对象也会修改。 只对一个对象的顶层进行了拷贝,浅拷贝可以通过copy.copy()来实现
19. synchronized的原理
synchronized可以修饰方法或代码块(在修饰代码块时,会在编译后在代码块前后加入monitorenter和monitorexit指令,修饰方法时会在方法上加入ACC_SYNCHRONIZED访问标志),在修饰静态方法时获取到的是类锁,否则是对象锁。线程在访问方法或代码块时,必须先获得锁,否则会进入阻塞状态。
synchronized在JDK 6进行了优化,每个对象有 无锁、偏向锁、轻量级锁和重量级锁四种状态,存在于对象头中。偏向锁指认为多数情况下不存在多线程竞争,对于第一个线程不需要获得锁;轻量级锁是在偏向锁被两个线程访问时,线程通过CAS和自旋的方式获得锁,不会发生阻塞。当有多个线程同时访问锁时,升级为重量级锁,一个线程获得锁,其他线程被阻塞。