月度归档:2013年05月

值类型与数组

在之前的值类型的一些细节 里说过“值类型在赋值给数组时会被装箱”,这个不严谨,当时上下文是自定义的值类型会被装箱,而系统原生的几个值类型不会,因为它们有默认值。

比如原生的Int,Unit,Double 等,创建好数组后,都以它们的默认值来填充的:

// Int的默认值是0
scala> val arr = new Array[Int](1)
arr: Array[Int] = Array(0)

// Unit的默认值是()
scala> val arr = new Array[Unit](1)
arr: Array[Unit] = Array(())

// Double的默认值是0.0
scala> val arr = new Array[Double](1)
arr: Array[Double] = Array(0.0)

而自定义的值类型,在创建一个数组后,则是用null填充的:

// 定义值类型A
scala> class A(val str:String) extends AnyVal
defined class A

// 创建A类型的数组,默认用null填充
scala> val arr = new Array[A](1)
arr: Array[A] = Array(null)

或许会怀疑上面的例子里,A内部包的是一个String类型,这是个引用类型,null是String的默认值,所以用null填充

// 把A内部换成Int
scala> class A(val i:Int) extends AnyVal
defined class A

// 默认还是用null填充,而不是Int的默认值0
scala> val arr = new Array[A](1)
arr: Array[A] = Array(null)

所以,自定义的值类型数组初始值都是null,与值类型的内部数据无关。
因为自定义的值类型赋给数组时会装箱,它们都是被当作引用来对待的。

但对值类型赋值时是不能用null来赋值的,不管是系统原生的值类型还是自定义的值类型。

// 对刚才的值类型数组的元素赋值
scala> arr(0) = null
<console>:10: error: type mismatch;
 found   : Null(null)
 required: A
          arr(0) = null
                   ^

这点初看有些矛盾,自定义值类型数组,既然都被装箱对待,且初始值为null,却又不允许用null来赋值?

展开看,在数组初始化时它被装箱为引用类型,访问arr(0)时,它是引用类型,值为null;而修改arr(0)时编译器又严格的按照值类型对待,不管它其实已经被装箱为引用类型,不能把null赋给它。 虽然别扭,但对于赋值操作编译器还是保持简单一致的原则。

赋值时把null赋给值类型的变量,编译器无法把Null类型造型为值类型A,这点参考:“Null与Nothing,造型问题” 一文

数组中对值类型元素初始化都做装箱处理,但对var变量默认赋值时却不是这样

scala> class A(val i:Int) extends AnyVal

scala> var a:A = _
a: A = A@0 //默认赋值是 A(0)

scala> a == 0
res0: Boolean = false

scala> a == new A(0)
res1: Boolean = true

默认值是值类型内部数据的默认值, 内部数据如果是引用类型,初始值为A(null),注意在验证时容易碰到scala的一个bug

scala> class A(val s:String) extends AnyVal

// repl下触发scala的一个bug,实际这条语句是没有问题的
scala> var a:A = _  
java.lang.NullPointerException
    at A$.hashCode$extension(<console>:7)
    at A.hashCode(<console>:7)

可以放到文件里编译,运行来看:

class A(val s:String) extends AnyVal

object Main extends App{
  var a:A = _
  println(a == null)
  println(a.s == null)
}

Null与Nothing,造型问题

Null与Nothing

scala类型系统以Any为根,分为AnyRefAnyVal 两个分支体系,在AnyRef分支的最底层,有个Null类型的特殊类型,它被当作是所有AnyRef类型的子类型。
更进一步在两个分支共同的最底层类型是Nothing类型,它被当作所有AnyRefAnyVal类型的子类型。

Null类型只有一个唯一的值:null,可以被赋给所有的AnyRef类型变量

scala> val s:String = null
s: String = null

但null不可以赋值给AnyVal类型:

scala> val i:Int = null
<console>:7: error: type mismatch;
 found   : Null(null)
 required: Int

注意,不要被Unit值类型在赋值时的障眼法给骗了,以为null可以被赋给Unit

scala> val u:Unit = null
u: Unit = ()

实际上把任何表达式结果不是Unit类型的表达式赋给Unit类型变量,都被转为 { expr; () },所以上面的等同于{null; ()} 把最终得到的()赋给了u变量。

Null在类型推导时只能被造型为AnyRef分支下的类型,不能造型为AnyVal分支下的类型,不过我们显式的通过asInstanceOf操作却又是可以把null造型为AnyVal类型的:

scala> null.asInstanceOf[Int]
res0: Int = 0

这是因为asInstanceOf操作导致了装箱拆箱的行为,在值类型的一些细节里有提到过;

