Java Web 前端高性能優化(二)

######一.上文回顧javascript

上回咱們主要從圖片的合併、壓縮等方面介紹前端性能優化問題(詳見Java Web 前端高性能優化(一)css

本次咱們主要從圖像BASE64 編碼、GZIP壓縮、懶加載與預加載以及 OneAPM Browser Insight 的定位分析功能四個方面介紹前端優化方法html

######二.圖像的 BASE64 編碼前端

無論如何,圖片的下載始終都要向服務器發出請求,要是圖片的下載不用向服務器發出請求,而能夠隨着 HTML 的下載同時下載到本地那就太好了。而目前,瀏覽器已經支持了該特性,咱們能夠將圖片數據編碼成 BASE64 的字符串,使用該字符串代替圖像地址。java

假設用 S表明這個 BASE64 字符串,那麼就可使用<img src="data:image/png;base64,S"> 來顯示這個圖像。能夠看出,圖像的數據包含在了 HTML 代碼裏,無需再次訪問服務器。那麼圖像要如何編碼成 BASE64 字符串呢?程序員

可使用 在線的工具---「Base64 Online」,這個工具能夠上傳圖片將圖片轉換爲 BASE64 字符串。固然,若是讀者有興趣,徹底能夠本身實現一個 BASE64 編碼工具,好比使用 Java 開發,它的代碼就如清單 1 所示。web

清單 1. BASE64 的 Java 代碼數組

<pre> public static String getPicBASE64(String picPath) { String content = null; try { FileInputStream fis = new FileInputStream(picPath); byte[] bytes = new byte[fis.available()]; fis.read(bytes); content = new sun.misc.BASE64Encoder().encode(bytes); // 具體的編碼方法 fis.close(); } catch (Exception e) { e.printStackTrace(); } return content; } </pre>瀏覽器

本文編碼了一個圖像,而且將編碼得到的 BASE64 字符串,寫到了 HTML 之中,以下清單 2 所示。緩存

清單 2. 嵌入 BASE64 的測試 HTML 代碼

<html> 
 <body> 
 <img src="data:image/png;base64,
 iVBORw0KGgoAAAANSUhEUgAAAeQAAAB8BAMAAABKwt5QAAAAA3NCSVQICAjb4U/gAAAAGFBMVEX/ 
 ……(省略了大部分編碼)… BJRU5ErkJggg=="> 
 </body> 
 </html>

因爲圖片數據包含在了 BASE64 字符串中,所以無需向服務器請求圖像數據,結果顯示以下圖所示。

圖 1. BASE64 顯示圖像

前端高性能資源優化(二)

然而這種策略並不能濫用,它適用的狀況是瀏覽器鏈接服務器的時間 > 圖片下載時間,也就是發起鏈接的代價要大於圖片下載,那麼這個時候將圖片編碼爲 BASE64 字符串,就能夠避免鏈接的創建,提升效率。若是圖片較大的話,使用 BASE64 編碼雖然能夠避免鏈接創建,可是相對於圖像下載,請求的創建只佔很小的比例,若是用 BASE64,對於動態網頁來講圖像緩存就會失效(靜態網頁能夠緩存),並且 BASE64 字符串的總大小要大於純圖片的大小,這樣一算就很是不合適了。

所以,若是你的頁面已經靜態化,圖像又不是很是大,能夠嘗試 BASE64 編碼,客戶端會將網頁內容和圖片的 BASE64 編碼一塊兒緩存;而若是你的頁面是動態頁面,圖像還較大,每次都要下載 BASE64 字符串,那麼就不能用 BASE64 編碼圖像,而正常引用圖像,從而使用到瀏覽器的圖像緩存,提升下載速度。從現實咱們接觸的角度看,如一些在線 HTML 編輯器,裏面的小圖標,如笑臉等,都使用到了 BASE64 編碼,由於它們很是小,數量多,BASE64 能夠幫助網頁減小圖標的請求數,提升效率。

######三.Browser Insight 定位分析

做爲一個網站的前端運維人員或者優化人員,大多數狀況下並不必定要注重每一位用戶的訪問狀況,只要大部分用戶訪問網站的時候處於一個滿意的程度就能夠了。

如今大多數前端性能優化工具每每注重的是某個時間段內的頁面平均響應時間,這就形成可能由於某個用戶偶然性的網絡卡頓而延長整個時間段內的頁面加載時間。

前一段時間發現 OneAPM 的Browser Insight 推出了定位分析功能,能夠從響應時間分佈來查看用戶的總體響應分佈,並能夠針對不一樣時間分佈內的用戶肯定影響其響應時間的因素。

圖 2.Browser Insight 定位分析

前端高性能資源優化(二)

這個功能確實對於前段優化人員來講很是實用,而且它的維度還很豐富。

######四.GZIP 壓縮

爲了減小傳輸的數據,壓縮是一個不錯的選擇,而 HTTP 協議支持 GZIP 的壓縮格式,服務器響應的報頭包含 Content-Encoding: gzip,它告訴瀏覽器,這個響應的返回數據,已經壓縮成 GZIP 格式,瀏覽器得到數據後要進行解壓縮操做。這在必定程度能夠減小服務器傳輸的數據,提升系統性能。

那麼如何給服務器響應添加 Content-Encoding: gzip 報頭,同時壓縮響應數據呢?

若是你用的是 Tomcat 服務器,打開 $tomcat_home$/conf/server.xml 文件,對 Connector 進行配置,配置如清單 3 所示。

清單 3. TOMCAT 配置清單

<Connector  port ="80"  maxHttpHeaderSize ="8192" 
 maxThreads ="150"  minSpareThreads ="25"  maxSpareThreads ="75" 
 enableLookups ="false"  redirectPort ="8443"  acceptCount ="100" 
 connectionTimeout ="20000"  disableUploadTimeout ="true"  URIEncoding ="utf-8"   
 compression="on" 
 compressionMinSize="2048" 
 noCompressionUserAgents="gozilla, traviata" 
 compressableMimeType="text/html,text/xml" />

咱們爲 Connector 添加了以下幾個屬性,他們意義分別是:

compression="on" 打開壓縮功能

compressionMinSize="2048" 啓用壓縮的輸出內容大小,這裏面默認爲 2KB

noCompressionUserAgents="gozilla, traviata" 對於如下的瀏覽器,不啓用壓縮

compressableMimeType="text/html,text/xml, image/png" 壓縮類型

有時候,咱們沒法配置 server.xml,好比若是咱們只是租用了別人的空間,可是它並無啓用GZIP,那麼咱們就要使用程序啓用 GZIP 功能。咱們將須要壓縮的文件,放到指定的文件夾,使用一個過濾器,過濾對這個文件夾裏文件的請求。

清單 4. 自定義 Filter 壓縮 GZIP

<pre> // 監視對 gzipCategory 文件夾的請求 @WebFilter(urlPatterns = { "/gzipCategory/*" }) public class GZIPFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String parameter = request.getParameter("gzip"); // 判斷是否包含了 Accept-Encoding 請求頭部 HttpServletRequest s = (HttpServletRequest)request; String header = s.getHeader("Accept-Encoding"); //"1".equals(parameter) 只是爲了控制,若是傳入 gzip=1,才執行壓縮,目的是測試用 if ("1".equals(parameter) && header != null && header.toLowerCase().contains("gzip")) { HttpServletResponse resp = (HttpServletResponse) response; final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); HttpServletResponseWrapper hsrw = new HttpServletResponseWrapper( resp) { @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding())); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public void write(int b) throws IOException { buffer.write(b); } }; } }; chain.doFilter(request, hsrw); byte[] gzipData = gzip(buffer.toByteArray()); resp.addHeader("Content-Encoding", "gzip"); resp.setContentLength(gzipData.length); ServletOutputStream output = response.getOutputStream(); output.write(gzipData); output.flush(); } else { chain.doFilter(request, response); } } // 用 GZIP 壓縮字節數組 private byte[] gzip(byte[] data) { ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(10240); GZIPOutputStream output = null; try { output = new GZIPOutputStream(byteOutput); output.write(data); } catch (IOException e) { } finally { try { output.close(); } catch (IOException e) { } } return byteOutput.toByteArray(); } …… } </pre>

該程序的主體思想是:在響應流寫回以前,對響應的字節數據進行 GZIP 壓縮。

由於並非全部的瀏覽器都支持 GZIP 解壓縮,若是瀏覽器支持 GZIP 解壓縮,會在請求報頭的 Accept-Encoding 裏包含 gzip。這是告訴服務器瀏覽器支持 GZIP 解壓縮,所以若是用程序控制壓縮,爲了保險起見,還須要判斷瀏覽器是否發送 accept-encoding: gzip 報頭,若是包含了該報頭,才執行壓縮。爲了驗證壓縮先後的狀況,使用 Firebug 監控請求和響應報頭。

清單 5. 壓縮前請求

<pre> GET /testProject/gzipCategory/test.html HTTP/1.1 Accept: */* Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) Host: localhost:9090 Connection: Keep-Alive </pre>

清單 6. 不壓縮的響應

<pre> HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ETag: W/"5060-1242444154000" Last-Modified: Sat, 16 May 2009 03:22:34 GMT Content-Type: text/html Content-Length: 5060 Date: Mon, 18 May 2009 12:29:49 GMT </pre>

清單 7. 壓縮後的響應

<pre> HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ETag: W/"5060-1242444154000" Last-Modified: Sat, 16 May 2009 03:22:34 GMT Content-Encoding: gzip Content-Type: text/html Content-Length: 837 Date: Mon, 18 May 2009 12:27:33 GMT </pre>

能夠看到,壓縮後的數據比壓縮前數據小了不少。壓縮後的響應報頭包含 Content-Encoding: gzip。

同時 Content-Length 包含了返回數據的大小。GZIP 壓縮是一個重要的功能,前面提到的是對單一服務器的壓縮優化,在高併發的狀況,多個 Tomcat 服務器以前,須要採用反向代理的技術,提升併發度,而目前比較火的反向代理是 Nginx(這在後續的文章會進行詳細的介紹)。

對 Nginx 的 HTTP 配置部分裏增長以下配置。

清單 8. Nginx 的 GZIP 配置

<pre> gzip on; gzip_min_length 1000; gzip_buffers 4 8k; gzip_types text/plain application/x-javascript text/css text/html application/xml; </pre>

因爲 Nginx 具備更高的性能,利用該配置能夠更好的提升性能。在高性能服務器上該配置將很是有用。

######五.懶加載與預加載

預加載和懶加載,是一種改善用戶體驗的策略,它實際上並不能提升程序性能,可是卻能夠明顯改善用戶體驗或減輕服務器壓力。

預加載原理是在用戶查看一張圖片時,就將下一張圖片先下載到本地,而當用戶真正訪問下一張圖片時,因爲本地緩存的緣由,無需從服務器端下載,從而達到提升用戶體驗的目的。爲了實現預加載,咱們能夠實現以下的一個函數。

清單 9. 預加載函數

<pre> function preload(callback) { var imageObj = new Image(); images = new Array(); images[0]="pre_image1.jpg"; images[1]=" pre_image2.jpg"; images[2]=" pre_image3.jpg"; for(var i=0; i<=2; i++) { imageObj.src=images[i]; if (imageObj.complete) { // 若是圖片已經存在於瀏覽器緩存,直接調用回調函數 callback.call(imageObj); } else { imageObj.onload = function () {// 圖片下載完畢時異步調用 callback 函數 callback.call(imageObj);// 將回調函數的 this 替換爲 Image 對象 }; } } } function callback() { alert(this.src + 「已經加載完畢 , 能夠在這裏繼續預加載下一組圖片」); } </pre>

上面的代碼,首先定義了 Image 對象,而且聲明瞭須要預加載的圖像數組,而後逐一的開始加載(.src=images[i])。若是已經在緩存裏,則不作其餘處理;若是不在緩存,監聽 onload 事件,它會在圖片加載完畢時調用。

而懶加載則是在用戶須要的時候再加載。當一個網頁中可能同時有上百張圖片,而大部分狀況下,用戶只看其中的一部分,若是同時顯示上百張,則浪費了大量帶寬資源,所以能夠當用戶往下拉動滾動條時,纔去請求下載被查看的圖像,這個原理與 word 的顯示策略很是相似。

在 JavaScript 中,它的基本原理是首先要有一個容器對象,容器裏面是 img 元素集合。用隱藏或替換等方法,中止 img 的加載,也就是中止它去下載圖像。而後歷遍 img 元素,當元素在加載範圍內,再進行加載(也就是顯示或插入 img 標籤)。

加載範圍通常是容器的視框範圍,即瀏覽者的視覺範圍內。當容器滾動或大小改變時,再從新曆遍元素判斷。如此重複,直到全部元素都加載後就完成。固然對於開發來說,選擇已有的成熟組件,並不失爲一個上策,Lazy Load Plugin for jQuery 是基於 JQuery 的懶加載組件,它有本身的官方網站

這是一個不錯的免費插件。能夠幫助程序員快速的開發懶加載應用。

######小結

Java Web 前端高性能優化(一)(二)總結了前端性能問題定位以及圖片優化的幾種方式,將它們歸結起來,在讀者須要的時候,能夠查看本文的內容,相信按照本文的方法,能夠輔助讀者進行前端性能優化

:本文轉載自 IBM 社區,由 OneAPM 產品運營編輯整理,原文連接爲: http://www.ibm.com/developerworks/cn/java/j-lo-javawebhiperf1/#icomments

相關文章
相關標籤/搜索