Java 核心技术 36 讲(一)

谈谈你对Java平台的理解?“Java是解释执行”,这句话正确吗?

典型回答

Java本身是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“一次书写,到处运行”(Write once,run anywhere),能够非常容易的获得跨平台能力;另外就是垃圾收集(GC),Java通过垃圾收集器回收分配内存,大部分情况下,程序猿不需要自己操心内存的分配和回收。

我们日常会接触到JRE或者JDK。JRE即Java运行环境,包含了JVM和Java类库,以及一些模块等。而JDK可以看做是JRE的一个超集,提供了更多工具,比如编译器、各种诊断工具等。

对于“Java是解释执行”这句话,这个说法不太准确。我们开发的Java源代码,首先通过Javac编译成字节码,然后在运行时,通过JVM内嵌的解释器将字节码转换为最终的机器码。但是常见的JVM,比如我们大多数情况下使用的Oracle JDK提供的Hotspot JVM,都提供了JIT(Just -In -Time)编译器,也就是通常所说的动态编译器,JIT能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。

知识扩展

对于Java平台的理解,可以从很多方面简明扼要的谈一下,例如:Java语言特性,包括泛型、Lambda等语言特性;基础类库,包括集合、IO/NIO、网络、并发、安全等基础类库。

或者谈谈JVM的一些基础概念和机制,比如类加载机制、垃圾收集的基本原理等等。

当然还有JDK包含的一些工具如编译器、运行时环境、安全工具、诊断和监控工具等等。

然后再回到解释执行和编译执行的问题。

众所周知,我们通常把Java分为编译器和运行时,这里说的Java编译和C/C++是有着不同的意义的,Javac的编译,编译Java源码生成字节码,而不是可以直接执行的机器码。Java通过字节码和JVM这种跨平台的抽象,屏蔽了操作系统和硬件的细节,这也是实现“一次编写,到处执行”的基础。

在运行时,JVM会通过类加载器加载字节码,解释或者编译执行。在主流的Java版本中,如JDK8实际是解释和编译混合的一种模式,即所谓的混合模式(-Xmixed)。通常运行在Server模式的JVM,会进行上万次调用以收集足够的信息进行高效的编译,client模式这个门限是1500次。Oracle Hotspot JVM内置了两个不同的JIT compiler,C1对应前面说的client模式,适用于对于启动速度敏感的应用,比如普通的Java桌面应用;C2对应server模式,它的优化是为长时间运行的服务器端应用设计的。默认采用所谓的分层编译。

Java虚拟机启动时,可以指定不同的参数对运行模式进行选择。比如,指定“-Xint”,就是告诉JVM只进行解释执行,不对代码进行编译,这种模式抛弃了JIT可能带来的性能优势。毕竟解释器是逐条读入,逐条解释运行的。与其相对应的,还有一个“-Xcomp”参数,这是告诉JVM关闭解释器,不要进行解释执行,或者叫做最大优化级别。那这种模式是不是高效呢?简单来说,还真未必。“-Xcomp”会导致JVM启动变慢非常多。

除了我们日常最常见的Java使用模式,其实还有一种新的编译方式,即所谓的AOT(Ahead -of - Time),直接将字节码编译成机器代码,这样就避免了JIT预热等各方面的开销,比如Oracle JDK 9就引入了实验性的AOT特性。

另外,JVM作为一个强大的平台,不仅仅只有Java语言可以运行在JVM上,本质上合规的字节码都可以运行,Java语言自身也为此提供了便利,可以看到类似Scala、Groovy等大量JVM语言,活跃在不同的场景。

更多声音

Java的跨平台特性与JVM的存在密不可分。Java语言本身与其他编程语言没有特别大的差异,也不是说Java语言可以跨平台,而是在不同的平台都有可以让Java语言运行的环境而已,所有才有了一些编写,到处运行的说法。

程序从源代码到运行的三个阶段:编码 — 编译 — 运行 —- 调试。Java在编译阶段则体现了跨平台的特点。编译过程大概是这样的:首先将Java源代码转化为.class文件字节码,这是第一次编译。.class文件就是可以到处运行的文件,然后java字节码会被转化为目标机器码,这是由JVM来执行的,即Java的第二次编译。

“到处运行”的关键和前提在于JVM。因为在第二次编译中JVM起着关键性作用,在可以运行Java虚拟机的地方都内含一个JVM操作系统。从而使Java提供了各种不同平台上的虚拟机制,因此实现了“到处运行”的效果。

C/C++编程是面向操作系统的,需要开发者极大的关系不同操作系统之间的差异性;而Java平台通过虚拟机屏蔽了操作系统的底层细节,使得开发者不需过多关注不同操作系统之间的差异性。

