Elasticsearch为啥这么快

Elasticsearch为啥这么快,第1张

思考几个问题:

这里有篇 文章 讲解的很形象:

这是集群cluster。

这是节点Node:就是个机器。

由一个或者多个节点,多个绿色小方块组合在一起形成一个ElasticSearch的索引。

在一个索引下,分布在多个节点里的绿色小方块称为分片:Shard。

一个分片就是一个Lucene Index。

在Lucene里面有很多小的Segment,即为存储的最小管理单元。

我们分别从Node维度、Shard维度、Segment维度来阐明为啥Elasticsearch这么快。

多节点的集群方案,提高了整个系统的并发处理能力。

路由一个文档到一个分片中:当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?实际上,这个过程是根据下面这个公式决定的:

routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。这就解释了为什么我们要在创建索引的时候就确定好主分片的数量,并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

确定了在哪个分片中,继而可以判定其在哪个节点上。

那么主分片数确定的情况下,如果做集群扩容呢?下图是一种主分片的扩容办法,开始设置为5个分片,在单个节点上,后来扩容到5个节点,每个节点有一个分片。也就是说单个分片的容量变大了,但是数量并不增加。

节点分为主节点 Master Node、数据节点 Data Node和客户端节点 Client Node(单纯为了做请求的分发和汇总)。每个节点都可以接受客户端的请求,每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。当接受请求后,节点变为「协调节点」。从这个角度,整个系统可以接受更高的并发请求,当然搜索的就更快了。

以更新文档为例:

Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的 *** 作。因为没有阻塞,所以提升了索引的速度,同时可以通过 _version 字段来保证并发情况下的正确性:

控制在我们索引中的文档只有现在的 _version 为 1 时,本次更新才能成功。

可以设置分片的副本数量来提升高并发场景下的搜索速度,但是同时会降低索引的效率。

在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。

怎样在保留不变性的前提下实现倒排索引的更新?即用上文提到的 _version ,创建更多的索引文档。实际上一个 UPDATE *** 作包含了一次 DELETE *** 作(仅记录标志待Segment Merge 的时候才真正删除)和一次 CREATE *** 作。

为了提升写索引速度,并且同时保证可靠性,Elasticsearch 在分段的基础上,增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行 *** 作时均进行了日志记录。

Segment在被refresh之前,数据保存在内存中,是不可被搜索的,这也就是为什么 Lucene 被称为提供近实时而非实时查询的原因。

但是如上这种机制避免了随机写,数据写入都是 Batch 和 Append,能达到很高的吞吐量。同时为了提高写入的效率,利用了文件缓存系统和内存来加速写入时的性能,并使用日志来防止数据的丢失。

LSM-Tree 示意图如下,可见 Lucene 的写入思想和 LSM-Tree 是一致的:

终于说到倒排索引了,都说倒排索引提升了搜索的速度,那么具体采用了哪些架构或者数据结构来达成这一目标?

如上是Lucene中实际的索引结构。用例子来说明上述三个概念:

ID是文档id,那么建立的索引如下:

Name:

Age:

Sex:

可见为每个 field 都建立了一个倒排索引。Posting list就是一个int的数组,存储了所有符合某个term的文档id。实际上,除此之外还包含:文档的数量、词条在每个文档中出现的次数、出现的位置、每个文档的长度、所有文档的平均长度等,在计算相关度时使用。

假设我们有很多个 term,比如:

如果按照这样的顺序排列,找出某个特定的 term 一定很慢,因为 term 没有排序,需要全部过滤一遍才能找出特定的 term。排序之后就变成了:

这样我们可以用二分查找的方式,比全遍历更快地找出目标的 term。这个就是 term dictionary。有了 term dictionary 之后,可以用 logN 次磁盘查找得到目标。

