知识点补充

1.浅复制和深复制有什么区别
在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误.
2.端口复用,一个端口可以被其他进程使用
①为什么要有这个端口复用呢
因为在服务端结束后,也就是第三次挥手的时候会有个等待释放时间,这个时间段大概是1-4分钟, 在这个时间内,端口不会迅速的被释放,所以可通过端口复用的方法来解决这个问题。
解决这个问题是使用setsockopt设置socket描述符的选项是SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符;
在server代码的socket()和bind()调用之间插入如下代码:
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
②用fork建立子进程,可以共用一个端口。
③linux 3.9以上内核支持SO_REUSEPORT选项,即允许多个socket bind/listen在同一个端口上。这样,多个进程就可以各自申请socket监听同一个端口,当数据来时,内核做负载均衡,唤醒监听的其中一个进程处理,用法类似于setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, &option, sizeof(option))
④nginx1.9之后,通过SO_REUSEPORT支持端口服用,该socket参数由操作系统提供,允许多个套接字侦听相同的IP地址和端口组合,内核负责调度。
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器

反向代理产生的背景:
在计算机世界里,由于单个服务器的处理客户端(用户)请求能力有一个极限,当用户的接入请求蜂拥而入时,会造成服务器忙不过来的局面,可以使用多个服务器来共同分担成千上万的用户请求,这些服务器提供相同的服务,对于用户来说,根本感觉不到任何差别。
反向代理服务的实现:
需要有一个负载均衡设备(即反向代理服务器)来分发用户请求,将用户请求分发到空闲的服务器上。
服务器返回自己的服务到负载均衡设备。
负载均衡设备将服务器的服务返回用户。

正向代理的过程隐藏了真实的请求客户端,服务器不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替请求。我们常说的代理也就是正向代理,正向代理代理的是请求方,也就是客户端;比如我们要访问youtube,可是不能访问,只能先安装个翻墙软件代你去访问,通过翻墙软件才能访问,翻墙软件就叫作正向代理。

反向代理:

反向代理的过程隐藏了真实的服务器,客户不知道真正提供服务的人是谁,客户端请求的服务都被代理服务器处理。反向代理代理的是响应方,也就是服务端;我们请求www.baidu.com时这www.baidu.com就是反向代理服务器,真实提供服务的服务器有很多台,反向代理服务器会把我们的请求分转发到真实提供服务的各台服务器。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。

https://www.cnblogs.com/jiangzhaowei/p/7927349.html

3.用户不设置主键会怎样
如果表没有主键的话会出现完全相同的记录,不利于查询.
主键的作用:
1)保证实体的完整性;
2)加快数据库的操作速度
3)在表中添加新记录时,数据库会自动检查新记录的主键值,不允许该值与其他记录的主键值重复。
4) 数据库自动按主键值的顺序显示表中的记录。如果没有定义主键,则按输入记录的顺序显示表中的记录。
主键不是非要不可,可以从:
1.是否满足业务要求
2.数据查询效率(主键可以提高查询效率,当然合理的索引替代也可以)
这两个角度权衡是否需要主键。

4,数据库的回表
是数据库根据索引(非主键)找到了指定的记录所在行后,还需要根据主键再次到数据块里获取数据。
覆盖索引 覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。 当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。 如,表covering_index_sample中有一个普通索引 idx_key1_key2(key1,key2)。当我们通过SQL语句:select key2 from covering_index_sample where key1 = ‘keytest’;的时候,就可以通过覆盖索引查询,无需回表。
5.B+ Tree索引和Hash索引区别
哈希索引适合等值查询,但是不无法进行范围查询 哈希索引没办法利用索引完成排序 哈希索引不支持多列联合索引的最左匹配规则 如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题
6.聚簇索引、覆盖索引
聚簇索引(innobe)的叶子节点就是数据节点 而非聚簇索引(myisam)的叶子节点仍然是索引文件 只是这个索引文件中包含指向对应数据块的指针
面试官:刚刚我们聊到B+ Tree ,那你知道B+ Tree的叶子节点都可以存哪些东西吗?
我:InnoDB的B+ Tree可能存储的是整行数据,也有可能是主键的值。
面试官:那这两者有什么区别吗? 我:(当他问我叶子节点的时候,其实我就猜到他可能要问我聚簇索引和非聚簇索引了)在 InnoDB 里,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引。
面试官:那么,聚簇索引和非聚簇索引,在查询数据的时候有区别吗?
我:聚簇索引查询会更快?
面试官:为什么呢?
我:因为主键索引树的叶子节点直接就是我们要查询的整行数据了。而非主键索引的叶子节点是主键的值,查到主键的值以后,还需要再通过主键的值再进行一次查询。
面试官:刚刚你提到主键索引查询只会查一次,而非主键索引需要回表查询多次。(后来我才知道,原来这个过程叫做回表)是所有情况都是这样的吗?非主键索引一定会查询多次吗?
我:(额、这个问题我回答的不好,后来我自己查资料才知道,通过覆盖索引也可以只查询一次)
科普时间——覆盖索引 覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。 当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。 如,表covering_index_sample中有一个普通索引 idx_key1_key2(key1,key2)。当我们通过SQL语句:select key2 from covering_index_sample where key1 = ‘keytest’;的时候,就可以通过覆盖索引查询,无需回表。如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+树如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,ID 的值为 500,再到 ID 索引树搜索一次。这个过程称回表也就是说,基于非主键索引的查询需要多扫描一棵索引树,因此,我们在应用中应该尽量使用主键查询。

对于 非聚簇索引 来说,每次通过索引检索到所需行号后,还需要通过叶子上的磁盘地址去磁盘内取数据(回行)消耗时间。为了优化这部分回行取数据时间,InnoDB 引擎采用了聚簇索引。
聚簇索引,即将数据存入索引叶子页面上。对于 InnoDB 引擎来说,叶子页面不再存该行对应的地址,而是直接存储数据:
在《数据库原理》一书中是这么解释聚簇索引和非聚簇索引的区别的:
聚簇索引的叶子节点就是数据节点,而MYISAM非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键
索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合,叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主
键索引和二级索引没有任何区别,主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引,且MYISAM引擎中可以不设主键

注意用in索引不会失效
7.为什么非主键索引存放的是主键ID而不是数据行的地址

1、索引组织表就是个索引, 索引的话, 索引块分裂是很常见的事情, 块分裂之后, 原有块上数据的rowid 就改变了。
如果非主键索引使用rowid的话, 那索引块分裂就需要将索引中对应行的地址都修改了,一次索引块分裂,对会修改非主键索多个块,如果有多个非主键索引,那修改开销会更多。 况且索引块分裂本就会很频繁。
2、减少了出现行移动或者数据页分裂时二级索引的维护工作(当数据需要更新的时候,二级索引不需要修改,只需要修改聚簇索引,一个表只能有一个聚簇索引,其他的都是二级索引,这样只需要修改聚簇索引就可以了,不需要重新构建二级索引)

