成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

緩存詳解

AlexTuan / 2334人閱讀

摘要:瀏覽器的緩存策略緩存的目標(biāo)一個(gè)檢索請(qǐng)求的成功響應(yīng)對(duì)于請(qǐng)求,響應(yīng)狀態(tài)碼為,則表示為成功。

前言

總括: 緩存從來都是前端的一個(gè)痛點(diǎn),很多前端搞不清楚緩存到底是何物,從而給自己創(chuàng)造了一些麻煩,本文一如既往的用通俗易懂的文字和實(shí)例來講述緩存,希望能讓您有所得。

原文博客地址: 緩存詳解

知乎專欄&&簡(jiǎn)書專題:前端進(jìn)擊者(知乎)

博主博客地址:Damonare的個(gè)人博客

天青色等煙雨,而我在等你。

正文
緩存是一種保存資源副本并在下次請(qǐng)求時(shí)直接使用該副本的技術(shù)。

說實(shí)話,我起始真的不知道怎么去介紹緩存,所以引用了上面相對(duì)官方的定義。我想幾乎每個(gè)開發(fā)者都碰到過緩存的問題吧,甚至有很多情況下我們會(huì)說這個(gè)問題已經(jīng)修復(fù)了,你清理下緩存就好了。這篇文章我們就細(xì)細(xì)的來挖掘下緩存的種種軼事。

?緩存的種類

很多開發(fā)者習(xí)慣把cookie、webStorage以及IndexedDB存儲(chǔ)的數(shù)據(jù)也稱之為緩存,理由是都是保存在客戶端的數(shù)據(jù),沒有什么區(qū)別。其實(shí)這是不嚴(yán)謹(jǐn)?shù)?,cookie的存在更多的是為了讓服務(wù)端區(qū)別用戶,webStorage和IndexedDB則更多用在保存具體的數(shù)據(jù)和在客戶端存儲(chǔ)大量結(jié)構(gòu)化數(shù)據(jù)(文件/blobs)上面。

實(shí)際上所謂的緩存只有一種——它是請(qǐng)求資源的副本。試想一下,如果每一個(gè)資源我們客戶端都會(huì)保存一份副本,這會(huì)怎么樣?客戶端會(huì)炸掉,開發(fā)者會(huì)瘋掉!所以我們需要一份協(xié)議來處理緩存,可以讓開發(fā)者控制緩存的建立和刪除。誰呢?還能有誰,HTTP唄。HTTP協(xié)議里定義了很多關(guān)于緩存的請(qǐng)求和響應(yīng)字段,這也是接下來我們重點(diǎn)要逼逼叨的對(duì)象,研究下究竟是哪些字段怎么影響緩存的。

納尼?你問我為什么要緩存??

那就太容易說道了?,緩存好處有很多:

緩解服務(wù)器壓力(不用每次去請(qǐng)求資源);

提升性能(打開本地資源速度當(dāng)然比請(qǐng)求回來再打開要快得多);

減少帶寬消耗(我相信你可以理解);

??♀?那么問題又來了,既然緩存這么好,如果我請(qǐng)求的服務(wù)器中間有代理也緩存了怎么辦?代理服務(wù)器緩存了我的資源導(dǎo)致我沒法從源服務(wù)器拿到最新的資源怎么辦?HTTP當(dāng)然也想到了這塊的訴求。接下來我們也會(huì)逐層剖析。

?緩存在宏觀上可以分成兩類:私有緩存共享緩存。共享緩存就是那些能被各級(jí)代理緩存的緩存(咋覺得有點(diǎn)繞)。私有緩存就是用戶專享的,各級(jí)代理不能緩存的緩存。

?微觀上可以分下面三類:

1. 瀏覽器緩存

我相信只要你經(jīng)常使用某個(gè)瀏覽器?(Chrome,Firefox,IE等),肯定知道這些瀏覽器在設(shè)置里面都是有個(gè)清除緩存功能,這個(gè)功能存在的作用就是刪除存儲(chǔ)在你本地磁盤上資源副本,也就是清除緩存。

緩存存在的意義就是當(dāng)用戶點(diǎn)擊back按鈕或是再次去訪問某個(gè)頁面的時(shí)候能夠更快的響應(yīng)。尤其是在多頁應(yīng)用的網(wǎng)站中,如果你在多個(gè)頁面使用了一張相同的圖片,那么緩存這張圖片就變得特別的有用。?

2. 代理服務(wù)器緩存

代理服務(wù)器緩存原理和瀏覽器端類似,但規(guī)模要大得多,因?yàn)槭菫槌汕先f的用戶提供緩存機(jī)制,大公司和大型的ISP提供商通常會(huì)將它們?cè)O(shè)立在防火墻上或是作為一個(gè)獨(dú)立的設(shè)備來運(yùn)營。(下文如果沒有特殊說明,所有提到的緩存服務(wù)器都是指代理服務(wù)器。)

由于緩存服務(wù)器不是客戶端或是源服務(wù)器的一部分,它們存在于網(wǎng)絡(luò)中,請(qǐng)求路由必須經(jīng)過它們才會(huì)生效,所以實(shí)際上你可以去手動(dòng)設(shè)置瀏覽器的代理,或是通過一個(gè)中間服務(wù)器來進(jìn)行轉(zhuǎn)發(fā),這樣用戶自然就察覺不到代理服務(wù)器的存在了。?

代理服務(wù)器緩存就是一個(gè)共享緩存,不只為一個(gè)用戶服務(wù),經(jīng)常為大量用戶使用,因此在減少相應(yīng)時(shí)間和帶寬使用方面很有效:因?yàn)橥粋€(gè)緩存可能會(huì)被重用多次。

3. 網(wǎng)關(guān)緩存

也被稱為代理緩存或反向代理緩存,網(wǎng)關(guān)也是一個(gè)中間服務(wù)器,網(wǎng)關(guān)緩存一般是網(wǎng)站管理員自己部署,從讓網(wǎng)站擁有更好的性能。?

CDNS(網(wǎng)絡(luò)內(nèi)容分發(fā)商)分布網(wǎng)關(guān)緩存到整個(gè)(或部分)互聯(lián)網(wǎng)上,并出售緩存服務(wù)給需要的網(wǎng)站,比如國內(nèi)的七牛云、又拍云都有這種服務(wù)。

