述 | 杨晓兵
编辑 | 伍杏玲
出品 | CSDN(ID:CSDNnews)
编者前记:
编译器是连接人类世界与机器世界之间的一座桥梁,它可将程序员理解的高级语言,转换成程序高效执行的机器码。在 C/C++ 编译器里,有 VC、Borland C++、GCC、Watcom C/C++ 等国外热门编译器,但属于国内自主研发的编译器较少。
毕竟开发一款实用的编译器不易,涉及前端词法、语法分析、语意分析、大量的编译优化等工作。而有一支团队,不惜花费十余年精力完全自主研发出一款 YC 编译器和 YC 浏览器内核。
为何他们不遗余力地自主研发编译器和浏览器内核?这款编译器有何优点呢?下面由 YC 编译器的主要作者之一——杨晓兵,来讲述这背后十多年来的漫漫研发路。
以下为杨晓兵自述:
初衷:“做一些对软件行业进步有帮助的东西”
十多年前,我在中国科学院电子学研究所工作,参与设计一些硬件电路。当时我对硬件的兴趣远超软件,后创业专门从事软件工作。
我在创业的过程中发现,做此类软件虽能赚钱,但无论做得怎样,对软件科学的进步都无丝毫作用。尽管付出很多,却无成就感。
操作系统、数据库、编译器以及浏览器内核是不需要特殊专业知识的、开发难度非常大、最基础的软件产品。
我想从这几种软件中选择其中一项来自主研发,虽然不能肯定做出什么成就,但我有希望能做出一些对软件行业进步有所帮助的东西,使自己不枉踏入软件这个行业。根据当时的情况,我发现可先从浏览器内核下手,于是我除了维护原有产品外,把主要精力都投入到浏览器的研发中。
创新将 C 代码内嵌到 HTML
两年后,我们研发完成浏览器内核的基本功能,如 HTML 的解析和显示、JavaScript 脚本的执行等。
此时,我们发现 HTML 的标准越来越复杂,导致开发难度越来越大,如果按照这样的发展,浏览器内核将无法走入市场。
于是我重新思考:如果把 C 语言处理成像 JavaScript 脚本嵌入到 HTML 中,用内嵌 C 代码的 HTML 超文本做软件的人机交互界面,这款内核应该会有点竞争优势。
于是我们花费两年半的时间将标准 C 语言以 JavaScript 相似的方式在 HTML 中执行,并扩展了一个 HTML 标签:<user>,每个 user 标签都可以用属性 src 指定一个 C 源码文件,user标签的显示界面和所有行为都由它的 C 代码决定。
同时将 C 编译器做成一个函数,用该函数编译生成 C 程序的可执行代码,执行代码可被存入文件或直接执行。此时,我们将编译器取名为 YC 编译器,浏览器内核取名为 YC 浏览器。
三年又三年,漫漫研发路
随后,我们继续完善浏览器内核,将其中的一些内核代码独立出来用内嵌编译器动态编译执行,并将大部分内核源代码开源。
与此同时,我们又遇到一个问题:YC 编译器虽然编译速度较快,生成的却是字节码,执行速度慢,而且与原生代码相互调用(特别是回调函数)的处理相当繁琐。因此用当时的 YC 编译器难以胜任开源代码的编译工作。
为了解决自编译浏览器内核代码的问题,我们决定修改 YC 编译器,使它的字节码转换为原生的执行码,并扩展语法,使之具有少量的 C++ 语法。这个工作持续了三年。
三年后,YC 编译器功能增多,它提供一个函数像调用动态链接库一样直接调用 C 源码中的函数。此时,浏览器内核开源部分都可以用 YC 编译器实时编译执行了。
我们继续改进浏览器内核,将速度很慢的 JavaScript 字节码改为二进制原生代码,使 JavaScript 的执行速度约提高约 100 多倍。同时将浏览器内核代码全部模块化并开源,每个模块都用 YC 编译器动态编译执行,编译器的部分源码也开源(如内嵌汇编编译器源码、反汇编源码、C/C++ 字节码的执行源码等),所有的开源代码均由内嵌的 YC 编译器自动检测编译,动态执行。这个工作大概耗时四年。
开发至此,我想起谷歌和火狐浏览器都已开源,为什么不去看看它们的源代码呢?于是找到这两个浏览器的源码。
当时由于一些原因,我分析谷歌浏览器源码没有编译通过,而火狐的源码很顺利就编译成功了,于是我就走上了分析火狐源码之路。
下载的火狐源码由纯 C 代码和 C++ 代码两部分组成,经 Visual C++ 2013 编译生成一个 xul.dll 文件和一个 firefox.exe 文件。
我首先分析了它的 C 代码,将所有的输出函数全部改为类接口,并让 xul.dll 通过 YC 编译器函数 YC_cppLoad 进行实时编译,然后用类接口调用 C 源码中的函数。这一步进行得很顺利,若修改了火狐的 C 代码,只要重新运行火狐浏览器便可生效,无需其它操作。
曾经的办公桌
接下来开始分析火狐 C++ 代码。YC 编译器只实现了少数几个 C++ 语法,不能编译火狐 C++ 代码,故分析起来非常困难。
为什么火狐 C 代码容易分析,而它的 C++ 代码难以分析呢?原来我用 YC 编译器将它的 C 代码生成汇编代码文件、变量结构定义文件、宏定义文件和预编译文件,通过这几个文件,大大减少了分析难度。
因此我再次决定修改 YC 编译器,使之完全支持 C++11 标准,因为火狐 C++ 代码几乎使用了所有的 C++11 语法特性。先使用 STL 标准模板库代码进行编译器的修改和调试,出乎预料,这个过程竟用了三年时间!之后,我用 YC++ 编译器开始调试火狐 C++ 代码。原以为 STL 那么复杂的代码都可以编译通过并正确执行,火狐 C++ 代码应该能很快就编译通过。没想到,很多语法细节 STL 没有用到,而火狐 C++ 源码用到了。于是又继续修改 YC 编译器,对火狐 C++ 的各个模块进行编译,这个过程持续了一年多。
虽然 YC 编译器可以编译全部火狐 C++ 代码,但如何生成执行代码呢?先从主程序 Firefox.cpp 入手,经整理,这个程序可用 YC 编译器生成执行代码 Firefox.exe,并能顺利运行。
由于火狐 C++ 各模块耦合紧密,很难拆分,经过一个多月的工作,仍未能将其拆成多个独立的源码模块以便于用 YC 编译器实时编译,动态执行,这也许是我对火狐 C++ 源码的整体结构还不甚清楚之故,只见其树木不见其森林。
杨晓兵
当我准备对火狐 C++ 代码进行再一次总体分析时,有个偶然的机会参与到一个学校管理系统的开发中,因原有的管理系统经常出故障,操作极其不方便。尽管没有开发 Web 服务程序的经历,但我做的软件与 Web 服务器有极大关系。
经了解,要开发这种管理系统需要的软件有:Apache 或 Nginx 服务器,数据库 MySQL 或其它,编程工具 ASP 或 JSP 或 PHP 等,于是启发我们自己研发这些工具。YC 的 C/C++ 和 JavaScript 编译器和 HTML 解析器正好派上用场。
经过一段时间,一个稳定的、可任意扩展的、多线程高并发的 HTTP 服务器就完成了。该服务器处理 YSP 文件生成网页传给浏览器。
YSP 是我设计的与 ASP、JSP 和 PHP 功能相似的一种网页编程语言。YC 服务器执行 YSP 文件中的内嵌 C/C++ 或 JavaScript 代码,生成 HTML 超文本传给终端设备。工具做好后,不久便做出了管理系统的雏形,这个雏形在发布的 YC 编译器中可见到。
做了上述这些工作后,我想是时候该写本书介绍一下 YC 编译器了,经过一段时间编写的《YC编译器—多语言程序设计》(暂名)即将出版。
当我把书完成后,便立即投入64位的C/C++和JavaScript编译器的开发,目前开发进展顺利,已进入测试阶段。
编者后记:
三年时间,可将一个呱呱落地的婴儿变成蹦蹦跳跳的幼儿,可将一名懵懂的职场新人变成沉稳的老兵。而杨晓兵团队沉下心,迎难而上,花费三年又三年、再一年、两年、四年的时间只为突破一个个技术难点,最终自研出 YC 编译器和 YC 浏览器内核。
在这过程中,杨晓兵坦言最大的挑战不仅是技术,还有思维的高度。这期间不仅有大量的研发工作,还为了优化,多次重写代码,让他坚持下来的是想为计算机软件科学的发展做贡献的匠心。
目前杨晓兵团队正在开发 64 位 C/C++ 编译器,谈及未来,杨晓兵表示先在国内推广,再走向海外。祝福杨晓兵。
YC编译器传送门:http://www.ycbro.com
果只想看js,直接从JavaScript标题开始。
在C#里面,深度clone有System.ICloneable。创建现有实例相同的值创建类的新实例
实现ICloneable接口使一个类型成为可克隆的(cloneable),这需要提供Clone方法来提供该类型的对象的副本。Clone方法不接受任何参数,返回object类型的对象(不管是何种类型实现该接口)。所以我们获得副本后仍需要进行显式地转换。
实现ICloneable接口的方式取决于我们的类型的数据成员。
拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝。
浅拷贝是容易实现的,就是使用前面提到的MemberwiseClone方法。开发人员往往希望使用的类型能够实现深拷贝,但会发现这样的类型并不 多。这种情况在System.Collections命名空间中尤其常见,这里面的类在其Clone方法中实现的都是浅拷贝。这么做主要出于两个原因:
C#克隆来自《实现可克隆(Cloneable)的类型》,代码实现参考原文。
回顾下基础知识,指针和引用主要有以下区别:
引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
引用初始化后不能被改变,指针可以改变所指的对象。
不存在指向空值的引用,但是存在指向空值的指针——引用不能为空,指针可以为空。
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名——指针是一个实体,而引用仅是个别名。
引用没有const,指针有const,const的指针不可变
引用是类型安全的,而指针不是 (引用比指针多了类型检查)
指针和引用的自增(++)运算意义不一样;
引用没有const,指针有const,const的指针不可变;
cont int p 这个p指针不是一个普通的指针,它是个常量指针,即只能对其初始化,而不能赋值
稍微有点c语言基础的人都能看得出深度拷贝和浅拷贝的差异。总而言之,拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝。
一般的赋值操作是深度拷贝:
//深度拷贝
int a = 5;//在内存中找一块区域,命名为 a,用它来存放整数数据类型 5
int b = a;//在内存中找一块区域,命名为 b,把a拷贝一份,赋给b
char str1 = "HelloWorld";
char str2 = str1;
简单的指针指向,则是浅拷贝:
//浅拷贝
int a = 5;
int *b = &a; //c指向a的地址; &为取地址符,&a就是a这个变量的地址。
int *b; //int *b:定义了一个变量b,它是指针型的,关联数据类型 为int.
b = &a; //int *b=&a表示b指针所指向的数据,等于a的地址. int *b =a 表示b指针指向a,即把a赋值给*b;
// *a=b表示a指针所指向的数据,等于b。*a=&b表示a指针所指向的数据,等于b的地址du。
char* str1 = "HelloWorld";
char* str2 = str1;
将上面的浅拷贝改为深度拷贝后:
//深度拷贝
int a = 8;
int *p = new int;//new int(a)
*p = a;
char* str1 = "HelloWorld";
int len = strlen(str1);
char *str2 = new char[len];
memcpy(str2, str1, len);
以字符串拷贝为例
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。
IplImage *p1 = cvLoadImage( "Lena.jpg" );
IplImage *p2 = p1;
p1 = NULL ;//or cvReleaseImage(p1);释放图像
以下的思考不知对不对——编程小翁
IplImage *是OpenCV里面的东西,它代表一张图。经过第二句后,p1与p2指向相同的对象,在底层就是指向同一块内存块。问题就来了,在第三句执行完毕后,p2还指向原来的对象吗?调试表明,YES。以前一直纠结着,p1都被置为空了(NULL),那原来的对象是不是也跟着被销毁了?其实,错了。
首先,我们应该把指针与其所指的对象分开看。指针重定向或者被置为NULL,对于其原先所指的对象的没有影响的。(但其实,应该会造成内存泄露,因为如果没有其他指针“接管”这部分内存块,就成无名的内存块摆在那边了,也就无法释放掉) 在p1重定向后,p2仍旧指向原来的对象。在此刻,p1与p2其实就是两个无关的事务了,也就是“分家”了。
java深度拷贝一般都用分装好的工具。没有必要重复造轮子。apache和spring都提供了BeanUtils的深度拷贝工具包。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
在项目中我们需要克隆的对象可能包含多层引用类型,这就要涉及到多层克隆问题,多层克隆不仅要将克隆对象实现序列化接口,引用对象也同样的要实现序列化接口:
翻看JDK源码,Object类里面的clone方法定义如下
protected native Object clone() throws CloneNotSupportedException;
是“bitwise(逐位)的复制, 将该对象的内存空间完全复制到新的空间中去”这样实现的。
JavaScript深度克隆,首先想到是JSON.parse(JSON.stringify(target)),但是
JSON.stringify() 将值转换为相应的JSON格式:
let obj1={},obj2={};
obj1.a = obj2;
obj2.b = obj1;
结果就是 。obj1.a.b.a.b.a.b.a.b.a.b.a……………………无限循环引用
obj1 这个对象和 obj2 会无限相互引用,JSON.tostringify 无法将一个无限引用的对象序列化为 JOSN 字符串。
目前几乎所有的直接深复制对象的都有这样那样的问题 都不是很完美,但实际工作中需要用到完美深复制对象的场景也少之又少,包括jquery提供的extend方法也由于考虑到内存占用问题 在多层嵌套的数据里捉襟见肘。所以我们很多时候需要定制 clone 函数
一般手写的克隆函数都是这个样子
function clone(Obj) {
var buf;
if (Obj instanceof Array) {
buf = [];
//创建一个空的数组
var i = Obj.length;
while (i--) {
buf[i] = clone(Obj[i]);
}
return buf;
} else if (Obj instanceof Object) {
buf = {};
//创建一个空对象
for (var k in Obj) {
//为这个对象添加新的属性
buf[k] = clone(Obj[k]);
}
return buf;
} else {
//普通变量直接赋值
return Obj;
}
}
精炼下
// 方法一:
function clone (obj) {
if (typeof obj !== 'object') return false
var o = obj.constructor === Array ? [] : {};
for (var e in this) {
o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
}
return o;
};
增加判断类型
switch (Object.prototype.toString.call(obj).toLowerCase()) {
case '[object Array]':
// clone array
break
case '[object Object]':
// clone object
break
case '[object Date]':
return new Date(obj)
break
case '[object RegExp]':
retrun =new RegExp(obj)
/* let flags = ''
if (obj.global) flags += 'g'
if (obj.ignoreCase) flags += 'i'
if (obj.multiline) flags += 'm' */
// ***
break
case '[object HTMLBodyElement]':
// Dom Element clone,// 遍历Dom树,每个节点 cloneNode(true),个人觉得没有必要。
return obj.cloneNode(true)
case '[object Function]':
// new function inherit && extent obj
// return (new obj()).constructor;
break
case '[object Symbol]':
// Symbol 既然定义为唯一的。那么久没有所谓的复制
throw new Error('')
// JavaScript 各种内置对象 类型太多了。不能入戏太深
default:
return obj
}
其实这个只是造火箭面试的一个考核。实际就是数据复制而已。但是,比较处理循环引用是重点。
循环引用的问题关键就是 obj1.a.b.a.b.a.b.a.b.a.b.a……………………无限循环引用,溢出问题。
WeakMap 其中的键是弱引用的。其键必须是对象,而值可以是任意的(我一般用此来缓存计算结果,参考java中利用WeakHashMap实现缓存)。
const deepClone = (obj, hash=new WeakMap) => {
let data = new obj.constructor();
// 取出循环引用
if(hash.get(obj)) return hash.get(obj)
hash.set(obj, data);
for(var k in obj) {
if(obj.hasOwnProperty(k)){
data[k] = deepClone(obj[k], hash);
}
}
return obj;
}
WeakMap 健弱引用,帮助我们解决问题。
function deepClone(source,uniqueList=[]){
// determineUnique
if(determineIteration){
return uniqueData.target;
}
uniqueList.push({source:source,target:target});
//TODO deep clone
}
function determineIteration(uniqueList,target){
retrun uniqueList.find(item=>item.source===target)
}
deepClone始终有性能问题,如果业务层(大概率)是担心修改引用数据,使用immutable库或者immer库才是解决问题的正路。
目前使用较多还是 lodash deepclone
参考文章:
实现可克隆(Cloneable)的类型 https://www.cnblogs.com/anderslly/archive/2007/04/08/implementingcloneabletype.html
ICloneable 的方法实现 不要轻易使用ICloneable https://blog.csdn.net/iteye_14608/article/details/82404997
关于c中int a=1; int b=a类型问题的思考 https://www.cnblogs.com/wengzilin/archive/2013/03/25/2980520.html
转载本站文章《深度克隆从C#/C/Java漫谈到JavaScript真复制》,请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js6/2018_1219_8450.html
TML (超文本标记语言)是所有浏览器都支持的主要网页文件格式。它经常用于将数据和信息显示为网页。在某些情况下,我们可能需要将 HTML 文档转换为JPG、PNG、TIFF、BMP、GIF等图像格式。在本文中,我们将学习如何将 HTML 转换为 PNG、JPEG、BMP、GIF、或 Python 中的 TIFF 图像。
Aspose.Words for .NET官方最新版免费下载试用,历史版本下载,在线文档和帮助文件下载-慧都网
为了将 HTML 转换为图像格式,我们将使用Aspose.Words for Python API。它是在 Python 应用程序中以编程方式读取和操作各种类型文档的完整解决方案。它使我们能够生成、修改、转换、渲染和打印 Microsoft Word(DOC、DOCX、ODT)、PDF和 Web(HTML、Markdown)文档。
请在控制台中使用以下 pip 命令从PyPI安装 API :
> pip install aspose-words
我们可以按照以下步骤轻松地将 HTML 文档转换为 JPG 图像:
以下代码示例展示了如何在 Python 中将 HTML 转换为 JPG 图像。
# This code example demonstrates how to convert HTML document to JPG images.
import aspose.words as aw
# Load an existing Word document
doc = aw.Document("C:\\Files\\sample.html")
# Specify image save options
# Set save format as JPEG
imageOptions = aw.saving.ImageSaveOptions(aw.SaveFormat.JPEG)
# Set the "JpegQuality" property to "10" to use stronger compression when rendering the document.
# This will reduce the file size of the document, but the image will display more prominent compression artifacts.
imageOptions.jpeg_quality = 10
# Change the horizontal resolution.
# The default value for these properties is 96.0, for a resolution of 96dpi.
# Similarly, change vertical resolution by setting vertical_resolution
imageOptions.horizontal_resolution = 72
# Save the pages as JPG
for page in range(0, doc.page_count):
extractedPage = doc.extract_pages(page, 1)
extractedPage.save(f"C:\\Files\\Images\\Page_{page + 1}.jpg", imageOptions)
我们可以按照以下步骤将 HTML 文档转换为 PNG 图像:
以下代码示例展示了如何在 Python 中将 HTML 转换为 PNG 图像。
# This code example demonstrates how to convert HTML document to PNG images.
import aspose.words as aw
# Load an existing Word document
doc = aw.Document("C:\\Files\\sample.html")
# Specify image save options
# Set save format as PNG
imageOptions = aw.saving.ImageSaveOptions(aw.SaveFormat.PNG)
# Change the image's brightness and contrast.
# Both are on a 0-1 scale and are at 0.5 by default.
imageOptions.image_brightness = 0.3
imageOptions.image_contrast = 0.7
# Save the pages as PNG
for page in range(0, doc.page_count):
extractedPage = doc.extract_pages(page, 1)
extractedPage.save(f"C:\\Files\\Images\\Page_{page + 1}.png", imageOptions)
我们可以按照以下步骤将 HTML 文档转换为 BMP 图像:
以下代码示例展示了如何在 Python 中将 HTML 转换为 BMP 图像。
# This code example demonstrates how to convert HTML document to BMP images.
import aspose.words as aw
# Load an existing Word document
doc = aw.Document("C:\\Files\\sample.html")
# Save the pages as BMP
for page in range(0, doc.page_count):
extractedPage = doc.extract_pages(page, 1)
extractedPage.save(f"C:\\Files\\Images\\Page_{page + 1}.bmp")
同样,我们也可以按照前面提到的步骤将 HTML 文档转换为 GIF 图像。但是,我们只需要在步骤 4 中将图像保存为带有“.gif”扩展名的 GIF。
以下代码示例展示了如何在 Python 中将 HTML 转换为 GIF 图像。
# This code example demonstrates how to convert HTML document to GIF images.
import aspose.words as aw
# Load an existing Word document
doc = aw.Document("C:\\Files\\sample.html")
# Save the pages as GIF
for page in range(0, doc.page_count):
extractedPage = doc.extract_pages(page, 1)
extractedPage.save(f"C:\\Files\\Images\\Page_{page + 1}.gif")
我们还可以按照以下步骤将 HTML 文档转换为 TIFF 图像:
我们还可以按照以下步骤将 HTML 文档转换为 TIFF 图像:
以下代码示例展示了如何在 Python 中将 HTML 文档转换为 TIFF 图像。
# This code example demonstrates how to convert HTML document to TIFF images.
import aspose.words as aw
# Load an existing Word document
doc = aw.Document("C:\\Files\\sample.html")
# Save the document as TIFF
doc.save(f"C:\\Files\\Images\\Output.tiff")
我们可以按照以下步骤从 HTML 字符串动态生成图像文件:
以下代码示例展示了如何在 Python 中将 HTML 字符串转换为 JPG 图像。
# This code example demonstrates how to convert HTML string to an image.
import aspose.words as aw
# Create document object
doc = aw.Document()
# Create a document builder object
builder = aw.DocumentBuilder(doc)
# Insert HTML
builder.insert_html("<ul>\r\n" +
"<li>Item1</li>\r\n" +
"<li>Item2</li>\r\n" +
"</ul>")
# Save the document as JPG
doc.save(f"C:\\Files\\Output.jpg")
在本文中,我们学习了如何:
*请认真填写需求信息,我们会在24小时内与您取得联系。