2、对于主键的更新,将导致该行数据在索引组织表上移动,也就是更新主键值会导致行的rowid的改变,那对应的其他索引中的行地址也要对应的修改。
8.
9.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。
答案:
BOOL : if ( !a ) or if(a)

综上,mysql在使用like查询的时候只有使用后面的%时,才会使用到索引。

11.单进程单线程的Redis如何能够高并发
基本原理
采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)

12、多线程中的i++线程安全吗?请简述一下原因?
i++是不安全的,因为java在操作i++的时候,是分步骤做的,可以理解为:
i++的总体过程可以分为 tp = i //1
tp2 = tp+1 //2
i = tp2 //3
如果线程1在执行第一条代码的时候,线程2访问i变量,这个时候,i的值还没有变化,还是原来的值,所以是不安全的。
13.c++右值引用&&
https://blog.csdn.net/a34140974/article/details/81948055
右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和精确传递 (Perfect Forwarding)。它的主要目的有两个方面:

  1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  2. 能够更简洁明确地定义泛型函数。
    C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象。 所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。 右值是指临时的对象,它们只在当前的语句中有效。
    MyString(“Hello”)和MyString(“World”)都是临时对象,临时对象被使用完之后会被立即析构,在析构函数中free掉申请的内存资源。 如果能够直接使用临时对象已经申请的资源,并在其析构函数中取消对资源的释放,这样既能节省资源,有能节省资源申请和释放的时间。 这正是定义转移语义的目的。
    通过加入定义转移构造函数和转移赋值操作符重载来实现右值引用(即复用临时对象):

&&解决了什么问题?
问题是当参数为右值时,不必要的深拷贝。
另外,我们还可以使用std::move()函数将左值变为右值来避免拷贝构造。
注意:在定义时调用=进行赋值,是调用了拷贝构造函数
A a=b; 但定义以后进行赋值是调用=,例 A a;a=b;

14.在C++中,必须使用构造函的参数列表来初始化的情况
①数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数;
②需要初始化const修饰的类成员或初始化引用成员数据;
③子类初始化父类的私有成员
15.DNS的两种查询方式

16.C/C++成员变量的初始化顺序
① 基类的静态变量或全局变量
②派生类的静态变量或全局变量
③基类的成员变量
④派生类的成员变量
注意:
①成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
②如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
③类中const成员常量必须在构造函数初始化列表中初始化。
④类中static成员变量,必须在类外初始化。 (静态成员是类所有的对象的共享的成员,而不是某个对象的成员。它在对象中不占用存储空间,这个属性为整个类所共有,不属于任何一个具体对象。所以静态成员不能在类的内部初始化。)
17.结构体和共用体大小的计算
① 共用体:1.联合体大小要至少能容纳最大的成员变量
2.联合体大小长度为联合中元类型(如数组,取其类型的数据长度)最大的变量长度的整数倍
②结构体:1.结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
2.在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。
3.如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int ,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)。

18.hash_map的原理
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
如果hashMap里的元素越来越多,那么冲突的概率会越来越大,因此有必要即时的对数组长度扩容。当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。每次扩容都是在原有的基础上×2。
19.位运算
进行移位运算时,当向左边移动时,如1 << 35, 对于int类型,由于其占有4个bytes(32bits), 因此在Java中,大于32的移位将对32取模,即1 << 35的结果等于1 << 3,以此类推,long将会对64取模。对于int类型而言,如果确实需要获取32位以上的移位,需要将返回值的类型提升到long即可
a%b取模的形式都被替换成了a&(b-1)
20.为什么构造函数不能为虚函数?
虚函数的调用需要虚函数表指针,而该指针存放在对象的内容空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数——构造函数了。
这里你需要知道一个概念,那就是虚函数表vtbl,每一个拥有虚成员函数的类都有一个指向虚函数表的指针。对象通过虚函数表里存储的虚函数地址来调用虚函数。
那虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有vtbl的。因此,构造函数不能是虚函数。
虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚的,就需要通过 虚指针执行那个虚函数表(编译期间生成属于类)来调用,可是对象还没有实例化,也就是内存空间还没有,就没有虚指针,所以构造函数不能是虚函数。vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数
虚函数的地址存放于虚函数表之中。运行期多态就是通过虚函数和虚函数表实现的。
类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数

21.redis数据结构
①对于Redis的字符串,却不是 C 语言中的字符串(即以空字符’\0’结尾的字符数组),它是自己构建了一种名为 简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示。
  SDS 定义:
1
2
3
4

9 struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}

 ②链表是一种常用的数据结构,C 语言内部是没有内置这种数据结构的实现,所以Redis自己构建了链表的实现

