面试总结
1、servlet生命周期
(1) 初始化阶段,调用init()
方法
(2) 响应客户端请求阶段,调用service()
方法
(3) 终止阶段,调用destroy()
方法
servlet容器装载servlet
- Servlet容器启动时自动装载某些Servlet,实现它只需要在web.XML文件中的<Servlet></Servlet>之间添加如下代码:
<loadon-startup>1</loadon-startup>
- 在Servlet容器启动后,客户首次向Servlet发送请求
- Servlet类文件被更新后,重新装载Servlet,Servlet被装载后,Servlet容器创建一个Servlet实例并且调用Servlet的init()方法进行初始化。在Servlet的整个生命周期内,init()方法只被调用一次。
servlet工作原理
首先客户发送一个请求,servlet
调用service()
方法对请求进行响应,service()
方法对请求的方式进行匹配,选择调用doGet()
, doPost()
这些方。servlet接口中没有 doGet,doPost这些方法,httpServlet中有这些方法,但返回值都是error信息,所有需要将doPost,doGet重写。
每一个自定义的servlet都需要实现servlet接口(init,service,destory就在这个接口中)。GenericServlet是一个通用的、不特定于任何协议的servlet,它实现了servlet接口,而httpServlet又继承与GenericServlet,所以我们定义servlet时只需要继承httpServlet即可。
httpServlet是特定于http协议的类,它实现了service()方法,并将servletRequest和servletResponse强转为httpRequest和httpResponse。
2、sql 左外连接、右外连接、内连接、全连接
--左外连接,就算emp表中员工的deptno没有与dept表中的deptno对应也会查出来,不过显示emp表中的字段
select e.*,d.*
from emp e left join dept d on e.deptno=d.deptno;
左连接.png
--右外连接,就算emp表中员工的deptno没有与dept表中的deptno对应也会查出来,不过显示dept表中的字段
select e.*,d.*
from emp e right join dept d on e.deptno=d.deptno;
右连接.png
-- 内连接,只会查询出两表deptno一一对应的数据
select e.*,d.*
from emp e inner join dept d on e.deptno=d.deptno;
内连接.png
-- 全连接,不管两张表的数据是否一一对应,全都会查出来
select e.*,d.*
from emp e full join dept d on e.deptno=d.deptno;
全连接.jpg
3、数据库并发问题
数据库带来的并发问题有:
- 丢失更新。
- 未确认的相关性(脏读)。
- 不一致的分析(非重复读)。
- 幻读。
1.1 丢失更新
当两个或多个事务选择同一行,然后基于最初选定的值进行更新,会发生丢失更新问题。每个事务都不知道其实事务的存在。最后进行提交的事务会将之前的更新覆盖,导致数据丢失。
此时需要对数据库进行加锁,两种方式,一种是一开始就锁住,防的很彻底,但是一旦锁的时间过长会影响他人操作;另一种是在最后做更新提交时上锁,将更新失败视为小概率事件,只在最后一步才上锁。
1.1.1 悲观锁
当我们对数据进行修改时,首先需要将数据查询出来,在查询语中加锁,即 select .......for update nowwait
,在一开始的查询中就开始加锁,避免其他用户更新
1.1.2 乐观锁
(a)旧值条件法:
在更新语句中,使用旧的状态值作为更新的条件(不推荐)
(b)版本列法:
当我们设计表的时候往往会多设置两个number/date类型的列,以便以后对表进行拓展。我们将一个number列作为版本列,每次修改数据时,以 限制条件 = 主键 + 版本列号
的形式进行修改,同时每修改一次数据,都对版本号进行更新。
我们通常在冲突较为严重的系统中使用悲观锁,其他情况优先使用乐观锁。
2.1 未确认的相关性(脏读)
当一个事务读取到另一个事务还未提交的修改时候,产生脏读。
张三的工资原本是2000,财务不小心将张三工资改成了5000,此时尚未提交,张三查询时发现自己工资变成5000后很开心,此时财务发现数据异常,回滚事务,张三工资又变成了2000,这就叫脏读。
解决办法:在事务提交之前,其他任何事务都不能读取其修改过的值。
3.1 不一致的分析(非重复读)
4.1 幻读
4、线程的两种创建方式
第一种:new Thread 重写 Thread 中的 run() 方法。
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("1: " + Thread.currentThread().getName());
System.out.println("2: " + this.getName());
}
}
};
第二种:Thread 的构造方法中有一个是以 Runnable对象为参数,Runnable是一个接口,当中有run()方法,Thread是implements了Runnable接口,继而实现了run()方法,我们可以在new Thread的时候,通过构造方法传一个Runnable对象,而Runnable对象中又有run()方法,通过这种方式,也能实现线程的创建。
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("3: " + Thread.currentThread().getName());
}
}
});
5、SVN多用户开发 --- 代码冲突&解决
修改同一文件的不同位置引发的冲突
现在有两个用户:zhangsan 和 lisi。他们需要同时修改一个文件,原始代码如下:
public void Test{
System.out.println("Test!");
return 1;
}
此时 zhangsan 需要修改第二行代,
public void Test{
System.out.println("Test!xingoo");
return 1;
}
lisi 则修改第三行代码,
public void Test{
System.out.println("Test!");
return 0;
}
假设 zhangsan 先提交代码,当 lisi 提交代码时,会提示错误:
提示用户该文件已经过期,需要先更新文件。
此时 lisi 需要先进行更新代码,再提交代码。
这种情况,是最简单的代码冲突样例。不同用户修改了不同位置的代码,因此只需要在提交前进行更新,就可以解决冲突。
修改同一文件相同位置引发的冲突
还是这个例子,zhangsan 当前代码如下,版本号为9,
public void Test{
System.out.println("Test!xingoo");
return 1;
}
lisi 代码如下,版本号为10,
public void Test{
System.out.println("Test!xingoo");
return 0;
}
此时 lisi 的代码是最新的版本。
这时,zhangsan 想要修改第三行的返回值如下,
public void Test{
System.out.println("Test!xingoo");
return 2;
}
这时提交代码会出现提示,发生错误,需要更新,OK,更新之后再次提交还是报错,提示 文件存在冲突!
观察文件目录,会发现多了三个文件
SVN文件冲突
分别打开三个文件:
其中<<<<<<.mine到====之间为当前用户修改的内容;
====到>>>>>.r10为版本库中的内容:
public void Test{
System.out.println("Test!xingoo");
<<<<<<< .mine
return 2;
=======
return 0;
>>>>>>> .r10
}
test.txt.mine 记录当前用户修改后的文件内容快照
public void Test{
System.out.println("Test!xingoo");
return 2;
}
test.txt.r9 记录当前用户修改前,版本库中的内容快照
public void Test{
System.out.println("Test!xingoo");
return 1;
}
test.txt.r10 记录当前版本库中内容快照
public void Test{
System.out.println("Test!xingoo");
return 0;
}
通过这四个文件就可以很快速的发现,哪里有冲突。
至于如何修改:
直接拷贝mine文件,然后把r10文件的r10后缀去掉,进行更新。更新后,对比mine中的内容在进行修改。
也可以直接与其他人员进行协商沟通;更新版本库中的内容。
6、排序算法
冒泡排序
左右相邻的两个数进行比较,将大的放到右边,小的放到左边,每次排序都会将最大的数放到序列的最后,最差时间复杂度为 O(n^2),最好情况为 O(n)
public class BubbleSort {
public static void sort(int[] num) {
for (int i = 0; i < num.length - 1; i++) {
int demo = 0;
for(int j = 0; j < num.length -1-i; j++) {
if (num[j] > num[j + 1]) {
demo = num[j];
num[j] = num[j + 1];
num[j + 1] = demo;
}
}
}
for(int k = 0; k < num.length; k++) {
System.out.print(num[k] + ", ");
}
}
public static void main(String[] args) {
int[] num = {8, 2, 3, 1, 6, 9, 7};
sort(num);
}
}
插入排序
将序列的第一个数默认为已排序序列,从第二个数开始(目标数get),对已排序序列进行从后往前的比较,如果已排序序列的当前数大于get,则该元素移动到下一位,重复比较,直到已排序序列的当前数小于get,则在该元素后一个位置将get插入。
最好空间复杂度为 O(n),最坏为O(n^2),与冒泡排序类似。
这个过程类似与打扑克摸手牌,此时已排序序列表左手已经抓好的牌,get表示右手新摸到的牌,从右往左进行比较,直到有小于get的牌,在其后插入get。
public class InsertionSort {
public static void insertion(int[] arr) {
int len = arr.length;
for(int i = 1; i < len; i++) {
int get = arr[i];
int j = i -1;
while(j >= 0 && get < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = get;
}
}
public static void main(String[] args) {
int[] arr = {2, 3, 1, 5, 6};
insertion(arr);
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}