测试开发面试准备 - 1

Test Dev

1. SQL注入
所谓SQL注入,就是通过把SQL命令插入到Web 表单 提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的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注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。
当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用 存储过程 ,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。sql注入可能导致攻击者使用应用程序登陆在数据库中执行命令。
在某些 表单 中,用户输入的内容直接用来构造动态sql命令,或者作为 存储过程 的输入参数,这些表单特别容易受到sql注入的攻击。而许多网站程序在编写时,没有对用户输入的合法性进行判断或者程序中本身的 变量 处理不当,使应用程序存在安全隐患。这样,用户就可以提交一段数据库查询的代码,根据程序返回的结果,获得一些敏感的信息或者控制整个服务器,于是sql注入就发生了。

SQL注入防范编辑

了解了SQL注入的方法,如何能防止SQL注入?如何进一步防范SQL注入的泛滥?通过一些合理的操作和配置来降低SQL注入的危险。
使用参数化的过滤性语句
  要防御SQL注入,用户的输入就绝对不能直接被嵌入到SQL语句中。恰恰相反,用户的输入必须进行过滤,或者使用参数化的语句。参数化的语句使用参数而不是将用户输入嵌入到语句中。在多数情况中,SQL语句就得以修正。然后,用户输入就被限于一个参数。
输入验证
检查用户输入的合法性,确信输入的内容只包含合法的数据。数据检查应当在客户端和服务器端都执行之所以要执行服务器端验证,是为了弥补客户端验证机制脆弱的安全性。
  在客户端,攻击者完全有可能获得网页的源代码,修改验证合法性的脚本(或者直接删除脚本),然后将非法内容通过修改后的表单提交给服务器。因此,要保证验证操作确实已经执行,唯一的办法就是在服务器端也执行验证。你可以使用许多内建的验证对象,例如Regular Expression Validator,它们能够自动生成验证用的客户端脚本,当然你也可以插入服务器端的方法调用。如果找不到现成的验证对象,你可以通过Custom Validator自己创建一个。
错误消息处理
防范SQL注入,还要避免出现一些详细的错误消息,因为黑客们可以利用这些消息。要使用一种标准的输入确认机制来验证所有的输入数据的长度、类型、语句、企业规则等。
加密处理
将用户登录名称、密码等数据加密保存。加密用户输入的数据,然后再将它与数据库中保存的数据比较,这相当于对用户输入的数据进行了“消毒”处理,用户输入的数据不再对数据库有任何特殊的意义,从而也就防止了攻击者注入SQL命令。
存储过程来执行所有的查询
SQL参数的传递方式将防止攻击者利用单引号和连字符实施攻击。此外,它还使得数据库权限可以限制到只允许特定的存储过程执行,所有的用户输入必须遵从被调用的存储过程的安全上下文,这样就很难再发生注入式攻击了。
使用专业的漏洞扫描工具
攻击者们目前正在自动搜索攻击目标并实施攻击,其技术甚至可以轻易地被应用于其它的Web架构中的漏洞。企业应当投资于一些专业的漏洞扫描工具,如大名鼎鼎的Acunetix的Web漏洞扫描程序等。一个完善的漏洞扫描程序不同于网络扫描程序,它专门查找网站上的SQL注入式漏洞。最新的漏洞扫描程序可以查找最新发现的漏洞。
确保数据库安全
锁定你的数据库的安全,只给访问数据库的web应用功能所需的最低的权限,撤销不必要的公共许可,使用强大的加密技术来保护敏感数据并维护审查跟踪。如果web应用不需要访问某些表,那么确认它没有访问这些表的权限。如果web应用只需要只读的权限,那么就禁止它对此表的 drop 、insert、update、delete 的权限,并确保数据库打了最新补丁。
安全审评
在部署应用系统前,始终要做安全审评。建立一个正式的安全过程,并且每次做更新时,要对所有的编码做审评。开发队伍在正式上线前会做很详细的安全审评,然后在几周或几个月之后他们做一些很小的更新时,他们会跳过安全审评这关, “就是一个小小的更新,我们以后再做编码审评好了”。请始终坚持做安全审评。
XSS攻击
SS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和 客户端 脚本
OWASP是世界上最知名的Web安全与 数据库安全 研究组织
SS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和 客户端 脚本
类型A,本地利用漏洞,这种漏洞存在于页面中 客户端 脚本 自身。
类型B,反射式漏洞,这种漏洞和类型A有些类似,不同的是Web 客户端 使用Server端 脚本 生成页面为用户提供数据时,如果未经验证的用户数据被包含在页面中而未经 HTML实体 编码,客户端代码便能够注入到 动态页面 中。
类型C,存储式漏洞,该类型是应用最为广泛而且有可能影响到Web服务器自身安全的漏洞, 骇客 将攻击 脚本 上传到Web服务器上,使得所有访问该页面的用户都面临信息泄漏的可能,其中也包括了Web服务器的 管理员