typedef struct listNode
{ //前置节点 struct listNode *prev;
//后置节点 struct listNode *next;
//节点的值 void *value; }listNode
③字典
字典又称为符号表或者关联数组、或映射(map),是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。C 语言中没有内置这种数据结构的实现,所以字典依然是 Redis自己构建的。
Redis 的字典使用 哈希表 作为底层实现。

③redis实现了一个跳跃表结构,跳跃表被用来实现缓存中的有序集合如ZADD等命令
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
④散列使用哈希表作为底层实现。
⑤整数集合(intset)是Redis用于保存整数值的集合抽象数据类型,它可以保存类型为int16_t、int32_t 或者int64_t 的整数值,并且保证集合中不会出现重复元素。
⑦压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
简单字符串、链表、字典、跳跃表、整数集合、压缩列表等数据结构就是Redis底层的一些数据结构,用来实现上一篇博客介绍的Redis五大数据类型
22.C++设计模式
①观察者模式
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都要得到通知并自动更新。
观察者模式从根本上讲必须包含两个角色:观察者和被观察对象。
被观察对象自身应该包含一个容器来存放观察者对象,当被观察者自身发生改变时通知容器内所有的观察者对象自动更新。
观察者对象可以注册到被观察者的中,完成注册后可以检测被观察者的变化,接收被观察者的通知。当然观察者也可以被注销掉,停止对被观察者的监控。
观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅(publishsubscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通
应用场景:观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。
②单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式

应用场景:在以下情况下可以使用单例模式:
系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
③工厂模式
什么是工厂模式?
我们在创建对象时不会对客户端直接暴露创建逻辑,而是 通过使用一个共同的接口根据不同的条件来指向具体想要创建的对象。
简单工厂模式中,可以根据参数的不同返回不同类的实例。
工厂模式的优点?
A:使用工厂模式的优点在于一个调用者想创建一个对象,只要知道其名称(也就是不同的标签)就可以在工厂获取具体的对象
B:扩展性强,如果想增加一个产品(也就是具体的对象),只要扩展工厂类就可以(也就是增加不同的标签,增加不同标签所对应的对象)。
C: 屏蔽产品的具体实现,调用者只关心产品的接口、无需关心内部实现。

23、快排的优化
①三数取中选取基准。它的思想是:选取数组开头,中间和结尾的元素,通过比较,选择中间的值作为快排的基准。其实可以将这个数字扩展到更大(例如5数取中,7数取中等)。这种方式能很好的解决待排数组基本有序的情况,而且选取的基准没有随机性。
②序列长度达到一定大小时,使用插入排序
当快排达到一定深度后,划分的区间很小时,再使用快排的效率不高。当待排序列的长度达到一定数值后,可以使用插入排序。由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形。
③在处理快排的时候,可以使用多线程提高排序的效率。
④在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
具体过程:在处理过程中,会有两个步骤
第一步,在划分过程中,把与key相等元素放入数组的两端
第二步,划分结束后,把与key相等的元素交换到到枢轴周围
冒泡的优化
①假设我们现在排序ar[]={1,2,3,4,5,6,7,8,10,9}这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,什么也没做。所以我们可以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去
②优化一仅仅适用于连片有序而整体无序的数据(例如:1, 2,3 ,4 ,7,6,5)。但是对于前面大部分是无序而后边小半部分有序的数据(1,2,5,7,4,3,6,8,9,10)排序效率也不可观,对于种类型数据,我们可以继续优化。既我们可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可。

24.Redis主从复制
主从复制是指将一台Redis服务器的数据,复制到其它的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
主从复制的作用主要包括:
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,但实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
1、建立复制
需要注意,主从复制的开启,完全是在从节点发起的,不需要我们在主节点做任何事情。
25.在某一个文件夹下寻找一个字符串
① find 路径|xargs grep -ri “要查找的字符串” 例如

-r表示递归查找目录下所有文件
-i表示字符串不区分大小写
find会显示那一行
xargs命令_Linux xargs命令:一个给其他命令传递参数的过滤器
find -name 字符串 或find -name “*.c”查找后缀为.c的所有文件
find 目录名 -name 文件名 在目录下查找文件
②grep -i 字符串 文件名(文本文件)
grep -r字符串 目录名

     grep会把所有匹配字符高亮显示
      grep -o 字符串  文件名|wc -l     //统计字符串在文件中出现的次数(可以是某一个字符串的子串)

grep -wo 字符串 文件名|wc -l //统计字符串在文件中出现的次数
-o :只显示被模式匹配到的字符串。
   -w :被匹配的文本只能是单词,而不能是单词中的某一部分,如文本中有liker,而我搜寻的只是like,就可以使用-w选项来避免匹配liker
 -c :显示总共有多少行被匹配到了,而不是显示被匹配到的内容,注意如果同时使用-cv选项是显示有多少行没有被匹配到。
-x 完全匹配输出,比如:grep -x hello a.txt,只会输出某一行存在hello字符串,并且此行仅包含hello的内容。假设a.txt中有一行“hello all”,执行上述命令,此行不会被搜索到。

26.kill 杀死进程
kill 1229 1229为进程ID
或kill -9 1229
执行kill(不加 -* 默认kill -15)命令,系统会发送一个SIGTERM信号给对应的程序。当程序接收到该signal信号后,将会发生以下事情:程序立刻停止,或当程序释放相应资源后再停止,或程序可能仍然继续运行
大部分程序接收到SIGTERM信号后,会先释放自己的资源,然后再停止。但是也有程序可能接收信号后,做一些其他的事情(如果程序正在等待IO,可能就不会立马做出响应,我在使用wkhtmltopdf转pdf的项目中遇到这现象),也就是说,SIGTERM多半是会被阻塞的。
然而kill -9命令,系统给对应程序发送的信号是SIGKILL,即exit。exit信号不会被系统阻塞,所以kill -9能顺利杀掉进程。
小结:在使用 kill -9 前,应该先使用 kill -15,给目标进程一个清理善后工作的机会。如果没有,可能会留下一些不完整的文件或状态,从而影响服务的再次启动。
27.g++编译个多个文件
① -c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
例子用法:g++ -c hello.c他将生成.o的obj文件
②-o连接目标代码,生成可执行程序
g++ filename.c -o filename
上面的意思是如果你不打 -o filename 那么默认就是输出a.out这个-o就是用来控制输出文件名的。
执行文件用 ./filename.out

g++ xx.o xxxx.o -o name
g++编译1.cpp 2.cpp
g++ -c 2.cpp
g++ -c 1.cpp
g++ 1.o 2.o -o test

或者直接用 g++ 1.cpp 2.cpp -o ppp
.h文件不需要编译,只需编译它对应的.cpp文件即可。例
④makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。

28.计算机网络各层的协议
第一层:物理层
第二层:数据链路层 802.2、802.3ATM、HDLC、FRAME RELAY
第三层:网络层 IP、IPX、ARP、APPLETALK、ICMP
第四层:传输层 TCP、UDP、SPX
第五层:会话层 RPC、SQL、NFS 、X WINDOWS、ASP
第六层:表示层 ASCLL、PICT、TIFF、JPEG、 MIDI、MPEG
第七层:应用层 HTTP,FTP,SNMP等
29.标准c++中的include “” 与<>的区别是什么

#include<>直接从编译器自带的函数库中寻找文件
#include""是先从自定义的文件中找 ,如果找不到在从函数库中寻找文件

如果是自己写的头文件 建议使用#include“”
29.Mysql中MVCC的使用及原理详解
https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc
https://blog.csdn.net/freedom_824/article/details/81591967
MVCC?
英文全称为Multi-Version Concurrency Control,翻译为中文即 多版本并发控制。在小编看来,他无非就是乐观锁的一种实现方式
MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。
在repeatable read的隔离级别下,创建事务trx结构的时候,就生成了当前的global read view。使用trx_assign_read_view函数创建,一直维持到事务结束。在事务结束这段时间内 每一次查询都不会重新重建Read View , 从而实现了可重复读。
与此同时,需要建立一个叫做Read View的数据结构,它有三个部分:
(1) 当前活跃的事务列表 ,即[101,102]
(2) Tmin ,就是活跃事务的最小值, 在这里 Tmin = 101
(3) Tmax, 是系统中最大事务ID(不管事务是否提交)加上1。 在这里例子中,Tmax = 103
(注: 在可重复读的隔离级别下,当第一个Read操作发生的时候,Read view就会建立。 在Read Committed隔离级别下,每次发出Read操作,都会建立新的Read view。)

31.结构体为什么要字节对齐
①每次的步长都为4字节,只对地址是4的整倍数的地址进行寻址,比如:0,4,8,100等进行寻址。对于程序来说,一个变量的地址最好刚在一个寻址步长内,这样一次寻址就可以读取到该变量的值,如果变量跨步长存储,就需要寻址两次甚至多次然后再进行拼接才能获取到变量的值,效率明显就低了,所以编译器会进行内存对齐,以保证寻址效率。
  32位CPU为例,寻址步长为4,程序中如果一个int变量的地址为8,那么一次寻址就可以拿到该变量的值,如果int变量的地址为10,那么需要先寻址地址为8的地址拿到数据的一部分,再寻址12的地址拿到另一部分,然后再进行拼接。
①就是为了提高数据读取的效率。那为什么字节对齐会提高数据存取的效率呢?主要原因是下面两个方面:
从内存中获取数据,并不是按照数据存储的小单位字节来的,而是4字节、8字节甚至更多,那这样,假如当前计算机是按照4字节读取数据的,那么,如果一个int类型的数据,在字节对齐的位置开始存放,则可以一次读取到,否则需要花两次才能取到这个数据。
为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
① 比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
②那么对于一个int 类型,若是按照内存对齐来存储,处理器只需要访存一次就可以读取完4个字节
若没有按照内存对其来读取,如上图所示,就需要访问内存两次才能读取出一个完整的int 类型变量
具体过程为,第一次拿出 4个字节,丢弃掉第一个字节,第二次拿出4个字节,丢弃最后的三个字节,然后拼凑出一个完整的 int 类型的数据。
32.C++内存池
为解决上述问题,一个(可能的)的解决方案就是使用内存池。
先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
“内存池”在初始化时,分配一个大块内存(称 原始内存块),并且将此内存分割为一些小的内存块。当你需要请求分配内存时,则从内存池中取出事先分配好的内存,而不是向OS申请。内存池最大的优势在于:
1、极少的(甚至没有)堆碎片整理
2、较之普通内存分配(如malloc,new),有着更快的速度
33.C++ 强制类型转换
const_cast,字面上理解就是去const属性。
  static_cast,命名上理解是静态类型转换。如int转换成char。(基本类型转换)
  dynamic_cast,命名上理解是动态类型转换。如子类和父类之间的多态类型转换。
reinterpret_cast,仅仅重新解释类型,但没有进行二进制的转换。(指针类型转换)
33.函数调用过程
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
在编译过程中,参数的传递顺序,参数、本地变量等应该放在栈的哪个位置(相对位置)都是定了的。当程序运行到相应程序后会按照编译好的顺序对栈进行操作。
35.内置函数
使用内置函数可以节省运行时间,但却增加了目标程序的长度。假设要调用10次max函数,则编译时先后10次将max代码复制并插入main函数,这就增加了目标文件main函数的长度。因此一般只将规模很小(一般为5个语句以下)而使用频繁的函数(如定时采集数据的函数声明为内置函数)。在函数规模很小的情况下,函数调用的时间开销可能相当于甚至超过执行函数本身的时间,把它定义为内置函数,可大大减少程序的运行时间。
内置函数中不能包括复杂的控制语句,如循环语句和switch语句。
对函数做inline声明,只是程序设计者对编译系统提出的一个建议,它是建议性的,而不是指令性的。并非指定为inline,编译系统必须这样做。它是根据具体情况决定的。例如对前面提到的包含循环语句和switch语句的函数或一个递归函数是无法进行代码置换的,又如一个1000行的函数,也不太可能在调用点展开。此时编译系统就会忽略inline声明,而按普通函数处理。
内联函数与宏(define)定义区别
(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义; 宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
inline函数是函数,但在编译中不单独产生代码,而是将有关代码嵌入到调用处。

if (c > a){a = c;}return a;}由于定义函数时指定它为内置函数,因此编译系统在遇到函数调用“max(i,j,k)”时,就用max函数体的代码代替“max(i,j,k)”,同时将实参代替形参。这样程序第10行“m = max(i,j,k);"就被置换成

35.应用程序的地址空间
操作系统通过虚拟内存的方式为所有应用程序提供了统一的内存映射地址。如图3所示,从上到下分别是用户栈、共享库内存、运行时堆和静态存储区(初始化和未初始化)、代码段。当然这个是一个大概的分段,实际分段比这个可能稍微复杂一些,但整个格局没有大变化。

36.菱形继承与虚继承
菱形继承问题本质上是一种多继承问题。比如我要定义一个Animal类,在此基类的基础上衍生出两个派生类Sheep、Tuo,但我又想构造一个SheepTuo类(同时具备Sheep和Tuo的属性)。这样一个子类继承多个父类的问题是多继承问题,如果父类之间有同名的方法或者属性,就会产生二义性。
假设A-B,A-C, BC-D; 若A中有成员变量a,则
继承多份父类的属性,超成资源的浪费
同名属性,产生二义性
使用作用域的方式,可以避免二义性。
虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用virtual声明继承关系就行了。这样一来,D就只有A的一份拷贝。如下:
class B:virtual public A
每个虚继承的子类都有一个虚基类表指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)。虚基类表指针(virtual base table pointer)指向虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址,通过偏移地址,就找到了虚基类成员。
在这里我们可以对比虚函数的实现原理:
它们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
虚基类依旧存在继承类中,占用存储空间;虚函数不占用存储空间。
虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
37、C++的结构体和C++类的区别
2.1 C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。
2.2 C++结构体的继承默认是public,而c++类的继承默认是private。
38、windows消息循环和消息队列
①windows消息还可以分为:
(1) 队列消息(Queued Messages)
消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理
如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠标,键盘消息等。
其中,WM_PAINT,WM_TIMER只有在队列中没有其他消息的时候才会被处理,
WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。
(2) 非队列消息(NonQueued Messages)
消息会绕过系统消息队列和线程消息队列,直接发送到窗口过程进行处理
如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED
②Windows中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。应用程序中含有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数(窗口消息处理程序)中。

按照所属的不同,消息队列分为系统消息队列和进程消息队列。系统消息队列由操作系统负责维护,进程消息队列由进程负责维护。
是不是每个进程都具有消息队列呢?回答是否定的。在Windows下,只有那些具备窗口(GUI用户图形界面)的进程,才会有消息队列,那些不具备GUI的进程是没有消息队列的。也就是说:操作系统在开启一个新的进程时,并没有为其创建消息队列,而是当进程第一次调用GDI函数后,才创建,并且进程持有窗口时,才持有消息队列。
GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。
39、在窗口上绘制图形的过程
①创建结构体并载入图像
CImage m_plane;
CImage m_enemyExplode;
CImage m_enemyNormal;
m_plane.Load(_T(“planeNormal_2.jpg”));
② 首先获得当前窗口的DC
CDC *cDC = this->GetDC();//获得当前窗口的DC (device context的缩写,翻译成中文是设备上下文,或者叫设备内容,用于绘制窗口的设备)
③调用Draw函数进行绘制
m_background.Draw(*cDC, 0, 0, 500, 900);
m_plane.Draw(*cDC, m_planepos)
//CRect m_planepos;结构体里有四个成员变量left,right,top,bottom
//BOOL Draw( HDC hDestDC, int xDest, int yDest, int nDestWidth, int nDestHeight );
(xDest, yDest)指定图像显示的位置,这个位置和源图像的左上角点相对应。nDestWidth和nDestHeight分别指定图像要显示的高度和宽度
④ReleaseDC(cDC);//释放DC
word中的“另存为”对话框就是模态对话框,你不把它关闭,不能进行其他操作。
word中的“查找与替换”对话框就是非模态对话框,你不把它关闭,能进行其他操作。

39.异步IO
而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
异步IO的概念和同步IO相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。预先知道这些数 据的位置,所以预先发起异步IO读请求。等到真正需要用到这些数据的时候,再等待异步IO完成。使用了异步IO,在发起IO请求到实际使用数据这段时间 内,程序还可以继续做其他事情
40.C++产生随机数
如果你只要产生随机数而不需要设定范围的话,你只要用rand()就可以了:rand()会返回一随机数值, 范围在0至RAND_MAX 间。RAND_MAX定义在stdlib.h, 其值为2147483647。
rand()函数可以用来产生随机数,但是这不是真真意义上的随机数,是一个伪随机数,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是定了的,除非你破坏了系统,为了改变这个种子的值,C提供了 srand()函数,它的原形是void srand( int a) 功能是初始化随机产生器既rand()函数的初始值,即使把种子的值改成a; 从这你可以看到通过sand()函数,我们是可以产生可以预见的随机序列,那我们如何才能产生不可预见的随机序列呢?我们可能常常需要这样的随机序列,利用srand((unsign)(time(NULL))是一种方法。
rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。
time(); 函数
函数原型: time_t time(time_t *timer)
函数用途: 得到机器的日历时间或者设置日历时间
头 文 件: time.h
输入参数: timer=NULL时,得到机器日历时间, =时间数值时 用于设置日历时
间;
40.strcpy代码 memcpy()代码

41.消息队列
我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ

如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。
通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:

42.耦合度、内聚
耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间接口的复杂程度、调用模块的方式以及哪些信息通过接口
内聚(Cohesion)是一个模块内部各成分之间相关联程度的度量。
43.数据库三种封锁协议

二、三种封锁协议
1.一级封锁协议
一级封锁协议是:事务T在修改数据之前必须先对其加X锁,直到事束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
一级封锁协议可以防止丢失修改,并保证事务T是可恢复的。使用一级封锁协议可以解决丢失修改问题。
在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,它不能保证可重复读和不读“脏”数据。
2.二级封锁协议
二级封锁协议是:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后方可释放S锁。
二级封锁协议除防止了丢失修改,还可以进一步防止读“脏”数据。但在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。
3.三级封锁协议
三级封锁协议是:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。
三级封锁协议除防止了丢失修改和不读“脏”数据外,还进一步防止了不可重复读。
44.cookie和session区别
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session
Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
45.正向代理是为客户端代理,反向代理是为服务端代理。
正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求
nginx能实现负载均衡,什么是负载均衡呢?就是我的项目部署在不同的服务器上,但是通过统一的域名进入,nginx则对请求进行分发,减轻了服务器的压力。
多个客户端给服务器发送的请求,Nginx 服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。
此时请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,Nginx 扮演的就是一个反向代理角色。
客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。因为客户端不需要任何配置就可以访问。
分布式部署;也就是通过部署多台服务器来解决访问人数限制的问题。

46.跳跃表

46.设计模式
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
①模板方法模式:模板方法模式是一种基于继承的代码复用技术,包含抽象类和子类,在抽象类中定义一个操作中算法的框架,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
可以同钩子方法返回一个布尔变量控制某个方法是否执行。
适用场景:
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
1.在多个类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中,以避免代码重复。
程序主框架相同,细节不同的情况下,也可以使用模板方法。
2.对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。
模板方法模式的主要缺点如下:
需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计。
②适配器模式是用于解决接口不兼容问题https://www.cnblogs.com/V1haoge/p/6479118.html
适配器就是一种适配中间件,它存在于不匹配的二者之间,用于连接二者,将不匹配变得匹配,简单点理解就是平常所见的转接头,转换器之类的存在。
适配器模式有两种:类适配器、对象适配器、接口适配器
1.类适配器模式:
 原理:通过继承来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法。可以创建一个新类c,public class c extends b implements a; c类继承b类 实现接口a;
在c类中重写接口a中的函数,让他调用b类里的目标函数。

2.对象适配器:创建一个新类c,由a继承而来,在c类中添加一个b类对象的数据成员,
在c类中重写接口a中的函数,让他调用b类对象里的目标函数。
3.接口适配器模式
  原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
4.适配器模式应用场景

(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
③装饰器模式
装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能。前提是目标存在抽象接口。
是装饰器类也是目标接口的一个子类,装饰器中持有的目标实例是从构造器传入的,对目标函数进行重写。
④代理模式
一个类(委托类)创建一个代理类,来代表它来对外提供功能。
代理模式很简单,只要记住以下关键点,简单易实现:
    (1)代理类与委托类实现同一接口
    (2)在委托类中实现功能,在代理类的方法中中引用委托类的同名方法
    (3)外部类调用委托类某个方法时,直接以接口指向代理类的实例,这正是代理的意义所在:屏蔽。
应用场景:
 (1)当我们想要隐藏某个类时,可以为其提供代理类,只对外提供某些功能。
  (2)当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中金进行权限判断来进行不同权限的功能调用)
  (3)当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)
装饰器模式与代理模式的区别:装饰器中持有的目标实例是从构造器传入的,而代理中持有的目标实例是自己创建的。
47.mysql 的binlog
mysql-binlog是MySQL数据库的二进制日志,用于记录用户对数据库操作的SQL语句((除了数据查询语句)信息。可以使用mysqlbin命令查看二进制日志的内容。我们执行SELECT等不涉及数据更新的语句是不会记binlog的,而涉及到数据更新则会记录
48.主从复制的作用
  1、主数据库出现问题,可以切换到从数据库。
  2、可以进行数据库层面的读写分离。
3、可以在从数据库上进行日常备份
原理:
将主数据库中的DDL和DML操作通过二进制日志传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

Binary log:主数据库的二进制日志。
Relay log:从服务器的中继日志。
第一步:master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。
第二步:salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。
第三步:SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。
48.redis主从复制
Redis复制工作原理:

  1. 如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;
  2. 当Master收到SYNC命令之后,会做两件事:
    a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);
    b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
  3. 当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
  4. 而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
  5. Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致

bgsave命令,是一个异步保存命令,也就是系统将启动另外一个进程,把Redis的数据保存到对应的数据文件中。
slaveof :如果您需要将某个节点设置为某个Master节点的Slave节点,您需要在这里指定Master节点的IP信息和端口信息。这个设置项默认是关闭的,也即是说Master节点不需要设置这个参数。另外,除了通过配置文件设置外,您还可以通过Redis的客户端命令进行slaveof设定。
1.通过socket ip端口号传送
2.通过频道 订阅-发送传送
48.数据库命令
1.数据去重 select distinct 字段名 from 表名 或用group by
2.
48.HashMap是线程不安全的,其主要体现:
#1.在jdk1.7中是头插法,在多线程环境下,扩容时会造成环形链或数据丢失。,扩容时也就是链表的顺序会翻转。一个线程在扩容过程中挂起,另一个线程完成了扩容,挂起的线程接着执行上次未完的扩容操作,造成环形链或数据丢失。
#2.在jdk1.8中尾插法,在多线程环境下,会发生数据覆盖的情况。,t时刻第一个线程将要在a节点后插入数据,t+1时刻另外一个线程插入数据,同时需要向a节点插入数据,t+2第一个线程插入b,t+3向a节点后另外一个线程插入数据c,则c就覆盖了a;
原因是缺乏同步机制,就像i++一样不安全,
49.可重入锁也叫递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
50.mysql中innodb和myisam对比
1.innoDB具有事务,支持4个事务隔离级别,回滚,MyISAM不支持事务。
2.InnoDB(索引组织表)使用的聚簇索引、索引就是数据,顺序存储,因此能缓存索引,也能缓存数据
MyISAM(堆组织表)使用的是非聚簇索引、索引和文件分开,随机存储,只能缓存索引
3.MyISAM适用于并发相对较低,以读为主的场景
51.10亿int型数,统计只出现一次的数
https://blog.csdn.net/u010983881/article/details/75097358
52.快速高考成绩排名
因数据有届,故采用桶排序
53.mysql与redis如何同步;
1.对于一致性要求不高,可以为redis缓存设置有效期,可以采取批量写操作的方式。开辟一个内存区域,当数据到达区域的一定阀值时如80%时
2.通过数据库中的触发器(Trigger)调用UDF(user defined function,自定义的函数库),来触发对Redis的相应操作
3.或分析MySQL的binlog文件并将数据插入Redis,
4.在我们的实际开发当中往往采用如下方式实现实现Mysql和Redis数据同步:当我们在MySQL数据库中进行增删改的时候,我们在增删改的service层将缓存中的数据清除,这个时候用户在此请求的时候我们缓存中没有数据了,直接去数据库中查询,查询回来之后将缓存中的数据放缓存当中,这个时候缓存中的数据就是最新的数据。
54.一致性哈希
一致性哈希可以有效地解决分布式存储结构下动态增加和删除节点所带来的问题。(缓存机器增减都需要重新迁移数据)
我们可以假设有一个 2 的 32 次方的环形,缓存节点通过 hash 落在环上。而对象的添加也是使用 hash,但很大的几率是 hash 不到缓存节点的。怎么办呢?找离他最近的那个节点。 比如顺时针找前面那个节点。
能解决问题吗?想象一下:当增减机器时,环形节点变化的只会影响一个节点,就是新节点的顺时针方向的前面的节点。这个时候,我们只需要清除那一个节点的数据就足够了,不用想取余 hash 那样,清除所有节点的数据。
使用 就近寻址 的方式找到最近的节点。如果缓存节点分布不均匀会引起负载不均衡,所以需要引入虚拟节点的方式,这些节点的数据映射到真实节点上,让整个集群的负载能够均衡。

55.渐进式哈希
redis的数据库使用字典来作为底层实现的,对数据库的增删查改操作也是构建在对字典的操作之上。redis的字典使用hash表作为底层实现。 扩展或收缩哈希表需要将 ht[0] 里面的所有键值对 rehash 到 ht[1] 里面, 但是, 这个 rehash 动作并不是一次性、集中式地完成的, 而是分多次、渐进式地完成的。如果哈希表里保存的键值对数量不是四个, 而是四百万、四千万甚至四亿个键值对, 那么要一次性将这些键值对全部 rehash 到 ht[1] 的话, 庞大的计算量可能会导致服务器在一段时间内停止服务。
字典会同时使用 ht[0] 和 ht[1] 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht[1] 里面进行查找, 诸如此类。
另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。
具体步骤
1.为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
2.在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。
3.在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
4.随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1, 表示 rehash 操作已完成。

56…给定一个无序数组,求这个数组变为有序后相邻元素之差的最大值是多少,要求时间复杂度是O(n)。
这题的大体思路就是桶排序,只需要遍历次数组,找出最大最小值,然后安装最大最小值,将其他数划分到n个桶里。然后求连续两个非空桶i j的bucket[j].min - bucket[i].max的最大值即可。
57.Redis的key如果过长或过多会有什么问题
由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,所以,业务上能拆则拆,下面举几个典型的分拆方案。
1.单个简单的key存储的value很大,分拆成几个key-value, 也可以将这个存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性
58.脏读
脏数据在临时更新(脏读)中产生。事务A更新了某个数据项X,但是由于某种原因,事务A出现了问题,于是要把A回滚。但是在回滚之前,另一个事务B读取了数据项X的值(A更新后),A回滚了事务,数据项恢复了原值。事务B读取的就是数据项X的就是一个“临时”的值,就是脏数据
59.redis缓存与数据库一致性问题
https://my.oschina.net/u/4101481/blog/3101080
一般常用的缓存方案有两种:
第一种 读的时候,先读缓存,缓存没有的话,读数据库,取出数据后放入缓存,同时返回响应。更新的时候,先删除缓存,在更新数据库。
第二种 读的时候,先读缓存,缓存没有的话,读数据库,取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,再删除缓存。
第一种方案引入了缓存-数据库双写不一致的问题,即读数据(写缓存)与修改数据(写数据库)并发的情况下,若修改数据数据库事务还没提交,但是已经把缓存从redis中删除,此时来了个读请求,会把旧的数据刷到缓存里面,这样就导致了缓存中的数据直到下一次修改数据库之前肯定是与数据库不一致的。
第二种方案引入了另外一个问题,在提交事务之后,若更新缓存失败,也会导致缓存数据库不一致。如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。
facebook公司用的是第二种方案,因为在高并发的情况下,第一种方案带来的影响肯定比第二种方案要大。因为:
第一:导致更新缓存失败的情况概率是很小的,就算发生了,那么问题就大了,比起解决缓存和数据库不一致,更应该加强Redis架构的可用性。
第二,高并发情况下第一种情况发生的概率是很高的。
还有两种方案:触发器,binlog日志

59.2为什么是删除缓存,而不是更新缓存

如果在更新数据库的时候直接更新缓存,有这样的场景,在一段时间内,数据库中的某个字段频繁更新,比如更新了20次,但是在这段时间内,你需要更新20次缓存,但是,这段时间内,只有一次读请求,于是发现其实只在读的时候,更新一次缓存就行,不需要更新20次缓存。
60.redis缓存与数据库一致性解决方案
一、延时双删策略
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。具体步骤是:
1)先删除缓存
2)再写数据库
3)休眠500毫秒(根据具体的业务时间来定)
4)再次删除缓存。
二、设置缓存的过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存
三、异步更新缓存(基于订阅binlog的同步机制或触发器的自定义函数)
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis,MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
(知识扩展,也可在redis中写入,高并发情况下此时如果写入请求较多,则直接写入Redis中去,然后间隔一段时间,批量将所有的写入请求,刷新到MySQL中去;如果此时写入请求不多,则可以在每次写入Redis,都立刻将该命令同步至MySQL中去。这两种方法有利有弊,需要根据不同的场景来权衡。)
61.分布式系统
分布式就是通过计算机网络将后端工作分布到多台主机上,多个主机一起协同完成工作。可以1、增大系统容量,2、数据冗余备份、整个系统不会因为一台机器出故障而导致整体不可用3、将服务拆分,增强系统的并发性。
分布式的一致性:系统的多个文件副本数据保持一致。
设成都上海各自的数据中心有记录变更,需要先同步到主数据中心,主数据中心更新完成之后,在把最新的数据分发到上海,成都的地方数据中心A,地方数据中心更新数据,保持和主数据中心一致性(数据库结构完全一致)
62、线程池的参数
1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行
3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器 自定义一些被拒绝任务的处理逻辑
ThreadPoolExecutor执行顺序
线程池按以下行为执行任务
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务
63.堆溢出:不断的new 一个对象,一直创建新的对象,while(true){int *p=new int}
栈溢出:死循环或者是递归太深,递归的原因,可能太大,也可能没有终止。
 Void a(int i) {a(i);}
