一、SDS介紹
Redis沒有使用C語言傳統(tǒng)的字符串表示(以空字符結(jié)尾的字符串?dāng)?shù)組,以下簡稱C字符串),?而是自己構(gòu)建了一種名為簡單動(dòng)態(tài)字符串(simple dynamic string,SDS)的抽象類型厂捞,并將SDS作為Redis默認(rèn)的字符串表示。
在 Redis 里面, C字符串只會(huì)作為字符串字面量(string literal)主卫, 用在一些無須對(duì)字符串值進(jìn)行修改的地方, 比如打印日志:
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
當(dāng) Redis 需要的不僅僅是一個(gè)字符串字面量鹃愤, 而是一個(gè)可以被修改的字符串值時(shí)簇搅,?Redis 就會(huì)使用 SDS 來表示字符串值: 比如在 Redis 的數(shù)據(jù)庫里面, 包含字符串值的鍵值對(duì)在底層都是由 SDS 實(shí)現(xiàn)的软吐。
舉個(gè)例子瘩将, 如果客戶端執(zhí)行命令:
redis>SET msg "hello world"
OK
那么 Redis 將在數(shù)據(jù)庫中創(chuàng)建了一個(gè)新的鍵值對(duì), 其中:
????鍵值對(duì)的鍵是一個(gè)字符串對(duì)象, 對(duì)象的底層實(shí)現(xiàn)是一個(gè)保存著字符串?"msg"?的 SDS 姿现。
????鍵值對(duì)的值也是一個(gè)字符串對(duì)象肠仪, 對(duì)象的底層實(shí)現(xiàn)是一個(gè)保存著字符串?"hello?world"?的 SDS 。
又比如說备典, 如果客戶端執(zhí)行命令:
redis>RPUSH fruits "apple" "banana" "cherry"
(integer) 3
那么 Redis 將在數(shù)據(jù)庫中創(chuàng)建一個(gè)新的鍵值對(duì)异旧, 其中:
????鍵值對(duì)的鍵是一個(gè)字符串對(duì)象, 對(duì)象的底層實(shí)現(xiàn)是一個(gè)保存了字符串?"fruits"?的 SDS 提佣。
????鍵值對(duì)的值是一個(gè)列表對(duì)象吮蛹, 列表對(duì)象包含了三個(gè)字符串對(duì)象, 這三個(gè)字符串對(duì)象分別由三個(gè) ????SDS 實(shí)現(xiàn): 第一個(gè) SDS 保存著字符串?"apple"?拌屏, 第二個(gè) SDS 保存著字符串?"banana"?潮针, 第三個(gè) ????SDS 保存著字符串?"cherry"?。
除了用來保存數(shù)據(jù)庫中的字符串值之外倚喂, SDS 還被用作緩沖區(qū)(buffer): AOF 模塊中的 AOF 緩沖區(qū)然低, 以及客戶端狀態(tài)中的輸入緩沖區(qū), 都是由 SDS 實(shí)現(xiàn)的务唐。
每個(gè)?sds.h/sdshdr?結(jié)構(gòu)表示一個(gè) SDS 值:
struct sdshdr {
// 記錄 buf 數(shù)組中已使用字節(jié)的數(shù)量
// 等于 SDS 所保存字符串的長度
int len;
// 記錄 buf 數(shù)組中未使用字節(jié)的數(shù)量
int free;
// 字節(jié)數(shù)組雳攘,用于保存字符串
char buf[];
};
下圖展示了一個(gè) SDS 示例:
????free?屬性的值為?0?, 表示這個(gè) SDS 沒有分配任何未使用空間枫笛。
????len?屬性的值為?5?吨灭, 表示這個(gè) SDS 保存了一個(gè)五字節(jié)長的字符串。
????buf?屬性是一個(gè)?char?類型的數(shù)組刑巧, 數(shù)組的前五個(gè)字節(jié)分別保存了?'R'?喧兄、?'e'?、?'d'?啊楚、?'i'?吠冤、?'s'?五個(gè)字符, 而最后一個(gè)字節(jié)則保存了空字符?'\0'?恭理。
SDS 遵循 C 字符串以空字符結(jié)尾的慣例拯辙, 保存空字符的?1?字節(jié)空間不計(jì)算在 SDS 的?len?屬性里面, 并且為空字符分配額外的?1?字節(jié)空間颜价, 以及添加空字符到字符串末尾等操作都是由 SDS 函數(shù)自動(dòng)完成的涯保, 所以這個(gè)空字符對(duì)于 SDS 的使用者來說是完全透明的。
遵循空字符結(jié)尾這一慣例的好處是周伦, SDS 可以直接重用一部分 C 字符串函數(shù)庫里面的函數(shù)夕春。
舉個(gè)例子, 如果我們有一個(gè)指向下圖所示 SDS 的指針?s?专挪, 那么我們可以直接使用?stdio.h/printf?函數(shù)及志, 通過執(zhí)行以下語句:
printf("%s", s->buf);
根據(jù)傳統(tǒng)片排, C 語言使用長度為?N+1?的字符數(shù)組來表示長度為?N?的字符串, 并且字符數(shù)組的最后一個(gè)元素總是空字符?'\0'?速侈。
比如說划纽,?下圖就展示了一個(gè)值為?"Redis"?的 C 字符串:
C 語言使用的這種簡單的字符串表示方式, 并不能滿足 Redis 對(duì)字符串在安全性锌畸、效率勇劣、以及功能方面的要求,接下來的內(nèi)容將詳細(xì)對(duì)比 C 字符串和 SDS 之間的區(qū)別潭枣, 并說明 SDS 比 C 字符串更適用于 Redis 的原因比默。
因?yàn)?C 字符串并不記錄自身的長度信息, 所以為了獲取一個(gè) C 字符串的長度盆犁, 程序必須遍歷整個(gè)字符串命咐, 對(duì)遇到的每個(gè)字符進(jìn)行計(jì)數(shù), 直到遇到代表字符串結(jié)尾的空字符為止谐岁, 這個(gè)操作的復(fù)雜度為?O(N)?醋奠。
和 C 字符串不同, 因?yàn)?SDS 在?len?屬性中記錄了 SDS 本身的長度伊佃, 所以獲取一個(gè) SDS 長度的復(fù)雜度僅為?O(1)?窜司。
舉個(gè)例子, 對(duì)于下圖所示的 SDS 來說航揉, 程序只要訪問 SDS 的?len?屬性塞祈, 就可以立即知道 SDS 的長度為?5?字節(jié):
設(shè)置和更新 SDS 長度的工作是由 SDS 的 API 在執(zhí)行時(shí)自動(dòng)完成的, 使用 SDS 無須進(jìn)行任何手動(dòng)修改長度的工作帅涂。
通過使用 SDS 而不是 C 字符串议薪, Redis 將獲取字符串長度所需的復(fù)雜度從?O(N)?降低到了?O(1)?, 這確保了獲取字符串長度的工作不會(huì)成為 Redis 的性能瓶頸媳友。
比如說斯议, 因?yàn)樽址I在底層使用 SDS 來實(shí)現(xiàn), 所以即使我們對(duì)一個(gè)非常長的字符串鍵反復(fù)執(zhí)行?STRLEN?命令醇锚, 也不會(huì)對(duì)系統(tǒng)性能造成任何影響哼御, 因?yàn)?STRLEN?命令的復(fù)雜度僅為?O(1)?。
除了獲取字符串長度的復(fù)雜度高之外搂抒, C 字符串不記錄自身長度帶來的另一個(gè)問題是容易造成緩沖區(qū)溢出(buffer overflow)艇搀。
舉個(gè)例子尿扯,?<string.h>/strcat?函數(shù)可以將?src?字符串中的內(nèi)容拼接到?dest?字符串的末尾:
char *strcat(char *dest,const char *src);
因?yàn)?C 字符串不記錄自身的長度求晶, 所以?strcat?假定用戶在執(zhí)行這個(gè)函數(shù)時(shí), 已經(jīng)為?dest?分配了足夠多的內(nèi)存衷笋, 可以容納?src?字符串中的所有內(nèi)容芳杏, 而一旦這個(gè)假定不成立時(shí)矩屁, 就會(huì)產(chǎn)生緩沖區(qū)溢出。
舉個(gè)例子爵赵, 假設(shè)程序里有兩個(gè)在內(nèi)存中緊鄰著的 C 字符串?s1?和?s2?吝秕, 其中?s1?保存了字符串?"Redis"?, 而?s2?則保存了字符串?"MongoDB"?空幻, 如下圖所示烁峭。
如果一個(gè)程序員決定通過執(zhí)行:
strcat(s1," Cluster");
將?s1?的內(nèi)容修改為?"Redis?Cluster"?, 但粗心的他卻忘了在執(zhí)行?strcat?之前為?s1?分配足夠的空間秕铛, 那么在?strcat?函數(shù)執(zhí)行之后约郁,?s1?的數(shù)據(jù)將溢出到?s2?所在的空間中, 導(dǎo)致?s2?保存的內(nèi)容被意外地修改但两, 如下圖所示。
與 C 字符串不同, SDS 的空間分配策略完全杜絕了發(fā)生緩沖區(qū)溢出的可能性: 當(dāng) SDS API 需要對(duì) SDS 進(jìn)行修改時(shí)进鸠, API 會(huì)先檢查 SDS 的空間是否滿足修改所需的要求各拷, 如果不滿足的話, API 會(huì)自動(dòng)將 SDS 的空間擴(kuò)展至執(zhí)行修改所需的大小紧阔, 然后才執(zhí)行實(shí)際的修改操作坊罢, 所以使用 SDS 既不需要手動(dòng)修改 SDS 的空間大小, 也不會(huì)出現(xiàn)前面所說的緩沖區(qū)溢出問題擅耽。
舉個(gè)例子艘绍, SDS 的 API 里面也有一個(gè)用于執(zhí)行拼接操作的?sdscat?函數(shù), 它可以將一個(gè) C 字符串拼接到給定 SDS 所保存的字符串的后面秫筏, 但是在執(zhí)行拼接操作之前诱鞠,?sdscat?會(huì)先檢查給定 SDS 的空間是否足夠, 如果不夠的話这敬,?sdscat?就會(huì)先擴(kuò)展 SDS 的空間航夺, 然后才執(zhí)行拼接操作。
比如說崔涂, 如果我們執(zhí)行:
sdscat(s, " Cluster");
其中 SDS 值?s?如下圖所示阳掐, 那么?sdscat?將在執(zhí)行拼接操作之前檢查?s?的長度是否足夠, 在發(fā)現(xiàn)?s?目前的空間不足以拼接?"?Cluster"?之后冷蚂,?sdscat?就會(huì)先擴(kuò)展?s?的空間缭保, 然后才執(zhí)行拼接?"?Cluster"?的操作, 拼接操作完成之后的 SDS 如下圖所示蝙茶。
正如前面所說艺骂, 因?yàn)?C 字符串并不記錄自身的長度, 所以對(duì)于一個(gè)包含了?N?個(gè)字符的 C 字符串來說隆夯, 這個(gè) C 字符串的底層實(shí)現(xiàn)總是一個(gè)?N+1?個(gè)字符長的數(shù)組(額外的一個(gè)字符空間用于保存空字符)钳恕。
因?yàn)?C 字符串的長度和底層數(shù)組的長度之間存在著這種關(guān)聯(lián)性别伏, 所以每次增長或者縮短一個(gè) C 字符串, 程序都總要對(duì)保存這個(gè) C 字符串的數(shù)組進(jìn)行一次內(nèi)存重分配操作:
如果程序執(zhí)行的是增長字符串的操作忧额, 比如拼接操作(append)厘肮, 那么在執(zhí)行這個(gè)操作之前, 程序需要先通過內(nèi)存重分配來擴(kuò)展底層數(shù)組的空間大小 —— 如果忘了這一步就會(huì)產(chǎn)生緩沖區(qū)溢出睦番。
如果程序執(zhí)行的是縮短字符串的操作类茂, 比如截?cái)嗖僮鳎╰rim), 那么在執(zhí)行這個(gè)操作之后托嚣, 程序需要通過內(nèi)存重分配來釋放字符串不再使用的那部分空間 —— 如果忘了這一步就會(huì)產(chǎn)生內(nèi)存泄漏大咱。
舉個(gè)例子, 如果我們持有一個(gè)值為?"Redis"?的 C 字符串?s?注益, 那么為了將?s?的值改為?"Redis?Cluster"?碴巾, 在執(zhí)行:
strcat(s, " Cluster");
之前, 我們需要先使用內(nèi)存重分配操作丑搔, 擴(kuò)展?s?的空間厦瓢。
之后, 如果我們又打算將?s?的值從?"Redis?Cluster"?改為?"Redis?Cluster?Tutorial"?啤月, 那么在執(zhí)行:
strcat(s, " Tutorial");
之前煮仇, 我們需要再次使用內(nèi)存重分配擴(kuò)展?s?的空間, 諸如此類谎仲。
因?yàn)閮?nèi)存重分配涉及復(fù)雜的算法浙垫, 并且可能需要執(zhí)行系統(tǒng)調(diào)用, 所以它通常是一個(gè)比較耗時(shí)的操作:
在一般程序中郑诺, 如果修改字符串長度的情況不太常出現(xiàn)夹姥, 那么每次修改都執(zhí)行一次內(nèi)存重分配是可以接受的。
但是 Redis 作為數(shù)據(jù)庫辙诞, 經(jīng)常被用于速度要求嚴(yán)苛辙售、數(shù)據(jù)被頻繁修改的場(chǎng)合, 如果每次修改字符串的長度都需要執(zhí)行一次內(nèi)存重分配的話飞涂, 那么光是執(zhí)行內(nèi)存重分配的時(shí)間就會(huì)占去修改字符串所用時(shí)間的一大部分旦部, 如果這種修改頻繁地發(fā)生的話, 可能還會(huì)對(duì)性能造成影響较店。
為了避免 C 字符串的這種缺陷士八, SDS 通過未使用空間解除了字符串長度和底層數(shù)組長度之間的關(guān)聯(lián): 在 SDS 中,?buf?數(shù)組的長度不一定就是字符數(shù)量加一梁呈, 數(shù)組里面可以包含未使用的字節(jié)婚度, 而這些字節(jié)的數(shù)量就由 SDS 的?free?屬性記錄。
通過未使用空間捧杉, SDS 實(shí)現(xiàn)了空間預(yù)分配和惰性空間釋放兩種優(yōu)化策略陕见。
空間預(yù)分配
空間預(yù)分配用于優(yōu)化 SDS 的字符串增長操作: 當(dāng) SDS 的 API 對(duì)一個(gè) SDS 進(jìn)行修改秘血, 并且需要對(duì) SDS 進(jìn)行空間擴(kuò)展的時(shí)候味抖, 程序不僅會(huì)為 SDS 分配修改所必須要的空間评甜, 還會(huì)為 SDS 分配額外的未使用空間。
其中仔涩, 額外分配的未使用空間數(shù)量由以下公式?jīng)Q定:
如果對(duì) SDS 進(jìn)行修改之后忍坷, SDS 的長度(也即是?len?屬性的值)將小于?1?MB?, 那么程序分配和?len?屬性同樣大小的未使用空間熔脂, 這時(shí) SDS?len?屬性的值將和?free?屬性的值相同佩研。 舉個(gè)例子, 如果進(jìn)行修改之后霞揉, SDS 的?len?將變成?13?字節(jié)旬薯, 那么程序也會(huì)分配?13字節(jié)的未使用空間, SDS 的?buf?數(shù)組的實(shí)際長度將變成?13?+?13?+?1?=?27?字節(jié)(額外的一字節(jié)用于保存空字符)适秩。
如果對(duì) SDS 進(jìn)行修改之后绊序, SDS 的長度將大于等于?1?MB?, 那么程序會(huì)分配?1?MB?的未使用空間秽荞。 舉個(gè)例子骤公, 如果進(jìn)行修改之后, SDS 的?len?將變成?30?MB?扬跋, 那么程序會(huì)分配?1?MB?的未使用空間阶捆, SDS 的?buf?數(shù)組的實(shí)際長度將為?30?MB?+?1?MB?+?1?byte?。
通過空間預(yù)分配策略钦听, Redis 可以減少連續(xù)執(zhí)行字符串增長操作所需的內(nèi)存重分配次數(shù)洒试。
舉個(gè)例子, 對(duì)于下圖所示的 SDS 值?s?來說朴上, 如果我們執(zhí)行:
sdscat(s, " Cluster");
那么?sdscat?將執(zhí)行一次內(nèi)存重分配操作儡司, 將 SDS 的長度修改為?13?字節(jié), 并將 SDS 的未使用空間同樣修改為?13?字節(jié)余指。
如果這時(shí)捕犬, 我們?cè)俅螌?duì)?s?執(zhí)行:
sdscat(s, " Tutorial");
那么這次?sdscat?將不需要執(zhí)行內(nèi)存重分配: 因?yàn)槲词褂每臻g里面的?13?字節(jié)足以保存?9?字節(jié)的?"?Tutorial"?, 執(zhí)行?sdscat?之后的 SDS 如圖?
在擴(kuò)展 SDS 空間之前酵镜, SDS API 會(huì)先檢查未使用空間是否足夠碉碉, 如果足夠的話, API 就會(huì)直接使用未使用空間淮韭, 而無須執(zhí)行內(nèi)存重分配垢粮。
通過這種預(yù)分配策略, SDS 將連續(xù)增長?N?次字符串所需的內(nèi)存重分配次數(shù)從必定?N?次降低為最多?N?次靠粪。
惰性空間釋放
惰性空間釋放用于優(yōu)化 SDS 的字符串縮短操作: 當(dāng) SDS 的 API 需要縮短 SDS 保存的字符串時(shí)蜡吧, 程序并不立即使用內(nèi)存重分配來回收縮短后多出來的字節(jié)毫蚓, 而是使用?free?屬性將這些字節(jié)的數(shù)量記錄起來, 并等待將來使用昔善。
舉個(gè)例子元潘,?sdstrim?函數(shù)接受一個(gè) SDS 和一個(gè) C 字符串作為參數(shù), 從 SDS 左右兩端分別移除所有在 C 字符串中出現(xiàn)過的字符君仆。
比如對(duì)于下圖所示的 SDS 值?s?來說翩概, 執(zhí)行:
sdstrim(s, "XY");????// 移除 SDS 字符串中的所有 'X' 和 'Y'
注意執(zhí)行?sdstrim?之后的 SDS 并沒有釋放多出來的?8?字節(jié)空間, 而是將這?8?字節(jié)空間作為未使用空間保留在了 SDS 里面返咱, 如果將來要對(duì) SDS 進(jìn)行增長操作的話钥庇, 這些未使用空間就可能會(huì)派上用場(chǎng)。
舉個(gè)例子咖摹, 如果現(xiàn)在對(duì)?s?執(zhí)行:
sdscat(s, " Redis");
那么完成這次?sdscat?操作將不需要執(zhí)行內(nèi)存重分配: 因?yàn)?SDS 里面預(yù)留的?8?字節(jié)空間已經(jīng)足以拼接?6?個(gè)字節(jié)長的?"?Redis"?评姨, 如下圖所示。
通過惰性空間釋放策略萤晴, SDS 避免了縮短字符串時(shí)所需的內(nèi)存重分配操作吐句, 并為將來可能有的增長操作提供了優(yōu)化。
與此同時(shí)硫眯, SDS 也提供了相應(yīng)的 API 蕴侧, 讓我們可以在有需要時(shí), 真正地釋放 SDS 里面的未使用空間两入, 所以不用擔(dān)心惰性空間釋放策略會(huì)造成內(nèi)存浪費(fèi)净宵。
二、字符串對(duì)象
字符串對(duì)象的編碼可以是?int?裹纳、?raw?或者?embstr?择葡。
如果一個(gè)字符串對(duì)象保存的是整數(shù)值, 并且這個(gè)整數(shù)值可以用?long?類型來表示剃氧, 那么字符串對(duì)象會(huì)將整數(shù)值保存在字符串對(duì)象結(jié)構(gòu)的?ptr屬性里面(將?void*?轉(zhuǎn)換成?long?)敏储, 并將字符串對(duì)象的編碼設(shè)置為?int?。
舉個(gè)例子朋鞍, 如果我們執(zhí)行以下?SET?命令已添, 那么服務(wù)器將創(chuàng)建一個(gè) int?編碼的字符串對(duì)象作為?number?鍵的值:
redis>SET number 10086?
OK
redis>OBJECT ENCODING number
"int"
如果字符串對(duì)象保存的是一個(gè)字符串值, 并且這個(gè)字符串值的長度大于?39?字節(jié)滥酥, 那么字符串對(duì)象將使用一個(gè)簡單動(dòng)態(tài)字符串(SDS)來保存這個(gè)字符串值更舞, 并將對(duì)象的編碼設(shè)置為?raw?。
舉個(gè)例子坎吻, 如果我們執(zhí)行以下命令缆蝉, 那么服務(wù)器將創(chuàng)建一個(gè)如 raw?編碼的字符串對(duì)象作為?story?鍵的值:
redis>SET story "Long, long, long ago there lived a king ..."
OK
redis>STRLEN story
(integer) 43
redis>OBJECT ENCODING story
"raw"
如果字符串對(duì)象保存的是一個(gè)字符串值, 并且這個(gè)字符串值的長度小于等于?39?字節(jié), 那么字符串對(duì)象將使用?embstr?編碼的方式來保存這個(gè)字符串值刊头。
embstr?編碼是專門用于保存短字符串的一種優(yōu)化編碼方式黍瞧, 這種編碼和?raw?編碼一樣, 都使用?redisObject?結(jié)構(gòu)和?sdshdr?結(jié)構(gòu)來表示字符串對(duì)象原杂, 但?raw?編碼會(huì)調(diào)用兩次內(nèi)存分配函數(shù)來分別創(chuàng)建?redisObject?結(jié)構(gòu)和?sdshdr?結(jié)構(gòu)印颤, 而?embstr?編碼則通過調(diào)用一次內(nèi)存分配函數(shù)來分配一塊連續(xù)的空間, 空間中依次包含?redisObject?和?sdshdr?兩個(gè)結(jié)構(gòu)污尉, 如下圖所示膀哲。
embstr?編碼的字符串對(duì)象在執(zhí)行命令時(shí)往产, 產(chǎn)生的效果和?raw?編碼的字符串對(duì)象執(zhí)行命令時(shí)產(chǎn)生的效果是相同的被碗, 但使用?embstr?編碼的字符串對(duì)象來保存短字符串值有以下好處:
embstr?編碼將創(chuàng)建字符串對(duì)象所需的內(nèi)存分配次數(shù)從?raw?編碼的兩次降低為一次。
釋放?embstr?編碼的字符串對(duì)象只需要調(diào)用一次內(nèi)存釋放函數(shù)仿村, 而釋放?raw?編碼的字符串對(duì)象需要調(diào)用兩次內(nèi)存釋放函數(shù)锐朴。
因?yàn)?embstr?編碼的字符串對(duì)象的所有數(shù)據(jù)都保存在一塊連續(xù)的內(nèi)存里面, 所以這種編碼的字符串對(duì)象比起?raw?編碼的字符串對(duì)象能夠更好地利用緩存帶來的優(yōu)勢(shì)蔼囊。
作為例子焚志, 以下命令創(chuàng)建了一個(gè)?embstr?編碼的字符串對(duì)象作為?msg?鍵的值, 值對(duì)象的樣子如下圖所示:
redis>SET msg "hello"
OK
redis>OBJECT ENCODING msg?
"embstr"
最后要說的是畏鼓, 可以用?long?double?類型表示的浮點(diǎn)數(shù)在 Redis 中也是作為字符串值來保存的: 如果我們要保存一個(gè)浮點(diǎn)數(shù)到字符串對(duì)象里面酱酬, 那么程序會(huì)先將這個(gè)浮點(diǎn)數(shù)轉(zhuǎn)換成字符串值, 然后再保存起轉(zhuǎn)換所得的字符串值云矫。
舉個(gè)例子膳沽, 執(zhí)行以下代碼將創(chuàng)建一個(gè)包含?3.14?的字符串表示?"3.14"?的字符串對(duì)象:
redis>SET pi 3.14
OK
redis>OBJECT ENCODING pi
"embstr"
在有需要的時(shí)候, 程序會(huì)將保存在字符串對(duì)象里面的字符串值轉(zhuǎn)換回浮點(diǎn)數(shù)值让禀, 執(zhí)行某些操作, 然后再將執(zhí)行操作所得的浮點(diǎn)數(shù)值轉(zhuǎn)換回字符串值巡揍, 并繼續(xù)保存在字符串對(duì)象里面痛阻。
舉個(gè)例子, 如果我們執(zhí)行以下代碼的話:
redis>INCRBYFLOAT pi 2.0
"5.14"
redis>OBJECT ENCODING pi
"embstr"
那么程序首先會(huì)取出字符串對(duì)象里面保存的字符串值?"3.14"?腮敌, 將它轉(zhuǎn)換回浮點(diǎn)數(shù)值?3.14?阱当, 然后把?3.14?和?2.0?相加得出的值?5.14?轉(zhuǎn)換成字符串?"5.14"?, 并將這個(gè)?"5.14"?保存到字符串對(duì)象里面糜工。