4. 數(shù)據(jù)庫緩存

數(shù)據(jù)庫緩存是指當(dāng)我們的應(yīng)用極其復(fù)雜,表自然也很繁雜,我們必須進(jìn)行頻繁的進(jìn)行數(shù)據(jù)庫查詢,這樣可能導(dǎo)致數(shù)據(jù)庫不堪重負(fù),一個(gè)好的辦法就是將查詢后的數(shù)據(jù)放到內(nèi)存中,下一次查詢直接從內(nèi)存中取就好了。關(guān)于數(shù)據(jù)庫緩存本篇不會(huì)展開。?

?瀏覽器的緩存策略

緩存的目標(biāo):

一個(gè)檢索請(qǐng)求的成功響應(yīng): 對(duì)于 GET請(qǐng)求,響應(yīng)狀態(tài)碼為:200,則表示為成功。一個(gè)包含例如HTML文檔,圖片,或者文件的響應(yīng);

不變的重定向: 響應(yīng)狀態(tài)碼:301;

可用緩存響應(yīng):響應(yīng)狀態(tài)碼:304,這個(gè)存在疑問,Chrome會(huì)緩存304中的緩存設(shè)置,F(xiàn)irefox;

錯(cuò)誤響應(yīng): 響應(yīng)狀態(tài)碼:404 的一個(gè)頁面;

不完全的響應(yīng): 響應(yīng)狀態(tài)碼 206,只返回局部的信息;

除了 GET 請(qǐng)求外,如果匹配到作為一個(gè)已被定義的cache鍵名的響應(yīng);

以上,對(duì)于我們可以和應(yīng)該緩存的目標(biāo)有個(gè)了解。?

瀏覽器對(duì)于緩存的處理是根據(jù)第一次請(qǐng)求資源時(shí)返回的響應(yīng)頭來確定的。

那么瀏覽器怎么確定一個(gè)資源該不該緩存,如何去緩存呢?響應(yīng)頭!響應(yīng)頭!響應(yīng)頭!重要的事情說三遍。??

我們看?:

Age:23146
Cache-Control:max-age=2592000
Date:Tue, 28 Nov 2017 12:26:41 GMT
ETag:W/"5a1cf09a-63c6"
Expires:Thu, 28 Dec 2017 05:27:45 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
1. 強(qiáng)緩存階段

以上請(qǐng)求頭來自百度首頁某個(gè)CSS文件的響應(yīng)頭。我去除了一些和緩存無關(guān)的字段,只保留了以上部分。我們來分析下,Expires是HTTP/1.0中的定義緩存的字段,它規(guī)定了緩存過期的一個(gè)絕對(duì)時(shí)間。Cache-Control:max-age=2592000是HTTP/1.1定義的關(guān)于緩存的字段,它規(guī)定了緩存過期的一個(gè)相對(duì)時(shí)間。優(yōu)先級(jí)上當(dāng)然是版本高的優(yōu)先了,max-age > Expires

這就是強(qiáng)緩存階段,當(dāng)瀏覽器再次試圖訪問這個(gè)CSS文件,發(fā)現(xiàn)有這個(gè)文件的緩存,那么就判斷根據(jù)上一次的響應(yīng)判斷是否過期,如果沒過期,使用緩存。加載文件,OVER!??

Firefox瀏覽器表現(xiàn)為一個(gè)灰色的200狀態(tài)碼。

Chrome瀏覽器狀態(tài)碼表現(xiàn)為:

200  (from disk cache)或是200 OK (from memory cache)

多說一點(diǎn):關(guān)于緩存是從磁盤中獲取還是從內(nèi)存中獲取,查找了很多資料,得出了一個(gè)較為可信的結(jié)論:Chrome會(huì)根據(jù)本地內(nèi)存的使用率來決定緩存存放在哪,如果內(nèi)存使用率很高,放在磁盤里面,內(nèi)存的使用率很高會(huì)暫時(shí)放在內(nèi)存里面。這就可以比較合理的解釋了為什么同一個(gè)資源有時(shí)是from memory cache有時(shí)是from disk cache的問題了。

那么當(dāng)這個(gè)CSS文件過期了怎么辦?ETagLast-Modified就該閃亮登場(chǎng)了。

先說Last-Modified,這個(gè)字段是文件最后一次修改的時(shí)間;

ETag呢?ETag是對(duì)文件的一個(gè)標(biāo)記,嗯,可以這么說,具體生成方式HTTP并沒有給出一個(gè)明確的方式,所以理論上只要不會(huì)重復(fù)生成方式無所謂,比如對(duì)資源內(nèi)容使用抗碰撞散列函數(shù),使用最近修改的時(shí)間戳的哈希值,甚至只是一個(gè)版本號(hào)。

2. 協(xié)商緩存階段

利用這兩個(gè)字段瀏覽器可以進(jìn)入協(xié)商緩存階段,當(dāng)瀏覽器再次試圖訪問這個(gè)CSS文件,發(fā)現(xiàn)緩存過期,于是會(huì)在本次請(qǐng)求的請(qǐng)求頭里攜帶If-Moified-SinceIf-None-Match這兩個(gè)字段,服務(wù)器通過這兩個(gè)字段來判斷資源是否有修改,如果有修改則返回狀態(tài)碼200和新的內(nèi)容,如果沒有修改返回狀態(tài)碼304,瀏覽器收到200狀態(tài)碼,該咋處理就咋處理(相當(dāng)于首次訪問這個(gè)文件了),發(fā)現(xiàn)返回304,于是知道了本地緩存雖然過期但仍然可以用,于是加載本地緩存。然后根據(jù)新的返回的響應(yīng)頭來設(shè)置緩存。(這一步有所差異,發(fā)現(xiàn)不同瀏覽器的處理是不同的,chrome會(huì)為304設(shè)置緩存,firefox則不會(huì))?

具體兩個(gè)字段攜帶的內(nèi)容如下(分別和上面的Last-Modified、ETag攜帶的值對(duì)應(yīng)):

If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMT
If-None-Match: W/"5a1cf09a-63c6"

到這協(xié)商緩存結(jié)束。

3. 啟發(fā)式緩存階段

我們把上面的響應(yīng)頭改下:

Age:23146
Cache-Control: public
Date:Tue, 28 Nov 2017 12:26:41 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding

