总结Java开发面试常问的问题,持续更新中~

GitHub地址:github.com/zaiyunduan1…,若是对你有帮助欢迎Starcss

数据库

mysql

为何用自增列做为主键

  1. 若是咱们定义了主键(PRIMARY KEY),那么InnoDB会选择主键做为汇集索引、若是没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的惟一索引做为主键索引、若是也没有这样的惟一索引,则InnoDB会选择内置6字节长的ROWID做为隐含的汇集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。html

  2. 数据记录自己被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,所以每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,若是页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)前端

  3. 若是表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页java

  4. 若是使用非自增主键(若是身份证号或学号等),因为每次插入主键的值近似于随机,所以每次新纪录都要被插到现有索引页得中间某个位置,此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增长了不少开销,同时频繁的移动、分页操做形成了大量的碎片,获得了不够紧凑的索引结构,后续不得不经过OPTIMIZE TABLE来重建表并优化填充页面。node

为何使用数据索引能提升效率

  1. 数据索引的存储是有序的
  2. 在有序的状况下,经过索引查询一个数据是无需遍历索引记录的
  3. 极端状况下,数据索引的查询效率为二分法查询效率,趋近于 log2(N)

B+树索引和哈希索引的区别

B+树是一个平衡的多叉树,从根节点到每一个叶子节点的高度差值不超过1,并且同层级的节点间有指针相互连接,是有序的 mysql

哈希索引就是采用必定的哈希算法,把键值换算成新的哈希值,检索时不须要相似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法便可,是无序的

哈希索引的优点:linux

  1. 等值查询。哈希索引具备绝对优点(前提是:没有大量重复键值,若是大量重复键值时,哈希索引的效率很低,由于存在所谓的哈希碰撞问题。)

哈希索引不适用的场景:git

  1. 不支持范围查询
  2. 不支持索引完成排序
  3. 不支持联合索引的最左前缀匹配规则

一般,B+树索引结构适用于绝大多数场景,像下面这种场景用哈希索引才更有优点:程序员

在HEAP表中,若是存储的数据重复度很低(也就是说基数很大),对该列数据以等值查询为主,没有范围查询、没有排序的时候,特别适合采用哈希索引,例如这种SQL:github

select id,name from table where name='李明'; — 仅等值查询
复制代码

而经常使用的InnoDB引擎中默认使用的是B+树索引,它会实时监控表上索引的使用状况,若是认为创建哈希索引能够提升查询效率,则自动在内存中的“自适应哈希索引缓冲区”创建哈希索引(在InnoDB中默认开启自适应哈希索引),经过观察搜索模式,MySQL会利用index key的前缀创建哈希索引,若是一个表几乎大部分都在缓冲池中,那么创建一个哈希索引可以加快等值查询。

注意:在某些工做负载下,经过哈希索引查找带来的性能提高远大于额外的监控索引搜索状况和保持这个哈希表结构所带来的开销。但某些时候,在负载高的状况下,自适应哈希索引中添加的read/write锁也会带来竞争,好比高并发的join操做。like操做和%的通配符操做也不适用于自适应哈希索引,可能要关闭自适应哈希索引。

B树和B+树的区别

  1. B树,每一个节点都存储key和data,全部节点组成这棵树,而且叶子节点指针为nul,叶子结点不包含任何关键字信息。
    这里写图片描述
  2. B+树,全部的叶子结点中包含了所有关键字的信息,及指向含有这些关键字记录的指针,且叶子结点自己依关键字的大小自小而大的顺序连接,全部的非终端结点能够当作是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含须要查找的有效信息)

这里写图片描述

为何说B+比B树更适合实际应用中操做系统的文件索引和数据库索引?

  1. B+的磁盘读写代价更低 B+的内部结点并无指向关键字具体信息的指针。所以其内部结点相对B树更小。若是把全部同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的须要查找的关键字也就越多。相对来讲IO读写次数也就下降了。

  2. B+-tree的查询效率更加稳定 因为非终结点并非最终指向文件内容的结点,而只是叶子结点中关键字的索引。因此任何关键字的查找必须走一条从根结点到叶子结点的路。全部关键字查询的路径长度相同,致使每个数据的查询效率至关。

mysql联合索引

  1. 联合索引是两个或更多个列上的索引。对于联合索引:Mysql从左到右的使用索引中的字段,一个查询能够只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 能够支持a 、 a,b 、 a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。
  2. 利用索引中的附加列,您能够缩小搜索的范围,但使用一个具备两列的索引 不一样于使用两个单独的索引。复合索引的结构与电话簿相似,人名由姓和名构成,电话簿首先按姓氏对进行排序,而后按名字对有相同姓氏的人进行排序。若是您知 道姓,电话簿将很是有用;若是您知道姓和名,电话簿则更为有用,但若是您只知道名不姓,电话簿将没有用处。

什么状况下应不建或少建索引

  1. 表记录太少
  2. 常常插入、删除、修改的表
  3. 数据重复且分布平均的表字段,假如一个表有10万行记录,有一个字段A只有T和F两种值,且每一个值的分布几率大约为50%,那么对这种表A字段建索引通常不会提升数据库的查询速度。
  4. 常常和主字段一块查询但主字段索引值比较多的表字段

MySQL分区

什么是表分区?

表分区,是指根据必定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,可是底层倒是由多个物理分区组成。

表分区与分表的区别

分表:指的是经过必定规则,将一张表分解成多张不一样的表。好比将用户订单记录根据时间成多个表。

分表与分区的区别在于:分区从逻辑上来说只有一张表,而分表则是将一张表分解成多张表。

表分区有什么好处?

  1. 分区表的数据能够分布在不一样的物理设备上,从而高效地利用多个硬件设备。 2. 和单个磁盘或者文件系统相比,能够存储更多数据
  2. 优化查询。在where语句中包含分区条件时,能够只扫描一个或多个分区表来提升查询效率;涉及sum和count语句时,也能够在多个分区上并行处理,最后汇总结果。
  3. 分区表更容易维护。例如:想批量删除大量数据能够清除整个分区。
  4. 能够使用分区表来避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问,ext3问价你系统的inode锁竞争等。

分区表的限制因素

  1. 一个表最多只能有1024个分区
  2. MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。在MySQL5.5中提供了非整数表达式分区的支持。
  3. 若是分区字段中有主键或者惟一索引的列,那么多有主键列和惟一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含所有主键和索引列。
  4. 分区表中没法使用外键约束
  5. MySQL的分区适用于一个表的全部数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。

如何判断当前MySQL是否支持分区?

命令:show variables like '%partition%' 运行结果:

mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ 1 row in set (0.00 sec) have_partintioning 的值为YES,表示支持分区。

MySQL支持的分区类型有哪些?

  1. RANGE分区: 这种模式容许将数据划分不一样范围。例如能够将一个表经过年份划分红若干个分区
  2. LIST分区: 这种模式容许系统经过预约义的列表的值来对数据进行分割。按照List中的值分区,与RANGE的区别是,range分区的区间范围值是连续的。
  3. HASH分区 :这中模式容许经过对表的一个或多个列的Hash Key进行计算,最后经过这个Hash码不一样数值对应的数据区域进行分区。例如能够创建一个对表主键进行分区的表。
  4. KEY分区 :上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。

四种隔离级别

  1. Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
  2. Repeatable read (可重复读):可避免脏读、不可重复读的发生。
  3. Read committed (读已提交):可避免脏读的发生。
  4. Read uncommitted (读未提交):最低级别,任何状况都没法保证。

关于MVVC

MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的好处:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是很是重要的,极大的增长了系统的并发性能,现阶段几乎全部的RDBMS,都支持了MVCC。

  1. LBCC:Lock-Based Concurrency Control,基于锁的并发控制。
  2. MVCC:Multi-Version Concurrency Control,基于多版本的并发控制协议。纯粹基于锁的并发机制并发量低,MVCC是在基于锁的并发控制上的改进,主要是在读操做上提升了并发量。

在MVCC并发控制中,读操做能够分红两类:

  1. 快照读 (snapshot read):读取的是记录的可见版本 (有多是历史版本),不用加锁(共享读锁s锁也不加,因此不会阻塞其余事务的写)。
  2. 当前读 (current read):读取的是记录的最新版本,而且,当前读返回的记录,都会加上锁,保证其余事务不会再并发修改这条记录。

行级锁定的优势:

  1. 当在许多线程中访问不一样的行时只存在少许锁定冲突。
  2. 回滚时只有少许的更改
  3. 能够长时间锁定单一的行。

行级锁定的缺点:

  1. 比页级或表级锁定占用更多的内存。
  2. 当在表的大部分中使用时,比页级或表级锁定速度慢,由于你必须获取更多的锁。
  3. 若是你在大部分数据上常常进行GROUP BY操做或者必须常常扫描整个表,比其它锁定明显慢不少。
  4. 用高级别锁定,经过支持不一样的类型锁定,你也能够很容易地调节应用程序,由于其锁成本小于行级锁定。

MySQL 触发器简单实例

  1. CREATE TRIGGER <触发器名称> --触发器必须有名字,最多64个字符,可能后面会附有分隔符.它和MySQL中其余对象的命名方式基本相象.
  2. { BEFORE | AFTER } --触发器有执行的时间设置:能够设置为事件发生前或后。
  3. { INSERT | UPDATE | DELETE } --一样也能设定触发的事件:它们能够在执行insert、update或delete的过程当中触发。
  4. ON <表名称> --触发器是属于某一个表的:当在这个表上执行插入、 更新或删除操做的时候就致使触发器的激活. 咱们不能给同一张表的同一个事件安排两个触发器。
  5. FOR EACH ROW --触发器的执行间隔:FOR EACH ROW子句通知触发器 每隔一行执行一次动做,而不是对整个表执行一次。
  6. <触发器SQL语句> --触发器包含所要触发的SQL语句:这里的语句能够是任何合法的语句, 包括复合语句,可是这里的语句受的限制和函数的同样。

什么是存储过程

简单的说,就是一组SQL语句集,功能强大,能够实现一些比较复杂的逻辑功能,相似于JAVA语言中的方法;

ps:存储过程跟触发器有点相似,都是一组SQL集,可是存储过程是主动调用的,且功能比触发器更增强大,触发器是某件事触发后自动调用;

有哪些特性

  1. 有输入输出参数,能够声明变量,有if/else, case,while等控制语句,经过编写存储过程,能够实现复杂的逻辑功能;
  2. 函数的广泛特性:模块化,封装,代码复用;
  3. 速度快,只有首次执行需通过编译和优化步骤,后续被调用能够直接执行,省去以上步骤;
DROP PROCEDURE IF EXISTS `proc_adder`;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
BEGIN
    #Routine body goes here...

    DECLARE c int;
    if a is null then set a = 0; 
    end if;
  
    if b is null then set b = 0;
    end if;

    set sum  = a + b;
END
;;
DELIMITER ;

set @b=5;
call proc_adder(0,@b,@s);
SELECT @s as sum;



