关于Java Memory Model

在这篇文章中我只打算讲一下Causality Requirements for Executions所提到的9条规则中的三条(5、6、7),这也是规范中不太容易理解的地方。当然,在此之前我会介绍一下与之相关的背景。我并不能保证这里所有的论述都是正确无误的,所以大家在阅读时如若发现问题还请指出。

1、背景

Java内存模型是由基本模型演化过来的,主要经历三个模型。

1.1、Sequential Consistency Memory Model

首先考虑的是Sequential Consistency Memory Model,它规定所有线程中的action需要按照一个total order来执行(每次执行时这个total order可以不一致),对于在单个线程中所执行的action,其顺序必须和代码中规定的顺序(program order)一致,并且每一步执行对所有线程来说都是可见的。这个模型太过强硬,要求和program order一致,如果使用这个模型,那么很多优化就做不了了,比如指令重排序以及读写缓存等。因此,需要找到一个稍微弱一点的模型。

1.2、Happens-Before Memory Model

接下来考虑的是Happens-Before Memory Model,这个模型规定了一系列happen-before关系,下面列出其中的一部分:

  • 如果x和y在同一个线程,并且在program order下x在y之前,那么x happen-before y
  • 一个volatile变量的写action happen-before后续对该变量的读action
  • 如果x happen-before y,并且y happen-before z,那么x happen-before z
  • ……

规范指出如果第一个action happen-before第二个,那么第一个action必须对第二个可见且排在第二个之前。看上面列出的第一条happen-before关系,这是不是意味着同一个线程内的指令无法重排了呢?当然不是,规范里面还提到一点来允许重排:

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

除了happen-before关系外,Happens-Before Memory Model还对一个读action(r)所能看见的写action(w)进行了规定:

  • w happen-before r,并且不存在其他的写操作w’满足w happen-before w’并且w’ happen-before r
  • w和r之间不存在happen-before关系

第一点还比较好理解,但是第二点又是讲了什么?在阐明之前先引入一个称作data race的概念,在对同一个共享变量所执行的action(读或写)中,如果两个action之间没有happen-before关系,且至少有一个为写action,那么称这两个action之间存在data race。下面看看去掉第二点就会导致什么,假设有两个线程,一个线程有一个对变量的写action,另一个线程有一个对相同变量的读action,这两个线程之间没有做任何同步,也就是说这两个action之间不存在happen-before关系。只根据第一点,那么这里所提到的读action将永远看不见写action,这是不符合常理的,而引入第二点则允许data race的发生。

关于这两点,举个例子说明下:

init:a = 0, b = 0

Thread1        | Thread2
---------------|----------
1, r1 = a      | 3, r2 = b
2, b = 1       | 4, a = 2

列出有效的happen-before关系如下,将a happen-before b记为hb(a, b):

hb(a  = 0, r1 = a)
hb(r1 = a, b  = 1)
hb(b  = 0, r2 = b)
hb(r2 = b, a  = 2)

根据第一条规则,r2 = b可以看见0,r1 = a可以看见0;根据第二条规则,r2 = b可以看见1,r1 = a可以看见2。也就是说,对于这段代码,下面四种结果在该内存模型下都是允许的:

(a) r1 = 0; r2 = 0
(b) r1 = 2; r2 = 0
(c) r1 = 0; r2 = 1
(d) r1 = 2; r2 = 1

得到其他结果比较简单,对于结果d,我们看看如何执行才能得到它。编译器在分析Thread1时会发现1和2之间并没有相关,所以可以因为某些原因将b = 1提前,执行过2后在Thread2中执行3,接着执行4,最后由Thread1执行1,这样就得到了结果d。

到目前来看,Happens-Before Memory Model没有什么问题,但事实上它是有缺陷的。我们将上面的例子修改如下:

init:a = 0, b = 0

Thread1        | Thread2
---------------|----------
r1 = a         | r2 = b
if(r1 != 0)    | if(r2 != 0)
   b = 1       |   a = 2