传传统防御技术编辑

2.1.1基于特征的防御
XSS漏洞和著名的SQL 注入漏洞一样,都是利用了Web页面的编写不完善,所以每一个漏洞所利用和针对的弱点都不尽相同。这就给XSS漏洞防御带来了困难:不可能以单一特征来概括所有XSS攻击。
传统XSS防御多采用 特征匹配方式,在所有提交的信息中都进行匹配检查。对于这种类型的XSS攻击,采用的模式匹配方法一般会需要对“javascript”这个 关键字进行检索,一旦发现提交信息中包含“javascript”,就认定为XSS攻击。
2.1.2 基于代码修改的防御
和SQL注入防御一样,XSS攻击也是利用了Web页面的编写疏忽,所以还有一种方法就是从Web应用开发的角度来避免:
步骤1、对所有用户提交内容进行可靠的输入验证,包括对URL、查询关键字、HTTP头、POST数据等,仅接受指定长度范围内、采用适当格式、采用所预期的字符的内容提交,对其他的一律过滤。
步骤2、实现Session标记(session tokens)、CAPTCHA系统或者HTTP引用头检查,以防功能被第三方网站所执行。
步骤3、确认接收的的内容被妥善的规范化,仅包含最小的、安全的Tag(没有javascript),去掉任何对远程内容的引用(尤其是样式表和javascript),使用HTTP only的cookie。
当然,如上操作将会降低Web业务系统的可用性,用户仅能输入少量的制定字符,人与系统间的交互被降到极致,仅适用于信息发布型站点。并且考虑到很少有Web编码人员受过正规的安全培训,很难做到完全避免页面中的XSS漏洞。
2. leftjoin rightjoin等
leftjoin  rightjoin分别是以左边的表格内容为基准,以右边表格内容为基准.

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)。

B+树 及B*树都是B树的变种,和B树相比,B+树:
1.每个节点的指针上限为2d而不是2d+1。
2.内节点不存储data,只存储key;叶子节点不存储指针。

4. 怎么测电梯

  面试一个测试人员的sense,喜欢问的问题就是你测试一下电话,或者电梯,或者一个具体的产品;

  那么如何测试电梯呢?

  电梯测试可以从几个方面来进行,功能测试性能测试压力测试,可用性测试(Usability),兼容性测试本地化/国际化测试,可维护性测试;

  功能测试,最基本的上下功能,开关功能,还有里面的各个按键

  性能测试(很多人忽略的),比如电梯的调度算法,用户的等待时间,平均等待时间,上下的速度,耗电量等等

  压力测试,比如承重量(你实际承受力是20,那么当进入19个人的时候就应该报警,或者是实际上用户有可能一股脑的全部冲进电梯,所以在静止的时候电梯需要考虑到这种情况),突然断电,门打不开等等

  可用性测试,按钮是否方便,按键的感觉是否好,视觉效果,现在很多人诟病的事情是,开和关两个按钮的图示很不友好,在紧急的时候很容易搞错

  兼容性测试,比如每个国家的电压不一样,是否考虑到这个情况

  本地化/国际化测试,曾经看到一部电梯的使用手册翻译成英文,翻译得很差

  可维护性,电梯如果坏了怎么去维修。

  HA,high availabity测试,如果一部坏了,另外一部是否可以正常的运行等等。

  关于性能测试,这里在多说几句, 我看到的一个很好的电梯调度算法是,有2部电梯,一部在7楼,一部在12楼,我在一楼按往上的按钮,由于7楼有人在搬家,他长时间把电梯霸占了(可以在门口站个人之类的),这个时候另外一部12楼的电梯就下来了。

  我看到一个不好的电梯调度算法是,它总共有4部电梯,比如说在不同的楼层,然后我按了5(往上),有一部电梯下来了,然后我走进去,这个时候另外一个人也在5楼,他按了往下,结果我的这部电梯门就打开了。。。

5. memcached实现