發(fā)現(xiàn)沒?瀏覽器用來確定緩存過期時(shí)間的字段一個(gè)都沒有!那該怎么辦?有人可能會(huì)說下次請(qǐng)求直接進(jìn)入?yún)f(xié)商緩存階段,攜帶If-Moified-Since唄,不是的,瀏覽器還有個(gè)啟發(fā)式緩存階段?

根據(jù)響應(yīng)頭中2個(gè)時(shí)間字段 Date 和 Last-Modified 之間的時(shí)間差值,取其值的10%作為緩存時(shí)間周期。

這就是啟發(fā)式緩存階段。這個(gè)階段很容讓人忽視,但實(shí)際上每時(shí)每刻都在發(fā)揮著作用。所以在今后的開發(fā)過程中如果遇到那種默認(rèn)緩存的坑,不要叫囂,不要生氣,瀏覽器只是在遵循啟發(fā)式緩存協(xié)議而已。

我畫了下面這張圖,來解釋瀏覽器整個(gè)緩存策略的過程:

?對(duì)于緩存策略介紹到這,接下來再細(xì)細(xì)分析不同的HTTP首部字段的內(nèi)容,以及它們之間的關(guān)系。

?HTTP中和緩存相關(guān)的首部字段

HTTP報(bào)文是什么呢?就是HTTP報(bào)文,這是一個(gè)概念,主要由以下兩部分構(gòu)成:

首部(header):包含了很多字段,比如:cookie、緩存、報(bào)文大小、報(bào)文格式等等);

主體(body):HTTP請(qǐng)求真正要傳輸?shù)牟糠?,比如:一個(gè)HTML文檔,一個(gè)js文件;

以上我們知道瀏覽器對(duì)于緩存的處理過程,也簡(jiǎn)單的提到了幾個(gè)相關(guān)的字段。?接下來我們具體看下這幾個(gè)字段:

1. 通用首部字段
字段名稱 說明
Cache-Control 控制緩存具體的行為
Pragma HTTP1.0時(shí)的遺留字段,當(dāng)值為"no-cache"時(shí)強(qiáng)制驗(yàn)證緩存
Date 創(chuàng)建報(bào)文的日期時(shí)間(啟發(fā)式緩存階段會(huì)用到這個(gè)字段)
2. 響應(yīng)首部字段
字段名稱 說明
ETag 服務(wù)器生成資源的唯一標(biāo)識(shí)
Vary 代理服務(wù)器緩存的管理信息
Age 資源在緩存代理中存貯的時(shí)長(取決于max-age和s-maxage的大小)
3. 請(qǐng)求首部字段
字段名稱 說明
If-Match 條件請(qǐng)求,攜帶上一次請(qǐng)求中資源的ETag,服務(wù)器根據(jù)這個(gè)字段判斷文件是否有新的修改
If-None-Match 和If-Match作用相反,服務(wù)器根據(jù)這個(gè)字段判斷文件是否有新的修改
If-Modified-Since 比較資源前后兩次訪問最后的修改時(shí)間是否一致
If-Unmodified-Since 比較資源前后兩次訪問最后的修改時(shí)間是否一致
4. 實(shí)體首部字段
字段名稱 說明
Expires 告知客戶端資源緩存失效的絕對(duì)時(shí)間
Last-Modified 資源最后一次修改的時(shí)間
?瀏覽器緩存控制

HTTP/1.1一共規(guī)范了47種首部字段,而和緩存相關(guān)的就有以上12個(gè)之多。接下來的兩個(gè)小節(jié)會(huì)一個(gè)一個(gè)介紹給大家。?

1. Cache-Control

通過cache-control的指令可以控制告訴客戶端或是服務(wù)器如何處理緩存。這也是11個(gè)字段中指令最多的一個(gè),我們先來看看請(qǐng)求指令

指令 參數(shù) 說明
no-cache 強(qiáng)制源服務(wù)器再次驗(yàn)證
no-store 不緩存請(qǐng)求或是響應(yīng)的任何內(nèi)容
max-age=[秒] 緩存時(shí)長,單位是秒 緩存的時(shí)長,也是響應(yīng)的最大的Age值
min-fresh=[秒] 必需 期望在指定時(shí)間內(nèi)響應(yīng)仍然有效
no-transform 代理不可更改媒體類型
only-if-cached 從緩存獲取
cache-extension - 新的指令標(biāo)記(token)

響應(yīng)指令