此时唯一正确的结果是r1 = 0; r2 = 0。但是在Happens-Before Memory Model下,r1 = 2; r2 = 1也是合法的,因为这里happen-before关系不变,r2 = b可以看见b = 1,这将导致a = 2的执行,然后得到r1 = a = 2,到此已经执行完了,当我们拿个r1 = 2这个结果时,后验的发现这确实将导致b = 1的执行,因此r2看见b = 1也就没问题了,这种循环推理被称作因果关系(causality)。看着都感觉有些荒唐,也就是说,抽象的模型已经无法适应现实的程序了,需要改进。

1.3、Java Memory Model

现在我们遇到的问题在于什么时候可以将一个写的位置提前,规范给出:

An action can occur earlier in an execution than it appears in program order. However, that write must have been able to occur in the execution without assuming that any additional reads see values via a data race.

注:引用来自[1]

前面Happens-Before Memory Model中我们讲了一个读action可以看到的写action满足两个条件,一个是满足happen-before关系,另一个是不存在happen-before关系。而这里要求的是如果一个写action可以提前,那么它必须在仅仅看见与其满足happen-before关系的write条件下会发生,而无须考虑不存在happen-before关系的写action。还是看下前面的例子,在Happens-Before Memory Model中我们将b = 1提前了,应用上面的限制我们会发现,在Thread1中happen-before关系如下:

hb(a = 0, r1 = a)
hb(b = 0, r1 = a)
hb(r1 = a, b = 1)

在这种happen-before的限制下,b = 1是无法执行的,因此它不能被提前。

至此,我们得到了Java Memory Model,Java Memory Model仅仅在Happens-Before Memory Model添加了对causality的限制。

另一个需要考虑的问题是,给出一段程序和一个输出结果,如何判断这个结果是否合法呢?思路是,找到所有的合法结果,看看给定的结果是否在其中。事实上,找到这些结果本身是不可能的,但不妨碍我们对简单的案例进行分析。整个过程分为两步:

第一步,列出所有待提交的action,首先提交初始条件里的action,然后根据上文提到的happen-before限制来递归的提交剩下的action,通过这种方式我们可以找到哪些action一定会发生,因而可以将其提前。

第二步,重新开始一个新的提交过程,列出所有待提交的action,首先提交上一步中收集的可以提前的action(由于提交的action是经过检验的action,因此后续提交的读action是可以看见它们),然后递归提交剩下的action,全部提交完后便会得到一个结果,在提交过程中选择不同的提交顺序便会得到不同的结果。

下面通过一个例子(来自JMM规范制定小组所发布的一系列Test Cases)来简要讲一下流程:

init:x = y = 0

Thread1        | Thread2
---------------|----------
r3 = x         | r2 = y
if (r3 == 0)   | x = r2
  x = 42       |    
r1 = x         |   
y = r1         |    

output: r1 == r2 == r3 == 42

如果要得到r2 = 42,则需要将y = 42提前,那么能否这样做呢?编译器在对Thread1做intra-thread分析时会发现写入x的值要么为0要么为42,如果r3 != 0,那么必然有x = 42,如果r3 == 0,那么接下来就会执行x = 42,所以可以保证r1 = x看到的值为42,因此将其改为r1 = 42,将y = r1改为y = 42并提前。第二步中的提交过程如下:

列出所有待提交的action如下,括号里代表当前可以看到的值,这里省略了初始状态x = 0以及y = 0:

r3 = x  (0)
x  = 42
r1 = x  (42)
y  = r1 (42)	# to be committed
r2 = y  (0)
x  = r2

经过分析,y = 42可以提前,因此首先提交y = r1 (42):

y  = r1 (42)    # committed
r3 = x  (0)
x  = 42
r1 = x  (42)	
r2 = y  (0)     # to be committed 可以看见y = r1(42)
x  = r2

起初r2 = y只能看见happen-before关系的write,也就是y = 0,提交y = r1(42)后,r2 = y现在也可以看见与它无happen-before关系的y = 42,为了得到结果,接下来就提交r2 = y:

y  = r1 (42)    
r2 = y  (42)    # committed
r3 = x  (0)
x  = 42
r1 = x  (42)	     
x  = r2 (42)    # to be committed

提交x = r2:

