面试知识点总结

来源:互联网 发布:淘宝自动提取卡密 编辑:程序博客网 时间:2024/05/17 09:14

就是水各大面经,然后自己总结下啦,怕忘QAQ

1.介绍一下信号量和互斥锁

信号量 互斥锁 条件变量的区别

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在哪里)。

互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,再解锁。有的时候锁和信号量会同时使用的

也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

参考:信号量与互斥锁

信号量与互斥锁之间的区别

1.互斥锁用于线程的互斥,信号量用于线程的同步。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2.互斥量值只能为0/1,信号量值可以为非负整数。

一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

3.互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

Semaphore(信号量)可以被抽象为五个操作:

1.创建 Create
2.等待 Wait
线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。
3.释放 Post
执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。
4.试图等待 TryWait
如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。
5.销毁 Destroy

Mutex(互斥量)可以被抽象为四个操作:

1.创建 Create
2.加锁 Lock
3.解锁 Unlock
4.销毁 Destroy

2.画出TCP的三次握手和四次挥手,标明每个过程数据报携带的信息

TCP的三次握手:

Establishing a normal TCP connection requires three separate steps:

1.The first host (Alice) sends the second host (Bob) a “synchronize” (SYN) message with its own sequence number x, which Bob receives.

2.Bob replies with a synchronize-acknowledgment (SYN-ACK) message with its own sequence number y and acknowledgement number x + 1, which Alice receives.

3.Alice replies with an acknowledgment message with acknowledgement number y + 1, which Bob receives and to which he doesn’t need to reply.

建立连接的过程如下图:
这里写图片描述

In this setup, the synchronize messages act as service requests from one server to the other, while the acknowledgement messages return to the requesting server to let it know the message was received.同步消息扮演了requests的角色,已接受消息是通知request server,该消息我已经收到了。

为什么要加1呢?为什么不直接传输接收到的number呢?
The reason for the client and server not using the default sequence number such as 0 for establishing connection is to protect against two incarnations of the same connection reusing the same sequence number too soon, which means a segment from an earlier incarnation of a connection might interfere with a later incarnation of the connection.为了避免前面的连接影响后面连接的可能性。

上面关于三次握手已经分析的很好了,在外网上没有找到很好的关于四次挥手的内容,这里参考简析TCP的三次握手与四次分手

这里写图片描述

多么清晰的图!

为什么需要三次握手呢?为什么不是两次呢?

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

为什么需要四次挥手呢?

TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

注意:发送断连接请求的主机可以是客户端,也可以是服务器,谁不想发送数据了,谁就请求嘛。

下面来看看四次分手中的状态变化

FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)

CLOSE_WAIT这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)注意特殊情况哦!

CLOSED: 表示连接中断。

3.关于linux的基础命令

参考我最常用的20条命令

参考:每天一个linux命令目录

whereis命令只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。

和find相比,whereis查找的速度非常快,这是因为linux系统会将 系统内的所有文件都记录在一个数据库文件中,当使用whereis和下面即将介绍的locate时,会从数据库中查找数据,而不是像find命令那样,通 过遍历硬盘来查找,效率自然会很高。

但是该数据库文件并不是实时更新,默认情况下时一星期更新一次,因此,我们在用whereis和locate 查找文件时,有时会找到已经被删除的数据,或者刚刚建立文件,却无法查找到,原因就是因为数据库文件没有被更新。

Linux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。

crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。

Linux下的任务调度分为两类,系统任务调度和用户任务调度。

系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。

用户任务调度:用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。

用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:

minute hour day month week command

command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。如图:
这里写图片描述

在以上各个字段中,还可以使用以下特殊字符

星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。

逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”

中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”

正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。

简言之,cookie是浏览器存储在用户电脑上的一些数据;每次发起请求时,浏览器都会将对应的cookie数据一起发送至服务器。

HTTP协议是无状态的,也就是说客户端和服务器端不需要建立持久的连接。由于客户端和服务器的连接是基于一种请求应答模式,及客户端和服务器建立一个连接,客户端提交一个请求,服务器端收到请求后返回一个响应,然后二者就断开连接

这里写图片描述

cookie是有生命周期的,一旦到了cookie的失效日期,客户端的cookie就会被删除。服务器在创建cookie时可以控制一个cookie可以在客户端“存活”多长时间。在以下几种情况下,cookie都会结束它自己的生命周期:

1.未指定过期时间的cookie;当服务器创建一个cookie的时候没有指定对应的过期时间时,客户端会将这类cookie写入浏览器开辟的一块内存中,当关闭浏览器以后,这块内存也就被释放了,对应的cookie也就是结束了它的生命;可看出,默认的生命周期很短。

2.指定过期时间的cookie;当服务器创建一个cookie的时候指定了对应的过期时间时,当到达了过期时间时,对应的cookie就会被删除;

3.当浏览器中的cookie数量达到了限制时,那么浏览器就会按照某种策略删除一些旧的cookie,腾出空间来创建新的cookie;

4.可以手动删除cookie。

但是浏览器对于cookie的写入肯定是有限制的,无规矩,不成方圆啊,cookie的管理

总之,在进行cookie操作的时候,应该尽量保证cookie个数小于20个,总大小小于4KB。

除了个数和大小的管理以外,每个站点各自的写的cookie,浏览器又如何管理呢?我们在服务器端创建一个cookie的时候,一般都会指定以下两个选项:

1.domain选项
2.path选项

这两个选项决定了创建的cookie属于哪个域名下的哪个位置。对于domain选项,默认情况下,domain会被设置为创建该cookie的页面所在的域名,所以当给相同域名发送请求时,该cookie会一起被发送至服务器。比如百度这样的大站,有很多的二级域名,例如:http://music.baidu.com/、http://picture.baidu.com/等,如果需要在所有的二级域名下都记录一个cookie,则需要在创建cookie的时候,将cookie的domain选项设置为baidu.com;此时域名为baidu.com下的所有二级域名都将拥有同样的一个cookie。创建域名时指定domain,这又是一个难点,经常会出现顶级域名和二级域名的cookie冲突问题

我们在发送请求时,浏览器会把domain的值与请求的域名做一个尾部比较(即从字符串的尾部开始比较),并将匹配的cookie发送至服务器。

1.当我们未指定domain时,默认的domain为用哪个域名访问就是哪个。如果是顶级域名访问,那么设置的cookie也可以被其他二级域名所共享,因此登录等操作一般都在顶级域名下进行操作

2.二级域名可以读取设置了domain为顶级域名或者自身的cookie,但是不能读取其他二级域名domain的cookie,因此想要cookie在多个二级域名中共享的时候,需要设置domain为顶级域名,这样就可以在所有二级域名里面使用该cookie,这里需要注意的是顶级域名只能获取到domain设置为顶级域名的cookie,无法获取domain设置为二级域名的cookie

3.顶级域名的cookie在顶级域名或者二级域名都可以删除,但是非顶级域名访问的网站要删除顶级域名的cookie的时候,必须要设置获取到的cookie的domain为顶级域名,这样才能删除掉顶级域名的cookie,否则会无法删除。这里默认是删除访问的域名下对应的cookie,而不是顶级域名的那个。

举个例子来说:

1.我通过浏览器访问http://www.jellythink.com,请求到达服务器以后,我创建cookie时,指定的domain只能是www.jellythink.com、.jellythink.com或者jellythink.com

2.而当我通过浏览器访问http://picture.jellythink.com,请求到达服务器以后,我创建cookie时,指定的domain只能是picture.jellythink.com、.jellythink.com或者jellythink.com

说完domain,再来说说path选项。 在创建cookie的时候,也可以指定一个path值,path选项指定了请求的资源URL中只有在存在指定的路径时,才会发送Cookie消息头,它决定了客户端发送cookie到服务器端的匹配规则。通常是将path选项的值与请求的URL从头开始逐字符比较,如果字符匹配,则发送Cookie消息头。需要注意的是,只有在domain选项满足之后才会对path属性进行比较。path属性的默认值是发送Set-Cookie消息头所对应的URL中的path部分

创建cookie

服务器可以指定以下常用内容:

1.domain
2.path
3.maxage
设置浏览器何时删除cookie
4.secure
Secure字段告诉浏览器在https通道时,对Cookie进行安全加密,这样即时有黑客监听也无法获取cookie内容;默认情况下,在HTTPS连接上传输的cookie都会被自动添加上secure选项。当secure值为true时,cookie在HTTP中是无效,在HTTPS中才有效
5.httponly
HttpOnly字段告诉浏览器,只有在HTTP协议下使用,对浏览器的脚本不可见,所以跨站脚本攻击时也不会被窃取,此时JS则无法访问带有httponly的cookie

5.关于HTTP Session

Cookie机制采用的是在客户端保持状态的方案;而Session机制采用的是在服务器端保持状态的方案。Cookie的作用主要是为了解决HTTP协议无状态的问题,而Session机制则是一种在客户端与服务器之间保持状态的解决方案。

Cookie使用起来很方便,使用cookie也能完成session的工作;但是使用cookie有一个很大的弊端:

1.cookie中的所有数据在客户端可以被修改,数据非常容易被伪造,那么一些重要的数据就不能存放在cookie中,安全考虑
2.如果cookie中数据字段太多会影响传输效率,效率考虑

Session是如何工作的呢?

