者: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 href="http://www.example.com">示例站点链接</a> <a href='http://www.example.com'>示例站点链接</a>
但你应该注意单引号和双引号不能在一个属性值里面混用。下面的语法是错误的:
<a href="http://www.example.com'>示例站点链接</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 href="https://www.baidu.com" 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小时内与您取得联系。