y  = r1 (42)    
r2 = y  (42)   
x  = r2         # committed
r3 = x  (0)     # to be committed, 可以看见x = r2(42) 
x  = 42
r1 = x  (42)	# to be committed, 可以看见x = r2(42)

把剩下的两个action提交了:

y  = r1 (42)    
r2 = y  (42)   
x  = r2 (42)    
r3 = x  (42)     # committed
r1 = x  (42)	 # committed

到此说明了给出的结果是合法的。

2、Rules

这里不打算列出其他规则,以及规范在上下文中所给出的各种形式化定义,只列出开头所提到的三条规则,然后对其进行分析。如果说前面的流程看明白了,那么这里应该是水到渠成,只是形式化的东西多少显得有些抽象。
5 \(W_i|C_{i-1}=W|C_{i-1}\)
6 For any read \(r\in A_i-C_{i-1}\) we have \(W_i(r)\stackrel{hb_i}{\longrightarrow} r\)
7 For any read \(r\in C_i-C_{i-1}\) we have \(W_i(r)\in C_{i-1}\) and \(W(r)\in C_{i-1}\)

先看rule 6,这一条讲了对于当前正在提交的每一个读action (r),它所能看到的写action(w)都必须满足w happen-before r。我们知道happen-before关系是偏序的,不会得到违反因果关系的结果,这一条已经满足了causality requirements。但是只有这一条还不够,它限制了data race的发生,这不是我们所希望的,这也是引入rule 7的原因。根据前面的描述,我们不能让这个读action看到所有与它有data race的写action,一个自然的想法是当这个读action被提交后,它可以看见于它先提交的写action(justified action),因为既然写action已经提交了,说明会发生,那么这个读action可以看见它就是正常的事情。这样操作会导致一个读action在提交时和最终所看到的写action是不一样的。

事实上,我们可以将这里的\(Ai\)放宽为\(A\),改为\(r\in A-C_{i-1}\),意思是,所有未提交的以及当前正在提交的读action所能看到的写action都应该满足happen-before关系。只有当一个读action被提交后,它才有机会看到与它有data race的写action。

接着看rule 5,这一条不是很容易理解,为什么要对上一步的集合\(C_{i-1}\)进行限制呢?在rule 6中我们讲了,一个读action在提交时和最终所看到的写action可能是不一样的,那么改变在什么时候发生呢?根据rule 5,我们可以看出改变发生在提交读action后的下一个步。rule 5规定了对于上一步所得集合\(C_{i-1}\)中的每个读action,在这一步中它所看见的写action需要和最终它所看见的写action一样,也就是说,如果要做出改变,必须在这一步改变完并且固定下来。

如果将rule 5的限制改为\(C_i\)又会怎样呢?根据rule 6,正在提交的读action所看见的写action与其满足happen-before关系,改变后的rule 5又会限制这个所看见的写action与最终该读action所看见的写action一致,也就是说所有提交的读action永远都只能看见与其满足happen-before关系的写action,这依然禁止了data race的发生。

事实上,我们过去过来就在讲同一件事,一个读action在什么时候能看到与其有data race关系的写action。

前两条弄明白了再看rule 7就比较简单了。rule7包含两条规则:

  • \(W_i(r)\in C_{i-1}\)规定了rule 6中满足happen-before的写action必须已经提交过了。
  • \(W(r)\in C_{i-1}\)规定了读action最终所看见的写action也必须已经提交过了,这里有两层意思:
    第一、如果读action所看见的写action不变(即一直是当前这一步中所看见的满足happen-before关系的写action),那么这两个规则表达的是同一个意思,即该写action必须已经提交了。
    第二、如果读action所看见写action会变,根据rule 5,当前所提交的写action在下一步可以改为看见满足data race的写action,但是该写action必须在上一步已经提交过了。

总之,rule 7的两条限制了一个读action所能看见的写action必须在它之前提交。