6. 用java实现一个链表,并测试有没有环,
  1.    
  2. package com.bb.bbs;  
  3.   
  4. import java.util.ArrayList;  
  5.   
  6. /**  
  7.  * 节点类:用于保存链表中的节点  
  8.  */  
  9. class Node{  
  10.     Node next;  
  11.     String data;//下一节点  
  12.     public static int maxs = 0;//getSize() 最大数量  
  13.     public static int maxg = 0;//getArray()最大数量  
  14.     public static int maxp = 0;//printNode()打印节点最大数量  
  15.     public static int maxc = 0;//contains()最大数量  
  16.          /**  
  17.           * 带参数的构造方法  
  18.           */  
  19.     public Node(String data){  
  20.         this.data = data;  
  21.     }  
  22.          /**  
  23.           * 在当前节点上增加一个节点  
  24.           */  
  25.     public void addNode(Node node){  
  26.         if(this.next==null){  
  27.             this.next = node;  
  28.         }else{  
  29.             this.next.addNode(node);  
  30.         }  
  31.     }  
  32.          /**  
  33.           * 从root开始寻找,目的是移除一个节点  
  34.           */  
  35.     public boolean removeNode(Node previous,String data){  
  36.         if(this.data.equals(data)){  
  37.             previous.next = this.next;  
  38.             return true;  
  39.         }else{  
  40.             return this.next.removeNode(this, data);  
  41.         }  
  42.     }  
  43.          /**  
  44.           * 是否包含节点  
  45.           */  
  46.     public boolean contains(String data){  
  47.         maxc++;  
  48.         if(maxc==10){  
  49.             return false;  
  50.         }  
  51.         if(this.data.equals(data)){  
  52.             return true;  
  53.         }  
  54.         if(this.next == null){  
  55.             return false;  
  56.         }else{  
  57.             return this.next.contains(data);  
  58.         }  
  59.     }  
  60.          /**  
  61.           * 打印一个节点  
  62.           */  
  63.     public void printNode(){  
  64.         maxp++;  
  65.         if(maxp==10){  
  66.             return;  
  67.         }  
  68.         if(this.next!=null){  
  69.             System.out.println(this.next.data);  
  70.             this.next.printNode();  
  71.         }  
  72.     }  
  73.          /**  
  74.           * 查找并返回一个节点  
  75.           */  
  76.     public Node findNode(String data){  
  77.         if(this.data.equals(data)){  
  78.             return this;  
  79.         }else{  
  80.             return this.next.findNode(data);  
  81.         }  
  82.     }  
  83.          /**  
  84.           * 得到链表大小  
  85.           */   
  86.     public int getSize(int currentNum){  
  87.         maxs++;  
  88.         if(maxs==10){  
  89.             return 10;  
  90.         }  
  91.         if(this!=null){  
  92.             currentNum++;  
  93.         }  
  94.         if(this.next!=null){  
  95.             return this.next.getSize(currentNum);  
  96.         }else{  
  97.             return currentNum;  
  98.         }  
  99.     }  
  100.          /**  
  101.           * 将节点里所有值封装到一个ArrayList中  
  102.           */  
  103.     public void getArray(ArrayList tArrayList){  
  104.         maxg++;  
  105.         if(maxg==10){  
  106.             return;  
  107.         }  
  108.         tArrayList.add(this.data);  
  109.         if(this.next!=null){  
  110.             this.next.getArray(tArrayList);  
  111.         }  
  112.     }  
  113. }  
  114.   
  115. /**  
  116.  * 链表类  
  117.  */  
  118. class Link{  
  119.     Node root;  
  120.     public void add(String data){  
  121.         if(data==null){  
  122.             return;  
  123.         }  
  124.         if(root ==null){  
  125.             root = new Node(data);  
  126.         }else{  
  127.             root.addNode(new Node(data));  
  128.         }  
  129.     }  
  130.     public boolean remove(String data){  
  131.         if(root.data.equals(data)){  
  132.             root = root.next;  
  133.             return true;  
  134.         }else{  
  135.             return this.root.next.removeNode(root, data);  
  136.         }  
  137.     }  
  138.     public boolean contains(String data){  
  139.         if(root.data.equals(data)){  
  140.             return true;  
  141.         }else{  
  142.             return root.contains(data);  
  143.         }  
  144.     }  
  145.     public void print(){  
  146.         if(root!=null){  
  147.             System.out.println(root.data);  
  148.             root.printNode();  
  149.         }  
  150.     }  
  151.     public Node find(String data){  
  152.         if(contains(data)){  
  153.             if(this.root.data.equals(data)){  
  154.                 return root;  
  155.             }else{  
  156.                 return root.findNode(data);  
  157.             }  
  158.         }  
  159.         return null;  
  160.     }  
  161.     public int size(){  
  162.         if(root.next ==null){  
  163.             return 1;  
  164.         }else{  
  165.             return root.next.getSize(1);  
  166.         }  
  167.     }  
  168.     public void getArray(ArrayList tArrayList){  
  169.         if(root!=null){  
  170.             tArrayList.add(root.data);  
  171.         }  
  172.         if(root.next!=null){  
  173.             root.next.getArray(tArrayList);  
  174.         }  
  175.     }  
  176. }  
  177.   

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.如何判断两个链表(不带环)是否相交?将其中的一个链表首尾相连,然后判断另一个链表是否带环即可。这个比较简单,程序就省略了。


  1. #include <stdio.h>  
  2.   
  3. typedef struct Node  
  4. {  
  5.     int val;  
  6.     Node *next;  
  7. }Node,*pNode;  
  8.   
  9.   
  10.   
  11. //判断是否有环  
  12. bool isLoop(pNode pHead)  
  13. {  
  14.     pNode fast = pHead;  
  15.     pNode slow = pHead;  
  16.     //如果无环,则fast先走到终点  
  17.     //当链表长度为奇数时,fast->Next为空  
  18.     //当链表长度为偶数时,fast为空  
  19.     while( fast != NULL && fast->next != NULL)  
  20.     {  
  21.   
  22.         fast = fast->next->next;  
  23.         slow = slow->next;  
  24.         //如果有环,则fast会超过slow一圈  
  25.         if(fast == slow)  
  26.         {  
  27.             break;  
  28.         }  
  29.     }  
  30.   
  31.     if(fast == NULL || fast->next == NULL  )  
  32.         return false;  
  33.     else  
  34.         return true;  
  35. }  
  36.   
  37. //计算环的长度  
  38. int loopLength(pNode pHead)  
  39. {  
  40.     if(isLoop(pHead) == false)  
  41.         return 0;  
  42.     pNode fast = pHead;  
  43.     pNode slow = pHead;  
  44.     int length = 0;  
  45.     bool begin = false;  
  46.     bool agian = false;  
  47.     while( fast != NULL && fast->next != NULL)  
  48.     {  
  49.         fast = fast->next->next;  
  50.         slow = slow->next;  
  51.         //超两圈后停止计数,挑出循环  
  52.         if(fast == slow && agian == true)  
  53.             break;  
  54.         //超一圈后开始计数  
  55.         if(fast == slow && agian == false)  
  56.         {             
  57.             begin = true;  
  58.             agian = true;  
  59.         }  
  60.   
  61.         //计数  
  62.         if(begin == true)  
  63.             ++length;  
  64.           
  65.     }  
  66.     return length;  
  67. }  
  68.   
  69.   
  70. //求出环的入口点  
  71. Node* findLoopEntrance(pNode pHead)  
  72. {  
  73.     pNode fast = pHead;  
  74.     pNode slow = pHead;  
  75.     while( fast != NULL && fast->next != NULL)  
  76.     {  
  77.   
  78.         fast = fast->next->next;  
  79.         slow = slow->next;  
  80.         //如果有环,则fast会超过slow一圈  
  81.         if(fast == slow)  
  82.         {  
  83.             break;  
  84.         }  
  85.     }  
  86.     if(fast == NULL || fast->next == NULL)  
  87.         return NULL;  
  88.     slow = pHead;  
  89.     while(slow != fast)  
  90.     {  
  91.         slow = slow->next;  
  92.         fast = fast->next;  
  93.     }  
  94.   
  95.     return slow;  
  96. }  

