1.思想:
赋予每一个页面一个访问字段,用来记录这个页面从上次被访问以来所经历的时间t,当必须淘汰一个页面时,选择t最大的,即最久未使用。
1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet还是doPost,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户。
doGet() 或 doPost() 是创建HttpServlet时需要覆盖的方法.
3.public class Test2
{
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + ” “);
add(b);
System.out.print(b + “”);
}
}
执行后,结果输出是 -128, 127
原因:
(1)add函数没有返回值,所以没有任何作用,经main()中调用后并不会修改变量的值。
(2)对于Byte类型的变量,大小范围为-128到127.所以给++a后变成了-128,越界。
4.CallableStatement extends PreparedStatement.
PrepareStatement extends Statement.
5.java的堆内存分为两块 : permantspace(持久带) 和 heap space。
持久带中主要存放用于存放静态类型数据,如 Java Class, Method 等, 与垃圾收集器要收集的Java对象关系不大。
年老代溢出原因有 循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存,既A B D选项
持久代溢出原因 动态加载了大量Java类而导致溢出
6.注意:非静态代码块的执行顺序在构造函数之前。
7.Java的数组复制方法,哪个效率最高?
(1)for循环逐一复制:很灵活,但是代码不够简洁
(2)System.arrayCopy:是一个原生态方法,是java与c语言的接口。效率最高。
(3)copyOf不是System的方法,是Arrays的方法,本质上调用了arrayCopy.
System.arraycopy > clone > Arrays.copyOf > for循环
简答题(共五道):
1. Object 的equals、hashCode、toString、clone、wait、 notify、notifyAll方法的用途和实现约定。
(1)Object类 ( java.lang.Object ) 是Java所有类的祖先,所有的类都使用Object类作为父类,所有对象(包括数组)都实现Object类的方法。
(2)接下来介绍Object类的常用方法:
① clone() :
创建并返回此对象的一个副本。
即x.clone().getClass() == x.getClass()。
② equals(Object obj) :
指示其他某个对象是否与此对象相等。
equals() 和“==” 的区别:
(1)java的数据类型分为两种,基本数据类型和引用数据类型
(2)对于基本数据类型来说,比较的时候用“==”,比较的是值是否相等。
(3)对于引用数据类型来说,“==”比较的是内存地址,equals比较的是值。
(4)equals底层也是通过"=="来实现的,只是对于引用类型来说,对equals方法进行了重写,所以在引用类型中equals比较的是值是否相等。
③ finalize() :
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。对于任何给定的对象,Java虚拟机最多只调用一次finalize方法。
④ hashCode() :
返回该对象的哈希码值,是为了提高哈希表的性能。对于equals()方法比较的两个对象如果相等,则其hashCode()方法返回的整数结果相等,若其equals()方法比较的两个对象不相等,则hashCode()返回的整数结果不同。
⑤ notify() :
唤醒在此对象监视器上等待的单个线程
⑥ notifyAll() :
唤醒在此对象监视器上等待的所有线程
⑦ toString() :
返回该对象的字符串表示,通常返回一个以文本方式表示此对象的字符串,即getClass().getName()+’@’+Integer.toHexString(hashCode())
⑧ wait() :
在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。当前线程必须拥有此对象监视器。
2. volatile和synchronized的异同点:
(1)synchronized解决java多线程的执行有序性和内存可见性。而volatile解决java多线程的内存可见性。
(2)volatile不拷贝到工作内存形成副本,直接在主存中修改数据。适合场景:一个变量被多个线程共享,线程直接给这个变量赋值,利用volatile开销较小.
(3)volatile是变量修饰符,而synchronized则作用于一段代码或方法。volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。
(4).volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
(5)volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化.
3.符号表构造和处理的方式:
(1)线性查找、二叉树、杂凑技术
1.sizeof():
c语言中用来计算对象所占的字节个数,通常用来查看变量或者结构体等所占的字节个数。
sizeof有三种语法形式,如下:
1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) sizeof object; // sizeof 对象;
所以:
int i;
sizeof( i ); // ok
sizeof i; // ok
sizeof( int ); // ok
sizeof int; // error
下面分类总结sizeof()的计算方法:
(1)基本数据类型的sizeof():
sizeof(char) 结果是1
sizeof(int) 结果是4
sizeof(unsigned int) 结果是4
sizeof(long int) 结果是4
sizeof(short int) 结果是2
sizeof(float) 结果是4
sizeof(double) 结果是8
(2)指针变量的sizeof() :
***32位计算机中指针变量占4字节
***64位计算机中指针变量占8字节
char *pc ="abc";
sizeof( pc ); 结果为4(指针)
sizeof(*pc); 结果为1(char类型)
int *pi;
sizeof( pi ); 结果为4(指针)
sizeof(*pi); 结果为4(int类型的)
char **ppc = &pc;
sizeof( ppc ); 结果为4(指针)
sizeof( *ppc ); 结果为4(指针)
sizeof( **ppc ); 结果为1(char类型)
void (*pf)(); 函数指针
sizeof( pf ); 结果为4(指针)
(3)数组的sizeof():
char a1[] = "abc";
int a2[3];
sizeof( a1 ); 结果为4,字符末尾还存在一个NULL终止符
sizeof( a2 ); 结果为3*4=12(依赖于int)
void foo3(char a3[3])
{
int c3 = sizeof( a3 ); // c3 == 4
}
void foo4(char a4[])
{
int c4 = sizeof( a4 ); // c4 == 4
}
注意:当数组被作为参数传进函数时,已经蜕变成指针,不再是数组形式。所以sizeof()的大小都是4.传递数组的时候,不是将整个数组传递过去,而是将整个数组的首地址传过去。所以是指针。
(4)结构体的sizeof() ;
会用到对齐方式对偏移量的约束问题;
struct A
{
char t; //直接填充1
int k; //int占4个字节,已经占用的char(1)不是4的倍数,先填充3个字节,1+3+4=8.
char i; //char占一个字节,已经占用的是8,直接填充1
double m; //double占8个字节,已经占用的9个字节不是8的倍数,先填充到16,再放入double.9+7+8=24.
};
按顺序从上向下判断,判断之前先看已经占用的字节数是不是自己的倍数,若不是则先填充已经占用的字节。sizeof(A)=24.对齐模数是double(8),24是8的整数倍,所以满足条件。
另一种写法:
struct A
{
int k; //int占4个字节,直接填充
char t; //char占1个字节,直接填充
char i; //char占一个字节,直接填充
double m; //double占8个字节,已经占用的是4+1+1=6个字节,不是8的倍数,先填充到8,再放入double.6+2+8=16.对齐模数是double(8),16是8的整数倍,所以满足条件。
};
sizeof(A)=16;
内存对齐和位域对齐:
(1)内存对齐:
分为基本数据对齐和结构数据对齐。
基本数据对齐:
在X86,32位系统下有如下数据对齐规则:
a、一个char(占用1-byte)变量以1-byte对齐。
b、一个short(占用2-byte)变量以2-byte对齐。
c、一个int(占用4-byte)变量以4-byte对齐。
d、一个long(占用4-byte)变量以4-byte对齐。
e、一个float(占用4-byte)变量以4-byte对齐。
f、一个double(占用8-byte)变量以8-byte对齐。
g、一个long double(占用12-byte)变量以4-byte对齐。
h、任何pointer(占用4-byte)变量以4-byte对齐。
而在64位系统下,与上面规则对比有如下不同:
a、一个long(占用8-byte)变量以8-byte对齐。
b、一个double(占用8-byte)变量以8-byte对齐。
c、一个long double(占用16-byte)变量以16-byte对齐。
d、任何pointer(占用8-byte)变量以8-byte对齐。
结构数据对齐:
在结构体中,成员数据对齐满足以下规则:
a、结构体中的第一个成员的首地址也即是结构体变量的首地址。
b、结构体中的每一个成员的首地址相对于结构体的首地址的偏移量(offset)是该成员数据类型大小的整数倍。
c、结构体的总大小是对齐模数(对齐模数等于#pragma pack(n)所指定的n与结构体中最大数据类型的成员大小的最小值)的整数倍。
注意:最后一定要判断c点是否满足,不满足了补充空字节。
(2)位域对齐:
位域是指以位为单位来定义某结构体的成员变量,它和结构体是分不开的。
使用位域的目的是进行数据压缩,在vc6.0的位域对齐规则:
①若相邻成员变量类型相同,且其位宽之和不大于成员变量类型位宽大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
②如果相邻位域字段的类型相同,但其位宽之和大于成员变量的类型宽度大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
③如果相邻的位域字段的类型不同,则转化为内存对齐问题;
④整个结构体的总大小为最宽基本类型成员大小的整数倍。
数据类型的自动转换和强制转换:
*从低向高可以自动转换,但是从高向低不能自动转换,需要强制声明。
可以这样理解:高位向低位转换时,可能会超过低位的最大允许范围,所以不能直接自动转换,但是低位向高位转换的过程中,不用担心超过最大范围,所以可以自动转换。
低--------------------------------------------->高
byte,short,char-> int -> long -> float -> double
2.关于bridge(桥接)模式:
例1:设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等.
方案1:为每一种形状都提供一套不同颜色的版本。
方案2即是桥接模式的应用,将继承关系转换为关联关系,从而降低类之间的耦合,减少代码量。
例2:实现不同的车在不同的路上行驶的过程。
(1)桥接模式主要是通过组合的方式,将外界的影响因素分开来处理,最后在客户端调用的时候按需求组合。将类的接口和实现分离。
(2)主要用来适应多维度变化的结构设计。
(3)桥接模式类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),所以多继承方案的复用性较差,桥接模式是比多继承方案更好的解决方案。
3.适配器模式:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式包括类的适配器模式和对象的适配器模式。
类的适配器:包括源接口,目标接口,和适配器类。适配器类和源接口是继承关系。
对象的适配器:包括源接口,目标接口和适配器类。适配器类和源接口是委派关系(适配器类包装了源接口)。
*关于java中的缺省适配器模式:
(1)在很多情况下,必须让一个具体类实现某一个接口,但是这个类又用不到接口所规定的所有的方法。
(2)通常的处理方法是,这个具体类要实现所有的方法,那些有用的方法要有实现,那些没有用的方法也要有空的、平庸的实现。
(3)但是这是没有必要的,会造成资源的浪费。所以引入缺省适配器。
(4)缺省适配器是一个类,为接口中的所有声明提供一个空方法,用户类只需要继承缺省适配器,并有选择实现需要的方法即可。
4.关于swap交换算法:
(1)最简单的一种,利用中间变量来交换:
数据类型不一定是整数,需要另外申请空间。
int x = 10;
int y = 5;
int tmp = x;
x = y;
y = tmp;
System.out.println("x: " + x + " y: " + y);
(2)利用加减法来交换:数据不一定是整数,不用另外申请空间
int x = 10;
int y = 5;
x = x + y; // x 15
y = x - y; // y 10
x = x - y; // x 5
System.out.println("x: " + x + " y: " + y);
(3)利用异或运算来交换:
利用位运算,所以效率高,不用另外申请空间。
int x = 10; // x 0000 1010
int y = 5; // y 0000 0101
x = x ^ y; // x 0000 1111
y = x ^ y; // y 0000 1010
x = x ^ y; // x 0000 0101
System.out.println("x: " + x + " y: " + y);
(4)利用表达式运算交换值:
int x = 10;
int y = 5;
x = y + 0 * (y = x);
// 先将y参与运算,然后将x值赋给y,最后再将结果(也就是y)赋给x
System.out.println("x: " + x + " y: " + y);
5..data和.bss的区别和联系:
(1)一般情况下,一个程序是由.data段、.bss段和.text段组成的。
(2).bss段存放未初始化的全局变量,不在可执行文件中,不占用.exe文件,由系统初始化,初始化时清零。属于静态内存分配。
(3).data和.text是在可执行文件中,占用.exe文件,.data段中存放已经初始化的数据。
(4).data和.bss统称为数据段。
6.TCP/UDP
(1)TCP协议和UDP协议都是处于运输层的协议
(2)TCP协议全称为传输控制协议,面向连接,数据传输单位是报文段,采用流模式,提供可靠服务。
(3)UDP协议全称为用户数据报协议,无连接,数据传输单位是用户数据报,提供不可靠服务。
1.primitive types 基本数据类型
Java数据类型分为基本数据类型(原始数据类型)和引用数据类型。
Boolean是引用类型,基本类型都有自己对应的引用类型。
注意大小写,大写字母开头都是引用类型,小写字母开头都是基本数据类型。
2.HttpServlet容器响应Web客户请求流程如下:
1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet还是doPost,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户。
doGet() 或 doPost() 是创建HttpServlet时需要覆盖的方法.
3.public class Test2
{
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + ” “);
add(b);
System.out.print(b + “”);
}
}
执行后,结果输出是 -128, 127
原因:
(1)add函数没有返回值,所以没有任何作用,经main()中调用后并不会修改变量的值。
(2)对于Byte类型的变量,大小范围为-128到127.所以给++a后变成了-128,越界。
4.CallableStatement extends PreparedStatement.
PrepareStatement extends Statement.
5.java的堆内存分为两块 : permantspace(持久带) 和 堆空间。
持久带中主要存放用于存放静态类型数据,如 Java Class, Method 等, 与垃圾
收集器要收集的Java对象关系不大。
老年代溢出原因有 循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。
持久代溢出原因 动态加载了大量Java类(大量字节码文件)而导致溢出
6.注意:非静态代码块的执行顺序在构造函数之前。
类A继承类B,则代码的执行顺序是:
B的静态代码块–》A的静态代码块—》B的非静态代码块—》B的构造方法–》A的非静态代码块—-》A的构造方法。
7.Java的数组复制方法,哪个效率最高?
(1)for循环逐一复制:很灵活,但是代码不够简洁
(2)System.arrayCopy:是一个原生态方法,是java与c语言的接口。效率最高。
(3)copyOf不是System的方法,是Arrays的方法,本质上调用了arrayCopy.
System.arraycopy > clone > Arrays.copyOf > for循环
找出N无序数中最大的M个数:
N个数可能有很大的数据量,用排序的话时间复杂度太高,不采取。
较好的办法:维护一个有M个数的小顶堆(根节点最小),从M+1个数开始,只和堆的根节点比较,若大于根节点,则交换,否则M++;
比较完之后,就形成了一个用小顶堆表示的最大M个数的堆。
//currentIndex代表当前局部小顶堆的根节点
//leftChileIndex代表局部小顶堆根节点的左子树
//rightChildIndex代表局部小顶堆根节点的右子树
//smallIndex是中间节点,用来存储比较后较小的值
private static void MinHeapify(int[] array, int currentIndex, int heapSize) {
int leftChileIndex = currentIndex * 2+1;
int rightChildIndex = currentIndex *2+2;
int smallIndex = currentIndex;
if(leftChileIndex < heapSize && array[leftChileIndex]<array[smallIndex]) {
smallIndex = leftChileIndex;
}
if(rightChildIndex < heapSize && array[rightChildIndex] < array[smallIndex]) {
smallIndex = rightChildIndex;
}
if(smallIndex != currentIndex) {
Swap(array[currentIndex],array[smallIndex]);
MinHeapify(array, smallIndex,array.length);
}
}
关于单链表的问题,这里引用了别人的博客:
给定单链表,检测是否有环等系列问题
java栈溢出原因:
(1)大量递归调用
(2)大量循环或者死循环
(3)全局变量过多
(4)数组、List、map数据是否过大
2.Java内存溢出:
没有引用的对象(垃圾)过多以至于虚拟机没有来得及回收,造成内存溢出。
3.堆溢出:
只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,时间久了就会有堆溢出现象发生。
heap和stack的区别:
//数据结构中的堆和栈:
是两种不同的用来存储数据的数据结构,即存储方式,两者最大的区别是堆采用先进先出的方式处理数据,但是栈采用先进后出的方式。
//内存中的堆和栈:
是两块不同的存储区域,堆区和栈区的区别如下:
(1)大小:堆区大小受限于虚拟内存,栈区大小是固定常数,大小为1M或者2M.
(2)内存分配和释放的方式:栈区由操作系统自动分配释放,堆区由程序员手动分配释放,若程序员不释放,则由操作系统回收。
(3)生命周期:栈区用完就被回收,堆区由虚拟机的垃圾回收算法决定什么时候回收。
(4)存放内容:堆区主要存放new出来的对象,栈区存放参数和局部变量。
(5)申请内存时的区别:对于栈来说,申请内存较方便,只要内存所剩余资源大于需要的内存大小,就可以直接申请,否则栈溢出。但是对于堆来说,就更加麻烦。
2.TCP和UDP区别:
TCP是一种流模式的协议,UDP是一种数据报模式的协议。
(1)TCP发送数据可以分很多次,接收方接受数据也可以分为很多次,这两者的次数可以不同,但发送的总数据量不能大于接收方的接收缓存(流量控制)。
(2)UDP发送数据的次数和接收方接受的次数必须一致。UDP基于报文,接收方每次读取只能读取一个报文。
why?
因为TCP面向连接,而UDP是无连接的。
其他的区别:
(1)传输控制协议(TCP)是一种面向连接的、可靠的、基于字节流的运输层通信协议.TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
(2)用户数据报协议(UDP)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
***TCP和UDP都是运输层的协议。
3。 TCP通信过程 :
TCP的通信过程分为三个步骤:建立TCP连接通道,传输数据和断开TCP连接通道
即三次握手--》数据传输--》四次挥手。
(1)三次握手:
第一次握手:客户端发送syn包(seq=x)到服务器,并进入syn_send状态,等待服务器确认。
第二次握手:服务器收到syn包,必须确认客户的syn(ack=x+1),同时自己也发送一个syn包(即ack+syn),此时服务器进入syn_recv状态。
第三次握手:客户端接到syn+ack包,向服务器发送确认包ack(ack=y+1),此时,客户端和服务器处于established状态,连接建立。
(2)四次挥手断开连接(假设A是主动关闭方,B是被动关闭方):
第一次挥手,A发送FIN给B,告诉B不会再发送数据。
第二次挥手:B接收到FIN后,发送一个ACK给B,表示确认。
第三次挥手:B发送一个FIN给A,告诉A不会再发数据。
第四次挥手:A接收到FIN后,发送一个确认ack给B。
(3)传输数据的过程:
1.超时重传:
发送方在发送了seq包后,若等待一段时间后没有收到对于的ack确认,就会认为报文丢失,会重传这个数据包。
2.快速重传:
接收方发现数据包丢失,发送给发送方ack信息(报文丢失),触发发送方重传报文。和超时重传的区别是发送方不用傻等。
3.流量控制:
即TCP滑动窗流量控制,即接收端告诉发送端自己还有多少缓冲区可以接受数据,这可以提高TCP传输效率。
4.拥塞控制:
基于整个网络来考虑,用来提高TCP传输效率。常用的拥塞策略算法有:慢启动、拥塞避免、拥塞发生和快速恢复。
互斥量实现线程的互斥,信号量实现线程的同步。
互斥量访问是无序的,信号量是指在互斥的基础上,通过其它机制实现访问者对资源的有序访问。
信号量可以实现互斥的功能。
获取手机验证码
1.思路:
(1) 用户输入手机号码,点击获取验证码
(2) 服务器自动创建验证码,并通过短信代理商发送到用户手机
(3) 用户查看验证码,输入并点击提交
(4) 服务器比较用户输入的验证码和自动随机生成的验证码是否相同,返回结果。
2.服务器用随机数发生器生成6位验证码方案:
import java.util.Random;
public class messageUtil {
private static final String[] chars = { "0", "1", "2", "3", "4", "5", "6","7", "8", "9" };
public static int[] createImage(int[] a) {
// 随机数发生器
Random ran = new Random();
// 产生随机数字
for (int i = 0; i < 6; i++) {
a[i] = ran.nextInt(chars.length);
}
return a;
}
public static void main(String[] args) {
int a[] = new int[6];
int b[] = createImage(a);
for (int i = 0; i < 6; i++) {
System.out.print(b[i] + " ");
}
}
}