2014年5月

Java 语言的 Exception

定义:
An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

类继承关系:
Object
--Throwable .............................. 鼻祖, 所有 exception 的祖先
----Error .................................... 程序外部问题, 无法预期, 不可恢复
----Exception ............................. 除 RuntimeException 外, 都可预期, 可恢复
------RuntimeException ............. 程序内部问题, 无法预期, 不可恢复

不可预期的 Error 和 RuntimeException 都是不强制需要 try 或 throws 声明捕获的.
除了 Error 和 RuntimeException, 及其之类之外, 都需要 try 或 throws 声明捕获, 包括 throw 的 Throwable.

Error 和 RuntimeException, 及其子类被认为是 unchecked exception, 其他被认为是 checked exception, checked exception 都必须被声明捕获.

除了 try-catch-finally 块之外, Java SE 7 引入了 try-with-resources 语句, 这种新的语句特别使用于可以 Closeable 的 resources. 凡是实现 java.io.Closeable 接口的对象, 都可以通过这种语句来自动 close resource;

try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
    br.readLine();
}

当然 try 后面的括号中可以 声明多个 resource, 用;分开就行.
同时, 它仍然还可以跟 catch, finally 块.

Java SE 7 还引入了下面的语句:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

一般的try语句, 必须有 catch 或者 finally 跟随, 但是 try-with-resource 语句之后, 就不必了.

Mac 下的 java 安装路径

Mac 上的 Java 安装过好多次 JDK 1.7 但是等到真正在 eclipse 里面切换系统 library 的时候, 却找不到它了, 只在下面的路径看到系统默认的 JDK 1.6

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/

在 stackoverflow 上查到如下命令:

/usr/libexec/java_home -v 1.7

发现原来另外安装的 JDK 1.7 在下面这个位置:

superman:~ root$ /usr/libexec/java_home -v 1.7
/Library/Java/JavaVirtualMachines/jdk1.7.0_60.jdk/Contents/Home

参考: Where is Java 7 Installed on Mac OS X?

用户注册, 登录, 密码的存储

以下的思路中有的是说的注册过程, 有的是登录的过程, 大概类似, 注册存储到 DB, 验证只是对比结果.
对于单向加密算法, 用的比较多的是 md5 或者 SHA1, 这里以 md5 为例.

最简单, 最原始:
客户端 密码: 123456 -------------> 服务器端 存储到数据库 123456

服务器端加密存储
客户端 密码: 123456 -------------> 服务器端 加密后存储到数据库 md5(123456)

客户端加密 再传输
客户端 先加密: md5(123456) -------------> 服务器端 直接存储到数据库 md5(123456)

加盐 加密 再传输
客户端 获得 salt <-----------------------服务器端 给 salt
客户端 先加盐, 再加密: md5(123456 + salt) -------------> 服务器端 直接存储到数据库 md5(123456 + salt)

加盐 加密 再 https 传输 (安全通道)
客户端 <----------- 建立 ssl 安全通道 ---------------> 服务器端
客户端 获得 salt <-----------https---------服务器端 给 salt
客户端 先加盐, 再加密: md5(123456 + slat) -----https-----> 服务器端 直接存储到数据库 md5(123456 + salt)

加盐 加密 再 https 传输 (安全通道) 一用户一盐
客户端 <----------- 建立 ssl 安全通道 ---------------> 服务器端
客户端 获得本用户的 salt <---------https-------服务器端 从盐表获取当前用户的salt (盐表单独 server, 单独数据, 单独表)
客户端 先加盐, 再加密: md5(123456 + slat) ----https-------> 服务器端 直接存储到数据库 md5(123456 + salt)

加盐 加密 再 https 传输 (安全通道) 一用户一盐 双因子认证
客户端 <----------- 建立 ssl 安全通道 ---------------> 服务器端
客户端 获得本用户的 salt <---------https-------服务器端 从盐表获取当前用户的salt (盐表单独 server, 单独数据, 单独表)
客户端 获得第二个因子, 如 短信验证码, hard token, soft token, 邮箱获取验证码 <==另外方式===> 服务器端
客户端 先加盐, 再加密: md5(123456 + slat) 第二个因子 ----https-------> 服务器端 双因子 认证 加密后密码 + 第二因子

Java 正则表达式

正则表达式除了查看是否匹配之外, 还可以获取匹配的内容, 甚至修改, 替换匹配的内容.

Java 的正则表达式只有3个类, 都在 java.util.regex 包下面: Pattern, Matcher, 和 PatternSyntaxException.

  1. Pattern 是一个正则表达式的编译后表示, 只能通过它的 compile 静态方法获得;
  2. Matcher 就是一个执行正则表达式的引擎, 可以不断的查询, 检索输入的字符串. 它只能通过 pattern 的 matcher 方法获得;
  3. PatternSyntaxException 不同于一般的 exception, 它能提供更过与表达式匹配相关的错误信息;

字符类的例子

  • [abc]................a, b, or c (simple class)
  • 1..............Any character except a, b, or c (negation)
  • [a-zA-Z]...........a through z, or A through Z, inclusive (range)
  • [a-d[m-p]]........a through d, or m through p: [a-dm-p] (union)
  • [a-z&&[def]].....d, e, or f (intersection)
  • [a-z&&2]....a through z, except for b and c: [ad-z] (subtraction)
  • [a-z&&3]..a through z, and not m through p: [a-lq-z]