64.1.Redis是基于内存存储的,MySQL是基于磁盘存储的
2.Redis存储的是k-v格式的数据。时间复杂度是O(1),常数阶,而MySQL引擎的底层实现是B+Tree,时间复杂度是O(logn),对数阶。Redis会比MySQL快一点点。.mysql B+树支持范围查询
3.MySQL数据存储是存储在表中,查找数据时要先对表进行全局扫描或者根据索引查找,这涉及到磁盘的查找,磁盘查找如果是按条点查找可能会快点,但是顺序查找就比较慢;而Redis不用这么麻烦,本身就是存储在内存中,会根据数据在内存的位置直接取出。
4.Redis是单线程的多路复用IO,单线程避免了线程切换的开销,而多路复用IO避免了IO等待的开销,在多核处理器下提高处理器的使用效率可以对数据进行分区,然后每个处理器处理不同的数据。Mysql使用了MVCC,并发性更高,但两者都支持事务。
5…mysql 可以进行多表查询,内连接 外连接。
65.301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
302 Found 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。
303 See Other
该状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源
新的URL会在响应的Location:头字段里找到

64.Cookie有数量和大小的限制(转)
浏览器一个域名下一般只允许有50-70个cookie,每个Cookie所占字节大小一般是4k,
当超过cookie最大个数时,浏览器最近最少使用算法”,当cookie已经达到限额时就将自动剔除最老的cookie,以给最新的cookie的留下可用的空间。
65.redis持久化方法
由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以AOF文件的大小随着时间的流逝一定会越来越大;影响包括但不限于:对于Redis服务器,计算机的存储压力;AOF还原出数据库状态的时间增加;
为了解决AOF文件体积膨胀的问题,Redis提供了AOF重写功能:Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件所保存的数据库状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。
AOF重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的。首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录该键值对的多个命令;
区别:
1.体积:相同的数据量rdb数据比aof的小,rdb用二进制压缩存储,OF文件的大小随着时间的流逝一定会越来越大。当aof文件大小到达一定程度的时候,后台会自动的去执行aof重写,此过程不会影响主进程,重写完成后,新的写入将会写到新的aof中,旧的就会被删除掉。
2.恢复速度:因为rdb是数据的快照,基本上就是数据的复制,不用重新读取再写入内存
3.安全方面:aof的记录能够精确到秒级追加甚至逐条追加,而rdb只能是全量复制,当并且发生故障停机,rdb会丢失一段时间内的数据,(从上次备份到现在)