当用户访问到一个服务器,服务器首先检查请求中是否包含了SessionID,如果包含,按照该ID找到内存中的Session,如果没有,则创建一个Session,并生成对应的SessionID。这个SessionID是唯一的、不重复的、不容易找到规律的字符串,这个SessionID将在本次响应中返回到客户端保存,而保存这个SessionID的正是cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。

从上面可以看出,没有了session,cookie依然可以玩的好好的;但是如果没有cookie的支持,session就不能愉快的玩耍了;session需要一种能够在客户端和服务器之间进行交互时,传递SessionID的机制,我们一般都是使用cookie来完成传递SessionID的任务,但是还有一种叫做URL重写的东西,也能完成这个任务。

session共享

集群之间Session共享的问题,具体可以看看大型网站技术架构中的一些解答。

6.linux基础命令,统计重复度

去除重复行sort file |uniq
查找非重复行sort file |uniq -u
查找重复行sort file |uniq -d
统计sort file | uniq -c

主要就是用到sort和uniq这两个命令,参考linux sort,uniq,cut,wc命令详解

uniq命令可以去除排序过的文件中的重复行,因此uniq经常和sort合用。也就是说,为了使uniq起作用,所有的重复行必须是相邻的

cut命令可以从一个文本文件或者文本流中提取文本列。

根据下列文本的第二列计算重复度,可以使用如下命令:

//文本for:10for:5454fsd:fdfs:10996:10for:kffor:914

命令:

cat ./Desktop/ff.txt | cut -d ':' -f 2 | sort | uniq -cd

输出:3 10

注意,在大多数情况下,sort和uniq是一对combo

7.进程和线程有什么区别?

一、关于进程和线程,首先从定义上理解就有所不同

1、进程是什么?
是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
2、线程又是什么?
线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。 在运行时,只是暂用一些计数器、寄存器和栈 。

二、他们之间的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。

三、从三个角度来剖析二者之间的区别
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

8.随机洗牌算法

一些随机算法,比如random,在多次实验后分布并不平均。

Fisher–Yates随机置乱算法也被称做高纳德置乱算法,通俗说就是生成一个有限集合的随机排列。Fisher-Yates随机置乱算法是无偏的,所以每个排列都是等可能的,当前使用的Fisher-Yates随机置乱算法是相当有效的,需要的时间正比于要随机置乱的数,不需要额为的存储空间开销。

算法过程:

1.需要随机置乱的n个元素的数组a
2.从0到n开始循环,循环变量为i
3.生成随机数K,K为0到n之间的随机数
4.交换i位和K位的值

9.tcp的最大连接数,tcp与udp的区别?

参考单机最大tcp连接数
参考最大连接数不再争论65535

系统用一个4四元组来唯一标识一个TCP连接:{local ip, local port,remote ip,remote port}。针对这个唯一标识来做文章。

参考tcp和udp的区别

这里写图片描述

TCP协议和UDP协议特性区别总结
1. TCP协议在传送数据段的时候要给段标号;UDP协议不
2. TCP协议可靠;UDP协议不可靠
3. TCP协议是面向连接;UDP协议采用无连接
4. TCP协议负载较高,采用虚电路;UDP采用无连接
5. TCP协议的发送方要确认接收方是否收到数据段(3次握手协议)
6. TCP协议采用窗口技术和流控制

10.tcp如何保证可靠传输?

参考TCP可靠传输&流量控制&拥塞控制

TCP可靠传输的工作原理

1.无差错

A发送分组M1,发送就暂停发送,等待B的确认,B收到M1就向A发送确认,A收到对M1的确认后再发送下一个分组。(若A收到连续的M1分组的确认信息,则证明M2缺失)

A只要超过一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,就重传前面发过的分组,叫超时重传。由重传计时器实现。

2.超时重传

其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。

3.停止等待协议

优点是简单,但是信道利用率太低了。解决方法是采用连续ARQ协议,发送方维持发送窗口,每次连续发送几个分组,接收方采用累积确认,对按序到达的最后一个分组发送确认。

缺点是不能向发送方反映出接收方已经正确收到的所有分组信息,例如丢失中间的分组。

TCP 连接的每一端都必须设有两个窗口——一个发送窗口和一个接收窗口。TCP 的可靠传输机制用字节的序号进行控制。TCP 所有的确认都是基于序号而不是基于报文段。

发送过的数据未收到确认之前必须保留,以便超时重传时使用。发送窗口不动(没收到确认)和前移(收到新的确认)

发送缓存用来暂时存放: 发送应用程序传送给发送方 TCP 准备发送的数据;TCP 已发送出但尚未收到确认的数据。
接收缓存用来暂时存放:按序到达的、但尚未被接收应用程序读取的数据; 不按序到达的数据。

必须强调三点

1> A 的发送窗口并不总是和 B 的接收窗口一样大(因为有一定的时间滞后)。

2> TCP 标准没有规定对不按序到达的数据应如何处理。通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。

3> TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销

滑动窗口图解:
这里写图片描述
这里写图片描述
这里写图片描述

TCP拥塞控制和流量控制的差别

1.拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能承受现有的网络负荷。

2.流量控制往往指的是点对点通信量的控制,是个端到端的问题。流量控制所要做的就是控制发送端发送数据的速率,以便使接收端来得及接受。

TCP流量控制

所谓的流量控制就是让发送方的发送速率不要太快,让接收方来得及接受。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。

TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值

TCP拥塞控制

在某段时间,若对网络中的某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变化,这种情况叫做拥塞。

因特网建议标准RFC2581定义了进行拥塞控制的四种算法,即1.慢开始(Slow-start)2.拥塞避免(Congestion Avoidance)3.快重传(Fast Restrangsmit)4.快恢复(Fast Recovery)

这里写图片描述

发送报文段速率的确定,1.既要根据接收端的接收能力2.又要从全局考虑不要使网络发生拥塞,这由接收窗口和拥塞窗口两个状态量确定。

接收窗口:接收端窗口(Reciver Window)又称通知窗口(Advertised Window),是接收端根据目前的接收缓存大小所许诺的最新窗口值,是来自接收端的流量控制

拥塞窗口:拥塞窗口cwnd(Congestion Window)是发送端根据自己估计的网络拥塞程度而设置的窗口值,是来自发送端的流量控制

慢启动的原理就是不一次将发送窗口的全部字节注入到网络中,而是通过拥塞窗口来试探网络中的拥塞程度,然后根据这个反馈来继续下一步行动,来自自动控制中的闭环思想。

一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传快恢复的拥塞控制方法。

快重传算法并非取消了重传机制,只是在某些情况下更早的重传丢失的报文段(如果当发送端接收到三个重复的确认ACK时,则断定分组丢失,立即重传丢失的报文段,而不必等待重传计时器超时)。

Seq:就是告诉接收方:我发送的数据是从seq开始的。

Ack:就是告诉接收方:我希望下次收到对端发过来的seq序号。

如下图:
这里写图片描述

该图中,将实施快重传来传输5500段。

11.两人比赛,赢第 i 场得 i 分,最后比分为 x:y ,问是否有可能

动态规划入门,非常易懂!

国王分配任务给左右大臣的例子很好!

思考动态规划的第一点—-最优子结构

国王相信,只要他的两个大臣能够回答出正确的答案(对于考虑能够开采出的金子数,最多的也就是最优的同时也就是正确的),再加上他的聪明的判断就一定能得到最终的正确答案。我们把这种子问题最优时母问题通过优化选择后一定最优的情况叫做“最优子结构”。

思考动态规划的第二点—-子问题重叠

实际上国王也好,大臣也好,所有人面对的都是同样的问题,即给你一定数量的人,给你一定数量的金矿,让你求出能够开采出来的最多金子数。我们把这种母问题与子问题本质上是同一个问题的情况称为“子问题重叠”。然而问题中出现的不同点往往就是被子问题之间传递的参数,比如这里的人数和金矿数。

思考动态规划的第三点—-边界

想想如果不存在前面我们提到的那些底层劳动者的话这个问题能解决吗?永远都不可能!我们把这种子问题在一定时候就不再需要提出子子问题的情况叫做边界,没有边界就会出现死循环。

思考动态规划的第四点—-子问题独立

要知道,当国王的两个大臣在思考他们自己的问题时他们是不会关心对方是如何计算怎样开采金矿的,因为他们知道,国王只会选择两个人中的一个作为最后方案,另一个人的方案并不会得到实施,因此一个人的决定对另一个人的决定是没有影响的。我们把这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做“子问题独立”。

这就是动态规划,具有“最优子结构”、“子问题重叠”、“边界”和“子问题独立”,当你发现你正在思考的问题具备这四个性质的话,那么恭喜你,你基本上已经找到了动态规划的方法

思考动态规划的第五点—-做备忘录

正如上面所说的一样,当我们遇到相同的问题时,我们可以问同一个人。讲的通俗一点就是,我们可以把问题的解放在一个变量中,如果再次遇到这个问题就直接从变量中获得答案,因此每一个问题仅会计算一遍,如果不做备忘的话,动态规划就没有任何优势可言了。

思考动态规划的第六点—-时间分析

正如上面所说,如果我们用穷举的方法,至少需要2^n个常数时间,因为总共有2^n种情况需要考虑,如果在背包问题中,包的容量为1000,物品数为100,那么需要考虑2^100种情况,这个数大约为10的30次方。

而如果用动态规划,最多大概只有1000*100 = 100000个不同的问题,这和10的30次方比起来优势是很明显的。而实际情况并不会出现那么多不同的问题,比如在金矿模型中,如果所有的金矿所需人口都是1000个人,那么问题总数大约只有100个。

