1. SQL注入
原理是基本的sql语句 简单的说就是把一个正常的sql语句的逻辑转换成另一种不正常的sql语句逻辑 基本的例子:查询数据 select * from tablename where id='admin' 现在我们查找 名为admin的用户 如果我们在后面加一个 or 1=1;sql语句变成了 select * from tablename where id='admin' or 1=1这样导致了 where 语句无用了 。查询出了所有的用户 信息.
SQL注入防范编辑
在客户端,攻击者完全有可能获得网页的源代码,修改验证合法性的脚本(或者直接删除脚本),然后将非法内容通过修改后的表单提交给服务器。因此,要保证验证操作确实已经执行,唯一的办法就是在服务器端也执行验证。你可以使用许多内建的验证对象,例如Regular Expression Validator,它们能够自动生成验证用的客户端脚本,当然你也可以插入服务器端的方法调用。如果找不到现成的验证对象,你可以通过Custom Validator自己创建一个。
传传统防御技术编辑
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
inner join(等值连接) 只返回两个表中联结字段相等的行
举例如下:
--------------------------------------------
表A记录如下:
aID aNum
1 a20050111
2 a20050112
3 a20050113
4 a20050114
5 a20050115
表B记录如下:
bID bName
1 2006032401
2 2006032402
3 2006032403
4 2006032404
8 2006032408
--------------------------------------------
1.left join
sql语句如下:
select * from A
left join B
on A.aID = B.bID
结果如下:
aID aNum bID bName
1 a20050111 1 2006032401
2 a20050112 2 2006032402
3 a20050113 3 2006032403
4 a20050114 4 2006032404
5 a20050115 NULL NULL
(所影响的行数为 5 行)
结果说明:
left join是以A表的记录为基础的,A可以看成左表,B可以看成右表,left join是以左表为准的.
换句话说,左表(A)的记录将会全部表示出来,而右表(B)只会显示符合搜索条件的记录(例子中为: A.aID = B.bID).
B表记录不足的地方均为NULL.
--------------------------------------------
2.right join
sql语句如下:
select * from A
right join B
on A.aID = B.bID
结果如下:
aID aNum bID bName
1 a20050111 1 2006032401
2 a20050112 2 2006032402
3 a20050113 3 2006032403
4 a20050114 4 2006032404
NULL NULL 8 2006032408
(所影响的行数为 5 行)
结果说明:
仔细观察一下,就会发现,和left join的结果刚好相反,这次是以右表(B)为基础的,A表不足的地方用NULL填充.
--------------------------------------------
3.inner join
sql语句如下:
select * from A
innerjoin B
on A.aID = B.bID
结果如下:
aID aNum bID bName
1 a20050111 1 2006032401
2 a20050112 2 2006032402
3 a20050113 3 2006032403
4 a20050114 4 2006032404
结果说明:
很明显,这里只显示出了 A.aID = B.bID的记录.这说明inner join并不以谁为基础,它只显示符合条件的记录.
--------------------------------------------
注:
LEFT JOIN操作用于在任何的 FROM 子句中,组合来源表的记录。使用 LEFT JOIN 运算来创建一个左边外部联接。左边外部联接将包含了从第一个(左边)开始的两个表中的全部记录,即使在第二个(右边)表中并没有相符值的记录。
语法:FROM table1 LEFT JOIN table2 ON table1.field1 compopr table2.field2
说明:table1, table2参数用于指定要将记录组合的表的名称。
field1, field2参数指定被联接的字段的名称。且这些字段必须有相同的数据类型及包含相同类型的数据,但它们不需要有相同的名称。
compopr参数指定关系比较运算符:"=", "<", ">", "<=", ">=" 或 "<>"。
如果在INNER JOIN操作中要联接包含Memo 数据类型或 OLE Object 数据类型数据的字段,将会发生错误.
3. 问了数据库索引的东西.
概念:
索引是由用户创建的、能够被修改和删除的、实际存储于数据库中的物理存在;创建索引的目的是使用户能够从整体内容直接查找到某个特定部分的内容。
优缺点:
一般来说,索引能够提高查询,但是会增加额外的空间消耗,并且降低删除、插入和修改速度
常用索引数据结构:
多叉平衡搜索树:B树 / B+树 / B*树
B树
1. d为大于1的一个正整数,称为B-Tree的度。
2. h为一个正整数,称为B-Tree的高度。
3. 每个非叶子节点由n-1个key和n个指针组成,其中d<=n<=2d。
4. 每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶节点的指针均为null 。
5. 所有叶节点具有相同的深度,等于树高h。
6. key和指针互相间隔,节点两端是指针。
7. 一个节点中的key从左到右非递减排列。
8. 所有节点组成树结构。
9. 每个指针要么为null,要么指向另外一个节点。
10. 如果某个指针在节点node最左边且不为null,则其指向节点的所有key小于v(key1),其中v(key1)为node的第一个key的值。
11. 如果某个指针在节点node最右边且不为null,则其指向节点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。
12. 如果某个指针在节点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向节点的所有key小于v(keyi+1)且大于v(keyi)。
1.每个节点的指针上限为2d而不是2d+1。
2.内节点不存储data,只存储key;叶子节点不存储指针。
4. 怎么测电梯
面试一个测试人员的sense,喜欢问的问题就是你测试一下电话,或者电梯,或者一个具体的产品;
那么如何测试电梯呢?
电梯测试可以从几个方面来进行,功能测试,性能测试,压力测试,可用性测试(Usability),兼容性测试,本地化/国际化测试,可维护性测试;
功能测试,最基本的上下功能,开关功能,还有里面的各个按键
性能测试(很多人忽略的),比如电梯的调度算法,用户的等待时间,平均等待时间,上下的速度,耗电量等等
压力测试,比如承重量(你实际承受力是20,那么当进入19个人的时候就应该报警,或者是实际上用户有可能一股脑的全部冲进电梯,所以在静止的时候电梯需要考虑到这种情况),突然断电,门打不开等等
可用性测试,按钮是否方便,按键的感觉是否好,视觉效果,现在很多人诟病的事情是,开和关两个按钮的图示很不友好,在紧急的时候很容易搞错
兼容性测试,比如每个国家的电压不一样,是否考虑到这个情况
本地化/国际化测试,曾经看到一部电梯的使用手册翻译成英文,翻译得很差
可维护性,电梯如果坏了怎么去维修。
HA,high availabity测试,如果一部坏了,另外一部是否可以正常的运行等等。
关于性能测试,这里在多说几句, 我看到的一个很好的电梯调度算法是,有2部电梯,一部在7楼,一部在12楼,我在一楼按往上的按钮,由于7楼有人在搬家,他长时间把电梯霸占了(可以在门口站个人之类的),这个时候另外一部12楼的电梯就下来了。
我看到一个不好的电梯调度算法是,它总共有4部电梯,比如说在不同的楼层,然后我按了5(往上),有一部电梯下来了,然后我走进去,这个时候另外一个人也在5楼,他按了往下,结果我的这部电梯门就打开了。。。
6. 用java实现一个链表,并测试有没有环,
- package com.bb.bbs;
- import java.util.ArrayList;
- /**
- * 节点类:用于保存链表中的节点
- */
- class Node{
- Node next;
- String data;//下一节点
- public static int maxs = 0;//getSize() 最大数量
- public static int maxg = 0;//getArray()最大数量
- public static int maxp = 0;//printNode()打印节点最大数量
- public static int maxc = 0;//contains()最大数量
- /**
- * 带参数的构造方法
- */
- public Node(String data){
- this.data = data;
- }
- /**
- * 在当前节点上增加一个节点
- */
- public void addNode(Node node){
- if(this.next==null){
- this.next = node;
- }else{
- this.next.addNode(node);
- }
- }
- /**
- * 从root开始寻找,目的是移除一个节点
- */
- public boolean removeNode(Node previous,String data){
- if(this.data.equals(data)){
- previous.next = this.next;
- return true;
- }else{
- return this.next.removeNode(this, data);
- }
- }
- /**
- * 是否包含节点
- */
- public boolean contains(String data){
- maxc++;
- if(maxc==10){
- return false;
- }
- if(this.data.equals(data)){
- return true;
- }
- if(this.next == null){
- return false;
- }else{
- return this.next.contains(data);
- }
- }
- /**
- * 打印一个节点
- */
- public void printNode(){
- maxp++;
- if(maxp==10){
- return;
- }
- if(this.next!=null){
- System.out.println(this.next.data);
- this.next.printNode();
- }
- }
- /**
- * 查找并返回一个节点
- */
- public Node findNode(String data){
- if(this.data.equals(data)){
- return this;
- }else{
- return this.next.findNode(data);
- }
- }
- /**
- * 得到链表大小
- */
- public int getSize(int currentNum){
- maxs++;
- if(maxs==10){
- return 10;
- }
- if(this!=null){
- currentNum++;
- }
- if(this.next!=null){
- return this.next.getSize(currentNum);
- }else{
- return currentNum;
- }
- }
- /**
- * 将节点里所有值封装到一个ArrayList中
- */
- public void getArray(ArrayList tArrayList){
- maxg++;
- if(maxg==10){
- return;
- }
- tArrayList.add(this.data);
- if(this.next!=null){
- this.next.getArray(tArrayList);
- }
- }
- }
- /**
- * 链表类
- */
- class Link{
- Node root;
- public void add(String data){
- if(data==null){
- return;
- }
- if(root ==null){
- root = new Node(data);
- }else{
- root.addNode(new Node(data));
- }
- }
- public boolean remove(String data){
- if(root.data.equals(data)){
- root = root.next;
- return true;
- }else{
- return this.root.next.removeNode(root, data);
- }
- }
- public boolean contains(String data){
- if(root.data.equals(data)){
- return true;
- }else{
- return root.contains(data);
- }
- }
- public void print(){
- if(root!=null){
- System.out.println(root.data);
- root.printNode();
- }
- }
- public Node find(String data){
- if(contains(data)){
- if(this.root.data.equals(data)){
- return root;
- }else{
- return root.findNode(data);
- }
- }
- return null;
- }
- public int size(){
- if(root.next ==null){
- return 1;
- }else{
- return root.next.getSize(1);
- }
- }
- public void getArray(ArrayList tArrayList){
- if(root!=null){
- tArrayList.add(root.data);
- }
- if(root.next!=null){
- root.next.getArray(tArrayList);
- }
- }
- }
1.如何判断是否有环?如果有两个头结点指针,一个走的快,一个走的慢,那么若干步以后,快的指针总会超过慢的指针一圈。
2.如何计算环的长度?第一次相遇(超一圈)时开始计数,第二次相遇时停止计数。
3.如何判断环的入口点:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。
为什么呢?需要一个简单的计算过程:
(1)当fast与slow相遇时,show肯定没有走完链表,而fast已经在还里走了n(n>= 1)圈。假设slow走了s步,那么fast走了2s步。fast的步数还等于s走的加上环里转的n圈,所以有:
2s = s + nr。因此,s = nr。
(2)设整个链表长为L,入口据相遇点X,起点到入口的距离为a。因为slow指针并没有走完一圈,所以:
a + x = s,带入第一步的结果,有:a + x = nr = (n-1)r + r = (n-1)r + L - a;即:
a = (n-1)r + L -a -x;
这说明:从头结点到入口的距离,等于转了(n-1)圈以后,相遇点到入口的距离。因此,我们可以在链表头、相遇点各设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
也许大家有一个问题,就是为什么“当fast与slow相遇时,show肯定没有走完链表”。这个问题比较坑,我也没有找到很好的证明。不过大家自己画几个试试,会发现的确是这样。
4.如何判断两个链表(不带环)是否相交?将其中的一个链表首尾相连,然后判断另一个链表是否带环即可。这个比较简单,程序就省略了。
- #include <stdio.h>
- typedef struct Node
- {
- int val;
- Node *next;
- }Node,*pNode;
- //判断是否有环
- bool isLoop(pNode pHead)
- {
- pNode fast = pHead;
- pNode slow = pHead;
- //如果无环,则fast先走到终点
- //当链表长度为奇数时,fast->Next为空
- //当链表长度为偶数时,fast为空
- while( fast != NULL && fast->next != NULL)
- {
- fast = fast->next->next;
- slow = slow->next;
- //如果有环,则fast会超过slow一圈
- if(fast == slow)
- {
- break;
- }
- }
- if(fast == NULL || fast->next == NULL )
- return false;
- else
- return true;
- }
- //计算环的长度
- int loopLength(pNode pHead)
- {
- if(isLoop(pHead) == false)
- return 0;
- pNode fast = pHead;
- pNode slow = pHead;
- int length = 0;
- bool begin = false;
- bool agian = false;
- while( fast != NULL && fast->next != NULL)
- {
- fast = fast->next->next;
- slow = slow->next;
- //超两圈后停止计数,挑出循环
- if(fast == slow && agian == true)
- break;
- //超一圈后开始计数
- if(fast == slow && agian == false)
- {
- begin = true;
- agian = true;
- }
- //计数
- if(begin == true)
- ++length;
- }
- return length;
- }
- //求出环的入口点
- Node* findLoopEntrance(pNode pHead)
- {
- pNode fast = pHead;
- pNode slow = pHead;
- while( fast != NULL && fast->next != NULL)
- {
- fast = fast->next->next;
- slow = slow->next;
- //如果有环,则fast会超过slow一圈
- if(fast == slow)
- {
- break;
- }
- }
- if(fast == NULL || fast->next == NULL)
- return NULL;
- slow = pHead;
- while(slow != fast)
- {
- slow = slow->next;
- fast = fast->next;
- }
- return slow;
- }
7. 第二个用C++实现链表逆置,
8. 比如java的多态体现在什么上,String与StringBuffer的区别,
StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。
9. 写了一个单例模式
-----------------------------------------------------------
QA
1. 程序题:倒序输出字符串,要考虑到最优算法,如果有上万条记录,如何输出?要求独立完成自动化测试。
1. Java String.toCharArray()
String value =
"test 1234567890"
;
StringBuffer result =
new
StringBuffer();
Stack stack =
new
Stack();
for
(
char
c : value.toCharArray()) {
stack.push(c);
}
while
(!stack.empty()) {
result.append(stack.pop());
}
value = result.toString();
2. StringBuffer.reverse()
2 public class reverseTest {
3
4 public static void main(String[] args) {
5 String originalString = "abcdefg";
6 StringBuffer stringBuffer = new StringBuffer(originalString);
7 System.out.println(stringBuffer.reverse());
8 }
9 }
2. 网络协议有几层,
ping使用的是网络层的ICMP 协议。 ICMP 协议是TCP/IP 协议集中的一个子 协议,属于网络 层协议。
TCP是美国国防部设计的两种传输协议之一,另一种是UDP。UDP是一种不可靠的网络服务,负载比较小,而TCP则是一种可靠的通信服务,负载相对而言比较大。TCP采用套接字(socket)或者端口(port)来建立通信。TCP给端口到端口通信提供了错误和流量控制机制,同时TCP还负责建立连接、处理终止和中断的端对端通信控制。 通常情况下我们认为TCP相比UDP具有更大的通信负载,UDP不具备TCP的控制特性,TCP用了大约20个字节来发送一个65Kbps的数据块,这个报头占整个数据块的比重也不过3%。总得来看,这个负载是合理的,何况还令通信具有了可靠性。
3. 问题:
python对mysql中检索出现的编码问题是如何解决的
回答:
查询数据库时编码使用unicode,查询结果返回后解码也是用unicode
4. 请自定义链表,实现增删改查功能。
5. 三角形测试用例设计。
7.java垃圾回收机制
Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。 需要注意的是:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身,很多人来我公司面试时,我都会问这个问题的,70%以上的人回答的含义是回收对象,实际上这是不正确的。 System.gc() Runtime.getRuntime().gc() 上面的方法调用时用于显式通知JVM可以进行一次垃圾回收,但真正垃圾回收机制具体在什么时间点开始发生动作这同样是不可预料的,这和抢占式的线程在发生作用时的原理一样。
8.堆和栈的区别
主要的区别由以下几点:
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:
打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
9.线程和进程的区别
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。 线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。 线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。 在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
10.多线程
11.数据库方面的
12.死锁,如何解决
争夺资源 不可剥夺造成 要避免 就预先检查是否可以并行 要么加入检测机制
产生死锁的原因:一是系统提供的资源数量有限,不能满足每个进程的使用;二是多道程序运行时,进程推进顺序不合理。 产生死锁的必要条件是:1、互斥条件;2、不可剥夺条件(不可抢占);3、部分分配;4、循环等待。 根据产生死锁的四个必要条件,只要使其中之一不能成立,死锁就不会出现。为此,可以采取下列三种预防措施: 1、采用资源静态分配策略,破坏"部分分配"条件; 2、允许进程剥夺使用其他进程占有的资源,从而破坏"不可剥夺"条件; 3、采用资源有序分配法,破坏"环路"条件。 死锁的避免不严格地限制死锁的必要条件的存在,而是系统在系统运行过程中小心地避免死锁的最终发生。最著名的死锁避免算法是银行家算法。死锁避免算法需要很大的系统开销。 解决死锁的另一条途径是死锁检测方法,这种方法对资源的分配不加限制,即允许死锁的发生。但系统定时地运行一个"死锁检测"程序,判断系统是否已发生死锁,若检测到死锁发生则设法加以解除。 解除死锁常常采用下面两种方法:1、资源剥夺法;2、撤消进程法 请采纳答案,支持我一下。
13. AaBbC1231237894674#$%sajdhsdfAbc。。。
求A区分大小写,a, 123数字出现的次数
14. 问题:
从哪些方面对引擎进行测试?具体怎么做或者你让开发协助你完成什么?
回答:
搜索引擎吗?主要对搜索内容和查询结果的性能方面测试吧!
------------------------------------------------------
Java Dev
1. hash算法,稳定性/非稳定性排序,
- void InOrder(BTNode *b) //中序遍历递归算法
- {
- if(b!=NULL)
- {
- InOrder(b->lchild);
- visit(b);
- InOrder(b->rchild);
- }
- }
- void InOrder(BTNode *b){
- Stack s;
- while(b!=NULL||!s.empty()){
- while (b!=NULL)
- {
- s.push(b);
- b=b->left;
- }
- if(!s.empty()){
- b=s.pop();
- visit(b);
- b=b->right;
- }
- }
- }
bash脚本对日志的处理(sed,awk忘了怎么用了),
#!/bin/bash if ls ./*.result &> /dev/null #判断当前目录中是否有后缀名为result的文件存在
then
rm *.result #如果有的话,删除这些文件
fi
touch log.result #创建一个空文件
for i in www-*.log #遍历当前目录中所有log文件
do
echo $i ... #输出一行字,表示开始处理当前文件
awk '$9 == 200 {print $7}' $i|grep -i '^/blog/2011/.*\.html$'|sort|uniq -c|sed 's/^ *//g' > $i.result #生成当前日志的处理结果
cat $i.result >> log.result #将处理结果追加到log.result文件
echo $i.result finished #输出一行字,表示结束处理当前文件
done
echo final.log.result ... #输出一行字,表示最终统计开始
sort -k2 log.result | uniq -f1 --all-repeated=separate |./log.awk |sort -rn > final.log.result #生成最终的结果文件final.log.result
echo final.log.result finished #输出一行字,表示最终统计结束
数据库设计原则
2. struts怎么知道调哪个action,
java的反射机制
反射就是:在任意一个方法里: 1.如果我知道一个类的名称/或者它的一个实例对象, 我就能把这个类的所有方法和变量的信息找出来(方法名,变量名,方法,修饰符,类型,方法参数等等所有信息)。 2.如果我还明确知道这个类里某个变量的名称,我还能得到这个变量当前的值。 2.当然,如果我明确知道这个类里的某个方法名+参数个数类型,我还能通过传递参数来运行那个类里的那个方法。
3. cdn设计、hashMap实现
4. 两点距离算法, TCP和HTTP,
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
5. 多线程,分布式,NIO
6. 问题:
高并发的优化
回答:
分布式,缓存等
7. Hashmap,然后是web服务器调优,
然后是jvm内存泄露怎么排查
8. java类加载机制
这个问题java的比较核心的一个难题,我就针对问题做简要回答,不做深入讨论了: 1、编译和运行概念要搞清:编译即javac的过程,负责将.java文件compile成.class文件,主要是类型、格式检查与编译成字节码文件,而加载是指java *的过程,将.class文件加载到内存中去解释执行,即运行的时候才会有加载一说。 2、类的加载时机,肯定是在运行时,但并不是一次性全部加载,而是按需动态,依靠反射来实现动态加载,一般来说一个class只会被加载一次,之后就会从jvm的class实例的缓存中获取,谁用谁取就可以了,不会再去文件系统中加载.class文件了。
--------------------------------------------
RD
1. 然后问spring的ioc和aop的原理,答出来后问aop的缺陷在哪?这个晕菜,接着问进程和线程的区别,答出来后问内存资源分配的具体情况,操作系统中堆栈的不同,
然后写了个归并排序
归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。
工作原理:
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4、重复步骤3直到某一指针达到序列尾
5、将另一序列剩下的所有元素直接复制到合并序列尾
- public class MergeSortTest {
- public static void main(String[] args) {
- int[] data = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };
- print(data);
- mergeSort(data);
- System.out.println("排序后的数组:");
- print(data);
- }
- public static void mergeSort(int[] data) {
- sort(data, 0, data.length - 1);
- }
- public static void sort(int[] data, int left, int right) {
- if (left >= right)
- return;
- // 找出中间索引
- int center = (left + right) / 2;
- // 对左边数组进行递归
- sort(data, left, center);
- // 对右边数组进行递归
- sort(data, center + 1, right);
- // 合并
- merge(data, left, center, right);
- print(data);
- }
- /**
- * 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
- *
- * @param data
- * 数组对象
- * @param left
- * 左数组的第一个元素的索引
- * @param center
- * 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
- * @param right
- * 右数组最后一个元素的索引
- */
- public static void merge(int[] data, int left, int center, int right) {
- // 临时数组
- int[] tmpArr = new int[data.length];
- // 右数组第一个元素索引
- int mid = center + 1;
- // third 记录临时数组的索引
- int third = left;
- // 缓存左数组第一个元素的索引
- int tmp = left;
- while (left <= center && mid <= right) {
- // 从两个数组中取出最小的放入临时数组
- if (data[left] <= data[mid]) {
- tmpArr[third++] = data[left++];
- } else {
- tmpArr[third++] = data[mid++];
- }
- }
- // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
- while (mid <= right) {
- tmpArr[third++] = data[mid++];
- }
- while (left <= center) {
- tmpArr[third++] = data[left++];
- }
- // 将临时数组中的内容拷贝回原数组中
- // (原left-right范围的内容被复制回原数组)
- while (tmp <= right) {
- data[tmp] = tmpArr[tmp++];
- }
- }
- public static void print(int[] data) {
- for (int i = 0; i < data.length; i++) {
- System.out.print(data[i] + "\t");
- }
- System.out.println();
- }
- }
2. 问题:
线程有什么缺点。。。
回答:
(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
(4) 对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误 是程序员无法预知的。
3. 比如说你了解数据库索引不?了解B+树吗?jvm垃圾回收,jvm新生代为什么需要有两个survior等等
4. webService接口是什么?
5. linux hash 索引用什么实现的,stl ,红黑树 ,b+树
6. web service采用什么协议
webservice 协议
Web Service使用的是 SOAP (Simple Object Access Protocol)协议
soap协议只是用来封装消息用的。封装后的消息你可以通过各种已有的协议来传输,比如http,tcp/ip,smtp,等等,你甚至还一次用自定义的协议,当然也可以用https协议。
Soap建立在http上,说白了是用http传送xml而已。
WebService采用HTTP协议传输数据,采用XML格式封装数据(即XML中说明调用远程服务对象的哪个方法,传递的参数是什么,以及服务对象的返回结果是什么)。WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议(simple object access protocol,简单对象访问协议) 。
7. B树,B+树
这两种处理索引的数据结构的不同之处:
1。B树中同一键值不会出现多次,并且它有可能出现在叶结点,也有可能出现在非叶结点中。而B+树的键一定会出现在叶结点中,并且有可能在非叶结点中也有可能重复出现,以维持B+树的平衡。
2。因为B树键位置不定,且在整个树结构中只出现一次,虽然可以节省存储空间,但使得在插入、删除操作复杂度明显增加。B+树相比来说是一种较好的折中。
3。B树的查询效率与键在树中的位置有关,最大时间复杂度与B+树相同(在叶结点的时候),最小时间复杂度为1(在根结点的时候)。而B+树的时候复杂度对某建成的树是固定的。