者:hetu666
來源:CSDN
HTML(HyperText Markup Language) 不是一門編程語言,而是一種用來告知瀏覽器如何組織頁面的標記語言。
HTML 可復雜、可簡單,一切取決于開發者。它由一系列的元素組成,這些元素可以用來包圍不同部分的內容,使其以某種方式呈現或者工作。 一對標簽可以為一段文字或者一張圖片添加超鏈接,將文字設置為斜體,改變字號,等等
元素的主要部分有:
1開始標簽(Opening tag):包含元素的名稱(本例為 p),被左、右角括號所包圍。表示元素從這里開始或者開始起作用 —— 在本例中即段落由此開始。
2結束標簽(Closing tag):與開始標簽相似,只是其在元素名之前包含了一個斜杠。這表示著元素的結尾 —— 在本例中即段落在此結束。初學者常常會犯忘記包含結束標簽的錯誤,這可能會產生一些奇怪的結果。
3內容(Content):元素的內容,本例中就是所輸入的文本本身。
4元素(Element):開始標簽、結束標簽與內容相結合,便是一個完整的元素。
塊級元素在頁面中以塊的形式展現 —— 相對于其前面的內容它會出現在新的一行,其后的內容也會被擠到下一行展現。塊級元素通常用于展示頁面上結構化的內容,例如段落、列表、導航菜單、頁腳等等。一個以block形式展現的塊級元素不會被嵌套進內聯元素中,但可以嵌套在其它塊級元素中。
<p>第四</p><p>第五</p><p>第六</p>
效果:
一個屬性必須包含如下內容:
1,在元素和屬性之間有個空格space (如果已經有一個或多個屬性,就與前一個屬性之間有一個空格.)
2屬性后面緊跟著一個“=”符號.
3,有一個屬性值,由一對引號“ ”引起來.
有時你會看到沒有值的屬性,它是合法的。這些屬性被稱為布爾屬性,他們只能有跟它的屬性名一樣的屬性值。
<!-- 使用disabled屬性來防止終端用戶輸入文本到輸入框中 --> <input type="text" disabled> <!-- 下面這個輸入框沒有disabled屬性,所以用戶可以向其中輸入 --> <input type="text">
在目前為止,本章內容所有的屬性都是由雙引號來包裹。也許在一些HTML中,你以前也見過單引號。這只是風格的問題,你可以從中選擇一個你喜歡的。以下兩種情況都可以:
<a >示例站點鏈接</a> <a >示例站點鏈接</a>
但你應該注意單引號和雙引號不能在一個屬性值里面混用。下面的語法是錯誤的:
<a >示例站點鏈接</a>
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>我的測試站點</title> </head> <body> <p>這是我的頁面</p> </body> </html>
1,<!DOCTYPE html>: 聲明文檔類型. 很久以前,早期的HTML(大約1991年2月),文檔類型聲明類似于鏈接,規定了HTML頁面必須遵從的良好規則,能自動檢測錯誤和其他有用的東西。使用如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
然而現在沒有人再這樣寫,需要保證每一個東西都正常工作已成為歷史。你只需要知道<!DOCTYPE html>是最短的有效的文檔聲明。
2,<html></html>: <html>元素。這個元素包裹了整個完整的頁面,是一個根元素。
3,<head></head>: <head>元素. 這個元素是一個容器,它包含了所有你想包含在HTML頁面中但不想在HTML頁面中顯示的內容。這些內容包括你想在搜索結果中出現的關鍵字和頁面描述,CSS樣式,字符集聲明等等。以后的章節能學到更多關于<head>元素的內容。
4.<meta charset="utf-8">: 這個元素設置文檔使用utf-8字符集編碼,utf-8字符集包含了人類大部分的文字。基本上他能識別你放上去的所有文本內容。毫無疑問要使用它,并且它能在以后避免很多其他問題。
5.<title></title>: 設置頁面標題,出現在瀏覽器標簽上,當你標記/收藏頁面時它可用來描述頁面。
6.<body></body>: <body>元素。 包含了你訪問頁面時所有顯示在頁面上的內容,文本,圖片,音頻,游戲等等。
代碼中包含的空格是沒有必要的;下面的兩個代碼片段是等價的:
<p>狗 狗 很 呆 萌。</p> <p>狗 狗 很 呆 萌。</p>
渲染這些代碼的時候,HTML解釋器會將連續出現的空白字符減少為一個單獨的空格符。
那么為什么我們會使用那么多的空白呢? 可讀性 —— 如果你的代碼被很好地進行格式化,那么就很容易理解你的代碼。
為了將一段HTML中的內容置為注釋,你需要將其用特殊的記號<!--和-->包括起來, 比如:
<p>我在注釋外!</p> <!-- <p>我在注釋內!</p> -->
HTML 頭部是包含在<head> 元素里面的內容。不像 <body>元素的內容會顯示在瀏覽器中,head 里面的內容不會在瀏覽器中顯示,它的作用是包含一些頁面的元數據。
元數據就是描述數據的數據,而HTML有一個“官方的”方式來為一個文檔添加元數據—— <meta> 。有很多不同種類的 <meta> 可以被包含進你的頁面的<head>元素,比如。
指定字符的編碼
<meta charset="utf-8">
這個元素簡單的指定了文檔的字符編碼 —— 在這個文檔中被允許使用的字符集。 utf-8 是一個通用的字符集,它包含了任何人類語言中的大部分的字符。
添加作者和描述
CSS和JavaScript
如今,幾乎你使用的所有網站都會使用css讓網頁更加炫酷,使用js讓網頁有交互功能,比如視頻播放器,地圖,游戲以及更多功能。這些應用在網頁中很常見,它們分別使用<link>元素以及 <script> 元素。
<link>元素經常位于文檔的頭部。這個link元素有2個屬性,rel="stylesheet"表明這是文檔的樣式表,而 href包含了樣式表文件的路徑:
<link rel="stylesheet" href="my-css-file.css">
<script>部分沒必要非要放在文檔頭部;實際上,把它放在文檔的尾部(在 </body>標簽之前)是一個更好的選擇,這樣可以確保在加載腳本之前瀏覽器已經解析了HTML內容(如果腳本加載某個不存在的元素,瀏覽器會報錯)。
<script src="my-js-file.js"></script>
在HTML中,每個段落是通過<p>元素標簽進行定義的, 比如下面這樣:
<p>我是一個段落,千真萬確。</p>
每個標題(Heading)是通過“標題標簽”進行定義的:
<h1>我是文章的標題</h1>
這里有六個標題元素標簽 —— <h1>、<h2>、<h3>、<h4>、<h5>、<h6>。每個元素代表文檔中不同級別的內容; <h1> 表示主標題(the main heading),<h2> 表示二級子標題(subheadings),<h3> 表示三級子標題(sub-subheadings),等等。
<ol> <li>先用蛋白一個、鹽半茶匙及淀粉兩大匙攪拌均勻,調成“腌料”,雞胸肉切成約一厘米見方的碎丁并用“腌料”攪拌均勻,腌漬半小時。</li> <li>用醬油一大匙、淀粉水一大匙、糖半茶匙、鹽四分之一茶匙、白醋一茶匙、蒜末半茶匙調拌均勻,調成“綜合調味料”。</li> <li>雞丁腌好以后,色拉油下鍋燒熱,先將雞丁倒入鍋內,用大火快炸半分鐘,炸到變色之后,撈出來瀝干油汁備用。</li> <li>在鍋里留下約兩大匙油,燒熱后將切好的干辣椒下鍋,用小火炒香后,再放入花椒粒和蔥段一起爆香。隨后雞丁重新下鍋,用大火快炒片刻后,再倒入“綜合調味料”繼續快炒。 <ul> <li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌幾下就可以起鍋了。</li> <li>如果你在北方,可加入黃瓜丁、胡蘿卜丁和花生米,翻炒后起鍋。</li> </ul> </li> </ol>
<i> 被用來傳達傳統上用斜體表達的意義:外國文字,分類名稱,技術術語,一種思想……
<b> 被用來傳達傳統上用粗體表達的意義:關鍵字,產品名稱,引導句……
<u> 被用來傳達傳統上用下劃線表達的意義:專有名詞,拼寫錯誤……
<strong>強調重要的詞
通過將文本轉換為<a>元素內的鏈接來創建基本鏈接, 給它一個href屬性(也稱為目標),它將包含您希望鏈接指向的網址。
<p>I'm creating a link to <a href="https://www.***.com/">***</a>. </p>
使用title屬性添加支持信息
<p>I'm creating a link to <a title="這是百度">百度</a>. </p>
I'm creating a link to 百度.
塊級鏈接
可以將一些內容轉換為鏈接,甚至是塊級元素。例如你想要將一個圖像轉換為鏈接,你只需把圖像元素放到<a></a>標簽中間。
文檔片段
超鏈接除了可以鏈接到文檔外,也可以鏈接到HTML文檔的特定部分(被稱為文檔片段)。要做到這一點,你必須首先給要鏈接到的元素分配一個id屬性。例如,如果你想鏈接到一個特定的標題,可以這樣做:
<h2 id="Mailing_address">Mailing address</h2>
然后鏈接到那個特定的id,可以在URL的結尾使用一個哈希符號(#)指向它,例如:
<p>Want to write us a letter? Use our <a href="contacts.html#Mailing_address">mailing address</a>.</p>
你甚至可以在同一份文檔下,通過鏈接文檔片段,來鏈接到同一份文檔的另一部分:
<p>The <a href="#Mailing_address">company mailing address</a> can be found at the bottom of this page.</p>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>增加學生</title> </head> <body> <form method="post" action="/community/alpha/student"> <p> 姓名:<input type="text" name="name"> </p> <p> 年齡:<input type="text" name="age"> </p> <p> <input type="submit" value="保存"> </p> </form> </body> </html>
//post @RequestMapping(path="/student",method=RequestMethod.POST) @ResponseBody public String saveStudent(String name,int age){ System.out.println(name); System.out.println(age); return "success"; }
大部分用來定義表單小部件的元素都有一些他們自己的屬性。然而,在所有表單元素中都有一組通用屬性,它們可以對這些小部件進行控制。下面是這些通用屬性的列表:
密碼框
通過設置type屬性值為password來設置該類型框:
<input type="password" id="pwd" name="pwd">
搜索框
通過設置 type屬性值為 search 來設置該類型框:
<input type="search" id="search" name="search">
電話號碼欄:
通過 type屬性的 tel 值設置該類型框:
<input type="tel" id="tel" name="tel">
URL 欄
通過type屬性的url 值設置該類型框:
<input type="url" id="url" name="url">
多行文本框
多行文本框專指使用<textarea>元素,而不是使用<input>元素。
<textarea cols="30" rows="10"></textarea>
在HTML表單中,有三種按鈕:
Submit
將表單數據發送到服務器。對于<button>元素, 省略 type 屬性 (或是一個無效的 type 值) 的結果就是一個提交按鈕.
Reset
將所有表單小部件重新設置為它們的默認值。
Anonymous
沒有自動生效的按鈕,但是可以使用JavaScript代碼進行定制。
每次向服務器發送數據時,都需要考慮安全性。到目前為止,HTML表單是最常見的攻擊路徑(可能發生攻擊的地方)。這些問題從來都不是來自HTML表單本身,它們來自于服務器如何處理數據。
根據你所做的事情,你會遇到一些非常有名的安全問題:
跨站腳本(XSS)和跨站點請求偽造(CSRF)是常見的攻擊類型,它們發生在當您將用戶發送的數據顯示給這個用戶或另一個用戶時。
XSS允許攻擊者將客戶端腳本注入到其他用戶查看的Web頁面中。攻擊者可以使用跨站點腳本攻擊的漏洞來繞過諸如同源策略之類的訪問控制。這些攻擊的影響可能從一個小麻煩到一個重大的安全風險。
CSRF攻擊類似于XSS攻擊,因為它們以相同的方式開始攻擊——向Web頁面中注入客戶端腳本——但它們的目標是不同的。CSRF攻擊者試圖將權限升級到特權用戶(比如站點管理員)的級別,以執行他們不應該執行的操作(例如,將數據發送給一個不受信任的用戶)。
XSS攻擊利用用戶對web站點的信任,而CSRF攻擊則利用網站對其用戶的信任。
為了防止這些攻擊,您應該始終檢查用戶發送給服務器的數據(如果需要顯示),盡量不要顯示用戶提供的HTML內容。相反,您應該對用戶提供的數據進行處理,這樣您就不會逐字地顯示它。當今市場上幾乎所有的框架都實現了一個最小的過濾器,它可以從任何用戶發送的數據中刪除HTML<script>、<iframe> 和<object>元素。這有助于降低風險,但并不一定會消除風險。
SQL 注入是一種試圖在目標web站點使用的數據庫上執行操作的攻擊類型。這通常包括發送一個SQL請求,希望服務器能夠執行它(通常發生在應用服務器試圖存儲由用戶發送的數據時)。這實際上是攻擊網站的主要途徑之一。
其后果可能是可怕的,從數據丟失到通過使用特權升級控制整個網站基礎設施的攻擊。這是一個非常嚴重的威脅,您永遠不應該存儲用戶發送的數據,而不執行一些清理工作(例如,在php/mysql基礎設施上使用mysql_real_escape_string()
這種類型的攻擊出現在當您的應用程序基于表單上用戶的數據輸入構建HTTP頭部或電子郵件時。這些不會直接損害您的服務器或影響您的用戶,但它們會引發一個更深入的問題,例如會話劫持或網絡釣魚攻擊。
這些攻擊大多是無聲的,并且可以將您的服務器變成僵尸。
偏執:永遠不要相信你的用戶
那么,你如何應對這些威脅呢?這是一個遠遠超出本指南的主題,不過有一些規則需要牢記。最重要的原則是:永遠不要相信你的用戶,包括你自己;即使是一個值得信賴的用戶也可能被劫持。
所有到達服務器的數據都必須經過檢查和消毒。總是這樣。沒有例外。
遠離有潛在危險的字符轉義。應該如何謹慎使用的特定字符取決于所使用的數據的上下文和所使用的服務器平臺,但是所有的服務器端語言都有相應的功能。
限制輸入的數據量,只允許有必要的數據。
沙箱上傳文件(將它們存儲在不同的服務器上,只允許通過不同的子域訪問文件,或者通過完全不同的域名訪問文件更好)。
最后,我自己是一名從事了多年開發的JAVA老程序員,辭職目前在做自己的java私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的java學習干貨,可以送給每一位喜歡java的小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:交流,即可免費獲取。
介: 對于Java對象序列化,由于JDK自帶的序列化性能很差,業界出現了hessian/kryo等框架來加速序列化。這些框架能夠序列化大部分Java對象,但如果對象實現了[writeObject](https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/output.html#the-writeobject-method)**/**[readObject](https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/input.html#the-re
Fury是一個基于JIT動態編譯的高性能多語言原生序列化框架,支持Java/Python/Golang/C++/JavaScript等語言,提供全自動的對象多語言/跨語言序列化能力,以及相比于別的框架最高20~200倍的性能。
對于Java對象序列化,由于JDK自帶的序列化性能很差,業界出現了hessian/kryo等框架來加速序列化。這些框架能夠序列化大部分Java對象,但如果對象實現了writeObject/readObject/ writeReplace/readResolve等JDK自定義序列化方法,這些框架便無能為力。由于用戶可能在這些方法當中執行任意邏輯,為了保證序列化的正確性,這些方法需要被以符合JDK序列化的行為方式被執行,這時候用戶只能選擇JDK自帶的序列化框架,忍受極其緩慢的性能。
而業務系統的數據對象自定義JDK序列化是很常見的事情,比如下方是某個復雜場景序列化使用Fury測試下來的火焰圖,里面就有相當一部分開銷在JDK序列化上面(Fury早期版本在遇到自定義JDK序列化的類型時會轉發給JDK進行序列化)。
為了提高序列化的性能,保證任意場景不回退,Fury從0.9.2版本開始完整實現了整套JDK序列化協議,兼容所有JDK自定義序列化行為,從而在任意場景避免使用JDK序列化,保證高效的序列化性能。
本文將首先分析JDK序列化原理,接下來基于JDK序列化原理展開hessian/kryo等框架的不足之處,然后介紹Fury的高效兼容實現,最后給出性能對比的數據。
JDK序列化框架使用ObjectOutputStream和ObjectInputStream序列化和反序列化,該框架允許用戶通過Externalizable/writeObject/readObject/readObjectNoData/writeReplace/readResolve等方法來自定義序列化的行為。當要序列化的對象不包含這些方法時,ObjectOutputStream會調用內部的defaultWriteObject來序列化類型層次結構的所有字段和類型信息,反序列化時會使用ObjectInputStream來讀取類型層次結構的每個類型相關信息和對應每個字段值并填充整個對象。如果包含自定義序列化方法,則需要走到單獨的執行流程。
當對象定義了writeReplace方法時,序列化會先調用該方法,然后使用該方法返回的對象引用取代引用表之前記錄的引用。如果返回對象類型不變,即返回類型仍有writeReplace方法,這時候該方法會被忽略,進入正常的writeObject/writeExternal流程。如果返回類型發生變化,則循環調用writeReplace方法重復前述流程。
當返回對象不再包含writeReplace方法時,這時候便進入到字段數據序列化的過程,如果對象實現了Externalizable接口,則調用writeExternal進行序列化,否則從對象層次結構的第一個定義了Serializable的父類開始,依次序列化每個類型以及屬于當前類型的所有字段數據。
當對象層次結構的某個類型定義了writeObject方法時,對于對應到該類型的字段的序列化,則會調用調用該類型定義的writeObject方法進行,writeObject方法內部可以調用ObjectOutputStream的defaultWriteObject完成默認字段的序列化,或者完全手寫序列化邏輯。
對于不同JDK版本字段不一致需要兼容的情況,則需要調用putFields方法獲取PutField對象,用于設置只在某些JDK版本存在但當前JDK版本不存在的字段數據,然后調用writeFields完成字段數據的寫入。
比如ThreadLocalRandom就是通過putFields來自定義序列化邏輯:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields=s.putFields();
fields.put("rnd", U.getLong(Thread.currentThread(), SEED));
fields.put("initialized", true);
s.writeFields();
}
需要注意defaultWriteObject寫的數據可能會通過readFields進行讀取,因此其格式需要和和putFields兼容。另外在自定義序列化時defaultWriteObject/putFields兩者只能調用一個。
整體流程如下圖:
反序列化首先會讀取對象類型,然后查詢該類型的無參構造函數用于創建對象,如果不存在無參數構造函數,則通過ReflectionFactory#newConstructorForSerialization(java.lang.Class<?>)向上遍歷類型層次結構直到獲取到第一個非Serializable父類的無參構造函數(該過程會進行緩存,避免重復查找)。
然后根據構造函數創建對象,并將對象放入引用表,避免循環引用找不到對象。
接下來從第一個Serializable父類開始依次反序列化每個類型和對應的字段數據,并填充到之前通過構造函數創建的對象里面。如果某個反序列化的類型不存在,則代表對象層次結構發生了變化,反序列化端對象增加了新的父類,如果該類型定義了readObjectNoData方法,則會調用該方法初始化字段狀態,否則這部分字段將出于默認狀態。
如果父類類型沒有定義readObject,則會通過調用defaultReadObject來依次讀取每個非transient非static字段的值并填充到對象里面。如果定義了readObject方法,則調用該方法完成該類型數據的反序列化。
readObject方法可以調用defaultReadObject來完成默認字段值的反序列化,然后執行其它自定義邏輯,或者完全手寫反序列化邏輯。
對于不同JDK版本字段不一致需要兼容的情況,則需要調用readFields方法獲取GetField對象,該對象可能包含當前Class版本沒有的字段數據,這時候可以直接忽略掉,其它字段可以從GetField里面查詢出來并設置到對象上面。需要注意defaultReadObject和readFields兩者只能調用一個。
某些情況下父類字段的反序列化依賴子類字段反序列化后的狀態,由于父類字段先反序列化,這時候無法獲取子類反序列化后的狀態,因此JDK提供了registerValidation回調來在整個對象完成反序列化后執行,這時可以執行額外的操作恢復對象的狀態。
在對象完成序列化之后,檢查對象所在類型是否定義了readResolve方法,如果定義了該方法,則調用該方法返回替代對象,如果返回類型發生變化,則循環調用readResolve方法重復前述流程。
在執行完readResolve之后,整個對象便完成了反序列化。
Hessian目前支持writeReplace/readResolve自定義方法,當對象定義了writeReplace方法時,會通過com.caucho.hessian.io.WriteReplaceSerializer進行序列化。該序列化器能夠滿足部分場景需求,但當writeReplace方法返回相同類型的新對象時,hessian會出現棧溢出:
public static class CustomReplaceClass implements Serializable {
Object writeReplace() {
return new CustomReplaceClass();
}
Object readResolve() {
return new CustomReplaceClass();
}
}
Exception in thread "main" java.lang.StackOverflowError
at java.base/java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:73)
at jdk.internal.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.caucho.hessian.io.WriteReplaceSerializer.writeReplace(WriteReplaceSerializer.java:184)
at com.caucho.hessian.io.WriteReplaceSerializer.writeObject(WriteReplaceSerializer.java:155)
at com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:465)
at com.caucho.hessian.io.WriteReplaceSerializer.writeObject(WriteReplaceSerializer.java:167)
at com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:465)
at com.caucho.hessian.io.WriteReplaceSerializer.writeObject(WriteReplaceSerializer.java:167)
at com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:465)
at com.caucho.hessian.io.WriteReplaceSerializer.writeObject(WriteReplaceSerializer.java:167)
at com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:465)
Hessian目前不支持writeObject/readObject方法,當要序列化的對象定義了這些方法時,hessian會直接忽略掉,而在實際場景中很多對象都定義了這兩個方法,JDK大部分類型也都定義了這兩個方法,導致hessian在序列化這些類型時出現狀態不一致的錯誤。
一般在這些類型里面,數據字段一般標記為transient,因此忽略這兩個方法直接序列化所有非transient字段會導致數據丟失,比如LinkedBlockingQueue的主要數據字段都全部是transient,在writeObject里面進行特殊的處理:
/**
* Head of linked list.
* Invariant: head.item==null
*/
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next==null
*/
private transient Node<E> last;
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
fullyLock();
try {
// Write out any hidden stuff, plus capacity
s.defaultWriteObject();
// Write out all elements in the proper order.
for (Node<E> p=head.next; p !=null; p=p.next)
s.writeObject(p.item);
// Use trailing null as sentinel
s.writeObject(null);
} finally {
fullyUnlock();
}
}
同時由于沒有執行這兩個方法里面的自定義邏輯,最終反序列化的對象狀態也會不對。比如Java java.util.concurrent.locks.AbstractQueuedLongSynchronizer的子類都需要自定義readObject方法來重設lock狀態:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
readHolds=new ThreadLocalHoldCounter();
setState(0); // reset to unlocked state
}
對于常見類型,或許可以通過內置序列化器來進行序列化,但這無法枚舉所有已知類型和未知類型,一旦出現序列化錯誤,比如多線程Lock狀態錯誤,將極其難以排查。
同時hessian不支持父子類出現重名字段,這在某些條件下也會成為一個使用限制。
因此在RPC框架里面,很多場景用戶會直接選擇JDK序列化,這些場景現在全部都可以切換為FURY進行加速。
Kryo為了保證序列化的正確性,在遇到定義了writeObject/readObject/readObjectNoData/writeReplace/ readResolve的對象時,會調用JDK的ObjectOutputStream和ObjectInputStream進行序列化。該方式存在三個問題:
kryo不支持父子類出現重名字段,這在某些條件下也會成為一個使用限制。
Fury早期版本序列化流程跟Kryo類型,在遇到writeObject/readObject/readObjectNoData/writeReplace/ readResolve的對象時,調用JDK的ObjectOutputStream和ObjectInputStream進行序列化。
在Fury 0.9.2版本,我們提供了一套基于JIT動態編譯的100%兼容JDK自定義序列化的實現,性能數量級提升。
整體實現流程模擬了JDK序列化的過程,但實現上使用了Fury內置的JIT序列化器來進行加速和減少序列化結果大小,同時對于對象層次結構的每個Serializable class,只序列化類名稱,不序列化類的元數據,減少開銷。
整體實現在io.fury.serializers.ReplaceResolveSerializer和io.fury.serializers.ObjectStreamSerializer兩個序列化器里面,分別負責writeReplace/readResolve自定義序列化和writeObject/readObject/readObjectNoData自定義序列化。
ReplaceResolveSerializer完整實現了JDK相同的replace/resolve行為,即使在writeReplace方法返回相同類型不同引用的對象,也能夠正常序列化,不會出現hessian一樣的棧溢出問題。同時在返回對象類型跟原始對象類型不同時,fury可以避免寫入原始對象的類名稱,減少序列化的結果大小。
如果對象同時定義了writeObject/readObject/readObjectNoData/writeReplace/readResolve方法,fury會分發給ReplaceResolveSerializer處理引用replace/resolve,將處理完之后的對象再交給ObjectStreamSerializer進行JDK自定義序列化流程。
ObjectStreamSerializer實現了整套JDK writeObject/readObject/readObjectNoData/registerValidation行為,保證行為跟JDK的一致性,在任意情況下序列化都不會報錯。由于用戶在writeObject/readObject/ readObjectNoData/registerValidation里面調用的是JDK ObjectOutputStream/ObjectInputStream /PutField/GetField的接口,因此Fury也實現了一套ObjectOutputStream/ObjectInputStream/PutField/ GetField的子類,保證實際序列化邏輯可以轉發給Fury。
為了保證類型前后兼容,同時保證defaultWriteObject/defaultReadObject跟putFields/readFields的兼容性,字段數據序列化使用的是Fury的CompatibleSerializer,在讀寫端類型不一致的情況下也可以爭取反序列化。為了保證高性能,在開啟JIT模式時會通過io.fury.serializers.CodegenSerializer#loadCompatibleCodegenSerializer創建JITCompatibleSerializer進行序列化。
整體實現分為序列化器初始化部分和執行部分。
點擊查看原文,獲取更多福利!
https://developer.aliyun.com/article/1102715?utm_content=g_1000365751
版權聲明:本文內容由阿里云實名注冊用戶自發貢獻,版權歸原作者所有,阿里云開發者社區不擁有其著作權,亦不承擔相應法律責任。具體規則請查看《阿里云開發者社區用戶服務協議》和《阿里云開發者社區知識產權保護指引》。如果您發現本社區中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社區將立刻刪除涉嫌侵權內容。
soup 是一款 Java 的 HTML 解析器,可直接解析某個 URL 地址、HTML 文本內容。它提供了一套非常省力的 API,可通過 DOM,CSS 以及類似于 jQuery 的操作方法來取出和操作數據。jsoup 的主要功能如下:
- 從一個 URL,文件或字符串中解析 HTML;
- 操作 HTML 元素、屬性、文本;
- 使用 DOM 或 CSS 選擇器來查找、取出數據。
學習 jsoup 可以參考以下步驟:
1.?解析和遍歷一個 HTML 文檔:
java 復制
String html="First parse" +
"
Parsed HTML into a doc.
";Document doc=Jsoup.parse(html);
?
上述代碼中,解析器能夠盡最大可能從提供的 HTML 文檔來創建一個干凈的解析結果,無論 HTML 的格式是否完整。
2.?解析一個 HTML 字符串:
java 復制
String html="First parse" +
"
Parsed HTML into a doc.
";Document doc=Jsoup.parse(html);
?
上述代碼中,使用靜態?Jsoup.parse(String html)?方法或?Jsoup.parse(String html, String baseUri)?方法來解析 HTML 字符串。
3.?從一個 URL 加載一個 Document:
java 復制
Document doc=Jsoup.connect("http://example.com/").get();
String title=doc.title();
?
上述代碼中,使用?Jsoup.connect(String url)?方法來創建一個新的 Connection,并使用?get()?方法取得和解析一個 HTML 文件。如果從該 URL 獲取 HTML 時發生錯誤,便會拋出 IOException,應適當處理。
4.?使用 DOM 方法來遍歷一個文檔:
java 復制
File input=new File("/tmp/input.html");
Document doc=Jsoup.parse(input, "UTF-8", "http://example.com/");
?
上述代碼中,將 HTML 解析成一個 Document 之后,就可以使用類似于 DOM 的方法進行操作。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。