7. 第二个用C++实现链表逆置,
link* p, q;
link* head;

q = head;
head = head->next;
while (head != null){
      p = head;
      head = head->next;
      p->next = q;
      q = p;
}
return q;

8. 比如java的多态体现在什么上,String与StringBuffer的区别,
方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。
重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。
 
如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)
。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了.
 
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型或有不同
的参数次序,则称为方法的重载(Overloading)。不能通过访问权限、返回类型、抛出的异常
进行重载.
1. Override 特点
  1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
  2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;
  3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
  4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

2.Overload 特点
  1、在使用重载时只能通过不同的参数样式。例如, 不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int));
  2、不能通过访问权限、返回类型、抛出的异常进行重载;
  3、方法的异常类型和数目不会对重载造成影响;
  4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

String与StringBuffer的区别
简单地说,就是一个变量和常量的关系。StringBuffer对象的内容可以修改;而String对象一旦产生后就不可以被修改,重新赋值其实是两个对象。
StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

9. 写了一个单例模式
public class Singleton{
         static private m_inst=null;
         Singleton(){
         }
         public syncronized static getInstance(){
                    if (m_inst == null){
                        m_inst = new Singleton();
                    }
                    return m_inst; 
         }
}


-----------------------------------------------------------
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算法,稳定性/非稳定性排序,
通过将单向数学函数(有时称为“哈希算法”)应用到任意数量的数据所得到的固定大小的结果。如果输入数据中有变化,则哈希也会发生变化。哈希可用于许多操作,包括身份验证和数字签名。也称为“消息摘要”。
简单解释:哈希(Hash)算法,即散列函数。它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以生成消息或者数据。
典型的哈希算法包括 MD2、MD4、MD5 和 SHA-1。哈希算法也称为“哈希函数”。