66.Mysql当前读和快照读
提供了两种事务隔离技术,第一个是mvcc,第二个是next-key技术。
当前读”读取到的时数据库最新的数据,而“快照读”读取到的数据是事务中第一次建立ReadView时的数据+事务中修改的数据。快照读是读的旧版本
当前读, 读取的是最新版本, 并且对读取的记录加锁,保证其他事务不会再并发的修改这条记录,避免出现安全问题。
67.· 什么是幻读
· 事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。(即同一事物两次查询数据条数不一致)
· · mysql如何实现避免幻读
· 在快照读读情况下,mysql通过mvcc来避免幻读。
在当前读读情况下,mysql通过next-key来避免幻读
68.InnoDB是一个支持行锁的存储引擎,它有三种行锁的算法:
Record Lock:行锁,单个行记录上的锁。
Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止幻读、防止间隙内有新数据插入、防止已存在的数据更新为间隙内的数据。
Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。InnoDB默认加锁方式是next-key 锁。左开右闭的空间(3,5]
这三种锁都是排它锁(X锁)。
for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。
select * from table_name where id =1 for update ;
间隙锁,锁的就是两个值之间的空隙。比如表 test_20,初始化插入了 6 个记录,这就产生了 7 个间隙。

把间隙锁记为开区间,把 next-keylock 记为前开后闭区间。
69.协程
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。
一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但这些函数都是串行运行的。当一个协程运行时,其它协程必须挂起。
协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(寄存器上下文及栈等)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。
70.Redis过期策略
一、定期删除
redis会把设置了过期时间的key放在单独的字典中,定时遍历来删除到期的key。
1).每100ms从过期字典中 随机挑选20个,把其中过期的key删除;
2).如果过期的key占比超过1/4,重复步骤1
为了保证不会循环过度,导致卡顿,扫描时间上限默认不超过25ms。根据以上原理,系统中应避免大量的key同时过期,给要过期的key设置一个随机范围。
二、惰性删除
过期的key并不一定会马上删除,还会占用着内存。 当你真正查询这个key时,redis会检查一下,这个设置了过期时间的key是否过期了? 如果过期了就会删除,返回空。这就是惰性删除。
71.分布式锁
分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。
负责管理分布式系统中对共享资源的访问。
互斥锁、乐观锁,悲观锁,MVCC、读写锁、奇偶锁、哈希表的锁分段技术,锁分段技术:减小锁的粒度,效率高,(可以建立5个锁,分别锁住取余值K%5的记录,奇偶锁即为建立两个锁)
ConcurrentHashMap中是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。
这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。