非正式地,我们可以很容易得到动态规划所需时间,如果共有questionCount个相同的子问题,而每一个问题需要面对chooseCount种选择时,我们所需时间就为questionCount * chooseCount个常数

那么遇到问题如何用动态规划去解决呢?根据上面的分析我们可以按照下面的步骤去考虑:

1、构造问题所对应的过程。

2、思考过程的最后一个步骤,看看有哪些选择情况。

3、找到最后一步的子问题,确保符合“子问题重叠”把子问题中不相同的地方设置为参数

4、使得子问题符合“最优子结构”。

5、找到边界,考虑边界的各种处理方式。

6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。

7、考虑如何做备忘录。

8、分析所需时间是否满足要求。

9、写出转移方程式。

回到这个问题,我们可以从比赛最开始来推测比分,当比分不符合某种规则时舍弃(比如若最终比分为10:5,那么中间过程是不会出现11:4或者10:6的),然后比较最终比分在不在所有可能比分的集合里。

当然,我们也可以从后往前,进行推测,都是ok的。

关于动态规划的经典问题有:

1.最长公共子序列(LCS)
2.最有排序二叉树
3.最长上升子序列(LIS)
4.01背包问题
5.最大m字段和

12.在浏览器中输入网址,经过了哪些过程?

参考URL后回车–知乎
参考What really happens when you navigate to a URL

步骤大概是:

  1. You enter a URL into the browser
  2. The browser looks up the IP address for the domain name
  3. The browser sends a HTTP request to the web server
  4. The facebook server responds with a permanent redirect
  5. The browser follows the redirect
  6. The server ‘handles’ the request
  7. The server sends back a HTML response
  8. The browser begins rendering the HTML
  9. The browser sends requests for objects embedded in HTML
  10. The browser sends further asynchronous (AJAX) requests

13.怎么看在程序中CPU 使用过高的地方在哪

如果知道一个进程的pid,可以通过下列命令查看一个进程的占用情况:

ps -p <pid> -o %cpu,%mem,cmd

如果要按照cpu和内存占用情况,从高到低排序,可以用下列命令:

ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem,-%cpu | head

I use --sort to sort by either %mem or %cpu. By default, the output will be sorted in ascendant form, but personally I prefer to reverse that order by adding a minus sign in front of the sort criteria.

14.计算机网络之面试常考

计算机网络知识点

每一层的协议如下:

物理层:RJ45、CLOCK、IEEE802.3 (中继器,集线器,网关)
数据链路:PPP、FR、HDLC、VLAN、MAC (网桥,交换机)
网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)
传输层:TCP、UDP、SPX
会话层:NFS、SQL、NETBIOS、RPC
表示层:JPEG、MPEG、ASII
应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS

每一层的作用如下:

物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)

数据链路层:将比特组装成帧和点到点的传递(帧Frame)

网络层:负责数据包从源到宿的传递和网际互连(包PackeT)

传输层:提供端到端的可靠报文传递和错误恢复(段Segment)

会话层:建立、管理和终止会话(会话协议数据单元SPDU)

表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)

应用层:允许访问OSI环境的手段(应用协议数据单元APDU)

IP地址与子网掩码相与得到主机号

ARP是地址解析协议(根据IP地址获取MAC物理地址的一个TCP/IP协议),简单语言解释一下工作原理。

1:首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。

2:当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机IP地址,源主机MAC地址,目的主机的IP地址。

3:当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。即,如果是的话,要更新自己的ARP列表,同时也要发送响应。

4:源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。

广播发送ARP请求,单播发送ARP响应

各种协议

ICMP协议:用于在IP主机、路由器之间传递控制消息。
TFTP协议:提供不复杂、开销不大的文件传输服务。
NAT协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术。
DHCP协议:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。

TCP三次握手和四次挥手的全过程

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

TCP对应的协议和UDP对应的协议

TCP对应的协议:

(1) FTP:定义了文件传输协议,使用21端口。

(2) Telnet:一种用于远程登陆的端口,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。