指令 參數(shù) 說明
public 任意一方都能緩存該資源(客戶端、代理服務(wù)器等)
private 可省略 只能特定用戶緩存該資源
no-cache 可省略 緩存前必須先確認(rèn)其有效性
no-store 不緩存請(qǐng)求或響應(yīng)的任何內(nèi)容
no-transform 代理不可更改媒體類型
must-revalidate 可緩存但必須再向源服務(wù)器進(jìn)確認(rèn)
proxy-revalidate 要求中間緩存服務(wù)器對(duì)緩存的響應(yīng)有效性再進(jìn)行確認(rèn)
max-age=[秒] 緩存時(shí)長,單位是秒 緩存的時(shí)長,也是響應(yīng)的最大的Age值
s-maxage=[秒] 必需 公共緩存服務(wù)器響應(yīng)的最大Age值
cache-extension - 新指令標(biāo)記(token

請(qǐng)注意no-cache指令很多人誤以為是不緩存,這是不準(zhǔn)確的,no-cache的意思是可以緩存,但每次用應(yīng)該去想服務(wù)器驗(yàn)證緩存是否可用。no-store才是不緩存內(nèi)容。另外部分指令也可以組合使用,比如:

Cache-Control: max-age=100, must-revalidate, public

上面指令的意思是緩存的有效時(shí)間為100秒,之后訪問需要向源服務(wù)器發(fā)送請(qǐng)求驗(yàn)證,此緩存可被代理服務(wù)器和客戶端緩存。

2. Pragma

這是HTTP/1.0里面的一個(gè)字段,但優(yōu)先級(jí)很高,測(cè)試發(fā)現(xiàn),Chrome和Firefox中Pragma的優(yōu)先級(jí)高于Cache-Control和Expires,為了向下兼容,這個(gè)字段依然發(fā)揮著它的作用。?一般可能我們會(huì)這么用:

Pragma屬于通用首部字段,在客戶端上使用時(shí),常規(guī)要求我們往html上加上上面這段meta元標(biāo)簽(而且可能還得做些hack放到body后面去。

事實(shí)上這種禁用緩存的形式用處很有限:

僅有IE才能識(shí)別這段meta標(biāo)簽含義,其它主流瀏覽器僅能識(shí)別Cache-Control: no-store的meta標(biāo)簽(見出處)

在IE中識(shí)別到該meta標(biāo)簽含義,并不一定會(huì)在請(qǐng)求字段加上Pragma,但的確會(huì)讓當(dāng)前頁面每次都發(fā)新請(qǐng)求(僅限頁面,頁面上的資源則不受影響)。——淺談瀏覽器http的緩存機(jī)制

讀者可以自行拷貝后面模擬服務(wù)端決策的代碼進(jìn)行測(cè)試。

服務(wù)端響應(yīng)添加"Pragma": "no-cache",瀏覽器表現(xiàn)行為和強(qiáng)制刷新類似。

3. Expires

這又是一個(gè)HTTP/1.0的字段,上面也說過了定義的是緩存到期的絕對(duì)時(shí)間。

同樣,我們也可以在html文件里直接使用:

如果設(shè)置的是已經(jīng)過去的時(shí)間會(huì)怎樣呢?YES!??!則刷新頁面會(huì)重新發(fā)送請(qǐng)求。

Pragma禁用緩存,如果又給Expires定義一個(gè)還未到期的時(shí)間,那么Pragma字段的優(yōu)先級(jí)會(huì)更高。?

?Expires有一個(gè)很大的弊端,就是它返回的是服務(wù)器的時(shí)間,但判斷的時(shí)候用的卻是客戶端的時(shí)間,這就導(dǎo)致Expires很被動(dòng),因?yàn)橛脩粲锌赡芨淖兛蛻舳说臅r(shí)間,導(dǎo)致緩存時(shí)間判斷出錯(cuò),這也是引入Cache-Control:max-age指令的原因之一。

4. Last-Midified

接下來這幾個(gè)字段都是校驗(yàn)字段,或者說是在協(xié)商緩存階段發(fā)揮作用的字段。第一個(gè)就是Last-modified,這個(gè)字段不光協(xié)商緩存起作用,在啟發(fā)式緩存階段同樣起到至關(guān)重要的作用。

在瀏覽器第一次請(qǐng)求某一個(gè)URL時(shí),服務(wù)器端的返回狀態(tài)碼會(huì)是200,響應(yīng)的實(shí)體內(nèi)容是客戶端請(qǐng)求的資源,同時(shí)有一個(gè)Last-Modified的屬性標(biāo)記此文件在服務(wù)器端最后被修改的時(shí)間。like this:

Last-Modified : Fri , 12 May 2006 18:53:33 GMT
If-Modified-Since

當(dāng)瀏覽器第二次請(qǐng)求這個(gè)URL的時(shí)候,根據(jù)HTTP協(xié)議規(guī)定,瀏覽器會(huì)把第一次Last-Modified的值存儲(chǔ)在If-Modified-Since里面發(fā)送給服務(wù)端來驗(yàn)證資源有沒有修改。like this:

If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT

服務(wù)端通過If-Modified-Since字段來判斷在這兩次訪問期間資源有沒有被修改過,從而決定是否返回完整的資源。如果有修改正常返回資源,狀態(tài)碼200,如果沒有修改只返回響應(yīng)頭,狀態(tài)碼304,告知瀏覽器資源的本地緩存還可用。

用途:

驗(yàn)證本地緩存是否可用

If-Unmodified-Since

這個(gè)字段字面意思和If-Modified-Since相反,但處理方式并不是相反的。如果文件在兩次訪問期間沒有被修改則返回200和資源,如果文件修改了則返回狀態(tài)碼412(預(yù)處理錯(cuò)誤)。

用途:

與含有 If-Range消息頭的范圍請(qǐng)求搭配使用,實(shí)現(xiàn)斷點(diǎn)續(xù)傳的功能,即如果資源沒修改繼續(xù)下載,如果資源修改了,續(xù)傳的意義就沒有了。

POST、PUT請(qǐng)求中,優(yōu)化并發(fā)控制,即當(dāng)多用戶編輯用一份文檔的時(shí)候,如果服務(wù)器的資源已經(jīng)被修改,那么在對(duì)其作出編輯會(huì)被拒絕提交。

?Last-Modified有幾個(gè)缺點(diǎn):沒法準(zhǔn)確的判斷資源是否真的修改了,比如某個(gè)文件在1秒內(nèi)頻繁更改了多次,根據(jù)Last-Modified的時(shí)間(單位是秒)是判斷不出來的,再比如,某個(gè)資源只是修改了,但實(shí)際內(nèi)容并沒有發(fā)生變化,Last-Modified也無法判斷出來,因此在HTTP/1.1中還推出了ETag這個(gè)字段?

5. ETag

服務(wù)器可以通過某種自定的算法對(duì)資源生成一個(gè)唯一的標(biāo)識(shí)(比如md5標(biāo)識(shí)),然后在瀏覽器第一次請(qǐng)求某一個(gè)URL時(shí)把這個(gè)標(biāo)識(shí)放到響應(yīng)頭傳到客戶端。服務(wù)器端的返回狀態(tài)會(huì)是200。

ETag: abc-123456

ETag的值有可能包含一個(gè) W/ 前綴,來提示應(yīng)該采用弱比較算法(這個(gè)是畫蛇添足,因?yàn)?If-None-Match 用且僅用這一算法)。?

If-None-Match

If-None-Match和If-Modified-Since同時(shí)存在的時(shí)候If-None-Match優(yōu)先級(jí)更高。

當(dāng)瀏覽器第二次請(qǐng)求這個(gè)URL的時(shí)候,根據(jù)HTTP協(xié)議規(guī)定,瀏覽器回把第一次ETag的值存儲(chǔ)在If-None-Match里面發(fā)送給服務(wù)端來驗(yàn)證資源有沒有修改。like this:

If-None-Match: abc-123456