72.CAP定理
https://www.jianshu.com/p/641726ee4eb3
https://blog.csdn.net/zhangyufeijiangxi/article/details/78286364
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
数据一致性(consistency):在分布式系统中的所有数据备份,在同一时刻数据保持一致。
服务可用性(availability):系统提供的服务必须一直处于可用状态,对用户的每个请求总是在有限的时间内返回结果。
分区容错性(partition-tolerance):在网络分区的情况下,某个网络分区故障,系统给依然可以对外服务。(当分布式系统中因为一些原因导致无法通信而分成多个分区,系统还能正常对外服务。)
分布式系统的知识包括:定义及作用,CAP、分布式存储(一致性哈希),分布式锁、缓存、消息队列
73.限流算法
1.1、计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:
1.2、滑动窗口
滑动窗口,又称rolling window。为了解决这个问题,我们引入了滑动窗口算法。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。下面这张图,很好地解释了滑动窗口算法:
把一分钟分成六个方格,每过十秒右移一次,当六个方格内请求数大于100,触发限流

2、令牌桶算法
令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)、根据限流大小,设置按照一定的速率往桶里添加令牌;
3)、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;

3、漏桶算法
漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。设置一个任务队列,每次取出一个任务处理,新添加的请求加入队列中,队列满后,不再接受请求,直到队列腾出空间
4