(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是25号端口。

(4) POP3:它是和SMTP对应,POP3用于接收邮件。POP3协议所用的是110端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议。

UDP对应的协议:

(1) DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。

(2) SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。

(3) TFTP(Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。

端口及对应的服务?

FTP:21
SSH:22
TELNET:23
SMTP:25
Domian(域名服务器):53
POP3:110
NTP(网络时间协议):123
shell或cmd:514
SQL Server:1433

IP数据包的格式

IP数据包由1.首部2.数据两部分组成。首部由1.固定部分2.可选部分组成。首部的固定部分有 20 字节。可选部分的长度变化范围为1——40字节。

TCP数据报的格式

一个TCP报文段分为1.首部2.数据两部分。首部由1.固定部分2.选项部分组成,固定部分是20字节。TCP首部的最大长度为60。

用户数据报UDP1.首部2.数据部分组成。首部只有8个字节,由4个字段组成,每个字段都是两个字节。

交换机、路由器、网关的不同

路由器的一个作用是连通不同的网络,另一个作用是选择信息传送的线路

在传统TCP/IP术语中,网络设备只分成两种,一种为网关(gateway),另一种为主机(host)。网关能在网络间转递数据包,但主机不能转送数据包。在主机(又称终端系统,end system)中,数据包需经过TCP/IP四层协议处理,但是在网关(又称中介系统,intermediate system)只需要到达网际层(Internet layer),决定路径之后就可以转送。在当时,网关(gateway)与路由器(router)还没有区别。

在现代网络术语中,网关(gateway)与路由器(router)的定义不同。网关(gateway)能在不同协议间移动数据,而路由器(router)是在不同网络间移动数据,相当于传统所说的IP网关(IP gateway)。

网关是连接两个网络的设备,对于语音网关来说,他可以连接PSTN网络和以太网,这就相当于VOIP(这个例子很好),把不同电话中的模拟信号通过网关而转换成数字信号,而且加入协议再去传输。在到了接收端的时候再通过网关还原成模拟的电话信号,最后才能在电话机上听到。

对于以太网中的网关只能转发三层以上数据包,这一点和路由是一样的。而不同的是网关中并没有路由表,他只能按照预先设定的不同网段来进行转发。网关最重要的一点就是端口映射,子网内用户在外网看来只是外网的IP地址对应着不同的端口,这样看来就会保护子网内的用户

15.java hashmap内部是怎样实现的?

参考1:hashmap-youtube

首先,hashmap是map的一种,那么也就意味着它内部存储的是key-value对。称为hashmap是因为其内部用了hashing这种技术。

在存储value时,我们存储的是对象的hashcode,这样就要求不同的对象的hashcode必须是不一样的。这也很好解释了为什么重写equals方法时要连带重写hashcode方法!

关于hashing:

transformation of a string of characters(text) to a shorted fixed-length value that represents orginal string.a shorter string helps in indexing and faster search.

in java,every object hash a method hashcode() that will return the hashcode of the given object.

hashmap 内部保持了一个table,然后在每个table[index]中实际上相当于存储了一个linked list,见下图:
这里写图片描述

仔细看看,很简单。

这个table默认初始化大小为16.

比如说scores.put("KING",100),会首先计算出”KING”的hashcode,然后找出该hashcode在此时大小的table中的index(最简单的方法是取余数啦),然后存入。

在java内部实现并不是单纯的取余数,而是下面这行代码(为了更快):

/**     * Returns index for hash code h.     */    static int indexFor(int h, int length) {        return h & (length-1);    }

当出现collision时,即在该index处已经有值了,那么就加在该index存储的list后面,如下图:
这里写图片描述

hashmap允许存储null key和null value,因为null的hashcode为0,所以所有的null都在index 0处。

get操作跟put操作相反,也需要先计算hashcode,然后得到index,然后从该index 存储的list头部开始比较hashcode,如果相等,则返回对应的value,如果不等,继续查找。

在java 8中,如果不同的hashcode得到的相同index很多,即某个index下的list很长,超过一定的值(TREEIFY_THREHOLD),那么在该list将转换为balanced tree,这将使得循环的最坏时间复杂度从O(N)降为O(logN)。

在java代码实现中,通过内部类Entry记录每一项:

static class Entry<K,V> implements Map.Entry<K,V> {        final K key;        V value;        Entry<K,V> next;        int hash;        }

参考2:How is Hashmap in Java implemented internally?

HashMap maintains an array of buckets. Each bucket is a linkedlist of key value pairs encapsulated as Entry objects

This array of buckets is called table. Each node of the linked list is an instance of a private class called Entry.

default values of these fields are :
initial capacity : 1 << 4 (ie 16)
load factor : 0.75

Whenever the element count of the hashmap reaches the load factor fraction of capacity, the map is resized and capacity is doubled.

Fail-fast iterator :

HashMap specifications need that iterators should throw ConcurrentMoodificationException if the map contents are changed while a client is iterating over an iterator

This done by keeping track of number of modifications. HashMap has a member int variable named modCount which is incremented everytime the map is altered (any invocation of put(), remove(), putAll() or clear() methods),代码如下:

transient int modCount;

同样,在HashIterator<E>中,也有类似的一个变量:

int expectedModCount;   // For fast-fail

For every call to any of iterator methods (next(), hasNext() and remove() ) the iteratorModCount is checked against the HashMap modCount.上述方法中,要检查是否一致,如果不一致,那么就报错。

collections representing keySet and values :

HashMap specifications require that keySet(), values() and entrySet() methods complete with O(1) space complexity - which basically means HashMap cant copy the data into a new collection and return it. Rather these collections have to point to the same location in memory as the actual HashMap contents.不可能存入实际的数据啦,只可能存入refere,对么?

This is done by maintaining non-static inner classes called KeySet and EntrySet both of which extend AbstractSet. By virtue of being non static, they implicitly carry a reference to the outer class object (HashMap.this)。这些内部类都引用了外部的map的this指针。

rehashing :

Note that capacity is always a power of 2 (ie 1 followed by a sequence of zeroes in binary). So (capacity - 1) is a sequence of 1’s in binary. So if the capacity is 2^n, then only the lower n bits of hash are useful and the upper bits beyond that are ignored.这个方式很聪明啊!在hash时确实只需要低位。

dynamic resizing

在转换旧的array到新的array中时,将会循环遍历每个元素。所以,在整个过程结束之后, the order of the linked list elements are reversed.

Suppose original linkedlist was 1->2->3
Lets assume after resizing, every element again goes into same bucket
So, after first iteration, 1->null
after second iteration, 2->1->null
after third iteratuion, 3->2->1->null thus the link list gets reversed

This creates a potential race condition during resizing. Suppose two threads parallely decide that the HashMap needs resizing and try to resize. This may lead to an infinite loop.当然,本来就不应该在多线程中使用hashmap。

put operation

The put operation performs the following steps :
1. calculate hashcode for key
2. rehash it. lets call the rehashed results as h
3. calculate bucket index as h & (capacity -1)
4. now iterate over the bucket and compare key with all existing keys using equals()
5. if the key already exists, change the value of that Entry object
6. else create a new Entry object and add to the head of the linked list
7. increment mod count
8. resize if necessary

注意在上述put操作中,是将新加入的元素置入到list的head中!也就是最近加入的元素具有更快的访问速度吧。这也是为什么resize的时候会出现order reversed的情况。如果是加入尾部的话,肯定就不存在order reversed的情况啦,但这样新加入元素的操作会随着list的增加而复杂度增大。所以,加入list的head是非常明智的!

get operation

The get operation performs the following steps :
1. calculate hashcode for key
2. rehash it. lets call the rehashed results as h
3. calculate bucket index as h & (capacity -1)
4. now iterate over the bucket and compare key with all existing keys using equals()
5. if the key already exists return the corresponding value in the Entry object

关于load factor:

这里写链接内容

An instance of HashMap has two parameters that affect its performance: 1.initial capacity and 2.load factor. The capacity is the number of buckets in the hash table, and the initial capacity is simply the capacity at the time the hash table is created. The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs(默认的load factor在时间和空间复杂度上进行了权衡). Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.

别想多了,他就是一个负载因子而已。

16.java int和Integer的区别?可以类比很多基本类型与封装类型

参考What is the difference between an integer and int in Java?

As others have said, Integer is a proper Object. It even extends from the Number interface, so it can be interchanged with other number alike object types. All of this to keep the Object Oriented aspect.

But then for performance reasons there are also access to the core primitive types, boolean, int, long, so they can be used when performance or simplicity is needed.

For example, int i = 2 will allocate 32 bits to store the number 2, whilst Integer i = Integer.valueOf(2) will allocate a full object (never mind the internal structures inside an object), which by design has an private int inside.分配的空间大小不同

If you do i = i + 2, with the int, the value inside those 32 bits gets incremented with a single CPU instruction. Fast.

If you do i = i + 2 with the Integer, in reality you’re doing i = Integer.valueOf(i.intValue() + 2), calling a method to retrieve the int value, incrementing by 2, and creating a new object with that value.

So in simple words, use Integer (Long, Boolean.TRUE/FALSE) when you need an object - e.g. for the List or Map - but use an int/long/boolean for fast calculations.

在需要性能的时候用primitive,在需要对象时用封装类。

Also, as mentioned, using Integer allows to use null as a mark of “no number”. Because Boolean has only two states (note: never do a new Boolean(val)!), I use and abuse it to define true/false/unknown :)

此外,封装类还多了一种状态。

参考java int与integer的区别

17.有关于treemap

TreeMap is just an implementation of Map that happens to use a red-black tree behind the scenes(使用了红黑树). The details of the tree aren’t exposed to you, so you can’t store elements in arbitrary locations.

参考How TreeMap works in java : 10 TreeMap Java Interview Questions

面试经常问哦。

According to Java doc :

Treemap is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the containsKey, get, put and remove operations. Algorithms are adaptations of those in Cormen, Leiserson, and Rivest’s Introduction to Algorithms.

既然是红黑树,那么红黑树有什么特征呢?

Red Black tree has the following properties :

  1. As the name of the algorithm suggests ,color of every node in the tree is either red or black.只有红黑

  2. Root node must be Black in color.根节点必为黑

  3. Red node can not have a red color neighbor node.

  4. All paths from root node to the null should consist the same number of black nodes .

Interviewer : Why and when we use TreeMap ?

We need TreeMap to get the sorted list of keys in ascending order.

Interviewer : What is the runtime performance of the get() method in TreeMap and HashMap?

TreeMap : log(n)
HashMap : Constant time performance assuming elements disperses properly

Interviewer : What is “natural ordering” in TreeMap ?

“Natural” ordering is the ordering implied by the implementation of the Comparable interface by the objects used as keys in the TreeMap.

Interviewer : Why do we need TreeMap when we have sortedMap ?

sortedMap is a interface and TreeMap is the class implementing it .

Interviewer : Which data structure you will prefer in your code : HashMap or TreeMap ?

想要顺序就TreeMap,想要速度就HashMap。

Interviewer : Which copy technique (deep or shallow ) is used by the TreeMap clone() method ?

clone() method returns the shallow copy of the TreeMap instance . In shallow copy object B points to object A location in memory . In other words , both object A and B are sharing the same elements .The keys and values themselves are not cloned .

注意分清浅拷贝和深拷贝。

17.有关于hashset

实现set,最重要的应该是怎样判断value的不同。

看hashset内部的代码实现:

public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable{    private transient HashMap<E,Object> map;    // Dummy value to associate with an Object in the backing Map    private static final Object PRESENT = new Object();    public HashSet() {        map = new HashMap<>();    }    // SOME CODE ,i.e Other methods in Hash Set    public boolean add(E e) {        return map.put(e, PRESENT)==null;    }    // SOME CODE ,i.e Other methods in Hash Set}

所以,我们知道了,hashset保证数据独立性是因为内部使用了hashmap。

hashmap:The main point to notice in above code is that put (key,value) will return

  1. null , if key is unique and added to the map
  2. Old Value of the key , if key is duplicate

在hashset的add方法中利用了上述特性:

public boolean add(E e) {            return map.put(e, PRESENT)==null;       }

如果之前不存在就插入,然后返回true;如果之前存在,那么返回false。

ok,hashset就是利用了hashmap的特性。

18.关于hashmap,treemap和linkedhashmap有什么不同呢?

参考Difference between HashMap, LinkedHashMap and TreeMap

All three classes implement the Map interface and offer mostly the same functionality. The most important difference is the order in which iteration through the entries will happen(最重要的区别是遍历的顺序):

1.HashMap makes absolutely no guarantees about the iteration order. It can (and will) even change completely when new elements are added.HashMap肯定不能保证顺序啦
2.TreeMap will iterate according to the “natural ordering” of the keys according to their compareTo() method (or an externally supplied Comparator). Additionally, it implements the SortedMap interface, which contains methods that depend on this sort order.TreeMap可以保证顺序,顺序是由小到大
3.LinkedHashMap will iterate in the order in which the entries were put into the map。LinkedHashMap也可以保证顺序,不过是按照放入的顺序来返回的。

19.有关于concurrenthashmap

参考Java集合—ConcurrentHashMap原理分析

通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行

ConcurrentHashMap和Hashtable**主要区别就是围绕着**1.锁的粒度以及2.如何锁,可以简单理解成把一个大的HashTable分解成多个,形成了锁分离。

ConcurrentHashMap中主要实体类就是三个:
1.ConcurrentHashMap(整个Hash表)
2.Segment(桶)
3.HashEntry(节点)
对应图如下:
这里写图片描述

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。

hashmap可以在中间添加或者删除元素?哦,删除肯定可以,添加的话,不是每个新元素都添加到list的head部吗?对于中间添加,存疑。

实现的细节是使用final,让引用不能够被改变:

static final class HashEntry<K,V> {       final K key;       final int hash;       volatile V value;       final HashEntry<K,V> next;   }  

因为除了value都是final的,所以添加和删除只能在头部发生!另,为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

这样,对于中间元素的删除操作就需要复制前面的元素然后再删除。因为只能在头部产生操作嘛。

为了加快1.定位段以及2.段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。
这里注意,每次定位需要两次,显而易见嘛!

定位段的方法:

final Segment<K,V> segmentFor(int hash) {       return segments[(hash >>> segmentShift) & segmentMask];   } 

下面是ConcurrentHashMap的数据成员:

1. public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>  2.         implements ConcurrentMap<K, V>, Serializable {  3.     /** 4.      * Mask value for indexing into segments. The upper bits of a 5.      * key's hash code are used to choose the segment. 6.      */  7.     final int segmentMask;  8.   9.     /** 10.      * Shift value for indexing within segments. 11.      */  12.     final int segmentShift;  13.   14.     /** 15.      * The segments, each of which is a specialized hash table 16.      */  17.     final Segment<K,V>[] segments;  18. }

所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。
每个Segment相当于一个子Hash表

count用来统计该段数据的个数,它是volatile,它用来协调修改和读取操作,以保证读取操作能够读取到几乎最新的修改。协调方式是这样的,每次修改操作做了结构上的改变,如增加/删除节点(修改节点的值不算结构上的改变),都要写count值,每次读取操作开始都要读取count的值。

modCount统计段结构改变的次数,主要是为了检测对多个段进行遍历过程中某个段是否发生改变。

remove操作

整个操作是在持有段锁的情况下执行的,注意有复制的过程。

put操作

该方法也是在持有段锁(锁定整个segment)的情况下执行的,这当然是为了并发的安全,修改数据是不能并发进行的。

1.必须得有个判断是否超限的语句以确保容量不足时能够rehash
2.接下来,是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步

get操作

不需要锁,第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count 变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。

get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。

例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。

也就是非结构性修改可以得到最新值,结构性修改可能得不到最新值。

参考How does ConcurrentHashMap work internally?

总结下来就几点:

I would read the source of ConcurrentHashMap as it is rather complicated in the detail. In short it has

1.Multiple partitions which can be locked independently. (16 by default)
2.Using concurrent Locks operations for thread safety instead of synchronized.
3.Has thread safe Iterators. synchronizedCollection’s iterators are not thread safe.
4.Does not expose the internal locks. synchronizedCollection does.

20.有关线程,进程的所有面试汇总

线程间的通信、同步方式与进程间通信方式

秒杀多线程面试系列

21.关于Mysql

一、存储过程

我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。

也就是相当于编写一些函数,执行特定的功能,当需要执行这些功能时,就调用呗。

存储过程通常有以下优点:

1.存储过程增强了SQL语言的功能和灵活性。
2.存储过程允许标准组件是编程。
3.存储过程能实现较快的执行速度。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
4.存储过程能过减少网络流量。
5.存储过程可被作为一种安全机制来充分利用。

存储过程的创建:

1.格式

MySQL存储过程创建的格式:CREATE PROCEDURE 过程名 ([过程参数[,…]])[特性 …] 过程体

一个例子:

 mysql> DELIMITER //  mysql> CREATE PROCEDURE proc1(OUT s int)      -> BEGIN     -> SELECT COUNT(*) INTO s FROM user;      -> END     -> //  mysql> DELIMITER ; 

(1)这里需要注意的是DELIMITER //DELIMITER ;两句,DELIMITER是分割符的意思,因为MySQL默认以”;”为分隔符,如果我们没有声明分割符,那么编译器会把存储过程当成SQL语句进行处理,则存储过程的编译过程会报错,所以要事先用DELIMITER关键字申明当前段分隔符,这样MySQL才会将”;”当做存储过程中的代码,不会执行这些代码,用完了之后要把分隔符还原。

(2)过程体的开始与结束使用BEGINEND进行标识。

参考mysql存储过程详解这个文章,基础就够啦,用到再查。

二、触发器

参考MySQL的学习–触发器

MySQL包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行

创建触发器

在MySQL中,创建触发器语法如下:

CREATE TRIGGER trigger_nametrigger_timetrigger_event ON tbl_nameFOR EACH ROWtrigger_stmt

trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句。

由trigger_time和trigger_event的组合,可以看出有6中不同的触发器形式。

有一个限制是不能同时在一个表上建立2个相同类型的触发器,因此在一个表上最多建立6个触发器

BEGIN … END 详解

在MySQL中,BEGIN … END 语句的语法为:

BEGIN[statement_list]END

其中,statement_list 代表一个或多个语句的列表,列表内的每条语句都必须用分号(;)来结尾

示例

假设系统中有两个表:
班级表 class(班级号 classID, 班内学生数 stuCount)
学生表 student(学号 stuID, 所属班级号 classID)
要创建触发器来使班级表中的班内学生数随着学生的添加自动更新,代码如下:

DELIMITER $create trigger tri_stuInsert after inserton student for each rowbegindeclare c int;set c = (select stuCount from class where classID=new.classID);update class set stuCount = c + 1 where classID = new.classID;end$DELIMITER ;

触发器的执行顺序

我们建立的数据库一般都是 InnoDB 数据库,其上建立的表是事务性表,也就是事务安全的。这时,若SQL语句或触发器执行失败,MySQL 会回滚事务,有:

①如果 BEFORE 触发器执行失败,SQL 无法正确执行。
②SQL 执行失败时,AFTER 型触发器不会触发。
③AFTER 类型的触发器执行失败,SQL 会回滚。

三、事务

参考说说MySQL中的事务

由去ATM上取钱,引出事务的概念。

事务由一条或者多条sql语句组成,在事务中的操作,这些sql语句要么都执行,要么都不执行,这就是事务的目的

对于事务而言,它需要满足ACID特性,下面就简要的说说事务的ACID特性。

1.A(Atomicity),表示原子性;原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,整个事务的执行才算成功。事务中任何一个sql语句执行失败,那么已经执行成功的sql语句也必须撤销,数据库状态应该退回到执行事务前的状态;

2.C(Consistency),表示一致性;也就是说一致性指事务将数据库从一种状态转变为另一种一致的状态,在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏;

3.I(Isolation),表示隔离性;隔离性也叫做并发控制、可串行化或者锁。事务的隔离性要求每个读写事务的对象与其它事务的操作对象能相互分离,即该事务提交前对其它事务都不可见,这通常使用锁来实现

4.D(Durability),持久性,表示事务一旦提交了,其结果就是永久性的,也就是数据就已经写入到数据库了,如果发生了宕机等事故,数据库也能将数据恢复。

在Mysql中,事务还分为很多种:

有哪些事务

1.扁平事务;
2.带有保存点的扁平事务;
3.链事务;
4.嵌套事务;
5.分布式事务。

1.扁平事务
扁平事务是最简单的一种,也是实际开发中使用的最多的一种事务。在这种事务中,所有操作都处于同一层次,最常见的方式如下:

BEGIN WORK     Operation 1     Operation 2     Operation 3     ...     Operation N COMMIT WORK //或者 BEGIN WORK     Operation 1     Operation 2     Operation 3     ...     Operation N     (Error Occured) ROLLBACK WORK

扁平事务的主要缺点是不能提交或回滚事务的某一部分,或者分几个独立的步骤去提交。比如有这样的一个例子,我从呼和浩特去深圳,为了便宜,我可能这么干:

BEGIN WORK     Operation1:呼和浩特---火车--->北京     Operation2:北京---飞机--->深圳 ROLLBACK WORK

但是,如果Operation1,从呼和浩特到北京的火车晚点了,错过了航班,怎么办?感觉扁平事务的特性,那我就需要回滚,我再回到呼和浩特,那么这样成本是不是也太高了啊,所以就有了下面的第二种事务——带有保存点的扁平事务。不需要回滚全部

2.带有保存点的扁平事务
这种事务除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态,这是因为可能某些事务在执行过程中出现的错误并不会对所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点用来通知系统应该记住事务当前的状态,以便以后发生错误时,事务能回到该状态。

3.链事务
链事务,就是指回滚时,只能恢复到最近一个保存点;而带有保存点的扁平事务则可以回滚到任意正确的保存点。

4.嵌套事务
看下面这个,你就能明白了,啥是嵌套事务:

BEGIN WORK     SubTransaction1:             BEGIN WORK                 SubOperationX             COMMIT WORK     SubTransaction2:             BEGIN WORK                 SubOperationY             COMMIT WORK     ...     SubTransactionN:             BEGIN WORK                 SubOperationN             COMMIT WORK COMMIT WORK

这就是嵌套事务,在事务中再嵌套事务,位于根节点的事务称为顶层事务事务的前驱称为父事务,其它事务称为子事务

子事务既可以提交也可以回滚,但是它的提交操作并不马上生效,除非由其父事务提交。因此就可以确定,任何子事务都在顶层事务提交后才真正的被提交了。同理,任意一个事务的回滚都会引起它的所有子事务一同回滚

5.分布式事务
分布式事务通常是指在一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点,比如:通过建设银行向招商银行转账,建设银行和招商银行肯定用的不是同一个数据库,同时二者的数据库也不在一个网络节点上,那么当用户跨行转账,就是通过分布式事务来保证数据的ACID的。

MySQL中使用事务

在MySQL命令行的默认设置下,事务都是自动提交的,即执行SQL语句后就会马上执行COMMIT操作。因此要显示地开启一个事务须使用命令BEGIN或START TRANSACTION,或者执行命令SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

来看看我们可以使用哪些事务控制语句。

1.BEGIN或START TRANSACTION;显示地开启一个事务;
2.COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;
3.ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
4.SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
5.RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
6.ROLLBACK TO identifier;把事务回滚到标记点;
7.SET TRANSACTION;用来设置事务的隔离级别。InnoDB存储引擎提供事务的隔离级别有1.READ UNCOMMITTED2.READ COMMITTED3.REPEATABLE READ4.SERIALIZABLE

这些不用你“管”

有的时候有些SQL语句会产生一个隐式的提交操作,即执行完成这些语句后,会有一个隐式的COMMIT操作。有以下SQL语句,不用你去“管”:

1.DDL语句,ALTER DATABASE、ALTER EVENT、ALTER PROCEDURE、ALTER TABLE、ALTER VIEW、CREATE TABLE、DROP TABLE、RENAME TABLE、TRUNCATE TABLE等;

既然谈到DDL语句,这里补充一下数据库还有哪些语句呢?

1.DDL(Data Definition Language)数据库定义语言
CREATE
ALTER
DROP
TRUNCATE
COMMENT
RENAME
2.DML(Data Manipulation Language)数据操纵语言
SELECT
INSERT
UPDATE
DELETE
MERGE
CALL
EXPLAIN PLAN
LOCK TABLE
3.DCL(Data Control Language)数据库控制语言
授权,角色控制等
GRANT 授权
REVOKE 取消授权
4.TCL(Transaction Control Language)事务控制语言
SAVEPOINT 设置保存点
ROLLBACK 回滚
SET TRANSACTION

关于这几种语言应该没有疑惑了吧。回到正题!

2.修改MYSQL架构的语句,CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD;

3.管理语句,ANALYZE TABLE、CACHE INDEX、CHECK TABLE、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE等。

以上三种情况并不需要手动地commit。

事务的隔离级别

什么是隔离级别呢?

在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。如下图:
这里写图片描述

脏读:一个事务读取到了另外一个事务没有提交的数据
比如:事务T1更新了一行记录的内容,但是并没有提交所做的修改。事务T2读取到了T1更新后的行,然后T1执行回滚操作,取消了刚才所做的修改。现在T2所读取的行就无效了;重点:没有提交!

不可重复读:在同一事务中,两次读取同一数据,得到内容不同
比如:事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录。然后T1又再次读取这行记录,发现与刚才读取的结果不同。这就称为“不可重复”读,因为T1原来读取的那行记录已经发生了变化;重点:同一事务中,两次读取内容不同!

幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
比如:事务T1读取一条指定的WHERE子句所返回的结果集。然后事务T2新插入 一行记录,这行记录恰好可以满足T1所使用的查询条件中的WHERE子句的条件。然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行。这个新行就称为“幻像”,因为对T1来说这一行就像突然出现的一样

隔离级别越低,事务请求的锁越少或保持锁的时间就越短。InnoDB存储引擎默认的支持隔离级别是REPEATABLE READ;在这种默认的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE级别隔离。

我们可以用SET TRANSACTION语句改变单个会话或者所有新进连接的隔离级别。它的语法如下:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

注意:默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。如果使用GLOBAL关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。你需要SUPER权限来做这个。使用SESSION 关键字为将来在当前连接上执行的事务设置默认事务级别。 任何客户端都能自由改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。

四、Mysql语句的执行顺序

参考SQL逻辑查询语句执行顺序

有如下数据:

mysql> select * from table1; +-------------+----------+ | customer_id | city     | +-------------+----------+ | 163         | hangzhou | | 9you        | shanghai | | baidu       | hangzhou | | tx          | hangzhou | +-------------+----------+ 4 rows in set (0.00 sec) mysql> select * from table2; +----------+-------------+ | order_id | customer_id | +----------+-------------+ |        1 | 163         | |        2 | 163         | |        3 | 9you        | |        4 | 9you        | |        5 | 9you        | |        6 | tx          | |        7 | NULL        | +----------+-------------+ 7 rows in set (0.00 sec)

执行下列sql语句:

SELECT a.customer_id, COUNT(b.order_id) as total_orders FROM table1 AS a LEFT JOIN table2 AS b ON a.customer_id = b.customer_id WHERE a.city = 'hangzhou' GROUP BY a.customer_id HAVING count(b.order_id) < 2 ORDER BY total_orders DESC;

该sql语句的意思是:获得来自杭州,并且订单数少于2的客户。

那么,亲,有没有想过,这些语句执行的顺序是什么呢?

SQL逻辑查询语句执行顺序

直接给出正确顺序吧:

(7)     SELECT (8)     DISTINCT <select_list>(1)     FROM <left_table>(3)     <join_type> JOIN <right_table>(2)     ON <join_condition>(4)     WHERE <where_condition>(5)     GROUP BY <group_by_list>(6)     HAVING <having_condition>(9)     ORDER BY <order_by_condition>(10)    LIMIT <limit_number>

在这些SQL语句的执行过程中,都会产生一个虚拟表,用来保存SQL语句的执行结果(这是重点)

下面按步骤来:

1.第一步,执行FROM语句。我们首先需要知道最开始从哪个表开始的,这就是FROM告诉我们的。现在有了<left_table><right_table>两个表,我们到底从哪个表开始,还是从两个表进行某种联系以后再开始呢?它们之间如何产生联系呢?——笛卡尔积

其实笛卡尔积很简单嘛,如图,so easy:
这里写图片描述

上述,将table1和table2做笛卡尔积,得到一个虚拟表,总共有28(table1的记录条数 * table2的记录条数)条记录。这就是VT1的结果,接下来的操作就在VT1的基础上进行

2.然后执行ON,进行过滤,得到VT2。

+-------------+----------+----------+-------------+| customer_id | city     | order_id | customer_id |+-------------+----------+----------+-------------+| 163         | hangzhou |        1 | 163         || 163         | hangzhou |        2 | 163         || 9you        | shanghai |        3 | 9you        || 9you        | shanghai |        4 | 9you        || 9you        | shanghai |        5 | 9you        || tx          | hangzhou |        6 | tx          |+-------------+----------+----------+-------------+

3.添加外部行

这一步只有在连接类型为OUTER JOIN时才发生,如LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN。在大多数的时候,我们都是会省略掉OUTER关键字的,但OUTER表示的就是外部行的概念。

LEFT OUTER JOIN把左表记为保留表,得到的结果为:

+-------------+----------+----------+-------------+| customer_id | city     | order_id | customer_id |+-------------+----------+----------+-------------+| 163         | hangzhou |        1 | 163         || 163         | hangzhou |        2 | 163         || 9you        | shanghai |        3 | 9you        || 9you        | shanghai |        4 | 9you        || 9you        | shanghai |        5 | 9you        || tx          | hangzhou |        6 | tx          || baidu       | hangzhou |     NULL | NULL        |+-------------+----------+----------+-------------+

相对于VT2,可以看出是left join是补全了左表的信息。right join同理。

添加外部行的工作就是在VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表VT3。

4.执行WHERE过滤

但是在使用WHERE子句时,需要注意以下两点

1.由于数据还没有分组,因此现在还不能在WHERE过滤器中使用where_condition=MIN(col)这类对分组统计的过滤

2.由于还没有进行列的选取操作,因此在SELECT中使用列的别名也是不被允许的,如:SELECT city as c FROM t WHERE c=’shanghai’;是不允许出现的

当通过where过滤后,得到结果:

+-------------+----------+----------+-------------+| customer_id | city     | order_id | customer_id |+-------------+----------+----------+-------------+| 163         | hangzhou |        1 | 163         || 163         | hangzhou |        2 | 163         || tx          | hangzhou |        6 | tx          || baidu       | hangzhou |     NULL | NULL        |+-------------+----------+----------+-------------+

5.执行GROUP BY分组

GROU BY子句主要是对使用WHERE子句得到的虚拟表进行分组操作

+-------------+----------+----------+-------------+| customer_id | city     | order_id | customer_id |+-------------+----------+----------+-------------+| 163         | hangzhou |        1 | 163         || baidu       | hangzhou |     NULL | NULL        || tx          | hangzhou |        6 | tx          |+-------------+----------+----------+-------------+

在group by后,其实| 163 | hangzhou | 2 | 163 |这条记录并没有删去,我觉得,不然下面调用count都会是1啊。

比如官方的一些sql语句SELECT species, COUNT(*) FROM pet GROUP BY species;, 想想这条语句,如果group by后后面记录删了,那么count(*)就没有意义啊,怎样都只会输出1。

6.执行HAVING过滤

HAVING子句主要和GROUP BY子句配合使用,对分组得到的VT5虚拟表进行条件过滤。当我执行测试语句中的HAVING count(b.order_id) < 2时,将得到以下内容:

这个count调用,我觉得应该在having之前,即紧接着group by后,这样才能去掉163那条记录。

+-------------+----------+----------+-------------+| customer_id | city     | order_id | customer_id |+-------------+----------+----------+-------------+| baidu       | hangzhou |     NULL | NULL        || tx          | hangzhou |        6 | tx          |+-------------+----------+----------+-------------+

关于count,有话要说。参考4.3.4.8 Counting Rows

If you name columns to select in addition to the COUNT() value, a GROUP BY clause should be present that names those same columns. Otherwise, the following occurs: 也就是在select中出现的columns,也要在group中得到反应,比如下面是合法的:

SELECT species, sex, COUNT(*) FROM pet GROUP BY species, sex;

否则会出现下面两种情况:
1.If the ONLY_FULL_GROUP_BY SQL mode is enabled, an error occurs:

mysql> SET sql_mode = 'ONLY_FULL_GROUP_BY';Query OK, 0 rows affected (0.00 sec)mysql> SELECT owner, COUNT(*) FROM pet;ERROR 1140 (42000): In aggregated query without GROUP BY, expression#1 of SELECT list contains nonaggregated column 'menagerie.pet.owner';this is incompatible with sql_mode=only_full_group_by

2.If ONLY_FULL_GROUP_BY is not enabled, the query is processed by treating all rows as a single group, but the value selected for each named column is indeterminate(如果没有打开,那么将所有行当成一个group,count(*)自然会得到所有行数,但是named column是不定的). The server is free to select the value from any row:

mysql> SET sql_mode = '';Query OK, 0 rows affected (0.00 sec)mysql> SELECT owner, COUNT(*) FROM pet;+--------+----------+| owner  | COUNT(*) |+--------+----------+| Harold |        8 |+--------+----------+1 row in set (0.00 sec)

7.SELECT列表

我们执行测试语句中的SELECT a.customer_id, COUNT(b.order_id) as total_orders,从虚拟表VT6中选择出我们需要的内容。我们将得到以下内容:

+-------------+--------------+| customer_id | total_orders |+-------------+--------------+| baidu       |            0 || tx          |            1 |+-------------+--------------+

8.执行DISTINCT子句

如果在查询中指定了DISTINCT子句,则会创建一张内存临时表(如果内存放不下,就需要存放在硬盘了)。这张临时表的表结构和上一步产生的虚拟表VT7是一样的,不同的是对进行DISTINCT操作的列增加了一个唯一索引,以此来除重复数据

由于我的测试SQL语句中并没有使用DISTINCT,所以,在该查询中,这一步不会生成一个虚拟表。

9.执行ORDER BY子句

对虚拟表中的内容按照指定的列进行排序,然后返回一个新的虚拟表,我们执行测试SQL语句中的ORDER BY total_orders DESC,就会得到以下内容:

+-------------+--------------+| customer_id | total_orders |+-------------+--------------+| tx          |            1 || baidu       |            0 |+-------------+--------------+

10.执行LIMIT子句

表示从第n条记录开始选择m条记录而很多开发人员喜欢使用该语句来解决分页问题。对于小数据,使用LIMIT子句没有任何问题,当数据量非常大的时候,使用LIMIT n, m是非常低效的因为LIMIT的机制是每次都是从头开始扫描,如果需要从第60万行开始,读取3条数据,就需要先扫描定位到60万行,然后再进行读取,而扫描的过程是一个非常低效的过程。所以,对于大数据处理时,是非常有必要在应用层建立一定的缓存机制(貌似现在的大数据处理,都有缓存哦)。各位,请期待我的缓存方面的文章哦。

有必要建立缓存机制!

五、MySQL联接查询操作

联接查询是一种常见的数据库操作,即在两张表(多张表)中进行匹配的操作。MySQL数据库支持如下的联接查询:
1.CROSS JOIN(交叉联接)
2.INNER JOIN(内联接)
3.OUTER JOIN(外联接)
4.其它

一定要记着第四节讲的执行顺序:
1.执行FROM语句(笛卡尔积)
2执行ON过滤
3.添加外部行

每个联接都只发生在两个表之间,即使FROM子句中包含多个表也是如此。每次联接操作也只进行逻辑查询语句的前三步,每次产生一个虚拟表,这个虚拟表再依次与FROM子句的下一个表进行联接,重复上述步骤,直到FROM子句中的表都被处理完为止。

1.CROSS JOIN联接(交叉联接)

CROSS JOIN对两个表执行FROM语句(笛卡尔积)操作,返回两个表中所有列的组合。如果左表有m行数据,右表有n行数据,则执行CROSS JOIN将返回m*n行数据。CROSS JOIN只执行SQL逻辑查询语句执行的前三步中的第一步

CROSS JOIN可以干什么?由于CROSS JOIN只执行笛卡尔积操作,并不会进行过滤,所以,我们在实际中,可以使用CROSS JOIN生成大量的测试数据

重点:只执行三步中的第一步

select * from table1 cross join table2;

2.INNER JOIN联接(内联接)

INNER JOIN比CROSS JOIN强大的一点在于,INNER JOIN可以根据一些过滤条件来匹配表之间的数据在SQL逻辑查询语句执行的前三步中,INNER JOIN会执行第一步和第二步;即没有第三步,不添加外部行,这是INNER JOIN和接下来要说的OUTER JOIN的最大区别之一。

重点:只执行三步中的第一步和第二步

select * from table1 inner join table2 on table1.customer_id=table2.customer_id;

对于INNER JOIN来说,如果没有使用ON条件的过滤,INNER JOIN和CROSS JOIN的效果是一样的。当在ON中设置的过滤条件列具有相同的名称,我们可以使用USING关键字来简写ON的过滤条件,这样可以简化sql语句,因为这里table1和table2中都有customer_id,所以可以使用using,例如:

select * from table1 inner join table2 using(customer_id);

在实际编写sql语句时,我们都可以省略掉INNER关键字,也就说inner join都可以简写成join。

3.OUTER JOIN联接(外联接)

left join和right join
重点:前三步都会执行

MySQL数据库支持LEFT OUTER JOIN和RIGHT OUTER JOIN,与INNER关键字一样,我们可以省略OUTER关键字。对于OUTER JOIN,同样的也可以使用USING来简化ON子句。所以,对于以下sql语句:

select * from table1 left join table2 using(customer_id);

4.NATURAL JOIN联接(自然连接)

NATURAL JOIN等同于INNER(OUTER) JOIN与USING的组合,它隐含的作用是将两个表中具有相同名称的列进行匹配。同样的,NATURAL LEFT(RIGHT) JOIN等同于LEFT(RIGHT) JOIN与USING的组合。比如:

select * from table1 join table2 using(customer_id);

可以写成:

select * from table1 natural join table2;

再比如:

select * from table1 left join table2 using(customer_id);

可以写成:

select * from table1 natural left join table2;

5.STRAIGHT_JOIN联接

主要跟性能优化有关!

6.多表联接

对于INNER JOIN的多表联接查询,可以随意安排表的顺序,而不会影响查询的结果。这是因为优化器会自动根据成本评估出访问表的顺序。如果你想指定联接顺序,可以使用上面总结的STRAIGHT_JOIN。

而对于OUTER JOIN的多表联接查询,表的位置不同,涉及到添加外部行的问题,就可能会影响最终的结果

六、MySQL子查询

一个例子:

现在需要查询所有杭州用户的所有订单号,这个SQL语句怎么写?首先,你可以这么写:

select table2.customer_id, table2.order_id from table2 join table1 on table1.customer_id=table2.customer_id where table1.city='hangzhou';

也可以写成子查询的样式:

select customer_id, order_id from table2 where customer_id in (select customer_id from table1 where city='hangzhou');

简而言之:

子查询就是指在一个select语句中嵌套另一个select语句。同时,子查询必须包含括号

我们可以在wherehaving子句中使用子查询,将子查询得到的结果作为判断的条件。

使用比较进行子查询

一个子查询会返回一个标量(就一个值)、一个行、一个列或一个表,这些子查询称之为标量、行、列和表子查询

当一个子查询返回一个标量时,我们就可以在where或者having子句中使用比较符与子查询得到的结果进行直接判断。比如,我现在要得到比用户tx订单数多的customer_id、city和订单数,这个sql语句怎么写。

select table1.customer_id,city,count(order_id) from table1 join table2 on table1.customer_id=table2.customer_id where table1.customer_id <> 'tx'group by customer_id having count(order_id) >                         (select count(order_id)                          from table2                          where customer_id='tx'                          group by customer_id);

使用ANY进行子查询

上面使用比较符进行子查询,规定了子查询只能返回一个标量值;但是,如果子查询返回的是一个集合,怎么办?

没问题,我们可以使用:any、in、some或者all来和子查询的返回结果进行条件判断

any:好比“10 >any(11, 20, 2, 30)”,由于10>2,所以,该该判断会返回TRUE;只要10与集合中的任意一个进行比较,得到TRUE时,就会返回TRUE。

in:in的意思就是指定的一个值是否在这个集合中,如何在就返回TRUE;否则就返回FALSE了。

all:all必须与比较操作符一起使用。all的意思是“对于子查询返回的列中的所有值,如果比较结果为TRUE,则返回TRUE”。

独立子查询

独立子查询是不依赖外部查询而运行的子查询。

看对比就知道了:

//独立select order_id from table2 where customer_id in                    (select customer_id                    from table1                    where city='hangzhou');//非独立select * from table1 where city='hangzhou' and exists                               (select *                                from table2                                where table1.customer_id=table2.customer_id);        

使用独立子查询,如果子查询部分对集合的最大遍历次数为n,外部查询的最大遍历次数为m时,我们可以记为:O(m+n)。而如果使用相关子查询,它的遍历次数可能会达到O(m+m*n)。可以看到,效率就会成倍的下降;所以,大伙在使用子查询时,一定要考虑到子查询的相关性。

派生表

上面也说到了,在子查询返回的值中,也可能返回一个表,如果将子查询返回的虚拟表再次作为FROM子句的输入时,这就子查询的虚拟表就成为了一个派生表。

七、MySQL处理数据库和表的常用命令

参考MySQL处理数据库和表的常用命令

复制表:create table tb_test2 select * from db_test.tb_test;

创建临时表:create temporary table emp_temp select firstname, lastname from tb_test;

更改表结构:

alter table tb_demo add column email varchar(45);alter table tb_demo change email new_name varchar(45) not null;alter table tb_demo drop email;

八、MySQL数据类型和属性

参考MySQL数据类型和属性

1.日期和时间数据类型
这里写图片描述

其中的timestamp,自动存储记录修改的时间,该功能记住!

2.数值数据类型
这里写图片描述

上面定义的都是有符号的,当然了,也可以加上unsigned关键字,定义成无符号的类型,那么对应的取值范围就要翻翻了,比如:

tinyint unsigned的取值范围为0~255。

这里写图片描述

3.字符串数据类型
这里写图片描述

可看出,只有char是固定长度的!

注意:

1.char(n)和varchar(n)中括号中n代表字符的个数,并不代表字节个数,所以当使用了中文的时候(UTF8)意味着可以插入m个中文,但是实际会占用m*3个字节。

2.超过char和varchar的n设置后,字符串会被截断

3.char在存储的时候会截断尾部的空格,varchar和text不会。

4.数据类型属性

1.auto_increment

MySQL要求将auto_increment属性用于作为主键的列。此外,每个表只允许有一个auto_increment列。

2.binary

binary属性只用于char和varchar值。当为列指定了该属性时,将以区分大小写的方式排序。与之相反,忽略binary属性时,将使用不区分大小写的方式排序。

3.default

subscribed enum('0', '1') not null default '0'

4.index

//创建索引create table employees(    id varchar(9) not null,    index lastname(lastname),    primary key(id));//添加索引create index lastname on employees (lastname(7));

这一次只索引了名字的前7个字符,因为可能不需要其它字母来区分不同的名字。因为使用较小的索引时性能更好,所以应当在实践中尽量使用小的索引。

5.not null

6.null

始终为null

7.primary key

primary key属性用于确保指定行的唯一性指定为主键的列中,值不能重复,也不能为空。为指定为主键的列赋予auto_increment属性是很常见的,因为此列不必与行数据有任何关系,而只是作为一个唯一标识符。

8.unique

被赋予unique属性的列将确保所有值都有不同的值,只是null值可以重复。primary key是不允许存在null。

22.关于设计模式

参考Java设计模式:十篇

我觉得的重点:单例,工厂,适配器,代理。

23.有向图中如何判断环的个数

无向图中环的检测应该有简单,用DFS即可,只需要增加parent,来判断不往parent方向回走就行。

有向图环的检测可以用拓扑排序,如果能够排出来,则没有环,否则有环。

关于无向图环检测Cycle in Undirected Graph Graph Algorithm

关于有向图环检测Detect Cycle in Directed Graph Algorithm

24.二叉树怎么判断同构?

参考二叉树怎么判断同构?
参考微软秋招

最优做法是hash+最小表示法,不过轮子的做法比较简单,也比较通用。

最优做法是hash+最小表示法,大致思路就是试着把二叉树用hash值表示出来。对整棵树进行后序遍历,将一个节点的左右子树hash值都求出之后,保证左子树的哈希值恒小于右子树,否则就交换左右子树,然后算该子树的hash值。
最后比对根节点hash值即可。

25.TOP K问题,找到最大或者最小的k个数,或者第k个数,或者出现频率为k的数

比如Top K Frequent Elements

26.海量数据问题

参考海量数据处理面试题

所谓海量数据处理,无非就是基于海量数据上的存储、处理、操作。何谓海量,就是数据量太大,所以导致要么是无法在较短时间内迅速解决,要么是数据太大,导致无法一次性装入内存。

那解决办法呢?针对时间,我们可以采用巧妙的算法搭配合适的数据结构,如Bloom filter/Hash/bit-map/堆/数据库或倒排索引/trie树,针对空间,无非就一个办法:大而化小:分而治之/hash映射,你不是说规模太大嘛,那简单啊,就把规模大化为规模小的,各个击破不就完了嘛。

处理海量数据问题,无非就是:

1.分而治之/hash映射 + hash统计 + 堆/快速/归并排序;
2.双层桶划分
3.Bloom filter/Bitmap;
4.Trie树/数据库/倒排索引;
5.外排序;
6.分布式处理之Hadoop/Mapreduce。

密匙一、分而治之/Hash映射 + Hash统计 + 堆/快速/归并排序

1、海量日志数据,提取出某日访问百度次数最多的那个IP

说白了,就是1.先映射2.而后统计3.最后排序

1.分而治之/hash映射:针对数据太大,内存受限,只能是:把大文件化成(取模映射)小文件,即16字方针:大而化小,各个击破,缩小规模,逐个解决
2.hash统计:当大文件转化了小文件,那么我们便可以采用常规的hash_map(ip,value)来进行频率统计。
3.堆/快速排序:统计完了之后,便进行排序(可采取堆排序),得到次数最多的IP。

2、搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节

假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

重复度高就用不着分而治之了,直接上hash统计,然后排序

利用hash统计和堆排序,我们最终的时间复杂度是:O(N) + N’*O(logK),(N为1000万,N’为300万)。

3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词

还是老套路:

1.分而治之/hash映射:顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200k左右。如果其中的有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M。
2.hash统计:对每个小文件,采用trie树/hash_map等统计每个文件中出现的词以及相应的频率。
3.堆/归并排序:取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件,这样又得到了5000个文件。最后就是把这5000个文件进行归并(类似于归并排序)的过程了。

这里为什么每个小文件取频率前100的就够了呢?因为用的是hash映射,相同的单词必然会被划分在一个文件中,所以不用担心会有漏掉的情况。

5、有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序

6、 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?

7、怎么在海量数据中找出重复次数最多的一个?

密匙二、双层桶划分

双层桶划分—-其实本质上还是分而治之的思想,重在“分”的技巧上
适用范围:第k大,中位数,不重复或重复的数字

基本原理及要点:因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行。可以通过多次缩小,双层只是一个例子。

10、2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数

有点像鸽巢原理,整数个数为2^32,也就是,我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便的解决。

关于bitmap还不是很懂。。。

11、5亿个int找它们的中位数

这个例子比上面那个更明显。首先我们将int划分为2^16个区域,然后读取数据统计落到各个区域里的数的个数,之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数。然后第二次扫描我们只统计落在这个区域中的那些数就可以了。

密匙三:Bloom filter/Bitmap

比如先把bitmap搞清楚啊!参考bitmap

什么是bitmap?

所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。主要是为了在存储空间方面有大大的提高!

比如下面的例子:

1)已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数

这个文件可能非常大哦,你能确保每个数读进内存不会爆炸吗?

这个时候可以用bitmap来缩小内存。8位数字,那么其范围是0到99999999,一共10的8次方。如果我们用bitmap,每个bit分别表示一个数,那么需要的空间为100000000/8/1024/1024 = 11.9MB。
只需要这点空间就能够存下所有的8为数字!

2)2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数