Get請(qǐng)求中,當(dāng)且僅當(dāng)服務(wù)器上沒有任何資源的ETag屬性值與這個(gè)首部中列出的相匹配的時(shí)候,服務(wù)器端會(huì)才返回所請(qǐng)求的資源,響應(yīng)碼為200。如果沒有資源的ETag值相匹配,那么返回304狀態(tài)碼。

POST、PUT等請(qǐng)求改變文件的請(qǐng)求,如果沒有資源的ETag值相匹配,那么返回412狀態(tài)碼。

If-Match

在請(qǐng)求方法為 GET) 和 HEAD的情況下,服務(wù)器僅在請(qǐng)求的資源滿足此首部列出的 ETag 之一時(shí)才會(huì)返回資源。而對(duì)于 PUT或其他非安全方法來說,只有在滿足條件的情況下才可以將資源上傳。

用途:

For GET和 HEAD 方法,搭配 Range首部使用,可以用來保證新請(qǐng)求的范圍與之前請(qǐng)求的范圍是對(duì)同一份資源的請(qǐng)求。如果 ETag 無法匹配,那么需要返回 416(范圍請(qǐng)求無法滿足) 響應(yīng)。

對(duì)于其他方法來說,尤其是 PUT, If-Match 首部可以用來避免更新丟失問題。它可以用來檢測(cè)用戶想要上傳的不會(huì)覆蓋獲取原始資源之后做出的更新。如果請(qǐng)求的條件不滿足,那么需要返回412(預(yù)處理錯(cuò)誤) 響應(yīng)。

當(dāng)然和Last-Modified相比,ETag也有自己的缺點(diǎn),比如由于需要對(duì)資源進(jìn)行生成標(biāo)識(shí),性能方面就勢(shì)必有所犧牲。?

關(guān)于強(qiáng)校驗(yàn)和弱校驗(yàn):

ETag 1 ETag 2 Strong Comparison Weak Comparison
W/"1" W/"1" no match match
W/"1" W/"2" no match no match
W/"1" "1" no match match
"1" "1" match match
?服務(wù)端緩存控制

當(dāng)ExpiresCache-Control:max-age=xxx同時(shí)存在的時(shí)候取決于緩存服務(wù)器應(yīng)用的HTTP版本。應(yīng)用HTTP/1.1版本的服務(wù)器會(huì)優(yōu)先處理max-age,忽略Expires,而應(yīng)用HTTP/1.0版本的緩存服務(wù)器則會(huì)優(yōu)先處理Expires而忽略max-age。接下來看下和緩存服務(wù)器相關(guān)的兩個(gè)字段。

6. Vary

Vary用來做什么的呢?試想這么一個(gè)場(chǎng)景:在某個(gè)網(wǎng)頁中網(wǎng)站提供給移動(dòng)端的內(nèi)容是不同的,怎么讓緩存服務(wù)器區(qū)分移動(dòng)端和PC端呢?不知道你是否注意,瀏覽器在每次請(qǐng)求都會(huì)攜帶UA字段來表明來源,所以我們可以利用User-Agent字段來區(qū)分不同的客戶端,用法如下:

Vary: User-Agent

再比如,源服務(wù)器啟用了gzip壓縮,但用戶使用了比較舊的瀏覽器,不支持壓縮,緩存服務(wù)器如何返回?就可以這么設(shè)定:

Vary: Accept-Encoding

當(dāng)然,也可以這么用:

Vary: User-Agent, Accept-Encoding

這意味著緩存服務(wù)器會(huì)以 User-Agent Accept-Encoding 兩個(gè)請(qǐng)求首部字段來區(qū)分緩存版本。根據(jù)請(qǐng)求頭里的這兩個(gè)字段來決定返回給客戶端什么內(nèi)容。

7. Age

這個(gè)字段說的是資源在緩存服務(wù)器存在的時(shí)長,前面也說了Cache-Control: max-age=[秒]就是Age的最大值。

這個(gè)字段存在的意義是什么呢?用來區(qū)分請(qǐng)求的資源來自源服務(wù)器還是緩存服務(wù)器的緩存的。

?但得結(jié)合另一個(gè)字段來進(jìn)行判斷,就是Date,Date是報(bào)文創(chuàng)建的時(shí)間。

Date

如果按F5頻繁刷新發(fā)現(xiàn)響應(yīng)里的Date沒有改變,就說明命中了緩存服務(wù)器的緩存以下面的一個(gè)響應(yīng)為?:

Accept-Ranges: bytes
Age: 1016859
Cache-Control: max-age=2592000
Content-Length: 14119
Content-Type: image/png
Date: Fri, 01 Dec 2017 12:27:25 GMT
ETag: "5912bfd0-3727"
Expires: Tue, 19 Dec 2017 17:59:46 GMT
Last-Modified: Wed, 10 May 2017 07:22:56 GMT
Ohc-Response-Time: 1 0 0 0 0 0
Server: bfe/1.0.8.13-sslpool-patch

如上圖來自百度首頁某個(gè)圖片的響應(yīng)字段。我們可以看到Age=1016859,說明這個(gè)資源已經(jīng)在緩存服務(wù)器存在了1016859秒。如果文件被修改或替換,Age會(huì)重新由0開始累計(jì)。

Age消息頭的值通常接近于0。表示此消息對(duì)象剛剛從原始服務(wù)器獲取不久;其他的值則是表示代理服務(wù)器當(dāng)前的系統(tǒng)時(shí)間與此應(yīng)答消息中的通用消息頭 Date的值之差。

上面這個(gè)結(jié)論歸結(jié)為一個(gè)等式就是:

靜態(tài)資源Age + 靜態(tài)資源Date = 原服務(wù)端Date
?用戶操作行為對(duì)緩存的影響

搜索了很久有沒有關(guān)于這方面的權(quán)威總結(jié),最后竟然在百度百科找到了也是很驚訝,我自己加了一條用戶強(qiáng)制刷新操作瀏覽器的反應(yīng)。強(qiáng)制刷新,window下是Ctrl+F5,mac下就是command+shift+R操作了。