create table tab2(
   tab2_id varchar(11)
);

DROP TRIGGER if EXISTS t_ai_on_tab1;
create TRAILING t_ai_on_tab1
AFTER INSERT ON tab1
for EACH ROW
BEGIN
   INSERT INTO tab2(tab2_id) values(new.tab1_id);
end;

INSERT INTO tab1(tab1_id) values('0001');

SELECT * FROM tab2;
复制代码

MySQL优化

  1. 开启查询缓存,优化查询
  2. explain你的select查询,这能够帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的
  3. 当只要一行数据时使用limit 1,MySQL数据库引擎会在找到一条数据后中止搜索,而不是继续日后查少下一条符合记录的数据
  4. 为搜索字段建索引
  5. 使用 ENUM 而不是 VARCHAR,若是你有一个字段,好比“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限并且固定的,那么,你应该使用 ENUM 而不是VARCHAR。
  6. Prepared Statements Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,咱们能够从使用 prepared statements 得到不少好处,不管是性能问题仍是安全问题。Prepared Statements 能够检查一些你绑定好的变量,这样能够保护你的程序不会受到“SQL注入式”攻击
  7. 垂直分表
  8. 选择正确的存储引擎

key和index的区别

  1. key 是数据库的物理结构,它包含两层意义和做用,一是约束(偏重于约束和规范数据库的结构完整性),二是索引(辅助查询用的)。包括primary key, unique key, foreign key 等
  2. index是数据库的物理结构,它只是辅助查询的,它建立时会在另外的表空间(mysql中的innodb表空间)以一个相似目录的结构存储。索引要分类的话,分为前缀索引、全文本索引等;

Mysql 中 MyISAM 和 InnoDB 的区别有哪些?

区别:

  1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,因此最好把多条SQL语言放在begin和commit之间,组成一个事务;

  2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;

  3. InnoDB是汇集索引,数据文件是和索引绑在一块儿的,必需要有主键,经过主键索引效率很高。可是辅助索引须要两次查询,先查询到主键,而后再经过主键查询到数据。所以,主键不该该过大,由于主键太大,其余索引也都会很大。而MyISAM是非汇集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。

  4. InnoDB不保存表的具体行数,执行select count(*) from table时须要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只须要读出该变量便可,速度很快;

  5. Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;

如何选择:

  1. 是否要支持事务,若是要请选择innodb,若是不须要能够考虑MyISAM;

  2. 若是表中绝大多数都只是读查询,能够考虑MyISAM,若是既有读写也挺频繁,请使用InnoDB。

  3. 系统奔溃后,MyISAM恢复起来更困难,可否接受;

  4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(以前是MyISAM),说明其优点是有目共睹的,若是你不知道用什么,那就用InnoDB,至少不会差。

数据库表建立注意事项

1、字段名及字段配制合理性

  1. 剔除关系不密切的字段

  2. 字段命名要有规则及相对应的含义(不要一部分英文,一部分拼音,还有相似a.b.c这样不明含义的字段)

  3. 字段命名尽可能不要使用缩写(大多数缩写都不能明确字段含义)

  4. 字段不要大小写混用(想要具备可读性,多个英文单词可以使用下划线形式链接)

  5. 字段名不要使用保留字或者关键字

  6. 保持字段名和类型的一致性

  7. 慎重选择数字类型

  8. 给文本字段留足余量

2、系统特殊字段处理及建成后建议

  1. 添加删除标记(例如操做人、删除时间)

  2. 创建版本机制

3、表结构合理性配置

  1. 多型字段的处理,就是表中是否存在字段可以分解成更小独立的几部分(例如:人能够分为男人和女人)

  2. 多值字段的处理,能够将表分为三张表,这样使得检索和排序更加有调理,且保证数据的完整性!

4、其它建议

  1. 对于大数据字段,独立表进行存储,以便影响性能(例如:简介字段)

  2. 使用varchar类型代替char,由于varchar会动态分配长度,char指定长度是固定的。

  3. 给表建立主键,对于没有主键的表,在查询和索引定义上有必定的影响。

  4. 避免表字段运行为null,建议设置默认值(例如:int类型设置默认值为0)在索引查询上,效率立显!

  5. 创建索引,最好创建在惟一和非空的字段上,创建太多的索引对后期插入、更新都存在必定的影响(考虑实际状况来建立)。

redis

redis单线程问题

单线程指的是网络请求模块使用了一个线程(因此不需考虑并发安全性),即一个线程处理全部网络请求,其余模块仍用了多个线程。

为何说redis可以快速执行

  1. 绝大部分请求是纯粹的内存操做(很是快速)
  2. 采用单线程,避免了没必要要的上下文切换和竞争条件
  3. 非阻塞IO - IO多路复用

redis的内部实现

内部实现采用epoll,采用了epoll+本身实现的简单的事件框架。epoll中的读、写、关闭、链接都转化成了事件,而后利用epoll的多路复用特性,不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,若是请求都是耗时的,采用单线程吞吐量及性能不好。redis为特殊的场景选择了合适的技术方案。

Redis关于线程安全问题

redis其实是采用了线程封闭的观念,把任务封闭在一个线程,天然避免了线程安全问题,不过对于须要依赖多个redis操做的复合操做来讲,依然须要锁,并且有多是分布式锁。

使用redis有哪些好处?

  1. 速度快,由于数据存在内存中,相似于HashMap,HashMap的优点就是查找和操做的时间复杂度都是O(1)
  2. 支持丰富数据类型,支持string,list,set,sorted set,hash
  3. 支持事务,操做都是原子性,所谓的原子性就是对数据的更改要么所有执行,要么所有不执行
  4. 丰富的特性:可用于缓存,消息,按key设置过时时间,过时后将会自动删除

redis相比memcached有哪些优点?

  1. memcached全部的值均是简单的字符串,redis做为其替代者,支持更为丰富的数据类型
  2. redis的速度比memcached快不少
  3. redis能够持久化其数据
  4. Redis支持数据的备份,即master-slave模式的数据备份。
  5. 使用底层模型不一样,它们之间底层实现方式 以及与客户端之间通讯的应用协议不同。Redis直接本身构建了VM 机制 ,由于通常的系统调用系统函数的话,会浪费必定的时间去移动和请求。
  6. value大小:redis最大能够达到1GB,而memcache只有1MB

Redis主从复制

过程原理:

  1. 当从库和主库创建MS关系后,会向主数据库发送SYNC命令
  2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
  3. 当快照完成后,主Redis会将快照文件和全部缓存的写命令发送给从Redis
  4. 从Redis接收到后,会载入快照文件而且执行收到的缓存的命令
  5. 以后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致

缺点:全部的slave节点数据的复制和同步都由master节点来处理,会照成master节点压力太大,使用主从从结构来解决

redis两种持久化方式的优缺点

  1. RDB 持久化能够在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)
  2. AOF 持久化记录服务器执行的全部写操做命令,并在服务器启动时,经过从新执行这些命令来还原数据集。
  3. Redis 还能够同时使用 AOF 持久化和 RDB 持久化。当redis重启时,它会有限使用AOF文件来还原数据集,由于AOF文件保存的数据集一般比RDB文件所保存的数据集更加完整