但是磁盘的随机读 *** 作仍然是非常昂贵的(一次 random access 大概需要 10ms 的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个 term dictionary 本身又太大了,无法完整地放到内存里。于是就有了 term index。term index 有点像一本字典的大的章节表。比如:

A 开头的 term …………… Xxx 页

C 开头的 term …………… Yyy 页

E 开头的 term …………… Zzz 页

如果所有的 term 都是英文字符的话,可能这个 term index 就真的是 26 个英文字符表构成的了。但是实际的情况是,term 未必都是英文字符,term 可以是任意的 byte 数组。而且 26 个英文字符也未必是每一个字符都有均等的 term,比如 x 字符开头的 term 可能一个都没有,而 s 开头的 term 又特别多。实际的 term index 是一棵 trie 树:

例子是一个包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的 trie 树。这棵树不会包含所有的 term,它包含的是 term 的一些前缀。通过 term index 可以快速地定位到 term dictionary 的某个 offset,然后从这个位置再往后顺序查找。

现在我们可以回答“为什么 Elasticsearch/Lucene 检索可以比 mysql 快了。Mysql 只有 term dictionary 这一层,是以 b-tree 排序的方式存储在磁盘上的。检索一个 term 需要若干次的 random access 的磁盘 *** 作。而 Lucene 在 term dictionary 的基础上添加了 term index 来加速检索,term index 以树的形式缓存在内存中。从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。

实际上,Lucene 内部的 Term Index 是用的「变种的」trie树,即 FST 。FST 比 trie树好在哪?trie树只共享了前缀,而 FST 既共享前缀也共享后缀,更加的节省空间。

一个FST是一个6元组 (Q, I, O, S, E, f):

例如有下面一组映射关系:

可以用下图中的FST来表示:

这篇文章讲的很好: 关于Lucene的词典FST深入剖析

想想为啥不用 HashMap,HashMap 也能实现有序Map?耗内存啊!牺牲了一点性能来节约内存,旨在把所有Term Index都放在内存里面,最终的效果是提升了速度。如上可知,FST是压缩字典树后缀的图结构,她拥有Trie高效搜索能力,同时还非常小。这样的话我们的搜索时,能把整个FST加载到内存。

总结一下,FST有更高的数据压缩率和查询效率,因为词典是常驻内存的,而 FST 有很好的压缩率,所以 FST 在 Lucene 的最新版本中有非常多的使用场景,也是默认的词典数据结构。

Lucene 的tip文件即为 Term Index 结构,tim文件即为 Term Dictionary 结构。由图可视,tip中存储的就是多个FST,

FST中存储的是<单词前缀,以该前缀开头的所有Term的压缩块在磁盘中的位置>。即为前文提到的从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。

可以形象地理解为,Term Dictionary 就是新华字典的正文部分包含了所有的词汇,Term Index 就是新华字典前面的索引页,用于表明词汇在哪一页。

但是 FST 即不能知道某个Term在Dictionary(tim)文件上具体的位置,也不能仅通过FST就能确切的知道Term是否真实存在。它只能告诉你,查询的Term可能在这些Blocks上,到底存不存在FST并不能给出确切的答案,因为FST是通过Dictionary的每个Block的前缀构成,所以通过FST只可以直接找到这个Block在tim文件上具体的File Pointer,并无法直接找到Terms。

回到上面的例子,给定查询过滤条件 age=24 的过程就是先从 term index 找到 18 在 term dictionary 的大概位置,然后再从 term dictionary 里精确地找到 18 这个 term,然后得到一个 posting list 或者一个指向 posting list 位置的指针。然后再查询 sex=Female 的过程也是类似的。最后得出 age= 24 AND sex=Female 就是把两个 posting list 做一个“与”的合并。

这个理论上的“与”合并的 *** 作可不容易。对于 mysql 来说,如果你给 age 和 gender 两个字段都建立了索引,查询的时候只会选择其中最 selective 的来用,然后另外一个条件是在遍历行的过程中在内存中计算之后过滤掉。那么要如何才能联合使用两个索引呢?有两种办法:

Elasticsearch 支持以上两种的联合索引方式,如果查询的 filter 缓存到了内存中(以 bitset 的形式),那么合并就是两个 bitset 的 AND。如果查询的 filter 没有缓存,那么就用 skip list 的方式去遍历两个 on disk 的 posting list。

用一个例子来说明如何使用 skip list 的思路来做合并(参考 Lucene学习总结之七:Lucene搜索过程解析(5) ):

Advance *** 作是什么?就是 skip list 提供的快速跳跃的特性。

另外一方面,对于一个很长的 posting list,比如:

[1,3,13,101,105,108,255,256,257]

我们可以把这个 list 分成三个 block:

[1,3,13] [101,105,108] [255,256,257]

然后可以构建出 skip list 的第二层:

[1,101,255]

1,101,255 分别指向自己对应的 block。这样就可以很快地跨 block 的移动指向位置了。

Lucene 自然会对这个 block 再次进行压缩。其压缩方式叫做 Frame Of Reference 编码。示例如下:

考虑到频繁出现的 term(所谓 low cardinality 的值),比如 gender 里的男或者女。如果有 1 百万个文档,那么性别为男的 posting list 里就会有 50 万个 int 值。用 Frame of Reference 编码进行压缩可以极大减少磁盘占用。这个优化对于减少索引尺寸有非常重要的意义。当然 mysql b-tree 里也有一个类似的 posting list 的东西,是未经过这样压缩的。

因为这个 Frame of Reference 的编码是有解压缩成本的。利用 skip list,除了跳过了遍历的成本,也跳过了解压缩这些压缩过的 block 的过程,从而节省了 cpu。

这也可以看到,Lucene 为了省内存真是做到了极致。

Bitset 是一种很直观的数据结构,对应 posting list 如:

[1,3,4,7,10]

对应的 bitset 就是:

[1,0,1,1,0,0,1,0,0,1]

每个文档按照文档 id 排序对应其中的一个 bit。Bitset 自身就有压缩的特点,其用一个 byte 就可以代表 8 个文档。所以 100 万个文档只需要 125 万个 byte。但是考虑到文档可能有数十亿之多,在内存里保存 bitset 仍然是很奢侈的事情。而且对于个每一个 filter 都要消耗一个 bitset,比如 age=18 缓存起来的话是一个 bitset,18<=age<25 是另外一个 filter 缓存起来也要一个 bitset。

所以秘诀就在于需要有一个数据结构:

可以很压缩地保存上亿个 bit 代表对应的文档是否匹配 filter;

这个压缩的 bitset 仍然可以很快地进行 AND 和 OR 的逻辑 *** 作。

Lucene 使用的这个数据结构叫做 Roaring Bitmap。

其压缩的思路其实很简单。与其保存 100 个 0,占用 100 个 bit。还不如保存 0 一次,然后声明这个 0 重复了 100 遍。

为什么是以65535为界限?程序员的世界里除了1024外,65535也是一个经典值,因为它=2^16-1,正好是用2个字节能表示的最大数,一个short的存储单位,注意到上图里的最后一行“If a block has more than 4096 values, encode as a bit set, and otherwise as a simple array using 2 bytes per value”,如果是大块,用节省点用bitset存,小块就豪爽点,2个字节我也不计较了,用一个short[]存着方便。

在 Lucene 70之后,Lucene 针对 bitset的稠稀性,采用不同的存储方式:当 bitset比较稀疏时,直接存储DocID;当 bitset 稠密时,则直接存储 bitset 的Bits数据。根据数据的分布情况不同,采用适当的结构不仅可以提高空间的利用率,还能提高遍历的效率。

Elasticsearch/Lucene 为了提升索引和搜索的效率,从上层到底层,使用了各种巧妙的数据结构和设计,靠优秀的理论加极致的优化,做到查询性能上的极致。

译者序 第一章 C语言入门 1 编程简介 2 创建ASCII码源文件 3 编译C程序 4 语法错误 5 典型的C程序结构 6 往程序里添加语句 7 在新的一行上显示输出结果 8 C语言区分大小写字母 9 逻辑错误BUG 10 程序开发过程 11 文件类型 12 进一步了解连接器 13 头文件 14 帮助编译器查找头文件 15 加速编译 16 注释程序 17 提高程序的可阅读性 18 注意编译器警告信息 19 控制编译器警告 20 用注释屏蔽警告 21 名字的重要性 22 分号的作用 23 变量 24 给变量赋值 25 变量类型 26 定义同一类型的多个变量 27 定义变量时加上注释 28 给变量赋初值 29 在定义时初始化多个变量 30 使用有意义的变量名 31 C关键字 32 整型变量 33 字符型变量 34 浮点型变量 35 双精度型变量 36 给浮点型变量赋值 37 类型标识符 38 无符号类型标识符 39 LONG长类型标识符 40 联合使用无符号和长类型标识符 41 使用大数值 42 寄存器类型标识符 43 短SHORT类型标识符 44 从类型申明中去掉INT 45 有符号类型示识符 46 多赋值运算符 47 把变量的值赋给另一种类型的变量 48 创建用户自己的类型 49 赋给十六进制或八进制值 50 溢出 51 精确度 52 赋值为引号或其他字符 53 PRINTF入门 54 使用PRINTF显示整型数值 55 打印八进制或十六进制整数 56 用PRINTF显示无符号整型数值 57 用PRINTF显示长整型数值 58 用PRINTF显示浮点型数值 59 用PRINTF显示字符型数值 60 用指数格式显示浮点数 61 显示浮点数 62 用PRINTF显示字符串 63 用PRINTF显示指针地址 64 在数值前添加正号和负号 65 用PRINTF格式化整数值 66 0填充整数输出 67 在八进制和十六进制数前显示前缀 68 用PRINTF格式化浮点数 69 格式化指数输出 70 左对齐PRINTF的输出 71 联合使用格式符 72 字符串的换行 73 显示NEAR和FAR字符 74 使用PRINTF的转义字符 75 判断PRINTF已显示的字符数目 76 使用PRINTF的返回值 77 使用ANSI设备驱动器 78 用ANSI驱动器清除屏幕显示 79 用ANSI驱动器显示屏幕颜色 80 用ANSI驱动器定位光标 81 在C中作基本的数学运算 82 模运算(取余运算) 83 运算符的优先级和结合性 84 强制 *** 作符运算顺序 85 C的自增运算符 86 C的自减运算符 87 按位或运算 88 按位与运算 89 按位异或运算 90 “取反”运算 91 对变量的值进行运算 92 C的条件运算符 93 C的长度SIZEOF运算符 94 移位运算 95 位循环运算 96 条件运算符 97 循环控制 98 C如何表示真TRUE和假FALSE 99 用IF判断条件 100 简单语句和复杂语句 101 判断是否相等 102 关系判断 103 用逻辑与判断两个条件 104 用逻辑或判断两个条件 105 逻辑非运算 106 将条件结果赋值给变量 107 在复合语句中定义变量 108 使用缩进来提高程序的可读性 109 使用扩展CTRL+BREAK检查 110 判断浮点数 111 永远循环下去 112 赋值判断 113 IF-IF-ELSE语句 114 按规定次数执行语句 115 FOR语句的有些部分是可选择的 116 在FOR语句中赋值 117 控制FOR循环的增值 118 在FOR循环中使用字符型和浮点型数值 119 空循环 120 无穷循环 121 在FOR循环中使用逗号运算符 122 不要在FOR循环中改变控制变量的值 123 用WHILE循环重复执行一条或多条语句 124 WHILE循环的组成部分 125 使用DO重复执行一条或多条语句 126 C的CONTINUE语句 127 使用C的BREAK语句来结束循环 128 GOTO语句分支 129 判断多个条件 130 在SWITCH中使用BREAK 131 使用SWITCH语句的DEFAULT CASE 第二章 宏与常量 132 在程序中定义常量 133 宏与常量扩展 134 给常量和宏命名 135 使用-FILE-预处理器常量 136 使用-LINE-预处理器常量 137 改变预处理器的行计数 138 生成无条件预处理器错误 139 其他预处理器常量 140 记录预处理器的日期和时间 141 判断是否进行ANSIC编译 142 判断是C++还是C 143 取消宏或常量 144 比较宏与函数 145 编译器PRAGMAS 146 预定义值和宏 147 创建用户自己的头文件 148 使用#INCLUDE<FILENAMEH>或#INCLUDE“FILENAMEH” 149 判断符号是否被定义 150 进行IF-ELSE预处理 151 更强大的预处理器条件判断 152 实现IF-ELSE和ELSE-IF预处理 153 定义需要多行的宏和常量 154 创建自定义宏 155 在宏定义中不要放置分号 156 创建MIN和MAX宏 157 创建SQUARE CUBE宏 158 注意宏定义中的空格 159 如何使用括号 160 宏是没有类型的 第三章 字符串 161 C字符串的形象化 162 编译器是如何表示字符串的 163 C是如何存储字符串的 164 ‘A’是如何区别于‘A’的 165 在字符串常量内表示引号 166 判断字符串的长度 167 使用STRLEN函数 168 将一个字符串的字符复制到另一个字符串中 169 将一个串的内容追加到另一个串上 170 给字符串追加N个字符 171 把一个字符串转移到另一个字符串 172 不要越过字符串的界限 173 判断两个字符串是否相同 174 比较字符串时忽略大小写 175 将字符串转换成大写或小写 176 获取字符串中第一次出现的某个字符 177 返回索引到串的首次出现 178 搜索字符在字符串中的末次出现 179 返回指向字符中末次出现的索引 180 使用FAR字符串 181 为FAR字符串编写字符串函数 182 计算字符串的内容反转 183 将字符串的内容反转 184 将某特定字符赋给整个字符串 185 比较两个字符串 186 比较两个字符中的前N个字符 187 不考虑大小写比较字符串 188 将字符串转换成数字 189 复制字符串的内容 190 从给定字符序列中查找字符的首次出现 191 在字符串中查找子字符串 192 计算子字符串出现的次数 193 给子字符串获取索引 194 获取子字符串的最右端出现 195 不使用%2格式标识符显示字符串 196 从字符串中删除子字符串 197 用另一个子字符串代替子字符串 198 转换数值的ASCII码形式 199 判断字符是否为字母数字 200 字符是否为字母 201 判断字符是否包含ASCII值 202 判断字符是否为控制符 203 判断字符是否为数字 204 判断字符是否为图形字符 205 判断字符是大写还是小写 206 判断字符是否可打印 207 判断字符是否为标点符号 208 判断字符是否包含空白符 209 判断字符是否为十六进制值 210 将字符转换成大写形式 211 将字符转换成小写形式 212 使用ASCII字符 213 将输出格式写进字符串变量 214 从字符串中读输入 215 标志字符串以节省空间 216 初始化字符串 第四章 函数 217 函数 218 在函数中使用变量 219 把MAIN当作函数 220 参数简介 221 使用多参数 222 老式C程序中的参数申明 223 函数返回值 224 RETURN语句 225 函数原型 226 运行时程序库 227 形参和实参 228 解决名称冲突 229 返回类型为非INT型的函数 230 局部变量 231 函数如何使用堆栈 232 函数的开销 233 C如何存储局部变量 234 申明全局变量 235 避免使用全局变量 236 解决全局和局部变量的名称冲突 237 更好地定义全局变量的有效范围 238 传值调用 239 使用传值调用防止参数值变化 240 传址调用 241 获取地址 242 使用变量的地址 243 改变参数的值 244 只改变指定参数 245 使用堆栈进行传址调用 246 记住函数变量的值 247 C是如何初始化静态变量的 248 使用PASCAL调用顺序 249 PASCAL关键字的影响 250 混合编程示例 251 CDECL关键字 252 递归函数 253 递归阶乘函数 254 另一个递归的例子 255 进一步理解递归 256 直接递归与间接递归 257 判断是否要使用递归 258 为什么递归函数慢 259 如何消除递归 260 将字符串传递给函数 261 传递指定的数组元素 262 形式参数中的CONST 263 使用CONST不会阻止参数值的修改 264 无界字符串的申明 265 指针的使用与字符串的申明 266 C是如何使用堆栈处理字符串参数的 267 外部变量 268 应用外部变量 269 外部静态变量 270 VOLATILE关键字 271 调用结构和基指针 272 调用汇编语言函数 273 从汇编语言函数中返回值 274 没有返回值的函数 275 不使用参数的函数 276 AUTO关键字 277 范围 278 范围的分类 279 名称空间和标识符 280 标识符的可见性 281 DURATION 282 支持参数个数可变的函数 283 支持个数可变的参数 284 VA-START、VA-ARG和VA-END是如何工作的 285 创建支持多参数多类型的函数 第五章 键盘 *** 作 286 从键盘读入字符 287 显示字符输出 288 缓冲输入 289 将键盘输入赋组合字符串 290 联合使用GETCHAR和PUTCHA 291 记住GETCHAR和PUTCHAR都是宏 292 使用直接I/O读入字符 293 不显示字符的直接键盘输入 294 知道何时使用‘\R’和‘\N’ 295 直接输出 296 将按键放回键盘缓存 297 使用CPPINTF快速格式化输出 298 快速格式化键盘输入 299 写字符串 300 使用直接I/O实现更快的字符串输出 301 从键盘读入字符串 302 以更快的速度从键盘输入字符串 303 以彩色显示输出 304 清除屏幕显示 305 删除当前行到行尾的内容 306 删除屏幕上的当前行 307 定位光标进行屏幕输出 308 判断行与列的位置 309 在屏幕上插入空行 310 将屏幕上的文本拷贝到缓冲区 311 将缓冲区中的文本拷贝到屏幕的指定位置 312 判断文本模式设置 313 控制屏幕颜色 314 指定背景色 315 使用TEXTCOLOR设置前景色 316 使用TEXTBACKGROUND设置背景色 317 控制文本的明暗度 318 决定当前文本模式 319 在屏幕上移动文本 320 定义文本窗口 第六章 数学 321 使用整型表达式的绝对值 322 使用ARCCOSINE反余弦 323 使用ARCSINE反正弦 324 使用ARCTANGENT反正切 325 求复数的绝对值 326 对浮点值进位舍入 327 使用角的余弦 328 使用角的双曲余弦 329 使用角的正弦 330 使用角的双曲正弦 331 使用角的正切 332 使用角的双曲正切 333 整数相除 334 使用指数 335 使用浮点型表达式的绝对值 336 使用浮点余数 337 使用浮点值的尾数和指数 338 计算X2E的结果 339 计算自然对数 340 计算LOG10X的值 341 判断最大值与最小值 342 把浮点值分解成整数和小数部分 343 计算Xn的结果 344 计算1010的结果 345 生成随机数 346 将随机值映射到指定范围 347 给随机数生成器赋初值 348 计算数值的平方根 349 创建定制数学错误处理程序 第七章 文件、目录和磁盘 350 判断当前盘驱动器 351 选择当前驱动器 352 判断可用的盘空间 353 当心DBLSPACE 354 读入文件分配表FAT信息 355 磁盘ID 356 绝对扇区读写 *** 作 357 进行BIOS磁盘I/O 358 测试软驱是否准备好 359 应用FOPEN打开文件 360 FILE结构 361 关闭一个打开的文件 362 每次读/写文件信息的一个字符 363 文件指针的位置指针 364 判断当前文件位置 365 文件流 366 文件翻译 367 CONFIGSYS文件的FILES=条目 368 使用低级和高级文件I/O 369 文件句柄FILE HANDLES 370 进程文件表PROCESS FILE TABLE 371 进程文件表入口 372 系统文件表 373 显示系统文件表 374 从流指针中导出文件句柄 375 进行格式化文件输出 376 重命名文件 377 删除文件 378 判断程序如何访问文件 379 设置文件的访问模式 380 深入掌握文件属性 381 检测文件流错误 382 判断文件的长度 383 刷新I/O流 384 一次关闭所有被打开的文件 385 获取文件流的文件句柄 386 使用P-TMPDIR创建临时文件名 387 使用TMP或TEMP创建临时文件名 388 创建真正的临时文件 389 删除临时文件 390 为文件搜索命令路径 391 为文件搜索环境入口的子目录 392 打开TEMP目录中的文件 393 最小化文件I/O *** 作 394 在目录名中使用反斜杠 395 改变当前目录 396 创建目录 397 删除目录 398 删除目录树 399 建立完全路径名 400 分解目录路径 401 建立路径名 402 使用低级函数打开和关闭文件 403 创建文件 404 进行低级读写 *** 作 405 判断文件是否结束 406 应用低级文件例行程序 407 为文件句柄翻译指定模式 408 打开LSEEK定位文件指针 409 打开多于20个的文件 410 使用DOS文件服务 411 获取文件的日期和时间标记 412 利用位域获取文件的日期与时间 413 设置文件的日期与时间标记 414 把文件日期和时间设置成当前日期和时间 415 每次读写一个字 416 改变文件的长度 417 控制文件打开 *** 作的读写模式 418 将缓冲区赋给文件 419 分配文件缓冲区 420 利用MKTEMP创建唯一文件名 421 读写结构 422 从文件流中读取结构数据 423 复制文件句柄 424 强制文件句柄设置 425 把文件句柄和文件流联系起来 426 文件共享 427 打开文件进行共享访问 428 锁定文件内容 429 获取更精细的文件锁定控制 430 使用DOS目录 431 打开目录 432 读取目录入口 433 利用目录服务读C:\WINDOWS 434 反绕目录 435 递归读取磁盘文件 436 判断当前文件位置 437 打开共享文件流 438 在指定目录中创建唯一文件 439 创建新文件 440 利用DOS服务访问文件 441 强制二进制或文本文件打开 442 按行写文本 443 按行读文本 444 应用FGETS和FPUTS 445 强制二进制文件翻译 446 为什么TEXTCOPY不能拷贝二进制文件 447 判断文件结尾 448 舍弃字符 449 读取格式化的文件数据 450 根据当前位置定位文件指针 451 获取文件句柄信息 452 重新打开文件流 第八章 数组、指针和结构 453 数组 454 申明数组 455 形象表示数组 456 数组的内存需求 457 初始化数组 458 访问数组元素 459 通过循环访问数组元素 460 使用常量定义数组 461 把一个数组传送给函数 462 把数组看作函数 463 区分字符串数组 464 在堆栈中传送数组 465 判断数组能存放多少个元素 466 为大数组使用HUGE内存模式 467 权衡数组与动态存储的利弊 468 多维数组 469 行与列 470 访问二维数组的元素 471 给二维数组元素赋初值 472 判断多维数组占用的内存 473 通过循环显示二维数组 474 遍历三维数组 475 初始化多维数组 476 把二维数组传送给函数 477 把多维数组当作一维数组 478 C是如何存放多维数组的 479 按行存放与按列存放 480 以数组为成员的结构数组 481 联合 482 使用联合节省内存 483 使用REGS——一种典型的联合 484 应用REGS联合中 485 位字段结构 486 形象表示位字段结构 487 位字段结构的取值范围 488 在数组中查找指定的值 489 对分查找 490 应用对分查找法 491 对数组进行排序 492 冒泡排序法 493 应用冒泡排序法 494 选择排序法 495 应用选择排序法 496 SHELL希尔排序法 497 应用SHELL排序法 498 快速排序法 499 应用快速排序法 500 上述排序方法的遗留问题 501 对字符串数组排序 502 利用LFIND搜索字符串 503 利用LSEARCH搜索数值 504 利用BSEARCH搜索已排序数组 505 利用QSORT对数组排序 506 判断数组元素的个数 507 把指针理解为地址 508 判断变量的地址 509 C是如何把数组当成指针的 510 对数组应用取地址运算符 (&) 511 申明指针变量 512 间接访问指针 513 使用指针值 514 指针与函数参数的使用 515 指针运算 516 指针的增值与减值 517 联合应用指针引用与增值 518 利用指针遍历数组 519 利用返回值为指针的函数 520 创建返回值为指针的函数 521 指针数组 522 形象表示字符串数组 523 遍历字符串数组 524 把字符串数组当成指针 525 使用指向一个指向字符串的指针的指针 526 利用指针申明字符串常量 527 VOID类型指针 528 创建指向函数的指针 529 使用指向函数的指针 530 使用三级指针 531 结构 532 结构是变量申明的模板 533 结构标记是结构的名称 534 用不同的方式申明结构 535 结构成员 536 形象表示结构 537 应用结构 538 把结构传递给函数 539 在函数内部改变结构 540 (point)member间接引用 541 使用pointer-->member格式 542 使用无标记结构 543 结构定义的范围 544 初始化结构 545 进行结构I/O 546 使用嵌套结构 547 包含数组的结构 548 创建结构数组 第九章 DOS和BIOS服务 549 DOS系统服务 550 BIOS服务 551 寄存器 552 标志寄存器 553 软件中断 554 利用BIOS访问指针 555 CONTROL+BREAK信息 556 可能的DOS副作用 557 暂时挂起程序 558 控制声音 559 获取国家专用的信息 560 磁盘传输地址 561 访问和控制磁盘传输区 562 BIOS键盘服务 563 获取BIOS设备列表 564 控制串行口I/O 565 利用BDOS访问DOS服务 566 获取扩展DOS错误信息 567 判断BIOS常规内存数量 568 创建远指针FAR PRINTER 569 把远端地址分解为段地址和偏移地址 570 判断自由核心内存 571 读段寄存器设置 572 内存的类型 573 常规内存 574 常规内存的分布 575 访问常规内存 576 为什么PC和DOS限制于1MB 577 从段和偏移量中产生地址 578 扩充内存 579 使用扩充内存 580 扩展内存 581 实模式和保护模式 582 访问扩展内存 583 高端内存区 584 堆栈 585 各种堆栈配置 586 判断程序的当前堆栈大小 587 使用-STKLEN控制堆栈空间 588 给内存区域赋值 589 拷贝内存区域 590 拷贝内存区域直到某指定字节 591 比较两个无符号字符数组 592 交换两个相邻字符串字节 593 分配动态内存 594 再谈类型转换 595 不再需要时释放内存 596 利用CALLOC函数分配内存 597 堆 598 解决64KB堆限制 599 从堆栈中分配内存 600 分配巨型数据 601 改变被分配内存区域的大小 602 BRK函数 603 检测堆 604 快速堆检测 605 填充自由堆空间 606 检测特定的堆入口 607 遍历堆入口 608 访问指定内存单元 609 向内存中置数 610 PC端口 第十章 内存管理 611 访问端口值 612 CMOS 613 内存模式 614 微型内存模式 615 小型内存模式 616 中型内存模式 617 压缩内存模式 618 大型内存模式 619 巨型内存模式 620 判断当前的内存模式 第十一章 日期和时间 621 获取当前日期与时间 622 将日期和时间从秒的形式转换成ASCII码 623 DAYLIGHT SAVINGS ADJUST MENT 624 延迟若干毫秒 625 判断程序的耗时 626 比较两个时间 627 获取数据串 628 获取时间串 629 读BIOS计时器 630 使用当地时间 631 使用格林威治平时 632 获取DOS系统时间 633 获取系统日期 634 设置DOS系统时间 635 设置DOS系统日期 636 把DOS日期转换为UNIX格式 637 利用TIMZONE计算时差 638 判断当前时区 639 利用TZSET设置时区区域 640 利用TZ环境入口 641 从用户程序中设置TZ环境入口 642 获取时区信息 643 以秒钟的形式设置自1/2/1970午夜以来的系统时间 644 把日期转换成自1/1/1970以来的秒数 645 判断日期的儒略历日期 646 创建格式化日期和时间串 647 PC时钟类型 第十二章 重定向I/O和进程命令行 648 等候按键 649 提醒用户输入密码 650 自己编写密码函数 651 输出重定向 652 输入重定向 653 联合使用INPUT和OUTPUT重定向 654 利用STDOUT和STDIN 655 管道运算符 656 GETCHAR和PUTCHAR 657 对重定向输入进行编号 658 确保信息出现在屏幕上 659 自定义MORE命令 660 显示重定向行的数目 661 显示得定向字符的个数 662 创建定时的MORE命令 663 防止I/O重定向 664 应用STDPRN文件句柄 665 把重定向输出分割到一个文件中 666 应用STDAUX文件句柄 667 在重定向输入人寻找子串的出现 668 显示重定义输入的头N行 669 命令行变元 670 显示命令行变元的个数 671 显示命令行 672 使用引号内的命令行变元 673 从命令行中显示文件内容 674 把ARGV当作指针 675 C是如何知道命令行的 676 环境 677 把ENV当作一个指针 678 对MAIN的参数使用VOID 679 使用命令行数字 680 出口状态值 681 为出口状态过程使用RETURN 682 判断是否把MAIN申明为VOID 683 在环境中搜索特定入口 684 DOS是如何对待环境的 685 应用ENVIRON全局变量 686 给当前环境添加入口 687 给DOS环境添加元素 688 退出当前程序 689 定义在程序结束时执行的函数 第十三章 编程工具 690 库 691 重复使用目标代码 692 编译C和OBJ文件时出现的问题 693 创建库文件 694 常用的库 *** 作 695 列出库文件中的子例行程序 696 利用库减少编译时间 697 库管理程序的其他功能 698 连接器 699 连接器的功能 700 使用连接映像 701 使用连接器响应文件 702 使用MAKE命令简化应用程序的创建 703 生成一个简单的MAKE文件 704 通过MAKE使用多依赖性文件 705 说明用户的MAKE文件 706 MAKE和命令行 707 在MAKE文件中放置多个依赖性 708 显现的和隐含的MAKE法则 709 使用MAKE宏 710 预定义MAKE宏 711 用MAKE执行条件进程 712 验证一个MAKE宏 713 再次包含一个MAKE文件 714 使用MAKE的宏修饰符 715 因错误结束MAKE文件 716 关闭命令显示 717 使用文件BUILTINSMAK 718 在MAKE中执行出口状态进程 719 同时激活和改变一个宏 720 为多个依赖文件执行一个MAKE命令 第十四章 高级C语言编程 721 判断是否有数学协处理器 722 理解CTYPEH,ISTYPE宏 723 控制直接的视像 724 检查系统和路径错误 725 显示预定义的错误信息 726 决定 *** 作系统版本号 727 理解可移值性 728 执行一个非本地的GOTO 729 获得进程ID(PID) 730 激活一个内部的DOS命令 731 使用-PSP全局变量 732 在变量申明中使用CONST修饰符 733 使用枚举类型 734 放置一个枚举类型来使用 735 理解一个枚举值 736 分配一个特殊的值给枚举类型 737 保存和恢复寄存器 738 动态列表简介 739 申明一个链接的列表结构 740 建立一个链接的列表 741 一个简单的链表例子 742 理解链表转移 743 创建一个更有用的列表 744 增加一个列表入口 745 插入一个列表入口 746 显示一个存储的目录 747 从一个列表中删除一个元素 748 使用一个双向链表 749 创建一个简单的双向链表 750 理解NODE-->PREVIOUS-->NEXT 751 从一个双向链表中移走一个元素 752 在一个双向链表中插入一个元素 753 理解子进程 754 派生一个子进程 755 使用其他的SPAWNLXX函数 756 使用SPAWNVXX函数 757 执行一个子进程 758 使用其他的EXECLXX函数 759 使用EXECVXX函数 760 理解覆盖 761 理解中断 762 PC机的中断 763 使用中断关键字 764 判断一个中断向量 765 设置一个中断向量 766 使能与禁止中断 767 生成简单的中断处理器 768 链接一个二次中断 769 生成一个中断 770 捕获PC机时钟 771 理解致命错误 772 C语言中的致命错误处理器 773 一个更完全的致命错误处理器 774 恢复改变过的中断 775 生成一个Ctrl+Break处理器 776 在用户的致命错误处理器使用DOS服务 777 使用指令集选择改善性能 778 直接插入内部函数 779 使能和禁止内在函数 780 理解快速函数调用 781 -FASTCALL参数传递的法则 782 理解不变代码 783 理解冗载入禁止 784 理解代码紧缩 785 理解循环紧缩 786 理解循环引入和强度削减 787 消除普通的子表达式 788 标准C语言转换 789 理解C语言的4个基本类型 790 基本类型与派生类型 791 理解初始化值 792 理解连接 793 理解临时申明 794 申明和定义 795 理解左值LVALUE 796 理解右值RVALUE 797 使用段寄存器关键字 798 谨慎使用远指针 799 理解正常化的指针 800 数学协处理器语句 801 理解变量中的CDECL和PASCAL 802 防止循环包含 第十五章 C++入门 803 C++介绍 804 C++源文件的差异 805 从简单的C++程序开始 806 理解COUT I/O流 807 使用COUT输出值和变量 808 用COUT连接不同的数据类型 809 显示十六进制和八进制数值 810 重定向COUT 811 如果钟情PRINTF,使用PRINTF 812 输出 CERR 813 用CIN得到输入 814 CIN不要使用指针 815 理解CIN如何选择数据域 816 理解输入输出流如何获得变量类型 817 使用CLOG实现输出 818 CIN、OCUT、CERR和CLOG是类的实例 819 使用FLUSH *** 纵符快速输出 820 理解ISOTREAMH头文件包含的内容 821 C++需要函数原型 822 C++增加的新关键字 823 C++支持匿名联合 824 分辨全局范围 825 提供缺省参数值 826 控制COUT的输出宽度 827 使用SETW设置COUT宽度 828 指定COUT的填充字符 829 左对齐和右对齐COUT的输出 830 控制COUT显示浮点数字的数目 831 以小数或科学记数格式显示数值 832 恢复COUT至默认值 833 设置输入输出基数 834 在需要的地方定义变量 835 在函数原型中放置默认参数值 836 使用按位运算符及COUT 837 理解迟缓或短路计算 838 在C++中使用CONST关键字 839 在C++中使用ENUM关键字 840 理解自由空间 841 用NEW分配内存 842 为多个数组分配内存 843 无自由空间的测试 844 关于堆空间 845 使用FAR指针和NEW运算符 846 释放内存至自由空间 847 理解C++中的引用 848 给函数传递引用 849 防止隐藏对象 850 用三种方法传递参数 851 使用引用的规则 852 函数可返回引用 853 使用INLINE关键字 854 使用C++的ASM关键字 855 用CIN读字符 856 用COUT写字符 857 简单过滤器程序 858 简单的TEE命令 859 简单的FIRST 860 更好的FIRST命令 861 文件结束测试 862 用ENDL产生新行 863 理解连接规范 864 理解重载 865 重载函数 866 重载函数的第二个例子 867 避免不明确的重载 868 使用CIN每次读一行 869 在循环中使用CINGETLINE 870 改变NEW运算符的缺省处理器 871 用SET-NEW-HANDLER函数设置NEW处理器 872 判断C++编译 873 理解C++中的结构 874 结构中定义函数成员 875 在结构内定义成员函数 876 在结构外定义成员函数 877 给成员函数传递参数 878 同一结构的多个变量 879 不同结构具有同名函数成员 880 同名成员不同函数 第十六章 对象 881 理解对象 882 理解面向对象编程 883 理解为什么使用对象 884 把程序分解成对象 885 理解对象和类 886 理解C++的类 887 理解封装 888 理解多态性 889 理解继承 890 类和结构的选择 891 创建简单类模型 892 实现简单类的程序 893 定义类构件 894 理解作用域分辨符 895 在申明中使用或省略类名 896 理解PUBLIC:标志 897 理解信息隐藏 898 理解PRIVATE:标志 899 理解PROTECTED:标志 900 使用公用和私数据 901 决定什么隐藏什么公开 902 公用方法常称为接口函数 903 在类外定义类函数 904 在类的内部和外部定义方法 905 理解对象实例 906 对象实例共享代码

JAVA中一共有八种基本数据类型,分别是:byte、short、int、long、float、double、char、boolean。

1、byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

2、short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。

3、int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

4、long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

5、float:32位,数据范围在34e-45~14e38,直接赋值时必须在数字后加上f或F。

6、double:64位,数据范围在49e-324~18e308,赋值时可以加d或D也可以不加。

7、boolean:只有true和false两个取值。

8、char:16位,存储Unicode码,用单引号赋值。

扩展资料:

Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。

Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。

参考资料:

JAVA_百度百科

以上就是关于Elasticsearch为啥这么快全部的内容,包括:Elasticsearch为啥这么快、CC程序员实用大全怎么样、java语言的所有数据类型分为哪几种等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/10123738.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存