操作 說明
打開新窗口 如果指定cache-control的值為private、no-cache、must-revalidate,那么打開新窗口訪問時(shí)都會(huì)重新訪問服務(wù)器。而如果指定了max-age值,那么在此值內(nèi)的時(shí)間里就不會(huì)重新訪問服務(wù)器,例如:Cache-control: max-age=5 表示當(dāng)訪問此網(wǎng)頁后的5秒內(nèi)不會(huì)去再次訪問服務(wù)器.
在地址欄回車 如果值為private或must-revalidate,則只有第一次訪問時(shí)會(huì)訪問服務(wù)器,以后就不再訪問。如果值為no-cache,那么每次都會(huì)訪問。如果值為max-age,則在過期之前不會(huì)重復(fù)訪問。
按后退按扭 如果值為private、must-revalidate、max-age,則不會(huì)重訪問,而如果為no-cache,則每次都重復(fù)訪問.
按刷新按扭 無論為何值,都會(huì)重復(fù)訪問.(可能返回狀態(tài)碼:200、304,這個(gè)不同瀏覽器處理是不一樣的,F(xiàn)ireFox正常,Chrome則會(huì)啟用緩存(200 from cache))
按強(qiáng)制刷新按鈕 當(dāng)做首次進(jìn)入重新請(qǐng)求(返回狀態(tài)碼200)

來自百度百科

如果想在瀏覽器點(diǎn)擊“刷新”按鈕的時(shí)候不讓瀏覽器去發(fā)新的驗(yàn)證請(qǐng)求呢?辦法找到一個(gè),知乎上面一個(gè)回答,在頁面加載完畢后通過腳本動(dòng)態(tài)地添加資源:

$(window).load(function() {
      var bg="http://img.infinitynewtab.com/wallpaper/100.jpg";
      setTimeout(function() {
        $("#bgOut").css("background-image", "url("+bg+")");
      },0);
});

來自知乎

?HTML5的緩存

這部分準(zhǔn)備的說應(yīng)該叫離線存儲(chǔ)?,F(xiàn)在比較普遍用的是Appcache,但Appcache已經(jīng)從web標(biāo)準(zhǔn)移除了,在可預(yù)見的未來里,ServiceWorker可能會(huì)是一個(gè)比較適合的解決方案。

1. Appcache

這是HTML5的一個(gè)新特性,通過離線存儲(chǔ)達(dá)到用戶在沒有網(wǎng)絡(luò)連接的情況下也能訪問頁面的功能。離線狀態(tài)下即使用戶點(diǎn)擊刷新都能正常加載文檔。

使用方法如下,在HTML文件中引入appcache文件:




  
  ***


  

?web 應(yīng)用中的 manifest 特性可以指定為緩存清單文件的相對(duì)路徑或一個(gè)絕對(duì) URL(絕對(duì) URL 必須與應(yīng)用同源)。緩存清單文件可以使用任意擴(kuò)展名,但傳輸它的 MIME 類型必須為 text/cache-manifest。

注意:在 Apache 服務(wù)器上,若要設(shè)置適用于清單(.appcache)文件的 MIME 類型,可以向根目錄或應(yīng)用的同級(jí)目錄下的一個(gè) .htaccess 文件中增加 AddType text/cache-manifest .appcache 。
CACHE MANIFEST
# 注釋:需要緩存的文件,無論在線與否,均從緩存里讀取
# v1 2017-11-30
# This is another comment
/static/logo.png

# 注釋:不緩存的文件,始終從網(wǎng)絡(luò)獲取
NETWORK:
example.js

# 注釋:獲取不到資源時(shí)的備選路徑,如index.html訪問失敗,則返回404頁面
FALLBACK:
index.html 404.html

上面就是一個(gè)完整的緩存清單文件的示例。

注意:主頁一定會(huì)被緩存起來的,因?yàn)锳ppCache主要是用來做離線應(yīng)用的,如果主頁不緩存就無法離線查看了,因此把index.html添加到NETWORK中是不起效果的。

實(shí)際上這個(gè)特性已經(jīng)web標(biāo)準(zhǔn)中刪除,但現(xiàn)在為止還有很多瀏覽器支持它,所以這里提一下。

你可以用最新的Firefox(版本 57.0.1)測(cè)試下,控制臺(tái)會(huì)有這么一行字?:

程序緩存 API(AppCache)已不贊成使用,幾天后將被移除。需離線支持請(qǐng)嘗試使用 Service Worker。

最新Chrome(版本 62.0.3202.94)倒是沒有這個(gè)警告。?

AppCache之所以不受待見我想了下面幾個(gè)原因:

一旦使用了manifest后,沒辦法清空這些緩存,只能更新緩存,或者得用戶自己去清空瀏覽器的緩存;

假如更新的資源中有一個(gè)資源更新失敗了,那么所有的資源就會(huì)全部更新失敗,將用回上一版本的緩存;

主頁會(huì)被強(qiáng)制緩存(使用了manifest的頁面),并且無法清除;

appache文件可能會(huì)無法被及時(shí)更新,因?yàn)楦鞔鬄g覽器對(duì)于appcache文件的處理方式不同;

以上幾個(gè)弊端一旦出問題,會(huì)讓用戶抓狂更會(huì)讓開發(fā)者抓狂!

2. Service Worker

Service worker還是一個(gè)實(shí)驗(yàn)性的功能,線上環(huán)境不推薦使用。?這里大概介紹一下。

Service worker本質(zhì)上充當(dāng)Web應(yīng)用程序與瀏覽器之間的代理服務(wù)器。

?首先講個(gè)小故事:

我們都知道瀏覽器的js引擎處理js是單線程的,它就好像一個(gè)大Boss高高在上,同一個(gè)時(shí)間它只做一個(gè)事情(就是那么傲嬌),基于這個(gè)弊端,W3C(HR)給大Boss招聘了一個(gè)秘書(web worker),大Boss可以把瑣碎的事情交給秘書web worker去做,做完了發(fā)個(gè)微信(postMessage)通知大Boss,大Boss通過onmessage來獲取秘書web worker做的事情的結(jié)果。傍晚時(shí)分,下班時(shí)間到!大Boss回家哄兒子了,秘書也出去約會(huì)去了,沒人加班了!這怎么行!W3C(HR)又提出了招個(gè)程序?的想法的想法,OK,Service Worker應(yīng)聘成功!于是,程序?就堅(jiān)持在工作崗位上了,從此開啟沒完沒了的加班之路??偟膩碚f這只猿的工作是這樣的:

后臺(tái)數(shù)據(jù)同步

響應(yīng)來自其它源的資源請(qǐng)求