再来看rule 7的第二个规则。这个规则使用了\(W(r)\),表达的是最终的结果,这里并没有限制改变会发生在哪一个步,根据rule 5我们可以得出改变发生在下一步,因此我们可以将 \(W(r)\in C_{i-1}\)改为\(W_{i+1}(r)\in C_{i-1}\)。事实上,进一步分析可知,当前为第i步,rule 5在这里的上下文中所讲的是下一步i+1,因此在当前这一步中可将rule 5写为\({W_{i+1}|C_{i}=W|C_{i}}\),由于\(r\in C_i-C_{i-1} \in C_i\),所以有\(W_{i+1}(r)=W(r)\),进而得出\(W_{i+1}(r)\in C_{i-1}\)。

3、举例

这一部分根据上面几条rules的描述来检验一下完整的提交流程,所使用的例子在前面出现过,为了方便,这里重新贴出来:

init:x = y = 0

Thread1        | Thread2
---------------|----------
r3 = x         | r2 = y
if (r3 == 0)   | x = r2
  x = 42       |    
r1 = x         |   
y = r1         |    

output: r1 == r2 == r3 == 42

提交流程如下表所示,Action即被操作的对象,Value指当前所写或所读的值,Commited In指明Action是在哪一个步中被提交的,Final Value In指明在哪一步中Action所看见的值被固定下来。

Action    | Value | Commited In | Final Value In | index
----------|-------|-------------|----------------|-------
x  = 0    | 0     | C1          | E1             |   1
y  = 0    | 0     | C1          | E1             |   2
y  = 42   | 42    | C1          | E1             |   3
r2 = y    | 0     | C2          | E2             |   4
r2 = y    | 42    | C2          | E3             |   5
x  = r2   | 42    | C3          | E3             |   6
r3 = x    | 0     | C4          | E4             |   7
r3 = x    | 42    | C4          | E5             |   8
r1 = x    | 0     | C5          | E5             |   9
r1 = x    | 42    | C5          | E6             |   10

根据前面的分析y = 42可以被提前。一开始集合为空集,此时提交r2 = y并不能使其看到42。所以第一步需要提交y = 42以及初始化的两个action,第二步提交r2 = y,注意第4行中Value为0,因为根据rule 6此时r2 = y只能看见与其满足happen-before的值,也就是y = 0。根据rule 5和rule 7,在第三步中我们可以让r2 = y看见y = 42,除此之外,第三步还必须再提交其他的action,不然上一节所提到的几条rule会因为i不满足关系而失效,因此在第三步中我们提交x = r2。从第四步开始我们回到Thread1,首先提交r3 = x,同前面r2 = y的分析,此时r3 = x还只能看见x = 0。接着在第五步中让r3 = x看见x = r2写入的值,并提交r1 = x,同样的道理,提交时只能看见满足happen-before关系的写action,虽然x = 42 happen-before r1 = x,但是根据rule 7,一个读所能看见的写必须先行提交,由于x = 42并未提交,所以r1 = x此时仍然只能看见x = 0。在最后的第六步中,我们将r1 = x修改为可以看见x = r2写入的值,也就是42。到此便完成了全部提交。

参考:
[1]:The Java Memory Model by. Jeremy Manson
[2]:Causality Test Cases
[3]:The Java Language Specification
[4]:The Java Memory Model: a Formal Explanation

一千个圆零一个姑娘

我参考Hanson在1960年发表的一篇论文(点击这里查看)做了这个动画。如果你看过我之前的一篇名为《图片迷宫》的文章,便不会对下面这位女同学感到陌生。与上次不一样,这一次我先征得了她的同意方才对照片进行了大刀阔斧的处理,由于手艺不好,勾勒出的人物效果也不尽人意,在此对她表达深深的歉意。

这个动画实际是一套行星圆系统,设想太阳不动,地球绕着太阳转,月球绕着地球转,XX绕着月球转……圆越多,得出的形状也就越复杂。Hanson在论文中论证了,如果给出的圆足够多,理论上是可以得出任何形状的轨迹线。下面简要介绍一下基本的制作步骤。

1,处理图片
首先需要勾勒出线条图,最好能够一笔从头画到尾。但在这个图里,由于人物的眉毛、嘴巴的存在,没办法一笔画出来,我采取的办法是加上辅助线,最后作图时将辅助线抹掉。

然后通过下面这段代码将上面的位图转化为坐标,但这里线段的顺序是乱的,需要很大的耐心去手动调整。目前没找到好的方法。