预先定义的字符类:

  • .------Any character (may or may not match line terminators)
  • \d----A digit: [0-9]
  • \D----A non-digit: 4
  • \s----A whitespace character: [ \t\n\x0B\f\r]
  • \S---A non-whitespace character: 5
  • \w---A word character: [a-zA-Z_0-9]
  • \W---A non-word character: 6

数量限定:

  • X?.................once or not at all
  • X*..................zero or more times
  • X+.................one or more times
  • X{n}...............exactly n times
  • X{n,}..............at least n times
  • X{n,m}...........at least n but not more than m times

要注意 0长度匹配 问题

边界匹配:

  • ^...........The beginning of a line
  • $...........The end of a line
  • \b..........A word boundary
  • \B..........A non-word boundary
  • \A..........The beginning of the input
  • \G..........The end of the previous match
  • \Z..........The end of the input but for the final terminator, if any
  • \z...........The end of the input

((A)(B(C))) 前面这个表达式有4 个分组.

Java 语言的正则表达式和 Perl 语言的最相近.

java.lang.String 的这4个方法都支持正则表达式: matches, split, replaceAll, replaceFirst. replace 方法不支持;

BackReference: 在后面要引用前面提到的分组:

在正则表达式中用 \ 后面跟数字, 代表引用表达式中第一个分组, 比如下面例子中的 \1 代表要再次出现前面的第一个一样的分组.
Pattern pattern2 = Pattern.compile("(\\d\\d)(\\w)\\1");
Matcher matcher2 = pattern2.matcher("12a12");
System.out.println(matcher2.find());
matcher2 = pattern2.matcher("12b13");
System.out.println(matcher2.find());

在 String 的 replaceAll replaceFirst 中可以使用 $后面跟数字代表正则表达式前面提到的分组, 下面例子中使用第1,3个分组替换原来
System.out.println("22:12".matches("(\\d\\d)(:)(\\d\\d)"));
System.out.println("22:12".replaceAll("(\\d\\d)(:)(\\d\\d)", "$1$3"));

参考:
Oracle Java2e Trail
Java Regex - Tutorial


  1. abc
  2. bc
  3. m-p
  4. 0-9
  5. \s
  6. \w

java.lang.Object 的 hashCode 方法 注意事项

hashCode: digests the data stored in an instance of the class into a single hash value (a 32-bit signed integer);
Technically, in Java, hashCode() by default is a native method, it is implemented directly in the native code in the JVM.

主要用在 hashTable, hashSet, HashMap等需要hash值的数据结构(hash-based collections), 有了这种离散分布的hash值, 对比操作更快;

基本的要求规范是(The hashCode contract):

1) 一个对象如果没有变化, 那么它返回的hashCode值必须前后一致;
2) 两个对象如果是 equals = true, 那么他们的hashCode 值必须一样;

没有要求说, 不同的JVM实现, 必须返回一致的hashCode值. 即使同一段程序, 在不同的执行(Run)当中, 也可以返回不同的hashCode值;
不同的对象, 返回同样的hash值, 是可以接受的(当然最好(理想)的情况下, 是返回不一样的hashCode值);
返回同样 hashCode值的对象, 不要求必须 equals;

为什么: 两个对象如果是 equals = true, 那么他们的hashCode 值必须一样?

举一个反例: 假如2个对象实例是equals的, 但是hashCode值不一样, 那么把它们两个同时放到一个hashSet, 那么它们就占用2个位置了, 但是因为它们是equals的, 所以我们认为它们应该在这个hashSet中只占一个位置, 是同一个值.

一个重写的例子 (奇数->更好的离散型):
An object’s hashCode method must take the same fields into account as its equals method.

public class Employee {
    int        employeeId;
    String     name;
    Department dept;
 
    // other methods would be in here 
 
    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 17 + employeeId;
        hash = hash * 31 + name.hashCode();
        hash = hash * 13 + (dept == null ? 0 : dept.hashCode());
        return hash;
    }
}

HashCode collisions

Whenever two different objects have the same hash code, we call this a collision. A collision is nothing critical, it just means that there is more than one object in a single bucket, so a HashMap lookup has to look again to find the right object. A lot of collisions will degrade the performance of a system, but they won’t lead to incorrect results.
But if you mistake the hash code for a unique handle to an object, e.g use it as a key in a Map, then you will sometimes get the wrong object. Because even though collisions are rare, they are inevitable. For example, the Strings "Aa" and "BB" produce the same hashCode: 2112. Therefore, Never misuse hashCode as a key

there’s one important detail in the hashCode contract that can be quite surprising: hashCode does not guarantee the same result in different executions.

Moreover, you should be aware that the implementation of a hashCode function may change from one version to another. Therefore your code should not depend on any particular hash code values. For example, your should not use the hash code to persist state. Next time you run the application, the hash codes of the “same” objects may be different.

java.lang.String 的hashCode 实现

public int hashCode() {
    if (hashCode == 0) {
        int hash = 0, end = offset + count;
        for (int i = offset; i < end; i++) {
            hash = (hash << 5) - hash + value[i];
        }
        hashCode = hash;
    }
    return hashCode;
}

参考:

  1. wiki
  2. The 3 things you should know about hashCode()