74.strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
75.memset
memset是“字节赋值“,第二个参数的含义是“每个字节的值”
而int数组每个数是4个字节,而0的二进制是全0,-1的二进制是全1(补码)
所以给0就可以赋值全0,-1就可以赋值全-1,这几个只是比较常用而已
76.求10万以内的所有素数,尽可能快https://leetcode-cn.com/problems/count-primes/
76.动态绑定与静态绑定
就是确定调用的函数具体是哪个,也就是静态多态与动态多态的区别。
77.c++结构体和类的区别

78.、单例模式
1、单例类只能有一个实例。指向唯一实例的静态指针
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
4、唯一一个公有函数可以获取唯一的实例。
5、构造函数私有,避免别的类创建该类的实例

第一个if是为了减少加锁次数,只有当指针为空才需要加锁,因为加锁会额外耗时。
76.消息队列应用场景
异步处理,应用解耦,流量削锋和消息通讯四个场景
1.异步,数据先写入消息队列,本程序返回,由另一程序从消息队列中将数据写入数据库,避免了本程序因数据库正在写入其他数据而等待的情况。
2.秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉,,,用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面,秒杀业务根据消息队列中的请求信息,再做后续处理
3.消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等
客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。

4.耦合关系是指某两个事物之间如果存在一种相互作用、相互影响的关系,耦合度越高,一方出现故障另一方更容易受到影响。
用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图