通过一个间接的中间层来进行解耦,是计算机领域非常常用的一种艺术手法,虚拟机是这样,操作系统是这样,HTTP也是这样。

写个程序直接执行字节码就是解释执行;写个程序运行时把字节码动态翻译成机器码就是JIT;写个程序把Java源代码直接翻译成机器码就是AOT;造个CPU直接执行字节码,字节码就是机器码。

解释执行和编译执行的区别,类别一下,一个是同声传译,一个是放录音。

参考

以上都是参考自谈谈你对Java平台的理解?

越读越有味道的文章。

Android层面的扩展

上面提到了JIT和AOT,实际上这也是ART虚拟机与Dalvik虚拟机之间的区别。

Dalvik是Google设计用于Android平台的Java虚拟机。Dalvik与JVM之间最大的区别在于虚拟机架构不同。JVM基于栈架构,程序在运行时虚拟机需要频繁的从栈上读写数据,这个过程需要更多的指令分配和内存访问次数,会耗费不少CPU时间,而且对于手机设备这种资源有限的条件下。是相当一大笔开销。Dalvik基于寄存器架构,数据访问通过寄存器直接传递,这样的访问方式比基于栈方式要快很多。

ART即Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个JIT编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户设备上运行,这一机制并不高效,但能让应用更容易的在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装时就预编译字节码到机器码,这一机制就是AOT编译。在移除解释代码这一过程中,应用程序执行将更有效率,启动更快。

ART优点:

  1. 系统性能显著提升
  2. 应用启动更快、运行更快、体验更加流畅、触摸反馈更加及时
  3. 更长的电池续航能力
  4. 支持更低的硬件

ART缺点:

  1. 更大的存储空间占用,可能会增加10%-20%
  2. 更长的应用安装时间

抢红包算法,了解一下?

问题描述

emmmm,就是我们平常的抢红包金额分配问题。按照正常的思路,就是每次抢到的金额为随机区间(0,剩余金额)。但是问题也很明显,这样先抢的人就会有很大的优势,越到后面区间就越小,那么怎么保证每个人抢到的金额几率相等呢?

二倍均值法

即每次抢到的金额为区间:(0,平均值x2),例如:

假设10个人抢100元

第一个人抢到的金额随机区间为:(0,100/10*2) –> (0,20) 均值为10

假设第一个人抢到10元,那么第二个人抢到的随机区间为:(0,90/9*2) –> (0,20) 均值为10

这样也就保证了每个人抢到的金额均值一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static List<Integer> divider(int totalMoney, int totalPeople) {
List<Integer> amountList = new ArrayList<>();
int restMoney = totalMoney;
int restPeople = totalPeople;
Random random = new Random();
for (int i = 0; i < totalPeople - 1; i++) {
int amount = random.nextInt(restMoney / restPeople * 2) + 1;
restMoney -= amount;
restPeople--;
amountList.add(amount);
}
amountList.add(restMoney);
return amountList;
}

实现起来也并不是很难。

想法来源于@小灰

漫画:如何实现抢红包算法?

但是你仔细阅读文章的话,还是能注意到一个地方是有问题的。那就是“除了最后一次,任何一次抢到的金额都要小于人均金额的两倍”。

事实上并不是如此,因为如果第一个人抢到的值不是10,而是5,就会导致第二人抢到的均值大于10。

我们再看看知乎对这个问题的一个高票讨论,

微信红包的随机算法是怎样实现的?

实现也是差不多的,还有就是有人说到抢到的金额应该是服从正态分布的,大多数人抢到的值应该在均值附近。

关于架构方面可以看上面一篇文章。

看来一个小问题仔细思考还是有点东西的。


Android — 自定义View(六)之Path 贝塞尔曲线

前言

忍不住要吐槽一下了,自定义View系列已经写了五篇了,有点烦了。在看这节的标题,贝塞尔曲线,哇,以前曾经了解过一点,感觉好难啊。但是要实现很酷炫的效果,离不开贝塞尔曲线,怎么办?还能怎么办,学吧学吧,向现实低头!

其实,我只是想放一个这个图而已,2333333

好了,言归正传。按照惯例,先回顾上一节所学的内容,Path的基本操作。首先浮现在脑海中的是moveTo()、LineTo()、setLastPoint()这三个方法,也是比较容易理解的,重点是要分别moveTo()和setLastPoint()对之前绘制内容是否有影响的区别。紧接着,我们讲到了close()方法用来封闭路径,之后就是addXxx()一系列方法,值得一提的就是该方法中的一个枚举类型的参数,用来控制是顺时针(CW)还是逆时针(CCW)来绘制图形。

本节目录如下:

  1. 贝塞尔曲线原理简介
  2. 二阶贝塞尔曲线示例

