闲话
到了现在还在面试的可能不多了,大多找到了心仪的公司。自己太菜,在牛客上看,便投了某家。
这时候好像不怎么手撕代码了,八股文也问的少了。
一、凉经 回忆
1.自我介绍。
2.项目介绍。
3.servlet是怎么暴露在外界的,servlet怎么处理请求的,Tomcat怎么运行,是一个容器,怎么工作的…
4.平时写好一个项目后,该怎么编译,具体原理是什么…等,(这里我都没回答出来,后来给我说了下,Maven的package打成jar包,然后运行在服务器上…)
5.hashmap什么时候扩容,扩容的数字为什么是8…,碰撞因子是多少?
6 ArrayList扩容怎么扩容的。是时间换空间还是空间换时间
7讲一下springMVC的原理,dispatcherservlet为什么能处理那么多请求,为什么功能强大,和servlet的区别是什么。
二、总结答案
1.项目介绍,我就介绍的十分糟糕,同学坐在我旁边,说要不是做过这些项目,可能根本就不知道我说的是什么。
我也觉得自己项目介绍的十分糟糕,面试了这么多回,还是没长进,自己没有好好总结,面试官说从项目背景,需求,技术栈,业务等方面来介绍,我两下就说完了。明显没什么条理性。
自己学习黑马视频的时候做的黑马旅游网项目,所以感觉没什么好说的,但面试官也能从中提取出来 要点。需要什么原理,技术场景 等。现在做个总结:
这是我在Java初期做的一个开源项目,做的是一个旅游网站,是一个javaweb类型项目 使用到的技术有HTML,css,servlet,ajax,servlet,MySQL等。
旅游网站就有基本的登录,注册,分类查看,分页,搜索等功能。
技术选型的话 分为三层
web层 service层 dao层
web层 servlet html Jackson
service层有javamail
dao层有mysql jdbctemplate
在dao中写出具体的方法,service调用dao进行查询等操作。servlet得到的数据转化为JSON传给前端。
第二个项目是一个企业权限管理系统,技术选型有maven springmvc spring mybatis oracle jsp等
基本功能有,登录,注册,订单的增删改查,货物的修改查询,用户管理。是一个权限管理系统,所以用到springsecurity,对用户的权限进行赋予。比如设置各个用户不同的权限,有的可以查询,有的可以增删等。
2.关于servlet这块知识一直没有掌握好,今天总结下:
如果你想吧自己桌面上的资源让其他人访问,那么其他人就需要一个url来对你的电脑进行访问,但是不能,此时servlet就出现了,servlet就可以处理别人的请求,让他们访问你的电脑资源。
servlet:
如我们学习使用的tomcat,就是一个servlet容器。servlet容器,那里面放着的就是servlet对象。他的处理过程大致分为三个方面:
- 接受请求
- 处理请求
- 响应请求
我们编写的就是处理请求这一块,后面又出现了一些框架,框架就很好的处理了这些事情。
在servlet中,我们实验web.xml进行配置servlet,如servlet-mapping和servlet-name这样让servlet暴露在外界的。
servlet大概的执行流程:
- 项目启动,创建servletcontext
- 读取web.xml,依次反射创建listener,filter,servlet。
- 再执行filter.dofilter方法
- 最后servlet处理请求,响应结果。
tomcat会传入三个对象:servletconfig,servletresquest,servletresponse,
我们写的web.xml其实就被servletconfig处理了。
而resquest和response:
http请求到了tomcat,tomcat通过字符串分析,会将请求头,请求地址,请求参数,都装进request对象。
request.getUrl();
request.getHeader();
而response,开始没有处理结果时,还是空的对象,当有结果时候,通过response.write(),将结果写入response结果的缓冲区,tomcat在servlet处理结束后,拿到response,遍历里面的信息,组成http响应,最后返回给客户端。
servlet是个接口,有5个方法:init,getServletConfig,service,getServletInfo,destroy。
其中init,service,destroy是生命周期方法,init和destroy各自只执行一次,service在每次请求时都会执行。
httpservlet实现了servlet的service方法。在httpservlet中只需要去重写dopost和doget方法即可。
4.我们编译了一些java类,其中肯定也包含了main方法。编译成class文件后,把它打成jar包,上传到服务器上。此时就可以使用java命令实现。
如果我们把jar包解压后,就可以看到META-INF的目录,目录里有MANIFEST.MF,这个文件就是定义如何导入外部jar包。
5.hashmap扩容:当元素个数超过数组大小loadfactor时 就回把数组长度乘以2倍。
存储结点>原数组长度0.75 (这里是大于,没有等于)。
当结点长度为8的时候,就回树化,变成红黑树,红黑树存储的是value。
为什么是8转化为红黑树?
树结构的空间是点结构空间的2倍,我们使用hash时,这些点是均匀分布的,符合泊松分布的,当链表越来越长的时候,就需要提高效率了,此时就需要设置阈值(8),转变成红黑树。
链表是符合泊松分布的,当长度为8时,这已经是千万分之一的概率了,这是通过概率所决定的,因此设为8的时候会树化。(当树的长度降为6的时候,会转变为链表)
6.arraylist的扩容机制:
arraylist的变量:
//定义一个空数组以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数组中的元素
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length
private int size;
arraylist里面有三个构造函数,不同的构造函数会影响不同的扩容机制。
1.默认的无参构造,返回空数组,长度为0。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.给定初始容量的构造函数。
就是构造一个指定长度的空数组。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3.包含特定元素集合的构造函数。
把传入的集合转换为数组,通过Arrays.copyOf方法把集合中的元素拷贝到elementData。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
扩容是从add方法开始的:
add有两种方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
都调用了ensureCapacityInternal这个方法,size + 1保证可以存入下一个元素。初始size为0,这里+1,括号传入的参数minCapacity就是1。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里先看内圈的方法
calculateCapacity方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA就是默认的数组,长度为10。
无参的构造方法就回返回DEFAULT_CAPACITY(10)和minCapacity(1)的最大值。
有参的构造方法时:此方法返回1.
minCapacity第一次长度为1,后面的调用的时候每次都是数组长度的size+1.
内圈的方法看完后,再看外圈的方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里比较minCapacity 和elementData的长度,这里的elementData长度是数组的实际长度。如果传入一个长度为5的有参构造方法,那么elementData.length=5,size为0,minCapacity=0+1=1;这里就不需要进行grow扩容,依次下去, 2 3 4 都不需要,但是到5的时候(5+1),当插入是就需要扩容了。
grow方法:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
这里右移一位,就是除以2,因此就是原来倍数的1.5倍。
因为我们的minCapacity每次+1,可能就会超过newCapacity (即原来倍数的1.5倍),超过就将大的值给newCapacity 。
再判断newCapacity 和设定的 MAX_ARRAY_SIZE。一般来说, MAX_ARRAY_SIZE= Integer.MAX_VALUE - 8。
如果大于;hugeCapacity
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果大于,返回 Integer.MAX_VALUE,否则是Integer.MAX_VALUE - 8。
最后通过复制将原数组元素放到新的大容量数组中。
参考链接:https://blog.csdn.net/qq_26542493/article/details/88873168
7.springmvc的工作原理,这里回答的还是model,view,controller,流程图。
问我为什么dispatcherservlet功能强大,hr给我说:要考虑多线程并发…。
这里 我参考了DispatcherServlet解析。
主要说到,dispatcherservlet 里的doService和doDispatch两个方法,其中doDispatch获取异步请求处理器。这里应该就用到了多线程的知识,处理逻辑比较复杂,doDispatch获取处理器适配器,执行handler,返回ModelAndView。里面的源码较多,大家可以看注释。
总结
这篇文章当时面完第二天小结了一下,自己对基础要么理解深入,要么记忆深一些,否则在面试中很吃亏,需要不断回顾一些知识点,还有需要看源码,自己不想看源码的话,就看别人的讲解和注释,知道大概的逻辑,比如,这里的Arraylist扩容,就比之前的了解深入一些了,dispatcherservlet不要只知道表面,深入理解最好。