77.设计模式的基本原则:
1.单一职责原则:一个类只做一件事,应该只有一个引起它变化的原因。
2.开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
3.里氏代换原则:在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常
4.接口隔离原则:使用多个专门的接口,而不使用单一的总接口,,当一个接口太大时,我们需要将它分割成一些更细小的接口
5.依赖倒置原则: 抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
6.迪米特法则:迪米特法则可降低系统的耦合度,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。
78.字典树

(标橙色的节点是“目标节点“,即根节点到这个目标节点的路径上的所有字母构成了一个单词。)
它是首先建立一个Root根节点,然后在读取后来的字符串的同时,从根节点出发,查找字符串每一位的节点是否存在。若存在,就从这一位出发继续查找下一位;若不存在,就建立这个节点。反复以上过程。注意,Trie树是将字符转换为ASCLL码存取,注意转换。

79.分布式缓存
https://blog.csdn.net/u011320646/article/details/85491103
分布式缓存则是通过内存或者其他高速存储来加速,但是由于用到了分布式环境中,涉及到并发和网络的问题,所以会更加复杂一些,例如redis缓存与mysql硬盘存储;
1.在缓存中查找,找到则返回,没找到则到数据库查找并添加到缓存中,写分为先删缓存再写、或先写再删缓存(优化方法:延时双删策略 )
或更新数据库后用binlog或触发器更新缓存。
2.缓存没找到则,先写到缓存里再异步更新到数据库。系统的写性能能够大大提升了。
3设置缓存的过期时间、分为主动删除与惰性删除。

80.红黑树

81.公平锁与非公平锁
前提: 线程等待时会被挂起,轮到他时会被唤醒
公平锁: 新进程发出请求,如果此时一个线程正持有锁,或有其他线程正在等待队列中等待这个锁,那么新的线程将被放入到队列中被挂起。
非公平锁: 新进程发出请求,如果此时一个线程正持有锁,新的线程将被放入到队列中被挂起,但如果发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。相当于插队
非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。
恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。相当于人从被叫醒要完全醒过来需要时间。
假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。

当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。
非公平锁缺点:保证不了每个线程都能拿到锁,有可能会出现某些线程等待时间过长,也就是存在有线程饿死
82.一个数组中,除两个不同的数字外,两两相同,找到两个不同的数字,输出
数组的数,两两相与,然后找到第一个为1的比特位,依据在该位上数值的不同,按位与操作,分成两类
每一类都两两相与,得到两个不同的数。
82.Redis缓存穿透、缓存雪崩、缓存击穿
缓存穿透是指用户查询数据,在数据库没有,缓存里也没有,每次查询都要去数据库再查询一遍,然后返回空。若黑客利用此漏洞对该数据进行大量查询可能压垮数据库。
如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库
缓存雪崩,是指在某一个时间段,缓存集中过期失效。由于原有缓存失效(过期),新缓存未到期间。所有请求都去查询数据库,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
分散缓存过期时间
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间(一般是缓存时间到期),持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞,引起数据库压力瞬间增大,造成过大压力
热点数据缓存不过期。
83.分布式事务用于在分布式系统中保证不同节点之间的数据一致性。
XA协议包含两阶段提交(2PC)和三阶段提交(3PC)两种实现,这里我们重点介绍两阶段提交的具体过程。
在XA协议中包含着两个角色:事务协调者和事务参与者。让我们来看一看他们之间的交互流程:
在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。

在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。
当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

XA三阶段提交
XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一致的问题仍然没有根本解决。

85.虚函数表函数内容在代码区,(虚函数表)函数指针在bss区。虚表指针在堆区。
1.虚函数表是全局共享的元素,即全局仅有一个.
2.虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表.即虚函数表不是函数,不是程序代码,不肯能存储在代码段.
3.虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针,而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的,不必动态分配内存空间存储虚函数表,所以不再堆中.
根据以上特征,虚函数表类似于类中静态成员变量.静态成员变量也是全局共享,大小确定.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值