pic = Import["location of your picture"];
lcp = ListCurvePathPlot@
   Position[ImageData[Thinning@Binarize[ColorNegate@pic]], 1];
lines = Cases[lcp[[1, 1, 3]], _Line] /. {x_Real, y_Real} :> {y, -x};

2,复数及傅里叶变换
下面仅简单介绍一下与本文有关的知识,更具体的内容参看其他资料。
对于平面上的一点,可以用复数\(z=x+iy\)来表示,极坐标形式为\(z=re^{i\theta}\)。对于一个以坐标\(c=x+iy\)为圆心,以\(\rho\)为半径,以\(T\)为周期,以\(\alpha \)为初始角的圆周运动,可用下式表示(其中t代表时间):
\[z=c+\rho e^{i(2\pi t/T+\alpha )}\]
例如当\(t=t_0\)时,点的坐标为:
\[\begin{split}z&=c+\rho e^{i(2\pi t_0/T+\alpha)}
\\&=x+iy+\rho \cdot cos(2\pi t/T+\alpha )+i\rho \cdot sin(2\pi t/T+\alpha )
\\&=(x+\rho \cdot cos(2\pi t/T+\alpha )+i(y+\rho \cdot sin(2\pi t/T+\alpha )))\end{split}\]
若\(f(t)\)表示运动的点的轨迹,那么以此点为圆心做圆周运动的点可以表示为:
\[z=f(t)+\rho_1 e^{i(2\pi t/T_1+\alpha _1)}\]
对于N个圆,除了第一个圆心是固定点(\(c=x+iy\))外,还剩下N个运动的点(除了N-1个运动的圆心,第N个圆上还有一个运动的点),由上可得出第N个圆上点的轨迹:
$$z=c+\sum_{k=1}^{N-1}\rho_k e^{i(2\pi t/T_k+\alpha _k)}$$
事实上,这已经和复数的离散傅里叶变换有了绝大部分的相似,下面就越来越简单了。首先,将之前得到的图片坐标转化为复数坐标,即:\((x,y)\rightarrow x+iy\),然后对这N个复数进行傅里叶变换,得到\(X[k]\):
\[X[n]=\frac{1}{N}\sum_{k=0}^{N-1}x[k]e^{-j2\pi kn/N}\]
然后再用逆变换通过另一种形式得到原来的坐标:
\[x[n]=\sum_{k=0}^{N-1}X[k]e^{j2\pi kn/N}\]
令\(n/N=t\),\(k=1/T_k\)。上面\(\rho_k\)为实数,这里\(X[k]\)为复数,好像不一样。那是因为\(X[k]\)中既包括了半径,也包括了初始角,进一步变换可知:
\[\begin{split}x[t]&=\sum_{k=0}^{N-1}X[k]e^{j2\pi t/T_k}=X[0]+\sum_{k=1}^{N-1}\left | X[k] \right |\frac {X[k]}{\left | X[k] \right |}e^{j2\pi t/T_k}
\\&=X[0]+\sum_{k=1}^{N-1}\left | X[k] \right |e^{j\alpha _k}e^{j2\pi t/T_k}=X[0]+\sum_{k=1}^{N-1}\left | X[k] \right |e^{j(2\pi t/T_k+\alpha _k)}
\\&=X[0]+\sum_{k=1}^{N-1}\left | X[k] \right |e^{j(2\pi t/T_k+\alpha _k)}\end{split}\]
这里的\(|X[k]|\)便是圆的半径,通过求和得到圆心坐标,当改变\(t(0\leqslant t\leqslant 1)\),圆心坐标就会得到更新,从而生成连续动画。

在作图时,可以根据半径大小排序,将半径小的作为“笔尖”,并且可以去掉一些半径太小的圆。

3,一千个圆零一个姑娘

试点班的花花草草

1,关于本文
首先声明,如果你对这篇文章持有异议,或者其中的观点、内容影响到你的个人情感生活,你可以通过各种方式联系我,我会对你做出妥协。事实上,我更希望的是你能够通过这篇文章收获一些什么,除了用它来打发时间,如果还能够从其他角度来更加全面的了解这个班级,我就感到很满足了。

