接收方的读缓冲区为0,当读缓冲区读完的时候,发送方如何知道该发送了?
1.接收方有空间时,会发送TCP包给发送方,带上窗口大小;
2.发送方收到0窗口TCP包,启动计时器,计时器到了之后,发送探测报文(带1byte数据)
如果B已经告诉A自己的缓冲区已满,于是A停止发送数据;等待一段时间后,B的缓冲区出现了富余,于是给A发送报文告诉A我的rwnd大小为400,但是这个报文不幸丢失了,于是就出现A等待B的通知||B等待A发送数据的死锁状态。为了处理这种问题,TCP引入了持续计时器(Persistence timer),当A收到对方的零窗口通知时,就启用该计时器,时间到则发送一个1字节的探测报文,对方会在此时回应自身的接收窗口大小,如果结果仍未0,则重设持续计时器,继续等待。
一个显而易见的问题是:单个发送字节单个确认,和窗口有一个空余即通知发送方发送一个字节,无疑增加了网络中的许多不必要的报文(请想想为了一个字节数据而添加的40字节头部吧!),所以我们的原则是尽可能一次多发送几个字节,或者窗口空余较多的时候通知发送方一次发送多个字节。
对于后者我们往往的做法是让接收方等待一段时间,或者接收方获得足够的空间容纳一个报文段或者等到接受缓存有一半空闲的时候,再通知发送方发送数据。
快排及四种优化方式
每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大
输入的数据是有序或部分有序的情况是相当常见的。因此,使用第一个元素作为枢纽元是非常糟糕的,为了避免这个情况,就引入了下面两个获取基准的方法。
- 随机选取基准
/*随机选择枢轴的位置,区间在low和high之间*/
int SelectPivotRandom(int arr[],int low,int high)
{
//产生枢轴的位置
srand((unsigned)time(NULL));
int pivotPos = rand()%(high - low) + low;
//把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数
swap(arr[pivotPos],arr[low]);
return arr[low];
}
- 当待排序序列的长度分割到一定大小后,使用插入排序
原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排 - 三数取中(median-of-three)
引入的原因:虽然随机选取枢轴时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取枢轴
待排序序列为:8 1 4 9 6 3 5 2 7 0
左边为:8,右边为0,中间为6.
我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6
注意:在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法
/*函数作用:取待排序序列中low、mid、high三个位置上数据,选取他们中间的那个数据作为枢轴*/
int SelectPivotMedianOfThree(int arr[],int low,int high)
{
int mid = low + ((high - low) >> 1);//计算数组中间的元素的下标
//使用三数取中法选择枢轴
if (arr[mid] > arr[high])//目标: arr[mid] <= arr[high]
{
swap(arr[mid],arr[high]);
}
if (arr[low] > arr[high])//目标: arr[low] <= arr[high]
{
swap(arr[low],arr[high]);
}
if (arr[mid] > arr[low]) //目标: arr[low] >= arr[mid]
{
swap(arr[mid],arr[low]);
}
//此时,arr[mid] <= arr[low] <= arr[high]
return arr[low];
//low的位置上保存这三个位置中间的值
//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了
}
实现两个变量的互换(不借助第3个变量
import java.util.Scanner;
/**
* 实现两个变量的互换(不借助第3个变量)
*/
public class Example {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);// 创建扫描器
System.out.println("请输入变量A的值");
long A = scan.nextLong();// 接收第一个变量值
System.out.println("请输入变量B的值");
long B = scan.nextLong();// 接收第二个变量值
System.out.println("这两个变量为:"+"A=" + A + "\tB=" + B);
A = A ^ B;// 执行变量互换,异或运算
B = B ^ A;
A = A ^ B;
System.out.println("执行变量互换的结果为:"+"A=" + A + "\tB=" + B);
}
}
优雅的谈论HTTP/1.0/1.1/2.0
http1.0被抱怨最多的就是连接无法复用,连接无法复用会导致每次请求都经历三次握手和慢启动
HTTP 1.1支持持久连接(HTTP/1.1的默认模式使用带流水线的持久连接),在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
HTTP 1.1通过增加更多的请求头和响应头来改进和扩充HTTP 1.0的功能,如,HTTP 1.0不支持Host请求头字段,WEB浏览器无法使用主机头名来明确表示要访问服务器上的哪个WEB站点,这样就无法使用WEB服务器在同一个IP地址和端口号上配置多个虚拟WEB站点。
HTTP 1.1还提供了与身份认证、状态管理和Cache缓存等机制相关的请求头和响应头。HTTP/1.0不支持文件断点续传
HTTP2.0
多路复用 (Multiplexing)
多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。在 HTTP/1.1 协议中浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。
HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码
首部压缩(Header Compression)
HTTP/1.1并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生
服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。
为何TCP采用随机序列号
防止接受网络上粘滞的TCP包,如果都从0开始的话,极其容易接受之前断开连接发送的粘滞包。虽然可以采用每次TCP会话都使用一个UUID作为标记,但是考虑到每次都要携带UUID,比较浪费流量,所以就采用随机序列号的方法。
防止Hack猜测序列号,然后伪装TCP报文,当然这种防御其实很弱。
mongodb、es、redis和mysql比较
Redis和MongoDB来说,大家一般称之为Redis缓存、MongoDB数据库。这也是有道有理有根据的,
Redis主要把数据存储在内存中,其“缓存”的性质远大于其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;
MongoDB却是一个“存储数据”的系统,增删改查可以添加很多条件,就像SQL数据库一样灵活,这一点在面试的时候很受用。
MongoDB和Redis都是NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,这也主要由于
二者在内存映射的处理过程,持久化的处理方法不同。MongoDB建议集群部署,更多的考虑到集群方案,Redis
更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。
redis/MySql数据一致性保持方法,定时任务+消息队列
8、多个用户同时点赞会怎么样?如何解决并发问题?
redis hash结构key=点赞人::被点赞人,value=1/0=点赞/取消点赞,配合Spring Task定时同步到数据库
一个类只有一个实例,并提供一个全局访问点
一般分类两大类: 饿汉模式、懒汉模式
使用: 以前在线白鹭H5游戏时,因为有很多的场景类, 而每个场景类不需要创建很多遍
DAO用过分页查询
首先需要知道pageHelper是mybatis 的一个插件 作用就是帮助我们实现分页
要注意的就是spring中的配置 在mybatis sqlSessionFactoryBean中需要插件配置
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="comboPooledDataSource"/>
<!-- 配置MyBatis工作过程中的插件 -->
<property name="plugins">
<list>
<!-- 以内部bean的形式配置PageHelper插件 -->
<bean class="com.github.pagehelper.PageHelper">
<!-- 配置PageHelper插件的相关属性参数 -->
<property name="properties">
<props>
<!-- dialect:数据库方言,指定当前具体使用的数据库 -->
<!-- MySQL分页使用LIMIT子句 -->
<!-- Oracle分页使用ROWNUM -->
<prop key="dialect">mysql</prop>
<!-- 将前台页面传入的页码修正到1~总页数之间的范围 -->
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</list>
</property>
</bean>
使用:
PageHelper.startPage(pageNum, pageSize);
Page<Employee> page = (Page<Employee>) list;//可以做强转
long total = page.getTotal(); 总计路数
//page.getResult();当前页的数据
//page.getPageNum(); 当前的页码
//page.getPageSize(); 当前页显示多少条
//page.getPages();总页数
存在哪些问题
数据库不符合3NF规范/前后端没有完全分离/数据传递不规范,很多冗余数据/未设计DTO/不应该使用外键)
2.md5函数
只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密
MD5不可逆的原因是由于它是一种散列函数
32/64位系统有什么区别
32位CPU:计算机中的位数指的是CPU一次能处理的最大位数
64位的CPU,相比较32位的CPU来说,64位CPU最为明显的变化就是增加了8个64位的通用寄存器,内存寻址能力提高到64位,以及寄存器和指令指针升级到64位等