根据自己的经验总结了一些面试经验,大家可以参考下,一般来说后端的东西都差不多,而语言的不同是次要的。
1. mysql存储引擎
myisam: 不支持事务安全、不支持外键、表级锁机制、B-tree、快读
innodb: 事务安全、有commit/rollback机制、支持外键、行级锁、也支持表级锁、b+tree、完整事务支持;
memory: 表存储在内存中,哈希索引、适合存储临时数据、表级锁机制、hash/b-tree、内存数据;
archive: 只支持insert/select查询、不支持delete/update等操作;
2. 事务的ACID
原子性、一致性、独立性、持续性;
3. MYSQL锁机制:for update
行级锁定:并发处理上有较大的优势、锁定颗粒度很小、行级锁定也最容易发生死锁;表级锁定:锁定整张表、写操作进程会阻塞读进程、读会阻塞对同一表的写进程、锁用队列实现;4. MYSQL索引
索引类型:
1. b-tree索引:以innodb存储引擎为例采用B+树索引、叶子节点存储指向表中记录的地址;(1)使用:全值匹配、匹配最左前缀、匹配列前缀(一例值的开头部分)、匹配取值范围、
2. 哈希索引:对每行的索引列会计算一个哈希码值,哈希表中存放着哈希码和批向行的指针;
3. 全文索引:查找的是文本中的关键词,而不是直接比较索引中的值;
索引优化:
(1)独立的例:不要包含表达式,始终将索引列单独放在比较符号的一侧;(2)多列索引:可以能过explain命令查看type字段来优化自己建的索引;type:从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
(3)选择合适的索引顺序:
(a)根据索引的选择性高的放在前面会好些;
5. mysql主从复制原理
Master 与 Slave 之间的实现整个复制过程主要由三个线程来完成,其中两个线程(SQL 线程和 I/O 线程)在 Slave 端,另外一个线程(I/O 线程)在 Master 端。要实现 MySQL 的 Replication ,首先必须打开 Master 端的 Binary Log,因为整个复制过程实际上就是 Slave 从 Master 端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。
主在执行sql之后,记录二进制log文件(bin-log)。 从连接主,并从主获取binlog,存于本地relay-log,并从上次记住的位置起执行sql,一旦遇到错误则停止同步。
6. mysql中varchar与char:
char:是固定长的;varchar:长度是可变的;
varchar(20),指的是20字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放20个,最大大小是65532字节 ;
区别:
char是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是:
char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在检索操作中那些填补出来的空格字符将被去掉)在varchar(M)类型的数据列里,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节(即总长度为L+1字节);
7. innodb的事务与日志的实现方式
在Innodb存储引擎中,事务日志是通过redo和innodb的存储引擎日志缓冲(Innodb log buffer)来实现的,当开始一个事务的时候,会记录该事务的lsn(log sequence number)号;当事务执行时,会往InnoDB存储引擎的日志的日志缓存里面插入事务日志;当事务提交时,必须将存储引擎的日志缓冲写入磁盘(通过innodb_flush_log_at_trx_commit来控制),也就是写数据前,需要先写日志。这种方式称为“预写日志方式”,innodb通过此方式来保证事务的完整性。也就意味着磁盘上存储的数据页和内存缓冲池上面的页是不同步的,是先写入redo log,然后写入data file,因此是一种异步的方式。8. sql优化
(1)explain出来的各种item的意义: 优化索引(2)profile的意义以及使用场景:优化SQL查询,可以看到每条SQL的执行时间等;
(3)explain中的索引问题。
9.JAVA垃圾回收机制
(1)引用计数法
其原理是:给对象添加一个引用计数器,每当有其他地方引用它时,计数器就增1;当失去一个引用时,计数器值减1;计数器为0时则说明这个对象可以被回收;(2)标记-清除算法
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。标记阶段:首先通过根节点,标记所有从根节点开始的可达对象,未被标记的对象就是未被引用的垃圾对象;清除阶段:清除所有未被标记的对象。这个思想的意思是:在程序的起点开始就是一个根节点,随着程序的运行就会引用不同的节点,被引用的做标记,未被引用的做清除标记,做清除标记的作为垃圾回收。
缺点:会产生空间碎片。
(3)复制算法
这是一种高效的回收算法,其核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中存活的对象复制到未使用的内存块中,
之后,清除正在使用的内存块的所有对象,然后再交换两个内存的角色,这样就完成了垃圾回收。
缺点:是当内存中大部分都是存活对象,那么这种算法都是在做无用功。
(4)标记-压缩算法
这是一种老年代的回收算法,在标记-清除算法做了改进,也是从根节点开始,对所有对象做一次标记,对标记清除的对象不是简单的清除,
而是先把存活对象压缩到内存的一端,确定好边界之后,再把所有的标记清除的对象全部删除,
这样就避免了碎片的产生,同时又不需要频繁交换空间。
(5)增量算法
在大部分垃圾回收过程中,一旦开始回收,应用程序所有线程都处在挂起状态,时间很长的情况下,会影响用户体验以及性能稳定。增量的算法就是:如果一次性将所有垃圾进行处理,需要造成系统长时间的停顿,那么可以让垃圾回收线程与程序线程交替进行,
每次只回收一片区域,接着再切换到程序进程,
缺点:线程之间的不停切换会影响整体成本的升高。
(6)分代
分代的基本思想就是:它将内存区间根据对象的特点分为几块,根据每块内存区间的特点,使用不同的算法,从而提高垃圾回收效率。
一般内存区域分为老生代和新生代,新建的对象建在新生代,常用的对象建在老生代,新生代用复制算法,老生代用标记-压缩算法。
这样两边的效率最高,这是目前最常用的垃圾回收算法。
10. 分表策略
(1)预先估计某个大表的数据量,按实际情况将其均分为固定数量表;
根据分表算法,将数据平均分散到不同的数据表中,常见处理方式有对自增id取模、对某个字段进行hash。比如某系统用户预计支持1亿用户数,分100个表存储用户数据,按照自增id的最后2位来分表,对100取模,那么用户数据表就是user_01~user_99。(2)按时间拆分
对于那种根据时间增长较快的数据可以按时间拆分,根据业务实际情况按天、按月、按年等进行拆分。比如进销存数据,我们可以按月分表,形如jxc_data_201201、jxc_data_201202(3)按每个表固定记录行数拆分
一般根据自增长ID拆表,每张表存储指定数量的数据。一张表的数据行数到了指定数量,就自动保存到新的表里。(4)将很久之前的数据迁移到一张历史表
比如日志记录,一般只会查询3个月之内的日志,对于超过三个月的日志记录我们可以迁移到到迁移到另一张表中,比如log_history10. java设计模式
单例模式: public class Single {
//用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值
private volatile static Single uiqueInstance;
private Single(){}
public static Single getInstance(){
if(uiqueInstance == null){
//synchronized后面括号里是类,此时,线程获得的是类锁
synchronized(Single.class){
if(uiqueInstance == null){
String s="single pattern";
System.out.println(s);
uiqueInstance = new Single();
}
}
}
return uiqueInstance;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Single.getInstance();
}
}
11. http协议的请求方式有哪些?
(1)表单提交数据组服务器的过程
form提交的第一步是创建数据集,并根据 ENCTYPE 指定的类型值对数据集进行编码;默认地,表单数据会编码为 "application/x-www-form-urlencoded";application/x-www-form-urlencoded对form数据集的编码规则:(1)如果是字母或数字,则直接使用其ascii码的十六进制。(2)对于非字母也非数字的字符,则不仅使用其ascii码的十六进制,还要在前面加上“%”。比如“\”,它的ascii码是92,92的十六进制是5c,所以“\”的urlencoded编码就是%5c;
(2)get和post的主要区别
get: form中的数据集(如input框的value)将被编码到URL中,作为URL的一部分,在地址栏上可以看到;请求任意次都返回同样的结果)时使用GET;get传送的数据量较小,不能大于2KB;post: form中的数据集则被编码到http协议的header中,构造成消息发送;当请求会改变服务器数据或状态时使用POST;post传送的数据量较大,一般被默认为不受限制;12. SQL注入过程及防止
(1)判断Web环境是否可以SQL注入,只有对数据库进行动态查询的业务才可能存在SQL注入;(2)寻找SQL注入点,通过输入一些特殊语句,可以根据浏览器返回信息,判断数据库类型,从而构建数据库查询语句找到注入点;
(3)猜解用户名和密码。数据库中存放的表名、字段名都是有规律可言的。通过构建特殊数据库语句在数据库中依次查找表名、字段名、用户名和密码的长度,以及内容。
这个猜测过程可以通过网上大量注入工具快速实现,并借助破解网站轻易破译用户密码;
(4)寻找WEB管理后台入口。通常WEB后台管理的界面不面向普通用户;
(5)入侵和破坏;
(6)防止:转义或过滤一些特殊字符,如%等。参数较验等。
13. Redis与Memcached的区别
Redis:
(1)Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;(2)Redis支持数据的备份,即master-slave模式的数据备份;
主从同步过程:
(a) slave服务器主动连接到Master服务器。(b) slave服务器发送SYCN命令到Master服务器请求同步。
(c) master服务器备份数据库到rdb文件。
(d) master服务器把rdb文件传输给slave服务器。
(e) slave服务器清空数据库,把rdb文件数据导入数据库中。
(f) mastere服务器把用户所有更改数据的操作,通过命令的形式转发给所有slave服务器,slave服务器只需执行master服务器发送过来的命令就可以达到同步的效果。
(3)Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用;
内存快照:是将内存中的数据以快照的方式写入二进制文件中,默认文件名为:dump.rdb;内存快照每次都把内存数据完整地写入硬盘,而不是 只写入增量数据,如数量大时,写入操作频繁会严重影响性能的。设置配置项来控制写入的间隔时间。
日志追加:把增加、修改数据的命令通过write函数追加到文件尾部(默认是appendonly.aof)。Redis重启时读取appendonly.aof文件中的所有命令并且执行,从而把数据写入内存中。
(4) redis内存淘汰算法:
LRU:从数据库中删除一个最近最少访问的key;
随机淘汰算法:从数据库中随机删除一个key;
TTL淘汰算法:从数据库中删除一个最快期的key;
Memcached:
(1)只有LRU内存淘汰算法;(2)只支持简单的key/value,不支付复杂的数据结构类型;
(3)采用多线程模型:主线程接收客户端连接,工作线程处理客户端连接的请求。
(a) 主线程监听客户端的连接,有客户端连接到memcached时,调用accept函数来接收到来的连接,把连接push到工作线程队列中,并向工作线程发送一个信号,通知工作线程有新的连接要处理;
(b) 工作线程收到主线程信号后,列会把队列上的客户端连接注册到libevent进行侦听,libevent会侦听客户端连接的读写事件,并调用相关的回调函数进行处理。
14. http协议header里有哪些信息,浏览器缓存控制的几个方法?
请求header中的常见字段:Accept: 告诉服务器能够发送哪些媒体查询,如text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8;
Accept-Encoding: 告诉服务器能发送哪些编码方式,gzip,deflate,sdch;
Accept-Language: 告诉服务器能发送哪些语言,zh-CN,zh;q=0.8,en;q=0.6;
Cache-Control: 缓存字段,如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,max-age=0,no-cache,则每次都重复访问;
Expires它表示缓存的存在时间,告诉客户端浏览器在这个时间之前不对服务器发送请求,而直接使用浏览器的缓存。
1. public 指示响应数据可以被任何客户端缓存
2. private 指示响应数据可以被非共享缓存所缓存。这表明响应的数据可以被发送请求的浏览器缓存,而不能被中介所缓存
3. no-cache 指示响应数据不能被任何接受响应的客户端所缓存
4. no-store 指示所传送的响应数据除了不能被缓存,也不能存入磁盘。一般用于敏感数据,以免数据被复制。
5. must-revalidate 指示所有的缓存都必须重新验证,在这个过程中,浏览器会发送一个If-Modified-Since头。
如果服务器程序验证得出当前的响应数据为最新的数 据,那么服务器应当返回一个304 Not Modified响应给客户端,否则响应数据将再次被发送到客户端。
6. proxy-revalidate 与must-revalidate相似,不同的是用来指示共享缓存。
7. max-age 数据经过max-age设置的秒数后就会失效,相当于HTTP/1.0中的Expires头。如果在一次响应中同时设置了max-age和 Expires,那么max-age将具有较高的优先级。
8. s-maxage 与max-age相似,不同的是用来指示共享缓存。
Connection: 连接类型,keep-alive:长连接,closed:不用长链接;
Cookie:安全首部,由客户端发送,包含在HTTP请求的头部中;
Host: 主机,tuan.baidu.com;
Referer: 告诉服务器我是从哪个页面链接过来的,http://tuan.baidu.com/
User-Agent: 浏览器及操作系统的信息,Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
响应header中的常见字段:
Connection:keep-alive
Content-Encoding:gzip
Content-Type:text/html
Date:Fri, 21 Mar 2014 10:50:46 GMT
Server:Apache
Set-Cookie:由服务器发送,它包含在响应请求的头部中。它用于在客户端创建一个Cookie,tn=baidutuan_tg; path=/
Content-Length:首部告诉浏览器报文中实体主体的大小,Content-Length就是压缩后的大小,HTTP协议中使用Content-Length这个头来告知数据的长度。
然后,在数据下行的过程中,Content-Length的方式要预先在服务器中缓存所有数据,然后所有数据再一股脑儿地发给客户端。
果要一边产生数据,一边发给客户端,WEB 服务器就需要使用"Transfer-Encoding: chunked"这样的方式来代替Content-Length。
Vary: Vary中有User-Agent,那么即使相同的请求,如果用户使用IE打开了一个页面,再用Firefox打开这个页面的时候,代理/客户端会认为这是不同的页面
如果Vary中没有User-Agent,那么代理/客户端缓存会认为是相同的页面,直接给用户返回缓存的内容,而不会再去web服务器请求相应的页面Accept-Encoding;
15. 如何实现一个线程?
(1)需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰;
(2)实现Runnalbe接口,重载Runnalbe接口中的run()方法。别适合多个具有相同代码的线程去处理同一资源的情况;
16. 内存溢出
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存;原因:
(1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据,如与数据库锁表有关;
(2)代码中存在死循环或循环产生过多重复的对象实体;
(3)启动参数内存值设定的过小;
解决:
(1)就是修改JVM启动参数,直接增加内存;
(2)检查错误日志;
(3)使用内存查看工具动态查看内存使用情况;
17. Heap memory和Stack memory的区别
(1)Heap Memory是堆内存,Stack Memory是栈内存;(2)Stack memory内存空间由操作系统自动分配和释放,Heap Memory内存空间手动申请和释放的,Heap Memory内存常用new关键字来分配;
(3)Stack Memory内存空间有限,Heap Memor的空间是很大的自由区几乎没有空间限制;
(4)堆内存由Java 虚拟机的自动垃圾回收器来管理,栈内存是当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
18. 非递归后续遍历二叉树
// 后序遍历伪代码:非递归版本,用栈实现
void PostOrder(TNode* root)
{
Stack S;
if( root != NULL )
{
S.push(root);
}
while ( !S.empty() )
{
TNode* node = S.pop();
if ( node->bPushed )
{ // 如果标识位为true,则表示其左右子树都已经入栈,那么现在就需要访问该节点了
Visit(node);
}
else
{ // 左右子树尚未入栈,则依次将 右节点,左节点,根节点 入栈
if ( node->right != NULL )
{
node->right->bPushed = false; // 左右子树均设置为false
S.push(node->right);
}
if ( node->left != NULL )
{
node->left->bPushed = false;
S.push(node->left);
}
node->bPushed = true; // 根节点标志位为true
S.push(node);
}
}
}
19. Throwable、Error、Exception以及RuntimeException的区别
(1)Throwable类是 Java 语言中所有错误或异常的超类。它的两个子类是Error和Exception;(2)Error是Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件;
(3)Exception类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件;
(4)RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
可能在执行方法期间抛出但未被捕获的RuntimeException 的任何子类都无需在 throws 子句中进行声明。它是Exception的子类;