将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,如果对应位置的值是0,则将其置为1;如果是1,将其置为2;如果是2,则保持不变。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map,都是一样的道理。

知道了bitmap,那么我们再来看看Bloom Filter详解

什么是bloom filter?

Bloom Filter是一种空间效率很高的随机数据结构,它的原理是,当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位阵列(Bit array)中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检索元素一定不在;如果都是1,则被检索元素很可能在。这就是布隆过滤器的基本思想。

啊,这个bloom filter真是太厉害了!

但Bloom Filter的这种高效是有一定代价的在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省

在计算机科学中,我们常常会碰到时间换空间或者空间换时间的情况,即为了达到某一个方面的最优而牺牲另一个方面。Bloom Filter在时间空间这两个因素之外又引入了另一个因素:错误率。在使用Bloom Filter判断一个元素是否属于某个集合时,会有一定的错误率。也就是说,有可能把不属于这个集合的元素误认为属于这个集合(False Positive),但不会把属于这个集合的元素误认为不属于这个集合(False Negative)。在增加了错误率这个因素之后,Bloom Filter通过允许少量的错误来节省大量的存储空间。

基本原理及要点:
  对于原理来说很简单,位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的。同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是 counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了
  还有一个比较重要的问题,如何根据输入元素个数n,确定位数组m的大小及hash函数个数当hash函数个数k=(ln2)*(m/n)时错误率最小。在错误率不大于E的情况下,m至少要等于n*lg(1/E)才能表示任意n个元素的集合。但m还应该更大些,因为还要保证bit数组里至少一半为0,则m应该>=nlg(1/E)*lge 大概就是nlg(1/E)1.44倍(lg表示以2为底的对数)。
  举个例子我们假设错误率为0.01,则此时m应大概是n的13倍。这样k大概是8个
  注意这里m与n的单位不同,m是bit为单位,而n则是以元素个数为单位(准确的说是不同元素的个数)。通常单个元素的长度都是有很多bit的。所以使用bloom filter内存上通常都是节省的