我从2012年10月11号到2013年1月4号将近3个月的时间里一共统计了43次上课时同学们的座位分布情况,收获了将近1000条数据,下面的内容全是基于这些数据进行的一些简单分析,试图从座位选择方面来理解这个集体。由于这3个月间我并不是每堂课都上,而且大都是在下课休息时间做的统计,因此可能会漏掉一部分同学的信息,请大家不要在意。

大家不需要怀疑数据的真实性,我不会为了迎合大家去更改某些信息。在文章最后会附上原始数据。

2,具体内容
2.1,整体上课情况
下面这张图显示的是所有同学的上课情况,这里数据是不完整的,具体原因前面已经讲过。另外,图上显示我上的课最多,那是因为只有我去了才能进行统计,并且为了方便统计,有时我不得不选择靠后一些的位置,我在选择座位时就会带有某种主观倾向,因此有关我的内容大可直接忽略。这里统计的都是全班一起上的课,对于那些公选课是没办法的,所以一共涉及5种类型的教室。在下图每种类型的教室中,数字代表座位数,横线代表过道。例如3-6-3, 表示面向黑板由左到右看,每一排有3个座位,一个过道,接着6个座位,一个过道,然后再3个座位。

不同类型的教室用不同的颜色区分,对于每一位同学都标记出了每种教室的上课次数,最右边是全部上课次数。

Continue reading

简单的网格划分

几天前我在做一个与Cluster Analysis有关的研究时偶然见看到了Voronoi这个神奇的概念,在维基百科上一查又发现它和一个叫Delaunay三角网的东西有着千丝万缕的联系,直觉告诉我这个Delaunay三角网可以用来搞网格划分,这不废话吗,人家本来就是划分网格的啊。搜一下发现这一块已经被研究烂了,由于我之前没接触过这些概念,因此这篇文章只是用Mathematica对Persson and Strang’s 的研究(论文)进行了重新演绎,甚至在易用性和代码效率上都不及前者,不过这是入门的不二之选。

在这篇短文里我没办法去实现所有的玩法,但一通则全通,更多的拓展内容可以看作者原来的论文,写的相当详细,并且介绍了2维向3维以及更高维数的过渡方法。另外,原文里的Matlab代码相当整洁速度也不慢,这得益于它非凡矢量运算特性,而我写的代码估计就会给Mathematica阵营丢脸了,一方面语言本身的因素,另一方面是因为我从没做过效率优化方面的内容,以后有经验了会将改进的版本补上。 下图是将要实现的效果:

Continue reading

Chrome历史记录分析

我上网首选的浏览器是Chrome,它凭借着整洁的界面,迅速的响应以及强大的同步和历史记录功能经常博得我的欢心。详实的上网记录捕捉了我的每一分钟和每一串脚印,可以说是我的半个生活史(本人宅居,死宅,玩命的宅),今天我打算透过这干巴巴的数据来窥视我长时间以来生活方式的秘密。下面先介绍如何导出历史记录中的数据,再对数据进行分析。

1,导出数据

1.1,首先要找到历史记录存放的位置,在win7下是:C:\Documents and Settings\Administrator\AppData\Local\Google\Chrome\User Data\Default,如果找不到此文件夹,先取消文件隐藏的选项,然后一步一步的进,如果哪一步打不开并显示没有权限,可选中此文件夹右键选择管理员取得所有权,掌握这两点便所向披靡了。进入最后一个文件夹后便可看到许多文件,其中有一个便是大名鼎鼎的History,它就是我们所需要的,如何将其打开呢?

1.2,History文件找到了,先拷贝一份出来,下面就可以对备份动手术了。如果你知道Sqlite,便一定听说过Sqlite是轻量级的数据库,全部代码多少多少万行,接着是Android支持它,iPhone支持它,Chrome支持它等等。据此可以大胆的猜测这个History是一个sqlite数据库,事实上它就是的。先下载一个非常小的查看软件Sqlite Database Browser,然后直接将History拖到这个软件里,效果如下:

提示:如果打开之后什么也看不到,可以用前面说的方法对History文件提升管理员权限,然后再次打开。