more >>


Android — 自定义Behavior实现Toolbar颜色渐变

前言

上一篇中了解了协调布局CoordinatorLayout,可以协调子View之间的交互,那么它是怎么协调的呢?核心就是Behavior。同样的,上一节中,在处理AppbarLayout与NestedScrollView联动的时候,我们给NestedScrollView设置了一个Behavior,即:

1
app:layout_behavior="@string/appbar_scrolling_view_behavior"

这个Behavior是系统内置的。

这一节就自定义Behavior实现Toolbar颜色渐变,一图胜前言:

more >>


Android — CoordinatorLayout总结

CoordinatorLayout

CoordinatorLayout 即协调布局,它作为Layout的最外一层布局,可以协调子View做出特定响应动作的一个容器,这些响应动作是通过Behavior来指定的,比如用于协调AppbarLayout与ScrollView滑动的Behavior,如下:

1
2
3
4
5
6
7
8
9
10
11
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:text="@string/large_text"/>
</android.support.v4.widget.NestedScrollView>

AppbarLayout

继承至LinearLayout,严重依赖于CoordinatorLayout ,必须用于CoordinatorLayout 的直接子View。它可以让你定制当某个可滑动的View滑动手势发生改变时,内部View响应一些动作。这些动作通过 app:layout_scrollFlags 来指定。

该值有以下几种:

  1. scroll 这个View会随着可滚动View一起滚动,就好像该子View属于ScrollView的一部分一样。
  2. enterAlways 当ScrollView向下滚动时,子View将直接向下滚动而不管ScrollView是否在滚动,要与 scroll 搭配使用,否则不能滑动。
  3. enterAlwaysCollapsed 当ScrollView向下滚动时,子View下滑至折叠的高度,当ScrollView到达滑动范围的结束值的时候,滑动View剩下的部分开始滑动,这个折叠的高度是通过View的minHeight(最小高度)指定的。必须要搭配 scroll|enterAlways一起使用。
  4. exitUntilCollapsed 当ScrollView滑出屏幕时,滑动View先响应滑动事件,滑动至折叠高度,也就是通过 minHeight设置的最小高度后,就固定不动了,再把滑动事件交给ScrollView继续滑动,需要搭配 srcoll 使用。
  5. snap 在滚动结束后,如果view只是部分可见,它将滑动到最近的边界。比如,如果View的底部只有49%可见,它将滚动离开屏幕,而如果底部有51%可见,它将滚动到完全显示。同样需要搭配 scroll 使用。

CollapsingToolbarLayout

是对Toolbar的包装并且实现了折叠app bar效果,使用时,要作为AppbarLayout的直接子View。有以下特性:

  1. Collapsing title(折叠标题)当布局全部可见的时候,title是最大的,当布局开始滑出屏幕,title将变得越来越小,可以通过setTitle来设置要显示的标题。

    但是当Toolbar和CollapsingToolbarLayout 同时设置了title时,不会显示Toolbar的title,如果要显示Toolbar的title,可以 collapsingToolbarLayout.setTitle(“”);

  2. Content scrim(内容纱布)当CollapsingToolbarLayout 滑动到一个确定的阈值时将显示会隐藏内容纱布,可以通过setContentScriom(Drawable)来设置内容纱布的图片。内容纱布也可以是颜色值 app:contentScrim=”@color/colorPrimary”。

  3. Status bar scrim(状态栏纱布)当CollapsingToolbarLayout 滑动到一个确定的阈值时,状态栏显示或隐藏纱布,可以通过 setStatusBarScrim(Drawable)来设置纱布图片。同内容纱布一样,状态栏纱布也可以是一个颜色值,在xml 中用statusBarScrim 属性指定。

  4. Parallax scrolling children (有视差的滚动子View)让CollapsingToolbarLayout 的子View可以有视差的滚动,需要在xml中通过app:layout_collapseMode=”parallax”指定。

  5. Pinned position children(固定子View的位置)子View可以固定在全局空间内,这对于实现了折叠并且允许通过滚动布局来固定Toolbar这种情况非常实用,通过 app:layout_collapseMode=”pin”来指定。

总结

CoordinatorLayout是协调子View的,通过Behavior指定子View动作。AppbarLayout就是一个竖直方向的LinearLayout,只不过它可以定制子View的滑动。CollapsingToolbarLayout 是对Toolbar的包装,它有五个特性。AppbarLayout要作为CoordinatorLayout的直接子View使用,而CollapsingToolbarLayout要作为AppbarLayout的直接子View使用。

Demo

没什么多说的,直接Fork:https://github.com/pinguo-zhouwei/MaterialDesignSamples

我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?