val i = null.asInstanceOf[Int]

// 类似于java里的
int i = (int)((Integer)null);

先装箱Int为引用类型,null被造型成了引用类型的Int(java.lang.Integer),然后又做了一次拆箱,把一个为null的Integer类型变量造型成Int值类型,但在拆箱这一点处理上,体现了scala与java的不同

// java里,编译通过,运行失败,空指针异常
int i = (int)((Integer)null);

// scala里,把值为null的Integer拆箱为值类型Int是ok的,得到Int的默认值0
val i = null.asInstanceOf[java.lang.Integer].asInstanceOf[Int]

在java里基本类型(primitive type) 与引用类型是有明确差异的,虽然提供了自动装箱拆箱的便捷,但在类型上两者是不统一的;而scala里修正这一点,Int类型不再区分int/Integer,类型一致,所以值为null的Integer在通过asInstanceOf[Int]时被当作一个未初始化的Int对待,返回了一个默认值Int(注:asInstanceOf不改变原值,返回一个新值)

这一点对自定义的值类型也一样

scala> class A(val i:Int) extends AnyVal
defined class A

scala> null.asInstanceOf[A]
res18: A = A@0

Nothing类型的特殊之处在于它没有对应的值,这个类型存在的意义或许只是为了类型推导:

scala> def foo = throw new RuntimeException
foo: Nothing

因为scala里每个表达式都有结果,也包括throw语句,它的返回类型是Nothing,而实际运行中会导致线程终止,所以只是编译时用于类型推导

scala> def foo(i:Int) = if (i==100) "OK" else throw new RuntimeException
foo: (i: Int)String

上面if分支返回String类型,而else分支是Nothing,返回值类型是String与Nothing在类型树上的最小公共父类型,因为Nothing是String的子类型,所以直接返回了 String类型。

另外Nothing可用在泛型情况下:

scala> val l:List[Int] = List[Nothing]()
l: List[Int] = List()

因为List[+A]定义是协变的,所以List[Nothing]是List[Int]的子类,但List[Null]不是List[Int]的子类

scala> val l:List[Int] = List[Null]()
<console>:7: error: type mismatch;
 found   : List[Null]
 required: List[Int]

lazy变量与双重检测锁(DCL)

scala里的lazy变量,背后的实现是个典型的双重检测锁(DCL)模式。比如:

class A{
    lazy val a=2
}

对a的访问,先通过状态标记变量(做一次位与操作)判断是否已经初始化过。通过scalac -Xprint:jvm来看对a的访问:

lazy private[this] var a: Int = _;

// 通过标记变量的与1做位与操作,判断是否已初始化
lazy def a(): Int = if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
  A.this.a$lzycompute()
else
  A.this.a;

看看状态变量(需要用volatile修饰) 和延迟计算的逻辑:

// 定义一个可变的Byte类型的变量 bitmap$0
@volatile private[this] var bitmap$0: Byte = 0.$asInstanceOf[Byte]();

// 延迟计算的函数
private def a$lzycompute(): Int = {
  {
    // 线程安全
    A.this.synchronized({
      // 按位与操作,
      if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
        {
          A.this.a = 2;
          // 改变 bitmap$0 的值,标记a已经赋值过了
          A.this.bitmap$0 = A.this.bitmap$0.|(1).$asInstanceOf[Byte]();
          ()
        };
      scala.runtime.BoxedUnit.UNIT
    });
    ()
  };
  A.this.a
};

为什么用 Byte 变量而非用Boolean,是当有多个lazy成员时,可以用同一个状态变量,按”位”标记,而不必用多个Boolean变量来标识各个lazy成员的状态。

比如有a,b,c 三个lazy成员,则 bitmap$0 第一位表示a,第二位表示b,第三位表示c,以此类推;判断时只要分别与1,2,4,8… 做位与操作就知道了。

不过Byte类型最大只有8位,一个类中如果超过8个lazy成员,编译器就把 bitmap$0 状态变量改用Int类型了。

String当作集合处理时的方法

String 会被隐式转换为 StringOps

StringOps extends StringLike

StringLike是个支持协变类型的trait,混入了 IndexedSeqOptimized[Char, Repr]Ordered[String]

StringLike[+Repr] extends collection.IndexedSeqOptimized[Char, Repr] with Ordered[String]

scala> val str = "hello"
str: java.lang.String = hello

scala> str.reverse   // GenSeqLike 中的方法
res6: String = olleh

scala> str.map(_.toUpper)  // map方法
res7: String = HELLO

scala> str(0)       //apply(i:Int) 是在GenSeqLike特质里定义的
res5: Char = h