12、给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?

根据这个问题我们来计算下内存的占用,4G=2^32大概是40亿*8大概是340亿,n=50亿,如果按出错率0.01算需要的大概是650亿个bit。现在可用的是340亿,相差并不多,这样可能会使出错率上升些。另外如果这些urlip是一一对应的,就可以转换成ip,则大大简单了。

13、在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数

方案1:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^32 * 2 bit=1 GB内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
方案2:也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。

14、腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?

方案1:frome oo,用位图/Bitmap的方法,申请512M的内存,一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。

密匙四、Trie树/数据库/倒排索引

Trie树
适用范围:数据量大,重复多,但是数据种类小可以放入内存
基本原理及要点:实现方式,节点孩子的表示方式
扩展:压缩实现。

数据库索引
适用范围:大数据量的增删改查
基本原理及要点:利用数据的设计实现方法,对海量数据的增删改查进行处理

倒排索引(Inverted index)
适用范围:搜索引擎,关键字查询
基本原理及要点:为何叫倒排索引?一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射

密匙五、外排序

适用范围:大数据的排序,去重
基本原理及要点:外排序的归并方法,置换选择败者树原理,最优归并树

问题实例:
1).有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16个字节,内存限制大小是1M。返回频数最高的100个词。
这个数据具有很明显的特点,词的大小为16个字节,但是内存只有1M做hash明显不够,所以可以用来排序。内存可以当输入缓冲区使用。

关于多路归并算法及外排序的具体应用场景,请参见此文:

2 0
原创粉丝点击