本期技术加油站《代码语言的魅力》给大家带来3个部分的内容:浅谈V8HiddenClasses和InlineCaches;浅析Java逻辑运算与位运算;理解Golang的typefunc(),希望能为大家的技术提升助力!
01
浅谈V8HiddenClasses和InlineCaches
Javascript是动态的、基于属性链的语言,V8是流行的JavaScript运行引擎。我们知道在运行时可以改变对象的属性和类型。为了定位对象的属性和类型,V8引入隐藏类(HiddenClasses)概念,用于优化属性访问速度。看如下代码:
functionPerson(name,age){this.name=name;this.age=age;}constzhangsan=newPerson(Zhangsan,20);constxiaofang=newPerson(Xiaofang,21);xiaofang.gender=female;
初始化Person类时会给对象zhangsan创建隐藏类C0,C0还没有包含任何属性。当初始化属性zhangsan.name时会基于C0创建出新隐藏类C1。由此类推,属性age初始化后,生成C2。当初始化对象xiaofang时,由于属性结构相同其会复用C0、C1、C2。但是给对象xiaofang增加属性gender时,其会基于C2生成新隐藏类C3。
但如果你不小心执行了delete操作,那么情况就比较糟糕,如下:
functionPerson(name,age){this.name=name;this.age=age;}for(leti=0;i;i++){constxiaofang=newPerson(Xiaofang,21);deletexiaofang.name;}
上述代码加上delete语句,执行耗时ms。而去掉delete语句,执行耗时4ms。差距如此大的原因就是在于隐藏类的破坏。我们可能一不小心在某些场景使用了delete,特别是大循环体内时,性能退化就明显了。我们可以使用赋值null替换delete。
V8更进一步做了InlineCaches策略,可进一步提高属性访问速度。当多次访问属性时,如果能保持一个类的方法的参数类型不变,上下文不变,执行过程点不变,那么根据该缓存策略,函数内对象属性查找过程就能避免,提高函数执行速度。我们经常可能喜欢在函数体内定义函数,这些应该尽量避免。
02
浅析Java逻辑运算与位运算
逻辑运算与位运算
算数、赋值、逻辑、关系、自增自减、条件以及位运算等丰富的运算和相应的运算符是Java语言的主要特点之一,也是我们学习一门语言时的基础。本文我们主要简单了解一下Java中的逻辑运算和位运算,以及我们实际开发过程中如何更有效的应用它们的特性。
首先了解什么是逻辑运算和位运算:
逻辑运算:逻辑运算又称布尔运算。逻辑运算符要求操作数的数据类型为逻辑型,其运算结果也是逻辑型(boolean)值。逻辑运算符把各个运算的关系表达式连接起来组成一个复杂的逻辑表达式,以判断程序中的表达式是否成立,判断的结果是true或false。
位运算:位运算是以二进制位为单位进行的运算,其操作数和运算结果都是整型值。这些整数类型包括long,int,short,char和byte。
运算符
Java中是如何执行这些运算的呢?那就是通过运算符。
逻辑运算符:短路与""、短路或"
"、逻辑非"!"、逻辑或"
"、逻辑与""。
(短路与)与(逻辑与)区别:看含义,区别就在于"短路",看说明"如果a为false则结果为false,且不会计算b"(因为不论b为何值,结果都为false)。
(短路或)与
(逻辑或)区别:同上区别同样在"短路"。a
b:如果a为true则结果为true,但会继续计算b(不具备短路能力时,会计算所有逻辑项)。
实践一
基于以上的学习,下面这个问题,在实际开发过程中你会如何决策呢?假设你有一个需求需要同时判断多个条件,这些条件你会有意识的做前后排序还是简单的无序罗列呢?如下你会选择方案1还是方案2呢?
publicclassTest{publicstaticvoidmain(String[]args){//方案1、a()b()System.out.println(a()b());//方案2、b()a()System.out.println(b()a());}/***一个耗时的逻辑判断*/publicstaticbooleana(){System.out.println("doa!");booleana=false;//耗时耗性能的逻辑for(inti=0;i;i++){//耗时操作a=true;}returna;}/***一个简单的逻辑判断*/publicstaticbooleanb(){System.out.println("dob!");returnfalse;}}
想必大家都能做出正确的选择,这其实是一个非常小的知识点,但平时开发过程很多人都会忽视。我们在日常开发过程中要注重细节,越是基础的逻辑越能积蓄更大的能量,养成良好的编程习惯以及性能意识。
短路与()和短路或(
)由于短路机制的存在,能够优化逻辑运算的计算,从而提高效率。在实际开发过程中,应该有意识的使用短路与和短路或的短路能力,来优化我们的逻辑运算。
位运算符:包含位逻辑运算符(位与,位或
,位非~,位异或^),位移运算符(右移,左移,右移补零)
逻辑比较简单,我们不再详述,我们主要看看位运算如何应用到我们的日常实际开发中。也许我们会发现,我们日常开发中好像并不经常用到位运算,但我们在看Android源码的时候确能经常看到相应的使用。比如View中的mPrivateFlags、mPrivateFlags1、mPrivateFlags2、mPrivateFlags3等等,它们一个变量甚至可以保存几十个不同的状态,这也就是通过位运算能够达到的一个优势,可以让“多状态”的管理像单状态管理一样简单高效。
实践二
假设一共有七种颜色,一个物体同时能拥有多种颜色,我们该如何简单高效的维护该物体的颜色属性呢?
1、维护一个颜色属性集合,每拥有一个颜色就向集合中添加,移除则删除?
2、维护7个布尔值指向不同的色值,每拥有一个就设置为true,否则为false?
以上只是简单的举几个例子,比如用布尔值来维护状态,这也是我们最常用的方法,这种对单状态的维护会很简单和直观,但对多状态的维护就会显得不那么适合。
这时我们就可以尝试考虑采用位运算的方式。回顾位运算的按位与和按位或,按位或在对应位不同时则为1,是不是等同于我们添加了一个状态?同理,按位与在对应位都是1时则为1,是不是就可以判断是否具有某个状态?我们则可用不同位的1来表示不同的状态,最终结果具有哪个位置的1就说明具有哪个状态,这样我们就能以一个属性来同时管理多个状态了。
publicclassTest{/**单属性维护多状态*/privatestaticintmColors=0B;privatestaticfinalintRED=0B;privatestaticfinalintORANGE=0B;privatestaticfinalintYELLOW=0B;privatestaticfinalintGREEN=0B;privatestaticfinalintBLUE=0B;privatestaticfinalintPURPLE=0B;privatestaticfinalintPINK=0B0;publicstaticvoidmain(String[]args){//同时具有红绿蓝三色,mColors=0BmColors=mColors
RED
GREEN
BLUE;System.out.println(mColors);//判断是否具有红色,trueSystem.out.println((mColorsRED)!=0);//判断是否具有黄色,falseSystem.out.println((mColorsYELLOW)!=0);//添加黄色,mColors=0BmColors
=YELLOW;//判断是否具有黄色,trueSystem.out.println((mColorsYELLOW)!=0);//移除黄色,mColors=0BmColors=~YELLOW;//判断是否具有黄色,falseSystem.out.println((mColorsYELLOW)!=0);}}
总结
基础语法中存在容易让人忽视的特性,有时却会有很大的妙用,掌握这些特性,能够为我们解决实际问题提供更多的可能性,以及拓宽我们的视野。
03
理解Golang的typefunc()
在Go语言中,可以用type关键字自定义类型,比如常用的:
typeMyintint//定义了MyInt类型,其基础类型是int,与int有相同的底层数据结构,但是是完全不通的两种类型typeBookstruct{//定义了一个Book结构体,包含Title和PageNum两个字段TitlestringPageNumint}
同样地,Go语言也支持自定义函数类型,具有相同的参数和返回值列表(不包括参数名与函数名,需要参数和返回值列表的类型与顺序一致)的函数被视为同一种类型。既然函数被当做一种类型可以被定义,那同样地,再Go语言里,函数可以像其他类型一样,被当做普通的值,再其他函数之间传递、作变量赋值、做类型判断和类型转换,举个例子:
typePowerCanculatorfunc(numint)int//幂次计算funcPowerBase2(numint)int{//以2为底的幂次计算return1num}funcmain(){varpcPowerCanculatorvarresultintpc=PowerBase2result=pc(2)fmt.Println(result)//结果为4}
在以上例子里,PowerBase2作为PowerCanculator类型的一种实现,被当做参数赋值给一个PowerCanculator类型的函数变量并调用。
那么函数被作为参数传递有什么好处或者妙用呢,我们可以考虑这样一个场景:我们希望有个计算器,能够进行任意数字计算,且所有参数和具体的操作都由调用方给出,这样我们的计算器仅做统一调度即可,该如何实现?
typePowerCanculatorfunc(numint)int//幂次计算funcPowerBase2(numint)int{//以2为底的幂次计算return1num}funcPowerBase4(numint)int{//以4为底的幂次计算return1(num+1)}funcCanculator(numint,pcPowerCanculator)(int,error){ifpc==nil{return0,errors.New("paramerror")}returnpc(num),nil}funcmain(){result1,err:=Canculator(2,PowerBase2)iferr!=nil{fmt.Println(err)}fmt.Println(result1)//结果为4result2,err:=Canculator(2,PowerBase4)iferr!=nil{fmt.Println(err)}fmt.Println(result2)//结果为16}
以上是一个函数被当做另外一个函数的参数传入的例子,那么函数既然被当做了一个普通参数,那么它是否也可以被当做另外一个函数的返回值返回呢,答案是可以:
funPowerCanculatorGenerator(baseint)PowerCanculator{switchbase{case2:returnPowerBase2case4:returnPowerBase4default:returnnil}}funcmain(){varbaseintvarpowerBase2PowerCanculatorvarpowerBase4PowerCanculatorpowerBase2=PowerCanculatorGenerator(2)powerBase4=PowerCanculatorGenerator(4)fmt.Println(powerBase2(2))//结果为4fmt.Println(powerBase4(2))//结果为16}
以上是一个简单的例子,其实函数类型最典型的应用场景,是进行