RDB的优势:

  1. RDB 是一个很是紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件很是适合用于进行备份: 好比说,你能够在最近的 24 小时内,每小时备份一次 RDB 文件,而且在每月的每一天,也备份一个 RDB 文件。 这样的话,即便赶上问题,也能够随时将数据集还原到不一样的版本。

  2. RDB 很是适用于灾难恢复(disaster recovery):它只有一个文件,而且内容都很是紧凑,能够(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。

  3. RDB 能够最大化 Redis 的性能:父进程在保存 RDB 文件时惟一要作的就是 fork 出一个子进程,而后这个子进程就会处理接下来的全部保存工做,父进程无须执行任何磁盘 I/O 操做。

  4. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快

Redis 常见的性能问题都有哪些?如何解决?

  1. Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工做,当快照比较大时对性能影响是很是大的,会间断性暂停服务,因此Master最好不要写内存快照。
  2. Master AOF持久化,若是不重写AOF文件,这个持久化方式对性能的影响是最小的,可是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要作任何持久化工做,包括内存快照和AOF日志文件,特别是不要启用内存快照作持久化,若是数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
  3. Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,致使服务load太高,出现短暂服务暂停现象。
  4. Redis主从复制的性能问题,为了主从复制的速度和链接的稳定性,Slave和Master最好在同一个局域网内

redis 提供 6种数据淘汰策略

  1. volatile-lru:从已设置过时时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过时时间的数据集(server.db[i].expires)中挑选将要过时的数据淘汰
  3. volatile-random:从已设置过时时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据

java

java虚拟机

这里写图片描述
图片来源(Hollis微信公众号)

何时会触发full gc

  1. System.gc()方法的调用
  2. 老年代空间不足
  3. 永生区空间不足(JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据)
  4. GC时出现promotion failed和concurrent mode failure
  5. 统计获得的Minor GC晋升到旧生代平均大小大于老年代剩余空间
  6. 堆中分配很大的对象

能够做为root的对象:

  1. 类中的静态变量,当它持有一个指向一个对象的引用时,它就做为root
  2. 活动着的线程,能够做为root
  3. 一个Java方法的参数或者该方法中的局部变量,这两种对象能够做为root
  4. JNI方法中的局部变量或者参数,这两种对象能够做为root

例子:下述的Something和Apple均可以做为root对象。

public AClass{
 
  public static Something;
  public static final Apple;
   ''''''
}
复制代码

Java方法的参数和方法中的局部变量,能够做为root.

public Aclass{

public void doSomething(Object A){
    ObjectB b = new ObjectB; 
    }
 }
复制代码

新生代转移到老年代的触发条件

  1. 长期存活的对象
  2. 大对象直接进入老年代
  3. minor gc后,survivor仍然放不下
  4. 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代

G1和CMS的区别

  1. G1同时回收老年代和年轻代,而CMS只能回收老年代,须要配合一个年轻代收集器。另外G1的分代更可能是逻辑上的概念,G1将内存分红多个等大小的region,Eden/ Survivor/Old分别是一部分region的逻辑集合,物理上内存地址并不连续。
    这里写图片描述
  2. CMS在old gc的时候会回收整个Old区,对G1来讲没有old gc的概念,而是区分Fully young gc和Mixed gc,前者对应年轻代的垃圾回收,后者混合了年轻代和部分老年代的收集,所以每次收集确定会回收年轻代,老年代根据内存状况能够不回收或者回收部分或者所有(这种状况应该是可能出现)。

双亲委派模型中有哪些方法。用户如何自定义类加载器 。怎么打破双亲委托机制

  1. 双亲委派模型中用到的方法:
  • findLoadedClass(),
  • loadClass()
  • findBootstrapClassOrNull()
  • findClass()
  • defineClass():把二进制数据转换成字节码。
  • resolveClass()

自定义类加载器的方法:继承 ClassLoader 类,重写 findClass()方法 。

  1. 继承ClassLoader覆盖loadClass方法 原顺序
  2. findLoadedClass
  3. 委托parent加载器加载(这里注意bootstrap加载器的parent为null)
  4. 自行加载 打破委派机制要作的就是打乱2和3的顺序,经过类名筛选本身要加载的类,其余的委托给parent加载器。

即时编译器的优化方法

字节码能够经过如下两种方式转换成合适的语言:

  1. 解释器
  2. 即时编译器 即时编译器把整段字节码编译成本地代码,执行本地代码比一条一条进行解释执行的速度快不少,由于本地代码是保存在缓存里的

编译过程的五个阶段

  1. 第一阶段:词法分析
  2. 第二阶段:语法分析
  3. 第三阶段:词义分析与中间代码产生
  4. 第四阶段:优化
  5. 第五阶段:目标代码生成

java应用系统运行速度慢的解决方法

问题解决思路:

  1. 查看部署应用系统的系统资源使用状况,CPU,内存,IO这几个方面去看。找到对就的进程。
  2. 使用jstack,jmap等命令查看是JVM是在在什么类型的内存空间中作GC(内存回收),和查看GC日志查看是那段代码在占用内存。 首先,调节内存的参数设置,若是仍是同样的问题,就要定位到相应的代码。
  3. 定位代码,修改代码(通常是代码的逻辑问题,或者代码获取的数据量过大。)

内存溢出是什么,什么缘由致使的

内存溢出是指应用系统中存在没法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。为了解决Java中内存溢出问题,咱们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不须要经过调用GC函数来释放内存,由于不一样的JVM实现者可能使用不一样的算法管理GC,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是中断式执行GC。但GC只能回收无用而且再也不被其它对象引用的那些对象所占用的空间。Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就做为垃圾回收。

引发内存溢出的缘由有不少种,常见的有如下几种:

  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

  2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

  3. 代码中存在死循环或循环产生过多重复的对象实体;

  4. 使用的第三方软件中的BUG;

  5. 启动参数内存值设定的太小;

内存溢出的解决

内存溢出虽然很棘手,但也有相应的解决办法,能够按照从易到难,一步步的解决。

第一步,就是修改JVM启动参数,直接增长内存。这一点看上去彷佛很简单,但很容易被忽略。JVM默承认以使用的内存为64M,Tomcat默承认以使用的内存为128MB,对于稍复杂一点的系统就会不够用。在某项目中,就由于启动参数使用的默认值,常常报“OutOfMemory”错误。所以,-Xms,-Xmx参数必定不要忘记加。

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。在一个项目中,使用两个数据库链接,其中专用于发送短信的数据库链接使用DBCP链接池管理,用户为不将短信发出,有意将数据库链接用户名改错,使得日志中有许多数据库链接异常的日志,一段时间后,就出现“OutOfMemory”错误。经分析,这是因为DBCP链接池BUG引发的,数据库链接不上后,没有将链接释放,最终使得DBCP报“OutOfMemory”错误。通过修改正确数据库链接参数后,就没有再出现内存溢出的错误。

查看日志对于分析内存溢出是很是重要的,经过仔细查看日志,分析内存溢出前作过哪些操做,能够大体定位有问题的模块。

第三步,找出可能发生内存溢出的位置。重点排查如下几点:

  1. 检查代码中是否有死循环或递归调用。

  2. 检查是否有大循环重复产生新对象实体。

  3. 检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。

  4. 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用状况。某个项目上线后,每次系统启动两天后,就会出现内存溢出的错误。这种状况通常是代码中出现了缓慢的内存泄漏,用上面三个步骤解决不了,这就须要使用内存查看工具了。

内存查看工具备许多,比较有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它们的基本工做原理大同小异,都是监测Java程序运行时全部对象的申请、释放等动做,将内存管理的全部信息进行统计、分析、可视化。开发人员能够根据这些信息判断程序是否有内存泄漏问题。通常来讲,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不该该是无限制的增加的。持续地观察系统运行时使用的内存的大小,能够看到在内存使用监控窗口中是基本规则的锯齿形的图线,若是内存的大小持续地增加,则说明系统存在内存泄漏问题。经过间隔一段时间取一次内存快照,而后对内存快照中对象的使用与引用等信息进行比对与分析,能够找出是哪一个类的对象在泄漏。

经过以上四个步骤的分析与处理,基本能处理内存溢出的问题。固然,在这些过程当中也须要至关的经验与敏感度,须要在实际的开发与调试过程当中不断积累。

整体上来讲,产生内存溢出是因为代码写的很差形成的,所以提升代码的质量是最根本的解决办法。有的人认为先把功能实现,有BUG时再在测试阶段进行修正,这种想法是错误的。正如一件产品的质量是在生产制造的过程当中决定的,而不是质量检测时决定的,软件的质量在设计与编码阶段就已经决定了,测试只是对软件质量的一个验证,由于测试不可能找出软件中全部的BUG。

java并发

JAVA 线程状态转换图示

这里写图片描述

synchronized 的底层怎么实现

  1. 同步代码块(Synchronization)基于进入和退出管程(Monitor)对象实现。每一个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:
  • 若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者。

  • 若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1.

  • 若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权。

  1. 被 synchronized 修饰的同步方法并无经过指令monitorenter和monitorexit来完成(理论上其实也能够经过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需经过字节码来完成

讲一下CAS

CAS,compare and swap的缩写,中文翻译成比较并交换。乐观锁用到的机制就是CAS,每次不加锁而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试。

原理:

  1. CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。

JDK文档说cas同时具备volatile读和volatile写的内存语义。

缺点:

  1. ABA问题。 由于CAS须要在操做值的时候检查下值有没有发生变化,若是没有发生变化则更新,可是若是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化

  2. 循环时间长开销大。 自旋CAS若是长时间不成功,会给CPU带来很是大的执行开销。

  3. 只能保证一个共享变量的原子操做。 对多个共享变量操做时,循环CAS就没法保证操做的原子性,这个时候就能够用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操做。好比有两个共享变量i=2,j=a,合并一下ij=2a,而后用CAS来操做ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你能够把多个变量放在一个对象里来进行CAS操做。

线程池

Executor线程池框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

ThreadPoolExecutor执行的策略

  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  2. 线程数量达到了corePools,则将任务移入队列等待
  3. 队列已满,新建线程(非核心线程)执行任务
  4. 队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常

新建线程 -> 达到核心数 -> 加入队列 -> 新建线程(非核心) -> 达到最大数 -> 触发拒绝策略

常见四种线程池

  1. CachedThreadPool():可缓存线程池。
  • 线程数无限制
  • 有空闲线程则复用空闲线程,若无空闲线程则新建线程
  • 必定程序减小频繁建立/销毁线程,减小系统开销
  1. FixedThreadPool():定长线程池。
  • 可控制线程最大并发数(同时执行的线程数)
  • 超出的线程会在队列中等待
  1. ScheduledThreadPool():定时线程池。
  • 支持定时及周期性任务执行。
  1. SingleThreadExecutor():单线程化的线程池。
  • 有且仅有一个工做线程执行任务
  • 全部任务按照指定顺序执行,即遵循队列的入队出队规则

四种拒绝策略

  1. AbortPolicy:拒绝任务,且还抛出RejectedExecutionException异常,线程池默认策略
  2. CallerRunPolicy:拒绝新任务进入,若是该线程池尚未被关闭,那么这个新的任务在执行线程中被调用
  3. DiscardOldestPolicy: 若是执行程序还没有关闭,则位于头部的任务将会被移除,而后重试执行任务(再次失败,则重复该过程),这样将会致使新的任务将会被执行,而先前的任务将会被移除。
  4. DiscardPolicy:没有添加进去的任务将会被抛弃,也不抛出异常。基本上为静默模式。

为何要用线程池

  1. 减小了建立和销毁线程的次数,每一个工做线程均可以被重复利用,可执行多个任务。
  2. 运用线程池能有效的控制线程最大并发数,能够根据系统的承受能力,调整线程池中工做线线程的数目,防止由于消耗过多的内存,而把服务器累趴下(每一个线程须要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  3. 对线程进行一些简单的管理,好比:延时执行、定时循环执行的策略等,运用线程池都能进行很好的实现

对象锁和静态锁之间的区别

  1. 对象锁用于对象实例方法,
  2. 类锁用于类的静态方法或一个类的class对象。
  3. 类的对象实例能够有不少,不一样对象实例的对象锁互不干扰,而每一个类只有一个类锁

简述volatile字

两个特性

  1. 保证了不一样线程对这个变量进行 读取 时的可见性,即一个线程修改 了某个变量的值 , 这新值对其余线程来讲是当即可见的 。(volatile 解决了 线程间 共享变量
  2. 禁止进行指令重排序 ,阻止编译器对代码的优化

要想并发程序正确地执行,必需要保证原子性、可见性以及有序性,锁保证了原子性,而volatile保证可见性和有序性

happens-before 原则(先行发生原则):

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操做先行发生于书写在 后面的操做
  2. 锁定规则:一个 unLock 操做先行发生于后面对同一个锁的 lock 操做
  3. volatile 变量规则:对一个变量的写操做先行发生于后面对这个变量的读操做
  4. 传递规则:若是操做 A 先行发生于操做 B,而操做 B 又先行发生于操做 C,则能够 得出操做 A 先行发生于操做 C
  5. 线程启动规则:Thread 对象的 start()方法先行发生于此线程的每一个一个动做
  6. 线程中断规则:对线程 interrupt()方法的调用先行发生于被中断线程的代码检测 到中断事件的发生
  7. 线程终结规则:线程中全部的操做都先行发生于线程的终止检测,咱们能够经过 T hread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  8. 对象终结规则:一个对象的初始化完成先行发生于他的 finalize()方法的开始

Lock 和synchronized 的区别

  1. Lock 是一个 接口,而 synchronized 是 Java 中的 关键字, synchronized 是 内置的语言实现;

  2. synchronized 在 发生异常时,会 自动释放线程占有的锁,所以 不会导 致死锁现象发生;而 Lock 在发生异常时,若是没有主动经过 unLock()去释放 锁,则很 可能形成死锁现象,所以用 使用 Lock 时须要在 finally 块中释放锁;

  3. Lock 可让 等待锁的线程响应中断 (可中断锁),而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去, 不可以响应中 断 (不可中断锁);

  4. 经过 Lock 能够知道 有没有成功获取锁 (tryLock ( ) 方法 : 若是获取 了锁 ,回 则返回 true ;回 不然返回 false e, , 也就说这个方法不管如何都会当即返回 。 在拿不到锁时不会一直在那等待。 ),而 synchronized 却没法办到。

  5. Lock 能够提升 多个线程进行读操做的效率( 读写锁)。

  6. Lock 能够实现 公平锁,synchronized 不保证公平性。 在性能上来讲,若是线程竞争资源不激烈时,二者的性能是差很少的,而 当竞争资源很是激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优 于 synchronized。因此说,在具体使用时要根据适当状况选择。

ThreadLocal(线程变量副本)

Synchronized实现内存共享,ThreadLocal为每一个线程维护一个本地变量。 采用空间换时间,它用于线程间的数据隔离,为每个使用该变量的线程提供一个副本,每一个线程均可以独立地改变本身的副本,而不会和其余线程的副本冲突。 ThreadLocal类中维护一个Map,用于存储每个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。 ThreadLocal在Spring中发挥着巨大的做用,在管理Request做用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。 Spring中绝大部分Bean均可以声明成Singleton做用域,采用ThreadLocal进行封装,所以有状态的Bean就可以以singleton的方式在多线程中正常工做了。

经过Callable和Future建立线程

Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很类似,但它能够返回一个对象或者抛出一个异常。

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。因为Callable任务是并行的,咱们必须等待它返回的结果。java.util.concurrent.Future对象为咱们解决了这个问题。在线程池提交Callable任务返回了一个Future对象,使用它咱们能够知道Callable任务的状态和获得Callable返回的执行结果。Future提供了get()方法让咱们能够等待Callable结束并获取它的执行结果

  1. 建立Callable接口的实现类,并实现call()方法,该call()方法将做为线程执行体,而且有返回值。
  2. 建立Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  3. 使用FutureTask对象做为Thread对象的target建立并启动新线程
  4. 调用FutureTask对象的get()方法来得到子线程执行结束后的返回值

什么叫守护线程,用什么方法实现守护线程(Thread.setDeamon()的含义)

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用个比较通俗的好比,任何一个守护线程都是整个JVM中全部非守护线程的保姆: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工做。 JVM内部的实现是若是运行的程序只剩下守护线程的话,程序将终止运行,直接结束。因此守护线程是做为辅助线程存在的,主要的做用是提供计数等等辅助的功能。

如何中止一个线程?

终止线程的三种方法:

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 在定义退出标志exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值,
thread.exit = true;  // 终止线程thread 
复制代码
  1. 使用stop方法强行终止线程(这个方法不推荐使用,由于stop和suspend、resume同样,也可能发生不可预料的结果)。 使用stop方法能够强行终止正在运行或挂起的线程。咱们能够使用以下的代码来终止线程: thread.stop(); 虽然使用上面的代码能够终止线程,但使用stop方法是很危险的,就象忽然关闭计算机电源,而不是按正常程序关机同样,可能会产生不可预料的结果,所以,并不推荐使用stop方法来终止线程。

  2. 使用interrupt方法中断线程,使用interrupt方法来终端线程可分为两种状况:

  • 线程处于阻塞状态,如使用了sleep方法。
  • 使用while(!isInterrupted()){……}来判断线程是否被中断。 在第一种状况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种状况下线程将直接退出。

注意:在Thread类中有两个方法能够判断线程是否经过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted能够用来判断其余线程是否被中断。所以,while (!isInterrupted())也能够换成while (!Thread.interrupted())。

什么是线程安全?什么是线程不安全?

  1. 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其余线程不能进行访问直到该线程读取完,其余线程才可以使用。不会出现数据不一致或者数据污染。
  2. 线程不安全就是不提供数据访问保护,有可能出现多个线程前后更改数据形成所获得的数据是脏数据 在多线程的状况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。

java容器

HashSet和TreeSet区别

HashSet

  1. 不能保证元素的排列顺序,顺序有可能发生变化
  2. 不是同步的
  3. 集合元素能够是null,但只能放入一个null 当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来获得该对象的hashCode值,而后根据 hashCode值来决定该对象在HashSet中存储位置。

TreeSet

  1. TreeSet是SortedSet接口的惟一实现类
  2. TreeSet能够确保集合元素处于排序状态。TreeSet支持两种排序方式,天然排序 和定制排序,其中天然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象

讲一下LinkedHashMap

LinkedHashMap的实现就是HashMap+LinkedList的实现方式,以HashMap维护数据结构,以LinkList的方式维护数据插入顺序

LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先获得的记录确定是先插入的。 在遍历的时候会比HashMap慢TreeMap可以把它保存的记录根据键排序,默认是按升序排序,也能够指定排序的比较器

利用LinkedHashMap实现LRU算法缓存(

  1. LinkedList首先它是一个Map,Map是基于K-V的,和缓存一致
  2. LinkedList提供了一个boolean值可让用户指定是否实现LRU)

Java8 中HashMap的优化(引入红黑树的数据结构和扩容的优化)

  1. if (binCount >= TREEIFY_THRESHOLD - 1) 当符合这个条件的时候,把链表变成treemap红黑树,这样查找效率从o(n)变成了o(log n) ,在JDK1.8的实现中,优化了高位运算的算法,经过hashCode()的高16位异或低16位实现的:
  2. 咱们使用的是2次幂的扩展(指长度扩为原来2倍),因此,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置

这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。

元素在从新计算hash以后,由于n变为2倍,那么n-1的mask范围在高位多1bit(红色),所以新的index就会发生这样的变化: hashMap 1.8 哈希算法例图2

这里写图片描述
所以,咱们在扩充HashMap的时候,不须要像JDK1.7的实现那样从新计算hash,只须要看看原来的hash值新增的那个bit是1仍是0就行了,是0的话索引没变,是1的话索引变成“原索引+oldCap”

Map遍历的keySet()和entrySet()性能差别缘由

Set<Entry<String, String>> entrySet = map.entrySet();
Set<String> set = map.keySet();` 
复制代码
  1. keySet()循环中经过key获取对应的value的时候又会调用getEntry()进行循环。循环两次
  2. entrySet()直接使用getEntry()方法获取结果,循环一次
  3. 因此 keySet()的性能会比entrySet()差点。因此遍历map的话仍是用entrySet()来遍历
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }    
复制代码
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}
复制代码

java基础

抽象类和接口的对比

参数 抽象类 接口
默认的方法实现 它能够有默认的方法实现 接口彻底是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。若是子类不是抽象类的话,它须要提供抽象类中全部声明的方法的实现。 子类使用关键字implements来实现接口。它须要提供接口中全部声明的方法的实现
构造器 抽象类能够有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类以外,它和普通Java类没有任何区别 接口是彻底不一样的类型
访问修饰符 抽象方法能够有public、protected和default这些修饰符 接口方法默认修饰符是public。你不能够使用其它修饰符。
main方法 抽象方法能够有main方法而且咱们能够运行它 接口没有main方法,所以咱们不能运行它。
多继承 抽象方法能够继承一个类和实现多个接口 接口只能够继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,由于它须要时间去寻找在类中实现的方法。
添加新方法 若是你往抽象类中添加新的方法,你能够给它提供默认的实现。所以你不须要改变你如今的代码。 若是你往接口中添加方法,那么你必须改变实现该接口的类。

建立一个类的几种方法?

  1. 使用new关键字 → 调用了构造函数
  2. 使用Class类的newInstance方法 → 调用了构造函数
Employee emp2 = (Employee)Class.forName("org.programming.mitra.exercises.Employee").newInstance();
复制代码
  1. 使用Constructor类的newInstance方法 → 调用了构造函数
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
复制代码
  1. 使用clone方法 → 没有调用构造函数
  2. 使用反序列化 }→ 没有调用构造函数
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
复制代码

Redirect和forward

  1. 上图所示的间接转发请求的过程以下: 浏览器向Servlet1发出访问请求; Servlet1调用sendRedirect()方法,将浏览器重定向到Servlet2; 浏览器向servlet2发出请求; 最终由Servlet2作出响应。

  2. 上图所示的直接转发请求的过程以下: 浏览器向Servlet1发出访问请求; Servlet1调用forward()方法,在服务器端将请求转发给Servlet2; 最终由Servlet2作出响应。

什么是泛型,为何要使用以及类型擦除。

  1. 泛型的本质就是“参数化类型”,也就是说所操做的数据类型被指定为一个参数。 建立集合时就指定集合元素的数据类型,该集合只能保存其指定类型的元素, 避免使用强制类型转换。
  2. Java 编译器生成的字节码是不包含泛型信息的,泛型类型信息将在 编译处理 时 被擦除,这个过程即 类型擦除。类型擦除能够简单的理解为将泛型 java 代码转 换为普通 java 代码,只不过编译器更直接点,将泛型 java 代码直接转换成普通 java 字节码。

类型擦除的主要过程以下:

  1. 将全部的泛型参数用其最左边界(最顶级的父类型)类型替换。
  2. 移除全部的类型参数。

Object跟这些标记符表明的java类型有啥区别呢?

Object是全部类的根类,任何类的对象均可以设置给该Object引用变量,使用的时候可能须要类型强制转换,可是用使用了泛型T、E等这些标识符后,在实际用以前类型就已经肯定了,不须要再进行类型强制转换。

Error类和Exception类区别

  1. Error类和Exception类的父类都是throwable类,他们的区别是: Error类通常是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的致使的应用程序中断,仅靠程序自己没法恢复和和预防,遇到这样的错误,建议让程序终止。 Exception类表示程序能够处理的异常,能够捕获且可能恢复。遇到这类异常,应该尽量处理异常,使程序恢复运行, 而不该该随意终止异常。
  2. Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能经过,可是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获,要么用throws字句声明抛出,交给它的父类处理,不然编译不会经过。

throw和throws区别

throw:(针对对象的作法) 抛出一个异常,能够是系统定义的,也能够是本身定义的

public void yichang(){
    NumberFormatException e = new NumberFormatException();
    throw e;
}
复制代码

throws:(针对一个方法抛出的异常) 抛出一个异常,能够是系统定义的,也能够是本身定义的。

public void yichang() throws NumberFormatException{
    int a = Integer.parseInt("10L");
}
复制代码
  1. throws出如今方法函数头;而throw出如今函数体。
  2. throws表示出现异常的一种可能性,并不必定会发生这些异常;throw则是抛出了异常,执行throw则必定抛出了某种异常。
  3. 二者都是消极处理异常的方式(这里的消极并非说这种方式很差),只是抛出或者可能抛出异常,可是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

.class 文件是什么类型文件

class文件是一种8位字节的二进制流文件

java中序列化之子类继承父类序列化

父类实现了Serializable,子类不须要实现Serializable

相关注意事项 1. 序列化时,只对对象的状态进行保存,而无论对象的方法; 2. 当一个父类实现序列化,子类自动实现序列化,不须要显式实现Serializable接口; c)当一个对象的实例变量引用其余对象,序列化该对象时也把引用对象进行序列化; 3. 并不是全部的对象均可以序列化,至于为何不能够,有不少缘由了,好比: 1.安全方面的缘由,好比一个对象拥有private,public等field,对于一个要传输的对象,好比写到文件,或者进行rmi传输等等,在序列化进行传输的过程当中,这个对象的private等域是不受保护的。 2. 资源分配方面的缘由,好比socket,thread类,若是能够序列化,进行传输或者保存,也没法对他们进行从新的资源分配,并且,也是没有必要这样实现。

2,反过来父类未实现Serializable,子类实现了,序列化子类实例的时候,父类的属性是直接被跳过不保存,仍是能保存但不能还原?(答案:值不保存)

解:父类实现接口后,全部派生类的属性都会被序列化。子类实现接口的话,父类的属性值丢失。

java中序列化之子类继承父类序列化

标识符

标识符能够包括这4种字符:字母、下划线、$、数字;开头不能是数字;不能是关键字

Integer i=new Integer(127);和Integer i=127;的区别

Integer i = 127的时候,使用Java常量池技术,是为了方便快捷地建立某些对象,当你须要一个对象时候,就去这个池子里面找,找不到就在池子里面建立一个。可是必须注意 若是对象是用new 建立的。那么无论是什么对像,它是不会放到池子里的,而是向堆申请新的空间存储。Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127之间的数时才可以使用对象池。超过了就要申请空间建立对象了

int i1=128;
    Integer i2=128;
    Integer i3=new Integer(128);//自动拆箱
    
    System.out.println(i1==i2);//true
    System.out.println(i1==i3);//true
    
    Integer i5=127;
    Integer i6=127;
    System.out.println(i5==i6);//true
    
    
    Integer i5=127;
    Integer ii5=new Integer(127);
    System.out.println(i5==ii5);//false
    
    Integer i7=new Integer(127);
    Integer i8=new Integer(127);
    System.out.println(i7==i8);//false
复制代码

手写单例模式

最好的单例模式是静态内部类,不要写双重检验

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}
复制代码

为何线程通讯的方法wait(), notify()和notifyAll()被定义在Object类里?

Java的每一个对象中都有一个锁(monitor,也能够成为监视器) 而且wait(),notify()等方法用于等待对象的锁或者通知其余线程对象的监视器可用。在Java的线程中并无可供任何对象使用的锁和同步器。这就是为何这些方法是Object类的一部分,这样Java的每个类都有用于线程间通讯的基本方法

Java中wait 和sleep 方法比较

  1. 这两个方法来自不一样的类分别是Thread和Object

  2. 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其余线程能够使用同步控制块或者方法。

  3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep能够在任何地方使用(使用范围)

  4. sleep必须捕获异常,而wait,notify和notifyAll不须要捕获异常

  5. sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待必定的时间以后,自动醒来进入到可运行状态,不会立刻进入运行状态,由于线程调度机制恢复线程的运行也须要时间,一个线程对象调用了sleep方法以后,并不会释放他所持有的全部对象锁,因此也就不会影响其余进程对象的运行。但在sleep的过程当中过程当中有可能被其余对象调用它的interrupt(),产生InterruptedException异常,若是你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,若是你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及之后的代码。

  • 注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,经过t.sleep()让t对象进入sleep,这样的作法是错误的,它只会是使当前线程被sleep 而不是t线程
  1. wait属于Object的成员方法,一旦一个对象调用了wait方法,必需要采用notify()和notifyAll()方法唤醒该进程;若是线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的全部同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也一样会在wait的过程当中有可能被其余对象调用interrupt()方法而产生

hashCode和equals方法的关系

在有些状况下,程序设计者在设计一个类的时候为须要重写equals方法,好比String类,可是千万要注意,在重写equals方法的同时,必须重写hashCode方法。 也就是说对于两个对象,若是调用equals方法获得的结果为true,则两个对象的hashcode值一定相等; 若是equals方法获得的结果为false,则两个对象的hashcode值不必定不一样; 若是两个对象的hashcode值不等,则equals方法获得的结果一定为false; 若是两个对象的hashcode值相等,则equals方法获得的结果未知。

Object类中有哪些方法,列举3个以上(能够引导)

Object方法:equals()、toString()、finalize()、hashCode()、getClass()、clone()、wait()、notify()、notifyAll()

String s=new String("xyz")究竟建立String Object分为两种状况:

  1. 若是String常理池中,已经建立"xyz",则不会继续建立,此时只建立了一个对象new String("xyz");
  2. 若是String常理池中,没有建立"xyz",则会建立两个对象,一个对象的值是"xyz",一个对象new String("xyz")。

什么是值传递和引用传递

值传递

public class TempTest {

  private void test1(int a) {
    a = 5;
    System.out.println("test1方法中的a=" + a);
  }

  public static void main(String[] args) {
    TempTest t = new TempTest();
    int a = 3;
    t.test1(11);
    System.out.println("main方法中a=" + a);
  }

}
复制代码

test1方法中的a=5 main方法中a=3 值传递:传递的是值的拷贝,传递后就互不相关了 引用传递:传递的是变量所对应的内存空间的地址

public class TempTest {
  private void test1(A a) {
    a.age = 20;
    System.out.println("test1方法中a=" + a.age);
  }

  public static void main(String[] args) {
    TempTest t = new TempTest();
    A a = new A();
    a.age = 10;
    t.test1(a);
    System.out.println("main方法中a=" + a.age);
  }
}

class A {
  public int age = 0;
}
复制代码

test1方法中a=20 main方法中a=20 传递前和传递后都指向同一个引用(同一个内存空间) 若是不互相影响,方法是在test1方法里面新new一个实例就能够了

讲一下netty

netty经过Reactor模型基于多路复用器接收并处理用户请求,内部实现了两个线程池,boss线程和work线程池,其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件

Nio的原理(同步非阻塞)

服务端和客户端各自维护一个管理通道的对象,咱们称之为 selector,该对 象能检测一个或多个通道(channel)上的事件。咱们以服务端为例,若是服务 端的 selector 上注册了读事件,某时刻客户端给服务端送了一些数据,阻塞 I/O 这时会调用 read()方法阻塞地读取数据,而 NIO 的服务端会在 selector 中添加 一个读事件。服务端的处理线程会轮询地访问 selector,若是访问 selector 时发 现有感兴趣的事件到达,则处理这些事件,若是没有感兴趣的事件到达,则处 理线程会一直阻塞直到感兴趣的事件到达为止。

这里写图片描述

缓冲区Buffer、通道Channel、选择器Selector

缓冲区Buffer

  • 缓冲区其实是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,全部数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任什么时候候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,全部数据都是直接写入或者直接将数据读取到Stream对象中。

通道Channel

  • 通道是一个对象,经过它能够读取和写入数据,固然了全部数据都经过Buffer对象来处理。咱们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。一样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。通道与流的不一样之处在于 通道是双向 的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类,好比 InputStream 只能进行读取操做,OutputStream 只能进行写操做),而通道是双向的,能够用于读、写或者同时用于读写。

选择器(Selector )

  • NIO 有一个主要的类 Selector,这个相似一个观察者,只要咱们把须要探知 的 socketchannel 告诉 Selector,咱们接着作别的事情, 当有事件发生时,他会 通知咱们,传回一组 SelectionKey, 咱们读取这些 Key, 就会得到咱们刚刚注册 过的 socketchannel, 而后,咱们从这个 Channel 中读取数据,放心,包准能 够读到,接着咱们能够处理这些数据。
  • Selector 内部原理实际是在作一个 对所注册的 channel 的轮询访问,不断 地轮询,一旦轮询到一个 channel 有所注册的事情发生,好比数据来了,他就 会站起来报告, 交出一把钥匙,让咱们 经过这把钥匙来读取这个 channel 的内 容。

BIO和NIO的区别

  1. BIO:同步阻塞式IO,服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就须要启动一个线程进行处理,若是这个链接不作任何事情会形成没必要要的线程开销,固然能够经过线程池机制改善。
  2. NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。

NIO的selector做用

Selector(选择器)是Java NIO中可以检测一到多个NIO通道,并可以知晓通道是否为诸如读写事件作好准备的组件。这样,一个单独的线程能够管理多个channel,从而管理多个网络链接。

为了实现Selector管理多个SocketChannel,必须将具体的SocketChannel对象注册到Selector,并声明须要监听的事件(这样Selector才知道须要记录什么数据),一共有4种事件:

  1. connect:客户端链接服务端事件,对应值为SelectionKey.OP_CONNECT(8)
  2. accept:服务端接收客户端链接事件,对应值为SelectionKey.OP_ACCEPT(16)
  3. read:读事件,对应值为SelectionKey.OP_READ(1)
  4. write:写事件,对应值为SelectionKey.OP_WRITE(4)

每次请求到达服务器,都是从connect开始,connect成功后,服务端开始准备accept,准备就绪,开始读数据,并处理,最后写回数据返回。

因此,当SocketChannel有对应的事件发生时,Selector均可以观察到,并进行相应的处理。

计算机网络

GET 和 POST 区别

(GET) 请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的: /test/demo_form.asp?name1=value1&name2=value2

  1. GET 请求可被缓存

  2. GET 请求保留在浏览器历史记录中

  3. GET 请求可被收藏为书签

  4. GET 请求不该在处理敏感数据时使用

  5. GET 请求有长度限制

  6. GET 请求只应当用于取回数据 POST 方法 (POST) 请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的: POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com name1=value1&name2=value2

  7. POST 请求不会被缓存

  8. POST 请求不会保留在浏览器历史记录中

  9. POST 不能被收藏为书签

  10. POST 请求对数据长度没有要求

dns使用的协议

既使用TCP又使用UDP

  • 首先了解一下TCP与UDP传送字节的长度限制:
  1. UDP报文的最大长度为512字节,而TCP则容许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。一般传统的UDP报文通常不会大于512字节。
  • 区域传送时使用TCP,主要有一下两点考虑:
  1. 辅域名服务器会定时(通常时3小时)向主域名服务器进行查询以便了解数据是否有变更。若有变更,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,由于数据同步传送的数据量比一个请求和应答的数据量要多得多。
  2. TCP是一种可靠的链接,保证了数据的准确性。
  • 域名解析时使用UDP协议:
  1. 客户端向DNS服务器查询域名,通常返回的内容都不超过512字节,用UDP传输便可。不用通过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也能够指定向DNS服务器查询的时候使用TCP,但事实上,不少DNS服务器进行配置的时候,仅支持UDP查询包。

幂等

一个幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指能够使用相同参数重复执行,并能得到相同结果的函数。这些函数不会影响系统状态,也不用担忧重复执行会对系统形成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数.

Cookies和session区别

  1. Cookies是一种可以让网站服务器把少许数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术。Cookies是当你浏览某网站时,由Web服务器置于你硬盘上的一个很是小的文本文件,它能够记录你的用户ID、密码、浏览过的网页、停留的时间等信息。 session: 当用户请求来自应用程序的 Web 页时,若是该用户尚未会话,则 Web 服务器将自动建立一个 Session 对象。当会话过时或被放弃后,服务器将终止该会话。 cookie机制:采用的是在客户端保持状态的方案,而session机制采用的是在服务端保持状态的方案。同时咱们看到因为服务器端保持状态的方案在客户端也须要保存一个标识,因此session机制可能须要借助cookie机制来达到保存标识的目的。

  2. Session是服务器用来跟踪用户的一种手段,每一个Session都有一个惟一标识:session ID。当服务器建立了Session时,给客户端发送的响应报文包含了Set-cookie字段,其中有一个名为sid的键值对,这个键值Session ID。客户端收到后就把Cookie保存浏览器,而且以后发送的请求报表都包含SessionID。HTTP就是经过Session和Cookie这两个发送一块儿合做来实现跟踪用户状态,Session用于服务端,Cookie用于客户端

TCP粘包和拆包产生的缘由

  1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
  2. 进行MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。因此MSS并非TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度
  3. 以太网的payload大于MTU进行IP分片。MTU指:一种通讯协议的某一层上面所能经过的最大数据包大小。若是IP层有一个数据包要传,并且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分红托干片,让每一片都不超过MTU。注意,IP分片能够发生在原始发送端主机上,也能够发生在中间路由器上。

TCP粘包和拆包的解决策略

  1. 消息定长。例如100字节。
  2. 在包尾部增长回车或者空格符等特殊字符进行分割,典型的如FTP协议
  3. 将消息分为消息头和消息尾。
  4. 其它复杂的协议,如RTMP协议等。

三次握手

第一次握手:创建链接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时本身也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据

四次挥手

  1. 客户端先发送FIN,进入FIN_WAIT1状态
  2. 服务端收到FIN,发送ACK,进入CLOSE_WAIT状态,客户端收到这个ACK,进入FIN_WAIT2状态
  3. 服务端发送FIN,进入LAST_ACK状态
  4. 客户端收到FIN,发送ACK,进入TIME_WAIT状态,服务端收到ACK,进入CLOSE状态

TIME_WAIT的状态就是主动断开的一方(这里是客户端),发送完最后一次ACK以后进入的状态。而且持续时间还挺长的。客户端TIME_WAIT持续2倍MSL时长,在linux体系中大概是60s,转换成CLOSE状态

TIME_WAIT

TIME_WAIT 是主动关闭连接时造成的,等待2MSL时间,约4分钟。主要是防止最后一个ACK丢失。 因为TIME_WAIT 的时间会很是长,所以server端应尽可能减小主动关闭链接

CLOSE_WAIT

CLOSE_WAIT是被动关闭链接是造成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,所以进入CLOSE_WAIT状态。但若是服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在不少CLOSE_WAIT状态的链接。此时,多是系统忙于处理读、写操做,而未将已收到FIN的链接,进行close。此时,recv/read已收到FIN的链接socket,会返回0。

为何须要 TIME_WAIT 状态?

假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以即可以重发最终的ACK,不然会发送RST,结果server认为发生错误。TCP实现必须可靠地终止链接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,由于client可能面 临重发最终ACK的情形。

为何 TIME_WAIT 状态须要保持 2MSL 这么长的时间?

若是 TIME_WAIT 状态保持时间不足够长(好比小于2MSL),第一个链接就正常终止了。第二个拥有相同相关五元组的链接出现,而第一个链接的重复报文到达,干扰了第二个链接。TCP实现必须防止某个链接的重复报文在链接终止后出现,因此让TIME_WAIT状态保持时间足够长(2MSL),链接相应方向上的TCP报文要么彻底响应完毕,要么被 丢弃。创建第二个链接的时候,不会混淆。

TIME_WAIT 和CLOSE_WAIT状态socket过多

若是服务器出了异常,百分之八九十都是下面两种状况:

1.服务器保持了大量TIME_WAIT状态

2.服务器保持了大量CLOSE_WAIT状态,简单来讲CLOSE_WAIT数目过大是因为被动关闭链接处理不当致使的。

一次完整的HTTP请求过程

域名解析 --> 发起TCP的3次握手 --> 创建TCP链接后发起http请求 --> 服务器响应http请求,浏览器获得html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户

讲一下长链接

  • 1、基于http协议的长链接
  1. 在HTTP1.0和HTTP1.1协议中都有对长链接的支持。其中HTTP1.0须要在request中增长”Connection: keep-alive“ header才可以支持,而HTTP1.1默认支持.
  • http1.0请求与服务端的交互过程:
  1. 客户端发出带有包含一个header:”Connection: keep-alive“的请求
  2. 服务端接收到这个请求后,根据http1.0和”Connection: keep-alive“判断出这是一个长链接,就会在response的header中也增长”Connection: keep-alive“,同是不会关闭已创建的tcp链接.
  3. 客户端收到服务端的response后,发现其中包含”Connection: keep-alive“,就认为是一个长链接,不关闭这个链接。并用该链接再发送request.转到a)
  • 2、发心跳包。每隔几秒就发一个数据包过去

TCP如何保证可靠传输?

  1. 三次握手。
  2. 将数据截断为合理的长度。应用数据被分割成 TCP 认为最适合发送的数据块(按字节编号,合理分片)
  3. 超时重发。当 TCP 发出一个段后,它启动一个定时器,若是不 能及时收到一个确认就重发
  4. 对于收到的请求,给出确认响应
  5. 校验出包有错,丢弃报文段,不给出响应
  6. 对失序数据进行从新排序,而后才交给应用层
  7. 对于重复数据 , 可以丢弃重复数据
  8. 流量控制。TCP 链接的每一方都有固定大小的缓冲空间。TCP 的接收端 只容许另外一端发送接收端缓冲区所能接纳的数据。这将防止较快主机导致较慢主机的缓冲 区溢出。
  9. 拥塞控制。当网络拥塞时,减小数据的发送。

详细介绍http

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

特色

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法经常使用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不一样。因为HTTP协议简单,使得HTTP服务器的程序规模小,于是通讯速度很快。

  2. 灵活:HTTP容许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

  3. 无链接:无链接的含义是限制每次链接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开链接。采用这种方式能够节省传输时间。

  4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺乏状态意味着若是后续处理须要前面的信息,则它必须重传,这样可能致使每次链接传送的数据量增大。另外一方面,在服务器不须要先前信息时它的应答就较快。

  5. 支持B/S及C/S模式。

请求消息Request

  1. 请求行,用来讲明请求类型,要访问的资源以及所使用的HTTP版本.
  2. 请求头部,紧接着请求行(即第一行)以后的部分,用来讲明服务器要使用的附加信息 从第二行起为请求头部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,而且在每一个请求中自动发送等等
  3. 空行,请求头部后面的空行是必须的
  4. 请求数据也叫主体,能够添加任意的其余数据。

响应消息Response

  1. 状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
  2. 消息报头,用来讲明客户端要使用的一些附加信息
  3. 空行,消息报头后面的空行是必须的
  4. 响应正文,服务器返回给客户端的文本信息。

状态码

  • 200 OK //客户端请求成功
  • 301 Moved Permanently //永久重定向,使用域名跳转
  • 302 Found // 临时重定向,未登录的用户访问用户中心重定向到登陆页面
  • 400 Bad Request //客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized //请求未经受权,这个状态代码必须和WWW-Authenticate报头域一块儿使用
  • 403 Forbidden //服务器收到请求,可是拒绝提供服务
  • 404 Not Found //请求资源不存在,eg:输入了错误的URL
  • 500 Internal Server Error //服务器发生不可预期的错误
  • 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

http的方法

  1. get:客户端向服务端发起请求,得到资源。请求得到URL处所在的资源。
  2. post:向服务端提交新的请求字段。请求URL的资源后添加新的数据。
  3. head:请求获取URL资源的响应报告,即得到URL资源的头部
  4. patch:请求局部修改URL所在资源的数据项
  5. put:请求修改URL所在资源的数据元素。
  6. delete:请求删除url资源的数据

URI和URL的区别

URI,是uniform resource identifier,统一资源标识符,用来惟一的标识一个资源。 Web上可用的每种资源如HTML文档、图像、视频片断、程序等都是一个来URI来定位的

URI通常由三部组成:

  1. 访问资源的命名机制
  2. 存放资源的主机名
  3. 资源自身的名称,由路径表示,着重强调于资源。

URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL能够用来标识一个资源,并且还指明了如何locate这个资源。 URL是Internet上用来描述信息资源的字符串,主要用在各类WWW客户程序和服务器程序上,特别是著名的Mosaic。 采用URL能够用一种统一的格式来描述各类信息资源,包括文件、服务器的地址和目录等。

URL通常由三部组成:

  1. 协议(或称为服务方式)
  2. 存有该资源的主机IP地址(有时也包括端口号)
  3. 主机资源的具体地址。如目录和文件名等

HTTPS和HTTP的区别

  1. https协议须要到CA申请证书,通常免费证书不多,须要交费。
  2. http是超文本传输协议,信息是明文传输;https 则是具备安全性的ssl加密传输协 议。
  3. http和https使用的是彻底不一样的链接方式,用的端口也不同,前者是80,后者是443。
  4. http的链接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
  5. http默认使用80端口,https默认使用443端口

https是如何保证数据传输的安全

https实际就是在TCP层与http层之间加入了SSL/TLS来为上层的安全保驾护航,主要用到对称加密、非对称加密、证书,等技术进行客户端与服务器的数据加密传输,最终达到保证整个通讯的安全性。

  • SSL/TLS协议做用:
  1. 认证用户和服务器,确保数据发送到正确的客户机和服务器;
  2. 加密数据以防止数据中途被窃取;
  3. 维护数据的完整性,确保数据在传输过程当中不被改变。
    这里写图片描述

数据结构与算法

动态规划的思想

动态规划过程是:每次决策依赖于当前状态,又随即引发状态的转移。一个决策序列就是在变化的状态中产生出来的,因此,这种多阶段最优化决策解决问题的过程就称为动态规划。

快速排序的思想

在数组中找到一个基准数(pivot) 分区,将数组中比基准数大的放到它的右边,比基准数小的放到它的左边 继续对左右区间重复第二步,直到各个区间只有一个数,这时候,数组也就有序了。

快速排序算法是不稳定的算法

27 23 27 3 以第一个27做为pivot中心点,则27与后面那个3交换,造成 3 23 27 27,排序通过一次结束,但最后那个27在排序之初先于初始位置3那个27,因此不稳定。

堆排序的思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。 其基本思想为(大顶堆): 1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区; 2)将堆顶元素R[1]与最后一个元素R[n]交换,此时获得新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且知足R[1,2...n-1]<=R[n]; 3)因为交换后新的堆顶R[1]可能违反堆的性质,所以须要对当前无序区(R1,R2,......Rn-1)调整为新堆,而后再次将R[1]与无序区最后一个元素交换,获得新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

字典树

字典树主要有以下三点性质:

  1. 根节点不包含字符,除根节点意外每一个节点只包含一个字符。
  2. 从根节点到某一个节点,路径上通过的字符链接起来,为该节点对应的字符串。
  3. 每一个节点的全部子节点包含的字符串不相同。

链表反转

  1. 首先让头节点与第一个元素节点断开,可是要注意在断开以前须要用p指针指向第一个元素节点来保存第一个元素节点的位置,而后再断开。在这里有一个指针q指向一个指针域为空的节点,这个节点用来作为链表反转后的最后一个节点。
  2. 让第二个元素节点的指针从指向第三个元素节点变为指向第一个元素节点,以此类推,直至指针p指向原链表最后一个元素。
  3. p指针指向NULL时,让原头节点的指针域指向原来最后一个元素节点。此时链表倒置已完成。
linkList reverse(linkList head){
  linkList p,q,pr;
  p = head->next;
  q = NULL;
  head->next = NULL;
  while(p){
    pr = p->next;
    p->next = q;
    q = p;
    p = pr;
  }
  head->next = q;
  return head;
}
复制代码

操做系统/Linux

进程有哪些状态

通常来讲,进程有三个状态,即就绪状态,运行状态,阻塞状态。

  1. 运行态:进程占用CPU,并在CPU上运行
  2. 就绪态:进程已经具有运行条件,可是CPU尚未分配过来
  3. 阻塞态:进程因等待某件事发生而暂时不能运行
    这里写图片描述

固然理论上上述三种状态之间转换分为六种状况;

  1. 运行——>就绪:1,主要是进程占用CPU的时间过长,而系统分配给该进程占用CPU的时间是有限的;2,在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行时,该进程就被迫让出CPU,该进程便由执行状态转变为就绪状态。
  2. 就绪——>运行:运行的进程的时间片用完,调度就转到就绪队列中选择合适的进程分配CPU
  3. 运行——>阻塞:正在执行的进程因发生某等待事件而没法执行,则进程由执行状态变为阻塞状态,如发生了I/O请求
  4. 阻塞——>就绪:进程所等待的事件已经发生,就进入就绪队列

如下两种状态是不可能发生的:

  1. 阻塞——>运行:即便给阻塞进程分配CPU,也没法执行,操做系统在进行调度时不会从阻塞队列进行挑选,而是从就绪队列中选取

  2. 就绪——>阻塞:就绪态根本就没有执行,谈不上进入阻塞态。

进程间通讯方式

  1. 管道pipe:管道是一种半双工的通讯方式,数据只能单向流动,并且只能在具备亲缘关系的进程间使用。进程的亲缘关系一般是指父子进程关系。
  2. 命名管道FIFO:有名管道也是半双工的通讯方式,可是它容许无亲缘关系进程间的通讯。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享存储SharedMemory:共享内存就是映射一段能被其余进程所访问的内存,这段共享内存由一个进程建立,但多个进程均可以访问。共享内存是最快的 IPC 方式,它是针对其余进程间通讯方式运行效率低而专门设计的。它每每与其余通讯机制,如信号两,配合使用,来实现进程间的同步和通讯。
  5. 信号量Semaphore:信号量是一个计数器,能够用来控制多个进程对共享资源的访问。它常做为一种锁机制,防止某进程正在访问共享资源时,其余进程也访问该资源。所以,主要做为进程间以及同一进程内不一样线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通讯机制,与其余通讯机制不一样的是,它可用于不一样及其间的进程通讯。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通讯方式,用于通知接收进程某个事件已经发生。

Linux中软连接和硬连接的区别

ln -s source dist # 创建软链接 ln source dist # 创建硬链接 创建硬连接时,连接文件和被连接文件必须位于同一个文件系统中,而且不能创建指向目录的硬连接

  1. 硬链接就像一个文件有多个文件名,
  2. 软链接就是产生一个新文件(这个文件内容,实际上就是记当要连接原文件路径的信息),这个文件指向另外一个文件的位置

I/O多路复用

单个线程,经过记录跟踪每一个I/O流(sock)的状态,来同时管理多个I/O流 。尽可能多的提升服务器的吞吐能力

select, poll, epoll 都是I/O多路复用的具体的实现

设计模式

设计模式主要分三个类型:建立型、结构型和行为型。

  1. 建立型有:
  • Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点

  • Abstract Factory,抽象工厂:提供一个建立一系列相关或相互依赖对象的接口,而无须指定它们的具体类。

  • Factory Method,工厂方法:定义一个用于建立对象的接口,让子类决定实例化哪个类,Factory Method使一个类的实例化延迟到了子类。

  • Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得一样的构建过程能够建立不一样的表示。

  • Prototype,原型模式:用原型实例指定建立对象的种类,而且经过拷贝这些原型来建立新的对象。

  1. 行为型有:
  • Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不须要暴露该对象的内部表示。
  • Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都获得通知自动更新。
  • Template Method,模板方法:定义一个操做中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类能够不改变一个算法的结构便可以重定义该算法得某些特定步骤。
  • Command,命令模式:将一个请求封装为一个对象,从而使你能够用不一样的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操做。
  • State,状态模式:容许对象在其内部状态改变时改变他的行为。对象看起来彷佛改变了他的类。
  • Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们能够互相替换,本模式使得算法能够独立于使用它们的客户。
  • China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
  • Mediator,中介者模式:用一个中介对象封装一些列的对象交互。
  • Visitor,访问者模式:表示一个做用于某对象结构中的各元素的操做,它使你能够在不改变各元素类的前提下定义做用于这个元素的新操做。
  • Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
  • Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象以外保存这个状态。
  1. 结构型有:
  • Composite,组合模式:将对象组合成树形结构以表示部分总体的关系,Composite使得用户对单个对象和组合对象的使用具备一致性。
  • Facade,外观模式:为子系统中的一组接口提供一致的界面,fa?ade提供了一高层接口,这个接口使得子系统更容易使用。
  • Proxy,代理模式:为其余对象提供一种代理以控制对这个对象的访问
  • Adapter,适配器模式:将一类的接口转换成客户但愿的另一个接口,Adapter模式使得本来因为接口不兼容而不能一块儿工做那些类能够一块儿工做。
  • Decrator,装饰模式:动态地给一个对象增长一些额外的职责,就增长的功能来讲,Decorator模式相比生成子类更加灵活。
  • Bridge,桥模式:将抽象部分与它的实现部分相分离,使他们能够独立的变化。
  • Flyweight,享元模式

动态代理和静态代理有什么区别

静态代理

这种代理方式须要代理对象和目标对象实现同样的接口。在程序运行前,代理类的.class文件就已经存在了。

优势:

  1. 能够在不修改目标对象的前提下扩展目标对象的功能。

缺点:

  1. 冗余。因为代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  2. 不易维护。一旦接口增长方法,目标对象与代理对象都要进行修改。

动态代理

动态代理利用了JDK API,运用反射机制动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必需要实现接口,经过Proxy里的newProxyInstance获得代理对象

优势:

  1. 动态代理对象不须要实现接口,可是要求目标对象必须实现接口,不然不能使用动态代理。

静态代理与动态代理的区别主要

  1. 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
  3. 静态代理一般只代理一个类,动态代理是代理一个接口下的多个实现类。
  4. 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。

JDK中的动态代理和CGLIB

动态代理

  1. JDK中的动态代理: 经过反射类Proxy以及InvocationHandler回调接口实现的,

  2. 动态代理缺点: JDK中所要进行动态代理的类必需要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具备必定的局限性,并且使用反射的效率也并非很高。

CGLIB

  1. CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的全部不是final的方法。在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

  2. CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,由于它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

  3. CGLIB优势:它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。一般能够使用Java的动态代理建立代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

  4. CGLIB缺点:对于final方法,没法进行代理

场景题和设计题

场景题:设计判断论文抄袭的系统

  1. 一类是基于字符串比较的方法;另外一类是基于词频统计的方法。
  2. 基于字符串比较的方法也称为数字指纹法,这类方法经过某种选取策略在文档中取一些字符串做为“指纹”,把指纹映射到Hash 表中,最后统计Hash
  3. 表中相同的指纹数目或者比率,做为文本类似度依据。
  4. 基于词频统计的方法也称为基于语义的方法。词频统计法源于信息检索技术中的向量空间模型,该类方法首先都要统计每篇文档中各个单词的出现次数,而后根据单词频度构成文档特征向量,最后采用点积、余弦或者相似方式度量两篇文档的特征向量,以此做为文档类似度的依据。

设计一个即时聊天的系统

  1. 用户经过客户端进入系统,向服务器发出消息,请求登录。
  2. 服务器收到请求后,向客户端返回应答消息,表示赞成接受该用户加入,并顺带将本身服务线程所在的监听端口号告诉用户。
  3. 客户端按照服务器应答中给出的端口号与服务器创建稳定的链接。
  4. 服务器经过该链接将当前在线用户的列表信息传给新加入的客户端。
  5. 客户端得到了在线用户列表,就能够独立自主地与在线的其余用户通讯了。
  6. 当用户退出系统时要及时地通知服务器。

分布式系统事务一致性解决方案

分布式系统事务一致性解决方案 MQ(事务消息)

举个例子,Bob向Smith转帐,那咱们究竟是先发送消息,仍是先执行扣款操做?

好像均可能会出问题。若是先发消息,扣款操做失败,那么Smith的帐户里面会多出一笔钱。反过来,若是先执行扣款操做,后发送消息,那有可能扣款成功了可是消息没发出去,Smith收不到钱。除了上面介绍的经过异常捕获和回滚的方式外,还有没有其余的思路呢?

下面以阿里巴巴的RocketMQ中间件为例,分析下其设计和实现思路。

RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段经过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,若是确认消息发送失败了怎么办?RocketMQ会按期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱究竟是减了仍是没减呢?若是减了是回滚仍是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚仍是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。以下图:

这里写图片描述

设计高并发的系统?

  1. HTML 页面静态化 访问频率较高但内容变更较小,使用网站 HTML 静态化方案来优化访问速度。将社区 内的帖子、文章进行实时的静态化,有更新的时候再从新静态化也是大量使用的策略。 优点: 1、减轻服务器负担。 2、加快页面打开速度, 静态页面无需 访问 数据库,打开速度较动态页面有明显提升; 3、不少搜索引擎都会优先收录静态页面,不只被收录的快,还收录的全,容易被搜 索引擎找到; 4、HTML 静态页面不会受程序相关漏洞的影响,减小攻击 ,提升安全性。
  2. 图片服务器和应用服务器相分离 如今不少的网站上都会用到大量的图片,而图片是网页传输中占主要的数据量,也是影 响网站性能的主要因素。所以不少网站都会将图片存储从网站中分离出来,另外架构一个 或多个服务器来存储图片,将图片放到一个虚拟目录中,而网页上的图片都用一个 URL 地 址来指向这些服务器上的图片的地址,这样的话网站的性能就明显提升了。 优点: 1、分担 Web 服务器的 I/O 负载-将耗费资源的图片服务分离出来,提升服务器的性能 和稳定性。 2、 可以专门对图片服务器进行优化-为图片服务设置有针对性的 缓存方案,减小带宽 成本,提升访问速度。 3、 提升网站的可扩展性-经过增长图片服务器,提升图片吞吐能力。
  3. 数据库 见“数据库部分的---若是有一个特别大的访问量到数据库上,怎么作优化?”。
  4. 缓存 尽可能使用缓存,包括用户缓存,信息缓存等,多花点内存来作缓存,能够大量减小与 数据库的交互,提升性能。 假如咱们能减小数据库频繁的访问,那对系统确定大大有利的。好比一个电子商务系 统的商品搜索,若是某个关键字的商品常常被搜,那就能够考虑这部分商品列表存放到缓 存(内存中去),这样不用每次访问数据库,性能大大增长。
  5. 镜像 镜像是冗余的一种类型,一个磁盘上的数据在另外一个磁盘上存在一个彻底相同的副本 即为镜像。
  6. 负载均衡 在网站高并发访问的场景下,使用负载均衡技术(负载均衡服务器)为一个应用构建 一个由多台服务器组成的服务器集群,将并发访问请求分发到多台服务器上处理,避免单 一服务器因负载压力过大而响应缓慢,使用户请求具备更好的响应延迟特性。
  7. 并发控制 加锁,如乐观锁和悲观锁。
  8. 消息队列 经过 mq 一个一个排队方式,跟 12306 同样。

设计高负载的系统

  1. 应用无状态
  2. 有效使用缓存
  3. 应用拆分
  4. 数据库拆分
  5. 异步通讯
  6. 非结构化数据存储 ( TFS,NOSQL)
  7. 监控、预警系统
  8. 配置统一管理

订票系统,某车次只有一张火车票,假定有 1w 我的同时打开 12306 网站来订票,如何解决并发问题?(可扩展到任何高并发网站要考虑的 并发读写问题

使用乐观锁,乐观锁意思是不锁定表的状况下,利用业务的控制来解决并发问题,这样既保证数据的并发 可读性 ,又保证保存数据的 排他性,保证性能的同时解决了并发带来 的脏数据问题。hibernate 中实现乐观锁。(乐观锁,使用版本标识来肯定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时能够采起丢弃和再次尝试的策略。)

分布式与集群的区别是什么

分布式:一个业务分拆多个子业务,部署在不一样的服务器上 集群:同一个业务,部署在多个服务器上

实时展示热门文章,好比近8小时点击量最大的文章前100名

  1. 数据接收
  • 客户端会为了减轻服务器的压力而选择延迟合并点击请求进行批量发送
  • 服务器确定会有多台机器多进程部署来接受点击请求,接收到的请求在进行参数解析后,被发送到存储单元。为了减轻存储的压力,每一个进程可能会使用小窗口聚合数据,每隔一小段时间将窗口内的数据聚合起来一块儿发给存储单元。
  1. 数据存储
  • 使用kafka存,ZeroCopy机制并发量很高,数据持久化在磁盘里成本低。不过kafka的数据通常是有过时时间的,若是想彻底记住用户的点击以便作长期的数据分析,须要要使用hdfs了
  1. 分布式TopN算法
  • 用户太多,用户表按用户ID哈希分红了1024张子表。用户表里有一个字段score,表示这个用户的积分数。如今咱们要计算前100名积分最多的用户以及积分数,该怎么查询?
  • 若是是单个表,一个SQL也就搞定了,
  • 若是是多个子表,你得在每一个子表上都进行一次TopN查询,而后聚合结果再作一次TopN查询。子表查询能够多线程并行,提升聚合效率。
  1. 滑动窗口
  • 8小时的滑动窗口,意味着新的数据源源不断的进来,旧的数据时时刻刻在淘汰,在业务能够差几分钟。
  • 咱们对时间片进行了切分,一分钟一个槽来进行计数,过时了8小时,移掉第一个,计算topn的帖子,维护窗口,移除过时的槽,而后统计topn,30s~60s调用一次
  1. 定时任务
  • 每一个子节点都会有一个定时任务去负责维持统计窗口,过时失效的统计数据,计算局部的topn热帖。
  • 如今每一个子节点都有了各自的局部topn热帖,那么还须要一个主节点去汇总这些局部热点,而后计算去全局热帖。
  1. 点击去重
  • 首先要从客户端下手,客户端自己能够过滤一部分无效点击。同一篇文章在过短的时间内被当前用户反复点击,这个模式仍是很好发现的。若是间隔时间比较长,那就是读者的回味点击,属于文章的正向反馈,应该记录下来
  • 服务器还须要防止用户的防刷行为。若是缺失防刷控制,能够经过这种漏洞来使得本身的文章非法得到大量点击,进入热门文章列表,打上热门标签,被海量的用户看到,就会得到较大的经济效益,即便这篇文章内容自己吸引力并不足够。

如何解决电商网站超卖现象

超卖是什么

  • 由于数据库底层的写操做和读操做能够同时进行,虽然写操做默认带有隐式锁(即对同一数据不能同时进行写操做)可是读操做默认是不带锁的,因此当用户1去修改库存的时候,用户2依然能够读到库存为1,致使两个用户同时减一次库存,因此出现了超卖现象。

解决方案

  1. 使用redis预减库存
  2. 当库存大于0,才能更新库存update sk_goods_seckill set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0
  3. 添加惟一索引,UNIQUE KEY u_uid_gid (user_id,goods_id) USING BTREE,防止同一用户同一商品下两次订单
  4. 乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是否是并发提交了,可是有个缺点就是只能是应用中控制,若是有跨应用修改同一条数据乐观锁就没办法了,这个时候能够考虑悲观锁。
  5. 悲观锁,就是直接在数据库层面将数据锁死,相似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其余线程将没法提交数据。
  6. 使用消息队列异步下单

mq异步调用失败,如何保证数据一致性?

  1. 按你的使用场景,推送数据必须得在数据建立事务成功以后执行,这里必须有个前后。你能够将推送这个操做异步执行,消息队列有一搬有ack机制,确保消息没丢失。这时候监听消息队列的程序会执行推送,若是推送成功作标记。若是推送失败也标记记录时间,也能够推到另外一个消息队列约定多少分钟重试。实在不行就完全标记失败,或者回滚以前建立的数据。这个才是最终一致性。
  2. 若是是并行的操做,就得使用消息队列的confirm机制了。

分布式锁的几种实现方式

  1. 基于数据库表
  • 要实现分布式锁,最简单的方式可能就是直接建立一张锁表,而后经过操做该表中的数据来实现了。

  • 咱们对method_name(方法名)作了惟一性约束,这里若是有多个请求同时提交到数据库的话,数据库会保证只有一个操做能够成功,那么咱们就能够认为操做成功的那个线程得到了该方法的锁,能够执行方法体内容。当方法执行完毕以后,想要释放锁的话就删除该记录

  1. 基于数据库排他锁
  • 能够经过数据库的排他锁来实现分布式锁
  • 在查询语句后面增长for update,数据库会在查询过程当中给数据库表增长排他锁(这里再多提一句,InnoDB引擎在加锁的时候,只有经过索引进行检索的时候才会使用行级锁,不然会使用表级锁。这里咱们但愿使用行级锁,就要给method_name添加索引
  • 值得注意的是,这个索引必定要建立成惟一索引,不然会出现多个重载方法之间没法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁以后,其余线程没法再在该行记录上增长排他锁。
  1. 基于缓存
  • 好比Tair的put方法,redis的setnx方法等。而且,这些缓存服务也都提供了对数据的过时自动删除的支持,能够直接设置超时时间来控制锁的释放。
  1. 基于Zookeeper
  • 每一个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个惟一的瞬时有序节点。 判断是否获取锁的方式很简单,只须要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除便可。同时,其能够避免服务宕机致使的锁没法释放,而产生的死锁问题。

消息队列

四种MQ区别

这里写图片描述

如何保证消息队列是高可用的

  1. 集群,以rcoketMQ为例,有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式。

这里写图片描述
Producer 与 NameServer集群中的其中一个节点(随机选择)创建长链接,按期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 创建长链接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,可是 Consumer 则不同,它同时和提供 Topic 服务的 Master 和 Slave创建长链接,既能够从 Broker Master 订阅消息,也能够从 Broker Slave 订阅消息。

如何保证消息不被重复消费

缘由:

  • 消费者在消费后,会发送一个确认信息给消息队列,消息队列收到后会将该消息从消息队列中删除,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka每个消息都有一个offset,kafka消费过消息后,须要提交offset,让消息队列知道本身已经消费过了。由于网络传输等等故障,确认信息没有传送到消息队列,致使消息队列不知道本身已经消费过该消息了,再次将该消息分发给其余的消费者。

解决方法:

  1. 消息能够使用惟一id标识
  2. 若是拿到这个消息作数据库的insert操做。给这个消息作一个惟一主键,那么就算出现重复消费的状况,就会致使主键冲突,避免数据库出现脏数据。
  3. 若是拿到这个消息作redis的set的操做,不管set几回结果都是同样的,set操做是算幂等操做。
  4. 使用第三方介质作消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将< id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录便可。

流行框架

SpringMVC的工做原理

这里写图片描述
SpringMVC流程

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(能够根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(若是有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter通过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

请求 ---> DispatcherServlet(前端控制器)---> 调用HandlerMapping(处理器映射器)---> DispatcherServlet调用 HandlerAdapter(处理器适配器)---> 适配调用具体的Controller ---> 返回ModelAndView ---> 传给ViewReslover视图解析器 ---> 解析后返回具体View ---> 根据View进行渲染视图响应用户

MyBatis原理

MyBatis完成2件事情

  1. 封装JDBC操做
  2. 利用反射打通Java类与SQL语句之间的相互转换

MyBatis的主要成员

  1. Configuration MyBatis全部的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
  2. SqlSession 做为MyBatis工做的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
  3. Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  4. StatementHandler 封装了JDBC Statement操做,负责对JDBC statement 的操做,如设置参数等
  5. ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
  6. ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
  7. TypeHandler 负责java数据类型和jdbc数据类型(也能够说是数据表列类型)之间的映射和转换
  8. MappedStatement MappedStatement维护一条select|update|delete|insert节点的封装
  9. SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  10. BoundSql 表示动态生成的SQL语句以及相应的参数信息

这里写图片描述

MyBatis缓存

MyBatis提供查询缓存,用于减轻数据库压力,提升性能。MyBatis提供了一级缓存和二级缓存。

这里写图片描述

  1. 一级缓存是SqlSession级别的缓存,每一个SqlSession对象都有一个哈希表用于缓存数据,不一样SqlSession对象之间缓存不共享。同一个SqlSession对象对象执行2遍相同的SQL查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返回缓存结果便可。一级缓存是MyBatis内部实现的一个特性,用户不能配置,默认状况下自动支持的缓存,用户没有定制它的权利

  2. 二级缓存是Application应用级别的缓存,它的是生命周期很长,跟Application的声明周期同样,也就是说它的做用范围是整个Application应用。MyBatis默认是不开启二级缓存的,能够在配置文件中使用以下配置来开启二级缓存

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
复制代码

后记

以上对面试相关的知识点作了系统的整理,但愿对你们有所帮助。想要支持楼主的话,在 Github 上点个 Star,还有就是有些知识点是从网上优秀博客摘抄下来的,若是做者不但愿文章部份内容被转载,能够通知楼主,会及时处理,感谢。

相关文章
相关标签/搜索