1.3,如果打开成功,单击Browse Data,选择表urls,便会看到所有的历史记录,里面有网址,浏览的时间,浏览的次数等等。打开的效果如下:

1.4,现在需要将数据导出来,按下面的步骤执行可导出CSV格式的文件。File->Export->Table as CSV file,选择表urls,按照提示便可导出CSV文件。如果需要其他格式的数据,可以用NotePad或者Excel将其打开,然后再进行转换。

2,分析数据

2.1,在所有数据中最重要的便是时间,因此得弄清楚里面时间到底代表何年何月,这里我借助了Wolfram Alpha来辅助分析。首先在数据库中找到最后一条记录的时间(最好是当天),在last_visit_time字段下,我的是12981655707785010。这么长的一串字肯定是微秒级的,得先找到起始值(例如Unix下是1970年1月1日0时0分0秒),首先除以10^6将其单位变为秒,即12981655708秒,再将其化为天,则是150251天。在Wolfram Alpha中询问一下:

现在很清楚了,起始值是1601年1月1日0时0分0秒,虽然不知道为什么要这样。然后按第一条和最后一条的时间差逆向算出第一条记录的时间,我这里算出的是2012年2月18日。下面我又用Alpha算出2012年2月18日0时0分0秒的绝对时间(相对于起始时间),接下来便可以对数据进行任意操作了。

2.2,下面我用Mathematica来分析数据

首先统计每天打开网页的次数,然后画出走势图:

由于这学期我新装了次系统,导致大批量的数据丢失,因此这里只记录了从18/02/2012到17/20120517/1012这些天的数据。图上绿色的点点代表星期六和星期天,从图上可以看出我的上网量还是很客观的,上个星期六竟高达700次,那是因为我在赶任务。基本上周末那两天占据了大部分的拐点,有时上网量惊人,有时又落入低谷,这个也很正常,一般来说星期六没事可以放心大胆的玩,但到了星期天就要开始考虑作业等问题,上网量自然降下来了。总体来说,规律性不是很强,因为我本人上网就没什么固定的习惯。

统计一天内各段时间的平均上网量:

这个图形可以很好的反应我的作息习惯,今年我绝少熬夜,因此1AM到6AM上网量基本上为0,后面画的散点图表现的很明显。从6AM到11AM接近于线性增长,这个现象可用我起床不定时来解释,越晚我就越有可能起来,因此上网的概率也会更大。到了12PM时有一个突变,此时放学,你懂得。今年我们的课下午一般只有一节课,4点钟时就又放学了,所以2PM到3PM处于一个低谷,4PM时又有一个突变。后面的上网量开始稳定,稳定在一个较高的水平,10PM后开始下降,因为停电,断网。接下来用散点图来表示各个时段所有的上网情况,其中粉红色的带状物表示我起床的大致范围:

刚画出这个图时我自己也感到难以相信,只要我睁开眼,竟然时时刻刻都在上网。我很喜欢上网吗?这个问题很值得我去思考。对于这个图我认为还有一点需要提一下,那就是自四月份以来,6PM~9PM之间出现不少时间空白,我不上网能干吗?我可以很高调的说去自习了,谁以后要是质疑我天天不学习,我便拿此图反驳。

说到上网,就得知道自己上的是什么网,这个很大程度上可以决定我的品位。因此我做了一个简单统计,列出前20个我最常访问的网页,统计如下:

我最常用的网站竟然是百度,而浏览器默认的网页是谷歌啊!不管怎样,从表中可以直观的看出我一直处于搜索状态,因为搜索引擎的存在我思考的越来越少。其次最经常访问的是stackexchange的两个子站,这个不难理解,它太火爆了,我已经彻底被它征服了,估计以后依然会投入更多的时间来浏览。其他的也没必要一一探讨,因为它们反应的就是我的真实生活,谁会无缘无故的对自己的生活指手画脚?这就是我的真实品味。

为了能够直观的进行比较,我又画出了前20个的柱状图和前10个的饼状图。

从图中还是可以得出一些关于我的结论,我喜欢搜索,喜欢提问,喜欢盗版,喜欢无所不知,喜欢动态新闻,喜欢看别人装文艺,喜欢。。。