集中接收計(jì)算成本高的數(shù)據(jù)更新,比如地理位置和陀螺儀信息,這樣多個(gè)頁面就可以利用同一組數(shù)據(jù)

在客戶端進(jìn)行CoffeeScript,LESS,CJS/AMD等模塊編譯和依賴管理(用于開發(fā)目的)

后臺(tái)服務(wù)鉤子

自定義模板用于特定URL模式

性能增強(qiáng),比如預(yù)取用戶可能需要的資源

——Service Worker API

注意:Service workers之所以優(yōu)于以前同類嘗試(如上面提到的AppCache)),是因?yàn)樗鼈儫o法支持當(dāng)操作出錯(cuò)時(shí)終止操作。Service workers可以更細(xì)致地控制每一件事情。如何控制的呢?

Service workers利用了ES6中比較重要的特性Promise,并且在攔截請(qǐng)求的時(shí)候使用的是新的fetch API,之所以使用fetch就是因?yàn)閒etch返回的是Promise對(duì)象??梢哉fService workers重要組成部分就是三塊:事件、Promise和Fetch請(qǐng)求。OK,talk is cheap,show you the code。?

首先我們看下app.js文件:告訴瀏覽器注冊(cè)某個(gè)JavaScript文件為service worker,檢查service worker API是否可用,如果可用就注冊(cè)service worker:

//使用 ServiceWorkerContainer.register()方法首次注冊(cè)service worker。
if (navigator.serviceWorker) {
      navigator.serviceWorker.register("./sw.js", {scope: "./"})
          .then(function (registration) {
              console.log(registration);
          })
          .catch(function (e) {
              console.error(e);
          });
} else {
      console.log("該瀏覽器不支持Service Worker");
}

再來看看具體作為service worker的文件sw.js,例子如下:

const CACHE_VERSION = "v1"; // 緩存文件的版本
const CACHE_FILES = [ // 需要緩存的文件
    "./test.js",
    "./app.js",
    "https://code.jquery.com/jquery-3.0.0.min.js"
];

self.addEventListener("install", function (event) { // 監(jiān)聽worker的install事件
    event.waitUntil( // 延遲install事件直到緩存初始化完成
        caches.open(CACHE_VERSION)
        .then(function (cache) {
            console.log("緩存打開");
            return cache.addAll(CACHE_FILES);
        })
    );
});

self.addEventListener("activate", function(event) {// 監(jiān)聽worker的activate事件
    event.waitUntil(// 延遲activate事件直到
        caches.keys().then(function(keys) {
            return Promise.all(keys.map(function(key, i){
                if(key !== CACHE_VERSION){
                    return caches.delete(keys[i]); // 清除舊版本緩存
                }
            }))
        })
    )
});

self.addEventListener("fetch", function(event) { // 截取頁面的資源請(qǐng)求
    event.respondWith(
        caches.match(event.request).then(function(res) { // 判斷緩存是否命中
            if (res) { // 返回緩存中的資源
                return res;
            }
            _request(event); // 執(zhí)行請(qǐng)求備份操作
        })
    )
});

function _request(event) {
    var url = event.request.clone();
    return fetch(url).then(function(res) {// 使用fetch請(qǐng)求線上資源
        // 錯(cuò)誤判斷
        if (!res || res.status !== 200 || res.type !== "basic") {
            return res;
        }

        var response = res.clone(); // 創(chuàng)建了一個(gè)響應(yīng)對(duì)象的克隆,儲(chǔ)藏在一個(gè)多帶帶的變量中

        caches.open(CACHE_VERSION).then(function(cache) {// 緩存從線上獲取的資源
            cache.put(event.request, response);
        });
        return res;
    })
}

清除一個(gè)Service Worker也很簡(jiǎn)單:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/sw.js", {scope: "./"}).then(function(registration) {
    // registration worked
    console.log("Registration succeeded.");
    registration.unregister().then(function(boolean) {
      // if boolean = true, unregister is successful
    });
  }).catch(function(error) {
    // registration failed
    console.log("Registration failed with " + error);
  });
};

相對(duì)AppCache來說,Service Worker的API增多了不少,用法也更復(fù)雜了些,但看得出Service Worker才是未來,對(duì)于web app來說,更是如虎添翼?,F(xiàn)在支持Service Worker的瀏覽器除了Chrome和Firefox,最近新添一個(gè)生力軍——Safari也支持Service Worker了。期待它在未來大放異彩吧。?

?模擬實(shí)現(xiàn)服務(wù)端決策

如下,使用node原生代碼簡(jiǎn)單的模擬下服務(wù)器發(fā)送響應(yīng)的過程,包括對(duì)于協(xié)商緩存的處理過程:

var http = require("http");
var fs = require("fs");
var url = require("url");

process.env.TZ = "Europe/London";

let tag = "123456";

http.createServer( function (request, response) {  

   var pathname = url.parse(request.url).pathname;

       console.log("Request for " + pathname + " received.");
       const fileMap = {
       "js": "application/javascript; charset=utf-8",
       "html": "text/html",
       "png": "image/png",
       "jpg": "image/jpeg",
       "gif": "image/gif",
       "ico": "image/*",
       "appcache": "text/cache-manifest"
       }
       fs.readFile(pathname.substr(1), function (err, data) {
        if (request.headers["if-none-match"] === tag) {
            response.writeHead(304, {
                "Content-Type": fileMap[pathname.substr(1).split(".")[1]],
                "Expires": new Date(Date.now() + 30000),
                "Cache-Control": "max-age=10, public",
                "ETag": tag,
                "Last-Modified": new Date(Date.now() - 30000),
                "Vary": "User-Agent"
            });
       } else {             
            response.writeHead(200, {
                "Content-Type": fileMap[pathname.substr(1).split(".")[1]],
                "Cache-Control": "max-age=10, public",
                "Expires": new Date(Date.now() + 30000),
                "ETag": tag,
                "Last-Modified": new Date(Date.now() - 30000),
                "Vary": "User-Agent"
            });
            response.write(fs.readFileSync(pathname.substr(1)));        
          }
          response.end();
       });   
}).listen(8081);

如上代碼。如果你沒使用過node,拷貝下代碼存為file.js,安裝node,命令行輸入node file.js,可以在同目錄下建立index.html文件,在html文件中引用一些圖片,CSS等文件,瀏覽器輸入localhost:8081/index.html進(jìn)行模擬。?