二叉树遍历,
  1. void InOrder(BTNode *b)   //中序遍历递归算法  
  2. {  
  3.     if(b!=NULL)  
  4.     {  
  5.         InOrder(b->lchild);  
  6.         visit(b);  
  7.         InOrder(b->rchild);  
  8.     }  
  9. }  
中序遍历: 
[cpp]  view plain copy
  1. void InOrder(BTNode *b){  
  2.     Stack s;  
  3.     while(b!=NULL||!s.empty()){  
  4.         while (b!=NULL)  
  5.         {  
  6.             s.push(b);  
  7.             b=b->left;  
  8.         }  
  9.         if(!s.empty()){  
  10.             b=s.pop();  
  11.             visit(b);  
  12.             b=b->right;  
  13.         }  
  14.     }  
  15. }  


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
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用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、将另一序列剩下的所有元素直接复制到合并序列尾


[java]  view plain copy print ?
  1. public class MergeSortTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         int[] data = new int[] { 536219487 };  
  5.         print(data);  
  6.         mergeSort(data);  
  7.         System.out.println("排序后的数组:");  
  8.         print(data);  
  9.     }  
  10.   
  11.     public static void mergeSort(int[] data) {  
  12.         sort(data, 0, data.length - 1);  
  13.     }  
  14.   
  15.     public static void sort(int[] data, int left, int right) {  
  16.         if (left >= right)  
  17.             return;  
  18.         // 找出中间索引  
  19.         int center = (left + right) / 2;  
  20.         // 对左边数组进行递归  
  21.         sort(data, left, center);  
  22.         // 对右边数组进行递归  
  23.         sort(data, center + 1, right);  
  24.         // 合并  
  25.         merge(data, left, center, right);  
  26.         print(data);  
  27.     }  
  28.   
  29.     /** 
  30.      * 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序 
  31.      *  
  32.      * @param data 
  33.      *            数组对象 
  34.      * @param left 
  35.      *            左数组的第一个元素的索引 
  36.      * @param center 
  37.      *            左数组的最后一个元素的索引,center+1是右数组第一个元素的索引 
  38.      * @param right 
  39.      *            右数组最后一个元素的索引 
  40.      */  
  41.     public static void merge(int[] data, int left, int center, int right) {  
  42.         // 临时数组  
  43.         int[] tmpArr = new int[data.length];  
  44.         // 右数组第一个元素索引  
  45.         int mid = center + 1;  
  46.         // third 记录临时数组的索引  
  47.         int third = left;  
  48.         // 缓存左数组第一个元素的索引  
  49.         int tmp = left;  
  50.         while (left <= center && mid <= right) {  
  51.             // 从两个数组中取出最小的放入临时数组  
  52.             if (data[left] <= data[mid]) {  
  53.                 tmpArr[third++] = data[left++];  
  54.             } else {  
  55.                 tmpArr[third++] = data[mid++];  
  56.             }  
  57.         }  
  58.         // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)  
  59.         while (mid <= right) {  
  60.             tmpArr[third++] = data[mid++];  
  61.         }  
  62.         while (left <= center) {  
  63.             tmpArr[third++] = data[left++];  
  64.         }  
  65.         // 将临时数组中的内容拷贝回原数组中  
  66.         // (原left-right范围的内容被复制回原数组)  
  67.         while (tmp <= right) {  
  68.             data[tmp] = tmpArr[tmp++];  
  69.         }  
  70.     }  
  71.   
  72.     public static void print(int[] data) {  
  73.         for (int i = 0; i < data.length; i++) {  
  74.             System.out.print(data[i] + "\t");  
  75.         }  
  76.         System.out.println();  
  77.     }  
  78.   
  79. }  

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+树的时候复杂度对某建成的树是固定的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值