之所以开这个分类,是因为感觉自己的博客都是些读书笔记,大多都是知识点的摘抄,缺乏总结,也没有把知识点串起来。每天分析一个面试题问题,一个算法题,也算是对自己成长的记录吧。也希望自己能像这个分类的名字一样“变强”。
主要内容
- Redis为什么这么快
- 动态规划求字符串的最长回文子串
Redis为什么这么快
看牛客网上的分享,许多老哥都遇到过这个问题。想要回答好这个问题,需要系统的了解Redis的原理才行,刚好前段时间拜读了《Redis设计与实现》,虽然看完了书,但是想要系统的回答还是挺难的。
Redis之所以快,主要有以下几个方面的原因:
- 完全基于内存
- 简洁高效的数据结构设计
- 采用单线程,避免了不必要的上下文切换和竞争条件
- 使用了多路复用IO,非阻塞IO。
- Redis自己构建了VM机制来提高效率
基于内存
这一点很好理解,Redis处理的许多请求都是直接内存操作的,内存的读写速率显然是要比磁盘高很多倍的,
简洁高效的数据结构设计
Redis在底层实现上使用了许多设计精妙的数据结构,来提高效率。
比如Redis的字符串类型的底层实现使用的SDS,它可以常数复杂度获取字符串长度,空间预分配、惰性空间释放来提高效率。又比如使用跳跃表来实现快速访问节点和进行范围操作。
而且每个Redis中每个数据库对象(RedisDB)中都维护了一个字典(dict)来保存数据库中的所有键值对。类似于HashMap,其查找和操作的时间复杂度都为O(1)
采用单线程
Redis为什么使用单线程,官方的解释是Redis的性能瓶颈不在CPU,最有可能的性能瓶颈在于机器的内存和网络带宽。而且单线程也符合Redis简洁的设计风格。
强调单线程是指在处理网络请求的时候只有一个线程,在其它时候也是不只一个线程的
详细原因:
- 不需要要各种锁的性能消耗
Redis中有一些复杂的数据结构如list,hash等结构复制的数据结构,当需对这些复制的数据结构进行细粒度的操作的时候,如果不是单线程的话,就需要加很多的锁,导致同步的开销大大增加,但是使用单线程就不会存在这个我呢提。 - 降低了CPU的消耗
多线程会到来上下文切换和竞争条件带来的开销,使用单线程就不会出现这样的问题。 - 可以使用单线程多进程的集群方案
当CPU的性能成为瓶颈的时候,可以使用单线程多进程的集群方案来解决这个问题。 - 代码简洁
使用单线程,代码将会变得简洁清晰,处理逻辑也更简单。
IO多路复用技术
Redis是单线程的,为了避免等待输入和输出带来的阻塞,redis使用了IO多路复用技术来解决这个问题。
Redis服务采用Reactor方式来实现文件事件处理器,每个网络连接都对应一个文件描述符。文件事件处理器使用IO多路复用模块同时监听多个套接字,每次套接字变为可应答,可写或者可读时,相应的文件事件就会产生。
当有多个文件事件并发出现的使用,IO多路复用程序会将所有产生事件的套接字放到一个队列里面。
IO多路复用技术,中“多路”是指多个网络连接,“复用”是指复用一个线程。
复用技术可以让单个线程处理多个连接请求,提高了吞吐率。
VM机制
VM机制主要是为了避免内存不足而造成的访问速度下降的问题。Redis并没有使用操作系统实现的SWAP,而是自己实现的。
当数据多到内存放不下了,那就不可避免地会把冷数据放到磁盘中去,把热数据继续留到内存中。Redis通过自己实现地VM来提高了这一过程地效率。
Redis的作者对为什么不使用操作系统的VM而是使用自己实现的VM做出了以下的解释:
- OS是基于page(4k)的,而Redis对象大多都小于4k,因此OS提供的page方案对于Redis来说粒度太大了。如果使用OS的VM,那么一个页面上可能有多个Redis对象,另外比如list这种数据结构可能在多个页面上,这就导致哪怕只有很少的键被经常访问,当许多的页面都会被OS认为是活跃的,这样只有内存真正耗尽的时候OS才会交换页面。
- 相比于OS的交换方式,redis可以对交换到磁盘的对象进行压缩。
- OS交换的时候是会阻塞线程的,而redis可以让工作线程来完成交换,主线程继续处理请求。
动态规划求字符串的最长回文子串
思路:
若长度为L的字符串是回文,那么去掉首尾也是回文字符串。及全局最优解包含局部最优解。
最小子问题:
- 每个单独的字符组成的字符串是回文的
- 两个相邻的相同的字符串是回文。
class Solution {
public String longestPalindrome(String s) {
char[] chars=s.toCharArray();
int len=s.length();
if(len==0){
return "";
}
if(len==1){
return s;
}
int[][] dp=new int[len][len];
int maxLen=1;
int resIndex=0;
//初始化矩阵,键最小子问题1的情况都设置为true
for(int i=0;i<len;i++){
dp[i][i]=1; //子问题一
if((i<len-1)&&chars[i]==chars[i+1]){
dp[i][i+1]=1; //子问题2
resIndex=i;
maxLen=2;
}
}
//从长度为3开始逐渐增长来检查回文字符串
for(int L=3;L<=len;L++){
for(int i=0;i<=len-L;i++){
//如果字符串是回文的,那么首尾加上相同的字符也是回文的
if(dp[i+1][i+L-2]==1&&chars[i]==chars[i+L-1]){
dp[i][i+L-1]=1; //首尾加上相同的字符也是回文的
maxLen=L;
resIndex=i;
}
}
}
return new String(chars,resIndex,maxLen);
}
}