?關(guān)于緩存的一些問答

1. 問題:請(qǐng)求被緩存,導(dǎo)致新代碼未生效

解決方案:

服務(wù)端響應(yīng)添加Cache-Control:no-cache,must-revalidate指令;

修改請(qǐng)求頭If-modified-since:0If-none-match

修改請(qǐng)求URL,請(qǐng)求URL后加隨機(jī)數(shù),隨機(jī)數(shù)可以是時(shí)間戳,哈希值,比如:http://damonare.cn?a=1234

2. 問題:服務(wù)端緩存導(dǎo)致本地代碼未更新

解決方案:

合理設(shè)置Cache-Control:s-maxage指令;

設(shè)置Cache-Control:private指令,防止代理服務(wù)器緩存資源;

CDN緩存可以使用管理員設(shè)置的緩存刷新接口進(jìn)行刷新;

3. 問題: Cache-Control: max-age=0 和 no-cache有什么不同

回答:

max-age=0no-cache應(yīng)該是從語氣上不同。max-age=0是告訴客戶端資源的緩存到期應(yīng)該向服務(wù)器驗(yàn)證緩存的有效性。而no-cache則告訴客戶端使用緩存前必須向服務(wù)器驗(yàn)證緩存的有效性。

后記

參考文檔:

淺談瀏覽器http的緩存機(jī)制

Cache-Control

What"s the difference between Cache-Control: max-age=0 and no-cache?

Header Field Definitions

Caching Tutorial

If-Unmodified-Since

If-Match

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92838.html

相關(guān)文章

  • [面試專題]Web緩存詳解

    摘要:緩存詳解標(biāo)簽空格分隔緩存緩存之于性能優(yōu)化請(qǐng)求更快通過將內(nèi)容緩存在本地瀏覽器或距離最近的緩存服務(wù)器如,在不影響網(wǎng)站交互的前提下可以大大加快網(wǎng)站加載速度。強(qiáng)制緩存不發(fā)請(qǐng)求到服務(wù)器,協(xié)商緩存會(huì)發(fā)請(qǐng)求到服務(wù)器。參考瀏覽器協(xié)議緩存機(jī)制詳解 Web緩存詳解 標(biāo)簽(空格分隔): 緩存 緩存之于性能優(yōu)化 請(qǐng)求更快:通過將內(nèi)容緩存在本地瀏覽器或距離最近的緩存服務(wù)器(如CDN),在不影響網(wǎng)站交互的前提...

    bladefury 評(píng)論0 收藏0
  • [面試專題]Web緩存詳解

    摘要:緩存詳解標(biāo)簽空格分隔緩存緩存之于性能優(yōu)化請(qǐng)求更快通過將內(nèi)容緩存在本地瀏覽器或距離最近的緩存服務(wù)器如,在不影響網(wǎng)站交互的前提下可以大大加快網(wǎng)站加載速度。強(qiáng)制緩存不發(fā)請(qǐng)求到服務(wù)器,協(xié)商緩存會(huì)發(fā)請(qǐng)求到服務(wù)器。參考瀏覽器協(xié)議緩存機(jī)制詳解 Web緩存詳解 標(biāo)簽(空格分隔): 緩存 緩存之于性能優(yōu)化 請(qǐng)求更快:通過將內(nèi)容緩存在本地瀏覽器或距離最近的緩存服務(wù)器(如CDN),在不影響網(wǎng)站交互的前提...

    Tikitoo 評(píng)論0 收藏0
  • 詳解web緩存

    摘要:指示對(duì)于單個(gè)用戶的整個(gè)或部分響應(yīng)消息,不能被共享緩存處理。參考文章瀏覽器協(xié)議緩存機(jī)制詳解的實(shí)現(xiàn)原理寫給后端程序員的緩存原理介紹 說說web緩存 網(wǎng)上關(guān)于WEB緩存的文章很多,今天匯總一下。 為什么要用緩存 一般針對(duì)靜態(tài)資源如CSS,JS,圖片等使用緩存,原因如下: 請(qǐng)求更快:通過將內(nèi)容緩存在本地瀏覽器或距離最近的緩存服務(wù)器(如CDN),在不影響網(wǎng)站交互的前提下可以大大加快網(wǎng)站加載速度...

    ACb0y 評(píng)論0 收藏0
  • 網(wǎng)站緩存詳解

    摘要:認(rèn)識(shí)緩存緩存原意是指可以進(jìn)行高速數(shù)據(jù)交換的存儲(chǔ)器。命中率指請(qǐng)求緩存次數(shù)與緩存返回正確結(jié)果次數(shù)的比例。如果相同,緩存直接使用副本相應(yīng)訪問,而不用向服務(wù)器發(fā)送請(qǐng)求緩存內(nèi)容應(yīng)用情形有完整的過期時(shí)間和壽命控制頭,并且沒過期。 1. 認(rèn)識(shí)緩存 緩存:原意是指可以進(jìn)行高速數(shù)據(jù)交換的存儲(chǔ)器。當(dāng)cpu處理數(shù)據(jù)時(shí),先到cache中尋找,如果數(shù)據(jù)已經(jīng)讀取,就不需要去RAM中讀取了 在web開發(fā)中,緩存主要...

    Ethan815 評(píng)論0 收藏0
  • 網(wǎng)站緩存詳解

    摘要:認(rèn)識(shí)緩存緩存原意是指可以進(jìn)行高速數(shù)據(jù)交換的存儲(chǔ)器。命中率指請(qǐng)求緩存次數(shù)與緩存返回正確結(jié)果次數(shù)的比例。如果相同,緩存直接使用副本相應(yīng)訪問,而不用向服務(wù)器發(fā)送請(qǐng)求緩存內(nèi)容應(yīng)用情形有完整的過期時(shí)間和壽命控制頭,并且沒過期。 1. 認(rèn)識(shí)緩存 緩存:原意是指可以進(jìn)行高速數(shù)據(jù)交換的存儲(chǔ)器。當(dāng)cpu處理數(shù)據(jù)時(shí),先到cache中尋找,如果數(shù)據(jù)已經(jīng)讀取,就不需要去RAM中讀取了 在web開發(fā)中,緩存主要...

    W_BinaryTree 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<