scala> str drop 3  // TraversableLike 里的方法
res8: String = lo

scala> str slice (1, 4)  // GenTraversableLike 里的方法
res9: String = ell

scala> val s: Seq[Char] = str
s: Seq[Char] = WrappedString(h, e, l, l, o)

linux下保留网站flash视频的方法

之前从commandlinefu上看到过这样的脚本,再说一下过程:

1)找到 plugin-co 进程的 pid 和 fd

$ lsof -n | grep deleted
......
plugin-co 2253  hongjiang   22u      REG                8,1   553608    2097199 /tmp/FlashXXRNz5bo (deleted)
plugin-co 2253  hongjiang   24r      REG                8,1  1230967    2097200 /tmp/FlashXXQYyZ2Q (deleted)

其实flash文件也会有规律的命名为 FlashXX 可以 lsof -n | grep /tmp/FlashXX

2) 将 /proc下该进程的句柄拷贝到指定路径

$ cp /proc/2253/fd/22   /tmp/song

3) 播放

$ mplayer /tmp/song

lsof查看进程在使用的已删除的文件

有时会遇到这种情况,当一个进程正在向一个文件写数据时,该文件的目录可能被移动,或该文件已被其他进程删除。
lsof +L1 可以查看那些在访问的已被删除的文件

lsof +L1 shows you all open files that have a link count less than 1, often indicative of a cracker trying to hide something

hongjiang@whj ~ % sudo lsof +L1
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NLINK    NODE NAME
mysqld  22070 mysql    4u   REG  202,1        0     0 1048935 /tmp/ibfwFj0I (deleted)
mysqld  22070 mysql    5u   REG  202,1        0     0 1048937 /tmp/ibpUnKvR (deleted)
mysqld  22070 mysql    6u   REG  202,1        0     0 1048942 /tmp/ibXYbb1Z (deleted)

这个参数不好记,也可以用 lsof -n | grep deleted 来查看。

高版本的sort支持–parallel选项

sort 在对大文件排序时很慢,8.11版本(遗憾我的xubuntu11.10还是8.5) 已经支持了 –parallel 并行运算。

这里也有一些介绍如何用sort做并行排序的方法:
http://linuxwebdev.blogspot.com/2009/02/howto-simple-parallel-sort-in-linux.html
http://bashcurescancer.com/sorting-large-files-faster-with-a-shell-script.html
http://stackoverflow.com/questions/930044/why-unix-sort-command-could-sort-a-very-large-file

commandlinefu上还看到可以设置buffersize,可以设置缓存大一些,这在大内存机器上应该有利,不知道默认情况这个buffer大小是多少。

补充:看了一下刚发布的 ubuntu12.04 beta版,里面的coreutils版本是8.13,下载了源码,sort.c 里面是有parallel选项的。
看里面的注释:

/* 
Heuristic(启发式的) value for the number of lines for which it is worth creating
a subthread, during an internal merge sort(内部是归并排序).  I.e., it is a small number of "average" lines for which sorting via two threads is faster than 
sorting via one on an "average" system.  On a dual-core 2.0 GHz i686
system with 3GB of RAM and 2MB of L2 cache, a file containing 128K
lines of gensort -a output is sorted slightly faster with --parallel=2  than with --parallel=1.  By contrast, using --parallel=1 is about 10%
faster than using --parallel=2 with a 64K-line input.  
*/

注意里面说的,128k行的用 parallel=2parallel=1 略微快一些,但64k行的用 parallel=1parallel=2快10% 我还没有测试过。需要对比一下才好说。

ubuntu下修改屏幕亮度

快捷键 Fn+Home/End 从最亮(15)往下调节时,直接降到12,跳过了中间的14,13,为了能更精确的调节屏幕亮度google了一番;自定义亮度的有效方法是:

$ echo 13 | sudo tee /sys/class/backlight/acpi_video0/brightness

再谈linux下随机数的产生

在创建临时账号初始密码时或许有用

1) 使用md5sum对已有数据加密产生

$ date +”%N” | md5sum

2) 通过/dev/urandom

$ < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32}; echo
$ < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6; echo 
$ tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1
$ dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev
$ strings /dev/urandom | grep -o "[0-9]" | head -n 30 | tr -d '\n'; echo

3) 使用SHA对已有数据加密产生

$ date +%s | sha256sum | base64 | head -c 32 ; echo   

4) 使用 openssl

$ openssl rand -base64 32 

相关阅读:shell里产生随机数的几种方式 ,用date的纳秒做随机数不严禁

另外推荐一篇关于随机数拖慢应用响应的文章,可以更好的了解一下linux的熵池。