前言:
最近在准备面试,把一年前看的redis深度历险和当时看的源码,重新拾到一下。
快速过一遍,顺便整理以便后续的成长所需。
以下内容整理自《redis深度历险》
1. 字符串内部结构
动态字符串,类似于ArrayList。
用途:常常用来存放一些json,如果存放数字可以用于自增(我们业务中的有需要递增的房间号就是用的redis~)
分配空间:也类似于ArrayList,实际空间会比字符串大。(但是指的是如果发生追加操作了)
扩容规则:如果字符串长度小于1MB的话,那么扩容是成倍的增加空间。
如果大于1MB的话,那么每次都会扩容1MB的空间
字符串的最大长度是500MB(应该指的是Redis中的结构类型)
1.1 字符串在java中的最大长度
关于存放位置:java字符串存在字符串常量池。字符串的常量池 是在方法区中。堆中存放实例对象。
字符串可以承受的最大长度,要分为两个阶段,编译时期还是运行时期。
因为Java有JVM常量池,对于字符串的性能优化,所以不用重复创建新的字符串,那么当我们用字符串
直接定义String时候,会把字符串存在常量池里面存储一份。
编译期看了一下资料65534是最大长度,
运行期:
String内部是以char数组的形式存储,数组的长度是int类型,那么String允许的最大长度就是Integer.MAX_VALUE了。又由于java中的字符是以16位存储的,因此大概需要4GB的内存才能存储最大长度的字符串。(如果使用javac的话编译不过去)
@Native public static final int MAX_VALUE = 0x7fffffff; // 2 的 31 次方 - 1 = 2147483648 - 1 = 2147483647
1.2 位图数据结构
字符串由多个字节组成,一个字节是8个bit 所以可以说是bitmap的数据结构。
2. 源码篇
redis的编程语言:Redis是一个开源的使用ANSI C语言编写
字符串的内部实现是什么样呢?
redis中的字符串是以字节数组的形式存在的。bitmap
redis中的字符串叫做SDS 也就是Simple Dynamic String
String结构:
struct SDS<T> {
T capacity; //shuzu 容量
T len; //数组长度
byte flags; //特殊标志位,不用理睬
byte[] content; //数组内容
}
字符串是可以修改的,所以支持append的,那么需要冗余空间(但是也是后面扩容之后有冗余空间)。
总之扩容机制概括:1.看冗余空间是否可以容纳
2.如果不能够,那么扩容,然后复制原字符串到新的数组中。
3.但是创建字符串的时候,len和capacity是一样长的,不会多分配冗余空间(第一次的时候),因为大部分 的场景下面,我们并不会用到append来操作修改字符串。
不过为什么SDS中的结构里面的容量和数组长度使用的是泛型T呢?
因为当字符串比较短的时候,len和和其中capacity
可以使用byte和short来表示吗,不一定一定要用int(感觉做设计就是能节省一点十一点)
不同长度的字符串使用不同的结构体表示。
redis规定字符串的长度不能超过512MB。
2.1 字符串的两种存储方式
2.2.1 embstr
在字符串特别短的时候使用这种方式
2.2.2 raw
超过44个字节使用这种方式。
2.2.3 为什么是44个字节?
主要原因是redis的的对象头结构,所有的redis对象都有下面的头结构!!!!
struct RedisObject {
int4 type;
int4 encoding;
int24 lru; //是一种缓存机制
int32 refcount; //引用计数,引用为0的时候,被销毁 (可以关联到JVM的内存回收理解)
void *ptr; //指向对象内容的具体存储位置,8byte
}
不同的对象有着不同的type。但是同一个type会有不同的存储形式也就是encoding.
LRU是一种内存淘汰机制。
那么我们需要上面的4 + 4 + 24 + 32 =64
然后再加上8字节的存储引用的空间,也就是16字节。
对于SDS的结构体,需要最小3个字节
struct SDS {
int8 capacity; //数组容量
int8 len; //数组长度
int8 flags; //特殊标志位,不用理睬
byte[] content; //数组内容
}
那么embstr就是一种将RedisObject 和 SDS的对象连续的存在了一起,然后使用malloc方法进行一次分配。
而raw是进行两次malloc的方法,也就是两个对象头在内存地址上面是不连续的。
总之因为字符串如果总体超过了64个字节,那么redis会认为是一个打的字符串不在适合用emdstr形式存储,应该用raw形式。
所以64个字节 应该是 44个字符串长度 + 字符串用null结尾(占了一个字节) + 19字节的头信息(是SDS+RedisObject 头信息)。
PS: 多出的null字节是为了方便直接使用glibc的字符串处理函数。
(需要知道redis字符串存储是null结尾的 占用一个字节)
2.2.4 为什么字符串长度大于1MB之后,每次只多分配1MB的冗余空间?
因为避免加倍之后冗余空间过大而浪费。
3. redis中的int4 ,int8
这个对java开发的我并不是很友好,因为忘记了是什么意思啊。
看了一下这个老哥的帖子: https://www.cnblogs.com/daguonice/p/11193884.html
这样会不会更直观:
Int8, 等于Byte, 占1个字节.
Int16, 等于short, 占2个字节. -32768 32767
Int32, 等于int, 占4个字节. -2147483648 2147483647
Int64, 等于long, 占8个字节. -9223372036854775808 9223372036854775807
这样, 看起来比short,int,long更加直观些!
另外, 还有一个Byte, 它等于byte, 0 - 255.
那么这个时候,熟悉开发的肯定会联想到:
在mysql中int和bigint对应的默认是int(11) bigint(20)
但是他们实际上还是4个和8个字节。
因为:
int有符号类型 取值在 - 2147483648 ~ 2147483647之间
int无符号类型 取值在 0 ~ 4294967295之间
那么其实mysql中的int(11) 这个11也就是数字是多少位~
我们要在mysql中展示多少位~
总结:
接下来还要看其他四种的源码和底层实现,还有HashMap和ArrayList
也会跟这个这些底层结构应运而生~
加油DK