《Redis设计与实现》[第一部分]数据结构与对象-C源码阅读(一)
一、简单动态字符串SDS 关键字:空间预分配,惰性空间释放,二进制安全 C字符串不易更改,所以Redis中把C字符串用在一些无须对字符串值进行修改的地方,作为字符串字面量(String literal),比如打印日志: redisLog(REDIS_WARING, “Redis is now ready to
一、简单动态字符串SDS
关键字:空间预分配,惰性空间释放,二进制安全
C字符串不易更改,所以Redis中把C字符串用在一些无须对字符串值进行修改的地方,作为字符串字面量(String literal),比如打印日志:
redisLog(REDIS_WARING, “Redis is now ready to exit, bye bye…”);
在Redis数据库中,包含字符串的键值对在底层都是由SDS实现的。
SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是SDS实现的。
源码
SDS结构的定义在sds.h中:
<code class="C hljs cpp"> <span class="hljs-comment">/* * 保存字符串对象的结构 */</span> <span class="hljs-keyword">struct</span> sdshdr { <span class="hljs-comment">// buf 中已占用空间的长度</span> <span class="hljs-keyword">int</span> len; <span class="hljs-comment">// buf 中剩余可用空间的长度,即未使用空间</span> <span class="hljs-keyword">int</span> <span class="hljs-built_in">free</span>; <span class="hljs-comment">// 数据空间</span> <span class="hljs-keyword">char</span> buf[]; };</code>
获取一个SDS长度的复杂度为O(1),由SDS的API在执行时自动设置和更新SDS长度,使用SDS无须进行任何手动修改长度的工作。
空间分配
SDS的空间分配策略是:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,若不满足,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,杜绝了发生缓冲区溢出的可能性。
通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略:
- 空间预分配
空间预分配用于减少连续执行字符串增长操作所需的内存分配次数。
通过这种预分配策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次。
其中额外分配的未使用空间数量由以下公式决定:
<code>1. 如果对SDS进行修改后,SDS的长度(即len属性的值)小于1MB,就分配和len属性同样大小的未使用空间,即len属性的值和free属性的值相同 2. 如果对SDS进行修改之后,SDS的长度大于等于1MB,就分配1MB的未使用空间。 </code>
- 惰性空间释放
惰性空间释放用于优化SDS字符串缩短操作的内存重分配操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
SDS的API都是二进制安全的(binary-safe),所有SDS API都会以二进制的方式处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,被读取时就是什么样。
Redis用SDS的buf数组保存二进制数据而不是字符。
SDS可以兼容部分C字符串函数。
二、链表
关键字:多态
当一个列表键包含了数量比较多的元素,或是列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
integers列表键的底层实现就是一个链表,链表中的每个结点都保存了一个整数值。
除了链表之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用链表保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区(output buffer)。
源码
链表结构的定义在adlist.h中:
<code class="C hljs cpp"> <span class="hljs-comment">/* - 双端链表节点 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> listNode { <span class="hljs-comment">// 前置节点</span> <span class="hljs-keyword">struct</span> listNode *prev; <span class="hljs-comment">// 后置节点</span> <span class="hljs-keyword">struct</span> listNode *next; <span class="hljs-comment">// 节点的值</span> <span class="hljs-keyword">void</span> *value; } listNode; <span class="hljs-comment">/* *双端链表迭代器 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> listIter { <span class="hljs-comment">// 当前迭代到的节点</span> listNode *next; <span class="hljs-comment">// 迭代的方向</span> <span class="hljs-keyword">int</span> direction; } listIter; <span class="hljs-comment">/* - 双端链表结构 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> <span class="hljs-built_in">list</span> { <span class="hljs-comment">// 表头节点</span> listNode *head; <span class="hljs-comment">// 表尾节点</span> listNode *tail; <span class="hljs-comment">// 节点值复制函数</span> <span class="hljs-keyword">void</span> *(*dup)(<span class="hljs-keyword">void</span> *ptr); <span class="hljs-comment">// 节点值释放函数</span> <span class="hljs-keyword">void</span> (*<span class="hljs-built_in">free</span>)(<span class="hljs-keyword">void</span> *ptr); <span class="hljs-comment">// 节点值对比函数</span> <span class="hljs-keyword">int</span> (*match)(<span class="hljs-keyword">void</span> *ptr, <span class="hljs-keyword">void</span> *key); <span class="hljs-comment">// 链表所包含的节点数量</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> len; } <span class="hljs-built_in">list</span>; </code>
list结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,dup、free和match成员则是用于实现多态链表所需的类型特定函数:
- dup函数用于复制链表结点所保存的值
- free函数用于释放链表结点所保存的值;
- match函数则用于对比链表结点所保存的值和另一个输入值是否相等。
Redis的链表实现的特性如下:
- 双端、无环、带表头指针和表尾指针、带链表长度计数器、多态
三、字典
关键字:多态,渐进式rehash,murmurhash2
Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、改、查也是构建在对字典的操作之上的。
字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,或是键值对中的元素都是比较长的字符串时,Redis就使用字典作为哈希键的底层实现。
Redis的字典使用哈希表作为底层实现,一个哈希表里可以有多个哈希表结点,每个哈希表结点就保存了字典中的一个键值对。
源码
字典所使用的哈希表在dict.h中定义:
<code class=" hljs objectivec"> <span class="hljs-comment">/* * 哈希表 * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> dictht { <span class="hljs-comment">// 哈希表数组,数组中的每个元素都是一个指向dictEntry结构的指针</span> dictEntry **table; <span class="hljs-comment">// 哈希表大小</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> size; <span class="hljs-comment">// 哈希表大小掩码,用于计算索引值</span> <span class="hljs-comment">// 总是等于 size - 1</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> sizemask; <span class="hljs-comment">// 该哈希表已有节点的数量</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> used; } dictht;</code>
- table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值对。
- size属性记录了哈希表的大小,即是table数组的大小。
- used属性则记录了哈希表目前已有结点(键值对的数量)
- sizemask属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面
<code class=" hljs d"> <span class="hljs-comment">/* * 哈希表节点 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> dictEntry { <span class="hljs-comment">// 键</span> <span class="hljs-keyword">void</span> *key; <span class="hljs-comment">// 值</span> <span class="hljs-keyword">union</span> { <span class="hljs-keyword">void</span> *val; uint64_t u64; int64_t s64; } v; <span class="hljs-comment">// 指向下个哈希表节点,形成链表</span> <span class="hljs-keyword">struct</span> dictEntry *next; } dictEntry;</code>
- key属性保存着键值对中的键
- v属性保存键值对中的值,其中键值对中的值可以是一个指针,或是一个uint64_t整数,或是一个int64_t整数
- next属性指向另一个哈希表结点的指针,使用链地址法解决键冲突问题。
<code class=" hljs d"> <span class="hljs-comment">/* * 字典 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> dict { <span class="hljs-comment">// 类型特定函数</span> dictType *type; <span class="hljs-comment">// 私有数据</span> <span class="hljs-keyword">void</span> *privdata; <span class="hljs-comment">// 哈希表</span> dictht ht[<span class="hljs-number">2</span>]; <span class="hljs-comment">// rehash 索引</span> <span class="hljs-comment">// 当 rehash 不在进行时,值为 -1</span> <span class="hljs-keyword">int</span> rehashidx; <span class="hljs-comment">/* rehashing not in progress if rehashidx == -1 */</span> <span class="hljs-comment">// 目前正在运行的安全迭代器的数量</span> <span class="hljs-keyword">int</span> iterators; <span class="hljs-comment">/* number of iterators currently running */</span> } dict;</code>
type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:
- type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
- privdata属性保存了需要传给那些类型特定函数的可选参数。
<code class=" hljs objectivec"><span class="hljs-comment">/* * 字典类型特定函数 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> dictType { <span class="hljs-comment">// 计算哈希值的函数</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> (*hashFunction)(<span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *key); <span class="hljs-comment">// 复制键的函数</span> <span class="hljs-keyword">void</span> *(*keyDup)(<span class="hljs-keyword">void</span> *privdata, <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *key); <span class="hljs-comment">// 复制值的函数</span> <span class="hljs-keyword">void</span> *(*valDup)(<span class="hljs-keyword">void</span> *privdata, <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *obj); <span class="hljs-comment">// 对比键的函数</span> <span class="hljs-keyword">int</span> (*keyCompare)(<span class="hljs-keyword">void</span> *privdata, <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *key1, <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *key2); <span class="hljs-comment">// 销毁键的函数</span> <span class="hljs-keyword">void</span> (*keyDestructor)(<span class="hljs-keyword">void</span> *privdata, <span class="hljs-keyword">void</span> *key); <span class="hljs-comment">// 销毁值的函数</span> <span class="hljs-keyword">void</span> (*valDestructor)(<span class="hljs-keyword">void</span> *privdata, <span class="hljs-keyword">void</span> *obj); } dictType;</code>
- ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
- rehashidx属性记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1.
<code class=" hljs d"> <span class="hljs-comment">/* * 字典迭代器 * - 如果 safe 属性的值为 1 ,那么在迭代进行的过程中, - 程序仍然可以执行 dictAdd 、 dictFind 和其他函数,对字典进行修改。 * - 如果 safe 不为 1 ,那么程序只会调用 dictNext 对字典进行迭代, - 而不对字典进行修改。 */</span> <span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> dictIterator { <span class="hljs-comment">// 被迭代的字典</span> dict *d; <span class="hljs-comment">// table :正在被迭代的哈希表号码,值可以是 0 或 1 。</span> <span class="hljs-comment">// index :迭代器当前所指向的哈希表索引位置。</span> <span class="hljs-comment">// safe :标识这个迭代器是否安全</span> <span class="hljs-keyword">int</span> table, index, safe; <span class="hljs-comment">// entry :当前迭代到的节点的指针</span> <span class="hljs-comment">// nextEntry :当前迭代节点的下一个节点</span> <span class="hljs-comment">// 因为在安全迭代器运作时, entry 所指向的节点可能会被修改,</span> <span class="hljs-comment">// 所以需要一个额外的指针来保存下一节点的位置,</span> <span class="hljs-comment">// 从而防止指针丢失</span> dictEntry *entry, *nextEntry; <span class="hljs-built_in">long</span> <span class="hljs-built_in">long</span> fingerprint; <span class="hljs-comment">/* unsafe iterator fingerprint for misuse detection */</span> } dictIterator;</code>
哈希
Redis计算哈希值和索引值的方法如下:
<code class=" hljs lasso"> <span class="hljs-comment">// 使用字典设置的哈希函数,计算键key的哈希值</span> hash <span class="hljs-subst">=</span> dict<span class="hljs-subst">-></span><span class="hljs-keyword">type</span><span class="hljs-subst">-></span>hashFunction(key); <span class="hljs-comment">// 使用哈希表的sizemask属性和哈希值,计算出索引值</span> <span class="hljs-comment">// 根据情况不同,ht[x]可以是ht[0]或ht[1]</span> index <span class="hljs-subst">=</span> hash <span class="hljs-subst">&</span> dict<span class="hljs-subst">-></span>ht<span class="hljs-preprocessor">[</span>x<span class="hljs-preprocessor">]</span><span class="hljs-markup">.sizemask;</span></code>
<code class=" hljs cpp"><span class="hljs-comment">/* ------------------------- hash functions ------------------------------ */</span> <span class="hljs-comment">/* Thomas Wang's 32 bit Mix Function */</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> dictIntHashFunction(<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> key) { key += ~(key << <span class="hljs-number">15</span>); key ^= (key >> <span class="hljs-number">10</span>); key += (key << <span class="hljs-number">3</span>); key ^= (key >> <span class="hljs-number">6</span>); key += ~(key << <span class="hljs-number">11</span>); key ^= (key >> <span class="hljs-number">16</span>); <span class="hljs-keyword">return</span> key; } <span class="hljs-comment">/* Identity hash function for integer keys */</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> dictIdentityHashFunction(<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> key) { <span class="hljs-keyword">return</span> key; } <span class="hljs-keyword">static</span> uint32_t dict_hash_function_seed = <span class="hljs-number">5381</span>; <span class="hljs-keyword">void</span> dictSetHashFunctionSeed(uint32_t seed) { dict_hash_function_seed = seed; } uint32_t dictGetHashFunctionSeed(<span class="hljs-keyword">void</span>) { <span class="hljs-keyword">return</span> dict_hash_function_seed; } <span class="hljs-comment">/* MurmurHash2, by Austin Appleby * Note - This code makes a few assumptions about how your machine behaves - * 1. We can read a 4-byte value from any address without crashing * 2. sizeof(int) == 4 * * And it has a few limitations - * * 1. It will not work incrementally. * 2. It will not produce the same results on little-endian and big-endian * machines. */</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> dictGenHashFunction(<span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *key, <span class="hljs-keyword">int</span> len) { <span class="hljs-comment">/* 'm' and 'r' are mixing constants generated offline. They're not really 'magic', they just happen to work well. */</span> uint32_t seed = dict_hash_function_seed; <span class="hljs-keyword">const</span> uint32_t m = <span class="hljs-number">0x5bd1e995</span>; <span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> r = <span class="hljs-number">24</span>; <span class="hljs-comment">/* Initialize the hash to a 'random' value */</span> uint32_t h = seed ^ len; <span class="hljs-comment">/* Mix 4 bytes at a time into the hash */</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *data = (<span class="hljs-keyword">const</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *)key; <span class="hljs-keyword">while</span>(len >= <span class="hljs-number">4</span>) { uint32_t k = *(uint32_t*)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += <span class="hljs-number">4</span>; len -= <span class="hljs-number">4</span>; } <span class="hljs-comment">/* Handle the last few bytes of the input array */</span> <span class="hljs-keyword">switch</span>(len) { <span class="hljs-keyword">case</span> <span class="hljs-number">3</span>: h ^= data[<span class="hljs-number">2</span>] << <span class="hljs-number">16</span>; <span class="hljs-keyword">case</span> <span class="hljs-number">2</span>: h ^= data[<span class="hljs-number">1</span>] << <span class="hljs-number">8</span>; <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>: h ^= data[<span class="hljs-number">0</span>]; h *= m; }; <span class="hljs-comment">/* Do a few final mixes of the hash to ensure the last few * bytes are well-incorporated. */</span> h ^= h >> <span class="hljs-number">13</span>; h *= m; h ^= h >> <span class="hljs-number">15</span>; <span class="hljs-keyword">return</span> (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>)h; } <span class="hljs-comment">/* And a case insensitive hash function (based on djb hash) */</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> dictGenCaseHashFunction(<span class="hljs-keyword">const</span> <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *buf, <span class="hljs-keyword">int</span> len) { <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> hash = (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>)dict_hash_function_seed; <span class="hljs-keyword">while</span> (len--) hash = ((hash << <span class="hljs-number">5</span>) + hash) + (<span class="hljs-built_in">tolower</span>(*buf++)); <span class="hljs-comment">/* hash * 33 + c */</span> <span class="hljs-keyword">return</span> hash; }</code>
当字典被用作数据库的底层实现,或是哈希键的底层实现时,Redis使用MurmurHash2算法计算键的哈希值:
- 该算法的优点在于,即使输入的键是有规律的,算法仍能给出一个很好的随机分布性,并且算法的计算速度也非常快。
为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或太少时,程序需要对哈希表的大小进行相应的扩展或收缩。
- 哈希表的负载因子计算公式:load_factor = ht[0].used/ht[0].size
rehash
扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成,Redis对字典的哈希表执行rehash的步骤如下:
-
为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(即ht[0].used属性的值)
- 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n(2的n次方幂);
- 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n。
将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
- 当ht[0]包含的所有键值对都迁移到ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。
当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:
- 服务器目前没有在执行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的负载因子大于等于1
- 服务器目前正在执行BGSAVE命令或BGREWRITEAOF命令,并且哈希表的负载因子大于等于5
在执行BGSAVE命令或BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高执行扩展操作所需的负载因子,从而尽可能地避免在子进程存在期间进行哈希表扩展操作,这避免了不必要的内存写入操作,最大限度地节约内存。
当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。
渐进式rehash
为了避免rehash对服务器性能造成影响,服务器不是一次性将ht[0]里面的所有键值对全部rehash到ht[1],而是分多次、渐进式地将ht[0]里面的键值对慢慢rehash到ht[1]。
以下是哈希表渐进式rehash的详细步骤:
为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。
在字典中维持一个索引计数器变量rehashidx,值设置为0,表示rehash工作正式开始
在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以为,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增一。
随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash到ht[1]上,这是程序将rehashidx属性的值设为-1,表示rehash操作已完成
渐进式rehash采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。
在进行渐进式rehash的过程中,字典会同时使用ht[0]和ht[1]两个哈希表,所以在渐进式rehash进行期间,字典的删除、查找、更新会在两个哈希表上进行,比如现在ht[0]中查找,没找到再去ht[1]查找
在渐进式rehash执行期间,新添加到字典的键值对一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,这样保证了ht[0]包含的键值对数量只减不增,随着rehash操作的执行最终变成空表。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java에서 복잡한 데이터 구조를 사용할 때 Comparator는 유연한 비교 메커니즘을 제공하는 데 사용됩니다. 구체적인 단계에는 비교기 클래스 정의, 비교 논리를 정의하기 위한 비교 메서드 재작성 등이 포함됩니다. 비교기 인스턴스를 만듭니다. Collections.sort 메서드를 사용하여 컬렉션 및 비교기 인스턴스를 전달합니다.

이 기사에서는 Windows PC의 몰입형 리더에서 Microsoft Reading Coach를 사용하는 방법을 보여줍니다. 읽기 지도 기능은 학생이나 개인이 읽기를 연습하고 읽고 쓰는 능력을 개발하는 데 도움이 됩니다. 지원되는 애플리케이션에서 구절이나 문서를 읽는 것부터 시작하고, 이를 기반으로 Reading Coach 도구를 통해 읽기 보고서가 생성됩니다. 읽기 보고서에는 읽기 정확도, 읽는 데 걸린 시간, 분당 올바른 단어 수, 읽으면서 가장 어려웠던 단어가 표시됩니다. 또한 단어를 연습할 수 있어 전반적인 읽기 능력을 개발하는 데 도움이 됩니다. 현재 Office 또는 Microsoft365(웹용 OneNote 및 We용 Word 포함)만

MySQL 쿼리 결과 배열을 객체로 변환하는 방법은 다음과 같습니다. 빈 객체 배열을 만듭니다. 결과 배열을 반복하고 각 행에 대해 새 개체를 만듭니다. foreach 루프를 사용하여 각 행의 키-값 쌍을 새 개체의 해당 속성에 할당합니다. 개체 배열에 새 개체를 추가합니다. 데이터베이스 연결을 닫습니다.

데이터 구조와 알고리즘은 Java 개발의 기초입니다. 이 기사에서는 Java의 주요 데이터 구조(예: 배열, 연결 목록, 트리 등)와 알고리즘(예: 정렬, 검색, 그래프 알고리즘 등)을 자세히 살펴봅니다. 이러한 구조는 배열을 사용하여 점수를 저장하고, 연결된 목록을 사용하여 쇼핑 목록을 관리하고, 스택을 사용하여 재귀를 구현하고, 대기열을 사용하여 스레드를 동기화하고, 트리 및 해시 테이블을 사용하여 빠른 검색 및 인증을 저장하는 등 실제 사례를 통해 설명됩니다. 이러한 개념을 이해하면 효율적이고 유지 관리가 가능한 Java 코드를 작성할 수 있습니다.

PHP 코드의 소스 코드를 해석 및 실행하지 않고 브라우저에 표시하는 방법은 무엇입니까? PHP는 동적 웹 페이지를 개발하는 데 일반적으로 사용되는 서버 측 스크립팅 언어입니다. 서버에서 PHP 파일이 요청되면 서버는 그 안에 있는 PHP 코드를 해석하고 실행한 후 최종 HTML 콘텐츠를 브라우저에 보내 표시합니다. 그러나 때때로 PHP 파일의 소스 코드를 실행하는 대신 브라우저에 직접 표시하고 싶을 때가 있습니다. 이 기사에서는 PHP 코드의 소스 코드를 해석 및 실행하지 않고 브라우저에 표시하는 방법을 소개합니다. PHP에서는 다음을 사용할 수 있습니다.

PHP 함수는 return 문과 객체 인스턴스를 사용하여 객체를 반환함으로써 데이터를 사용자 정의 구조로 캡슐화할 수 있습니다. 구문: functionget_object():object{}. 이를 통해 사용자 정의 속성과 메소드를 사용하여 객체를 생성하고 객체 형태로 데이터를 처리할 수 있습니다.

PHP에서 배열은 순서가 지정된 시퀀스이며 요소는 인덱스로 액세스됩니다. 객체는 new 키워드를 통해 생성된 속성과 메서드가 있는 엔터티입니다. 배열 액세스는 인덱스를 통해 이루어지며, 객체 액세스는 속성/메서드를 통해 이루어집니다. 배열 값이 전달되고 객체 참조가 전달됩니다.

AVL 트리는 빠르고 효율적인 데이터 작업을 보장하는 균형 잡힌 이진 검색 트리입니다. 균형을 이루기 위해 좌회전 및 우회전 작업을 수행하고 균형을 위반하는 하위 트리를 조정합니다. AVL 트리는 높이 균형을 활용하여 노드 수에 비해 트리 높이가 항상 작게 되도록 함으로써 로그 시간 복잡도(O(logn)) 검색 작업을 달성하고 대규모 데이터 세트에서도 데이터 구조의 효율성을 유지합니다.
