乐正

Actions speak louder than words.

搜索引擎优化要点

本文翻译自SEO CHECKLIST。 欢迎各位在Github上或者留言中指正错误。

每次开始新的网站工作时,为了搭建SEO基础,我发现我总是在一遍一遍做同样的事情。 令人啼笑皆非的是,我最初写这个SEO要点只是为了我个人的SEO练习,现在却以“SEO Checklist”为关键字,每天获得数百的访问者。

为了让新网站在搜索引擎中生存下来,这些要点覆盖了三十条你最需要考虑的事情。 这些要点已经为2013年搜索引擎做了更新。我使用了一个简单的脚本去告诉你所有 需要核对的要点。

在你踏上SEO旅程之前,有一件事情我要重申:在页面优化中,最好的思路就是 增量受益(原文是incrementally beneficial,不知道怎么翻译。) 人们经常会范的一个大错误,当他们找到一个要点清单,他们就严格执行其中的 每一条,他们认真地一行一行读每一个要点,还不时发出:“我的天!这条我没做! 我的搜索排名工作全毁了。”我想,这不是你思考SEO的方式。

如果你能做到下面每一条,那真正儿是极好了(雪姨状)!如果你做到了其中的大 部分,你也非常棒。但是,通常情况下,如果你想让你的网站对SEO更加友好,你 大可不必完全按照下面的要点来做。这就是说,做你认为最应该做的事情。

2014 SEO CHECKLIST

勾选你已经完成的条目。请不要关闭本页面,否则你勾选的条目会重新变成未 勾选状态。

这些事项,不一定满足你的网站需求。

海边的卡夫卡和叫乌鸦的少年

约莫二月,于书店之中偶遇《海边的卡夫卡》,作为我喜爱的作者的得意之作,自是不由分说, 当即拿下。说来惭愧,购书至今四月有余,每日只拾缀两节,至多不过倍数,仍是尚有十之一二 并未阅读。虽然如此,心中之十之八九已是按捺不住,一起倾露。

《海边的卡夫卡》这部长篇小说出来的的基本构思浮现出来的时候,我的脑袋里的念头最先是 写一个十五岁的少年为主人公的故事。至于如何发展则全然心中无数。——村上春树

诚然如之所述,全书故事发展天马行空,甚至荒诞离奇,却又因作者文笔老练,使情节生动自然, 时常精妙的哲学思辨,犹如羚羊挂角,妙不可言。然而白玉微瑕,以我来看,不足之处至少有二: 其一,情节铺垫冗长,兼之使用两条叙事线索,半本之前,线索之间也并无关联,难免让人不知 所云,所幸半本之后,峰回路转,精彩纷呈;其二,所述许多思想不该是一位十五岁少年应有的 ,是具有高度哲学境界不惑之士的所思所想。作者似是意识到这个问题,于是安排一位名叫乌鸦 (卡夫卡在捷克语中意为乌鸦)的少年栖息少年体内,作为一种隐喻的存在,每每悄然出现。

故事的开始也是源于叫乌鸦的少年。

“毕竟什么都没开始,不好尽说泄气话。不管怎么说是你自己的人生,基本上只能按照你自己的 想法来做。”

听到来自叫乌鸦的少年的肯定,田村卡夫卡君——幼年被母亲抛弃,又被父亲诅咒——决定离家出走 成为最坚强的少年。

他不知道,他将面对的是波涛汹涌,充满荒谬与悖论的成人世界。

这样的书评数不胜数,而且空洞乏力,用几个词语概括主角的经历,又以一段哲学性的话陈诉总 结。初时我也有此意,旋即一想,如此一来何必费心费神写什么劳什子的书评,摘抄借鉴,省时 省力。还是换个思路来吧。

《海边的卡夫卡》主线故事固然精彩,更吸引我的却是几位配角人物的故事。年老不识字的中田 和少不上进的星野,端庄智慧的佐伯与男心女身的上岛。他们四人组成了两队奇怪的组合,既对 立又统一。同样只有一半影子的中田和佐伯,前者只知道现在,后者却活在回忆中。唯中田马首 是瞻,愚钝的星野;爱慕佐伯,以佐伯为中心,聪慧的上岛。目不识丁,偏爱看书的中田;识字 但从不看书的星野;彬彬有礼,和蔼热情,然而心却不在那里的佐伯;慎思自立,知识渊博,管 理图书馆的中岛。还有着墨了了,隐喻为卡夫卡姐姐的樱花。他们五人,伴随着各自的悖谬与荒 诞的经历——人与猫语,有鱼天降,灵魂出窍,入口之石……——有的来自现实之中,有的来自现实 之外,愿意拯救而后从结果上拯救了十五岁少年的灵魂,让他从内心的冲动、不安、懦弱与绝望 中挣脱。于是,我们领教了世界是何等的凶顽,同时又得知世界可以变得温存和美好。人的精神 也在这种矛盾、离奇和复杂的过程中不断蜕变,伸张和成长。甚至需要一些心灵魔术才能跟随作 者在这座心灵迷宫里的脚步。

总的想来,被拯救的不只是十五岁的少年,每个人都在各自的角度得到了不同意义上的拯救。中 田做回了普通的自己,佐伯得以打破回忆枷锁,星野找到了人生中正确的方向……每个人在帮助他 人的同时,也在接受别人的帮助,最终是两个人的灵魂都得到了升华。这何尝不是作者想要传达 的信息。帮助他人又因此帮助自己打破了桎梏,多么美好的社会啊!

闲话多谈。应该从这个故事中学习什么?以我之论,理所当然是态度。何谓态度?

首先,面对苛刻不公的生活要勇敢机智地抗争。勇敢和智慧好比斯巴达手中的长矛和坚盾,一个 斗争,一个保护。第二,人是具有社会性的动物,很容易地迷失在群体意志中。保持独立思考, 追求真理,是应当学习的。

重新认识Java,发现你的细节

序言

“人和动物的本质区别是会制造和使用劳动工具”,这是初中历史课上的内容。
“工欲善其事,必先利其器”,先贤留下的警世恒言。
“高明的剑客,通晓其剑。是何材质,轻重几许,剑刃几分……”。

自古以来,人们便被教育工具的重要性。而人类,也是从文明伊始便发明种类繁复的工具,用以提高人的劳动 效率或者便利人的生活。计算机,人类有史以来最伟大的工具之一,引爆了整个人类发展的进程,使得最近三 十年以来人类信息增加的总量超过了过去几千年的信息增长量。语言,人类最伟大的工具,在整个人类文明中 扮演了最重要的角色。编程语言相对于计算机而言便等于语言至于人类,其重要性可见一斑。

有了计算机和编程语言,那么自然便衍生了一种以它们为生的职业——程序员,使用编程语言建设计算机世界的 劳动者。我作为一名使用Java编程语言的程序员,试问自己,我是否了解我所使用的工具呢?答案是没有。所 以我写下这篇博文。

操纵字符串

显而易见,字符串操作是计算机程序中最常见的行为。在Java大展拳脚的Web系统中更是如此。

String,其实是不可变的

Strings are constant; their values cannot be changed after they are created. String buffers s upport mutable strings. Because String objects are immutable they can be shared.

举例来说:

1
String str = "abc";

实际上是:

1
2
char[] data = { 'a', 'b', 'c' };
String str  = new String(data);

通过查询JDK文档会发现,String类每一个看起来会修改字符串的方法,实际上都是创建了一个全新的字符串。 通过一则代码说明:

1
2
3
4
5
6
7
8
9
10
11
12
public class Immutable {
    public static void main(String[] args) {
        String s = "immutable";
        String ss = s.toUpperCase();
        System.out.println(s);
        System.out.println(ss);
    }
}
/** Output:
  *  immutable
  *  IMMUTABLE
  */

在调用toUpperCase方法的实际上在内存中为该对象创建了一个副本,对其的操作都是在这个副 本上进行的。

重载的“+”与StringBuilder

在Java中针对String类,重新定义了操作符“+”的行为,它可以连接多个字符串。先看一段代码:

1
2
String mongo = "mongo";
String str = "This is a database named " + mongo + " it's awesome.";

想象一下这段代码是如何工作的:先创建一个mongo对象,在执行连接操作时,先为This is a database named 创建一个对象,再为连接后的字符串创建一个对象,以此类推。那么在内存中不 可避免的会出现巨大的浪费,带来性能问题。实际的情况是怎样的呢?使用

1
javap -c CLASSNAME

命令来反编译这段代码的二进制文件,看看在Java中到底是以什么样的方式执行字符串的连接操作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Compiled from "Concatenation.java"

public class Concatenation {
  public Concatenation();
    Code:
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
        4: return

  public static void main(java.lang.String[]);
    Code:
        0: ldc           #2                  // String mongo
        2: astore_1
        3: new           #3                  // class java/lang/StringBuilder
        6: dup
        7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String This is a database named
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: ldc           #7                  // String  it's awesome.
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: astore_2
      28: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: return
}

这段反编译的代码相当于运行在Java虚拟机上的汇编语句。从中可以看到,编译器为我们自动的引入了 java.lang.StringBuilder对象,并且在每一次的连接操作时会调用它的append方法,最 后使用toString生成结果。无疑这是一个更高效的行为,但是StringBuilder又是 什么呢?

A mutable sequence of characters.

StringBuilder中最核心的两个方法是`append和’toString`。它们接收 任何对象,将对象转成字符串后添加或者插入到String Builder中。 通常

1
sb.append(x);

1
sb.inset(sb.length(), x);

有着一样的效果。

StringBuilder的方法还包括:insert, repleace, substring, delete 甚至是reverse。

有了编译器的自动优化就可以随意的使用字符串了吗?下面代码使用两种方式生成一个字符串:第一种使用多个 String对象,让编译器为我们优化;第二种显式的调用StringBuilder。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String implicit(String[] strs) {
    String result = "";
    for(String s : strs)
        result += s;
    return result;
}

public String explicit(String[] strs) {
    StringBuilder result = new StringBuilder();
    for(String s : strs)
        result.append(s);
    return result.toString();
}

反编译一下,看看实际调用过程:A mutable sequence of characters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Compiled from "WithStringBuilder.java"

public class WithStringBuilder {
  public WithStringBuilder();
    Code:
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
        4: return

  public java.lang.String implicit(java.lang.String[]);
    Code:
        0: ldc           #2                  // String 
        2: astore_2
        3: aload_1
        4: astore_3
        5: aload_3
        6: arraylength
        7: istore        4
        9: iconst_0
      10: istore        5
      12: iload         5
      14: iload         4
      16: if_icmpge     51
      19: aload_3
      20: iload         5
      22: aaload
      23: astore        6
      25: new           #3                  // class java/lang/StringBuilder
      28: dup
      29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      32: aload_2
      33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      36: aload         6
      38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      44: astore_2
      45: iinc          5, 1
      48: goto          12
      51: aload_2
      52: areturn

  public java.lang.String explicit(java.lang.String[]);
    Code:
        0: new           #3                  // class java/lang/StringBuilder
        3: dup
        4: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        7: astore_2
        8: aload_1
        9: astore_3
      10: aload_3
      11: arraylength
      12: istore        4
      14: iconst_0
      15: istore        5
      17: iload         5
      19: iload         4
      21: if_icmpge     43
      24: aload_3
      25: iload         5
      27: aaload
      28: astore        6
      30: aload_2
      31: aload         6
      33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      36: pop
      37: iinc          5, 1
      40: goto          17
      43: aload_2
      44: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      47: areturn
}

在反编译的代码中,第一种方法的循环体从12到48行,而第二种方法的循环体是从17到40行,更加的精简。 并且第二种方法只生成了一个StringBuilder对象。显式地创建StringBuilder对象还支持为其制定大小。如 果你知道需要的字符串大概的长短,那么指定StringBuilder的大小可以避免多次重新分配缓存。

因此在为一个类编写toString方法时,如果操作比较简单,那么可以信赖编译器。如果要在方 中使用循环,最好还是显式地声明StringBuilder

格式化输出,打印出美丽的字符串

在 JAVASE5 中,终于添加了格式化输出的功能。可以使用printf(), System.out.format()和Formatter类来 控制与排版字符串的输出。

  • printf(), 类似于c语言中的printf()函数,与c中不同的是java的printf()支持使用“+”符号连接字符串。
  • System.out.format(), 与printf()是等价的。只需要一个简单地格式化字符串和一些对应的参数。
  • Formatter类,将你需要格式化的信息由一个OutputStream或者OutputWriter打印出去。

Formatter类的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Formatter;

public class Turtle {
    private String name;
    private Formatter f;
    public Turtle(String name, Formatter f) {
        this.name = name;
        this.f = f;
    }

    public void move(int x, int y) {
        f.format("%s The Turtle is at (%d, %d)\n", name, x, y);
    }

    public static void main(String[] args) {
        Turtle t = new Turtle("Li Lei", new Formatter(System.out));
        t.move(3, 4);
        t.move(8, 9);
        t.move(1, 42);
        t.move(3, 2);
    }
}

本例中,将格式化的结果打印到System.out中。

格式化说明符

在插入数据时,如果想要控制空格与对齐,那么就需要更精细的格式修饰符。一下是其抽象的语法:

1
%[arguments_index$][flages][width][.precision] conversion

用一段代码来说明这个语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.*;

public class Receipt {
    private double total;
    private Formatter f = new Formatter(System.out);

    public void printTitle() {
        f.format("%-15s %5s %10s\n", "Item", "Qty", "Price"); //
默认的对齐方式为右对齐加上-之后变为左对齐
        f.format("%-15s %5s %10s\n", "----", "---", "-----");
    }

    public void print(String name, int qty, double price) {
        f.format("%-15.15s %5d %10.2f\n", name, qty, price);
        total += price;
    }

    public void printTotal() {
        f.format("%-15s %5s %10.2f\n", "Tax", "", total * 0.06);
        f.format("%-15s %5s %10s\n", "", "", "-----");
        f.format("%-15s %5s %10.2f\n", "Total", "", total * 1.06);
    }

    public static void main(String[] args) {
        Receipt recerp = new Receipt();
        recerp.printTitle();
        recerp.print("NanJing Tofu", 5, 5.8);
        recerp.print("LaoGanMa DouBanJiang", 2, 7.2);
        recerp.print("Little Cabbage", 1, 2.8);
        recerp.printTotal();
    }
}

输出结果如下:

Item              Qty      Price
----              ---      -----
NanJing Tofu        5       5.80
LaoGanMa DouBan     2       7.20
Little Cabbage      1       2.80
Tax                         0.95
                           -----
Total                      16.75

字符转换说明

转换符一般被分成以下几类:

  • General,可以转化任意对象
  • Character,可以转化用unicode表示的字符。比如char, Character, byte, Byte, short, Short. 也可以 用来转换Character.validCodePoint()返回值为true的int或者Integer对象。
  • Numeric,转换整数或者浮点数。
  • Date/Time,接受long、Long、Date和Calendar类型的参数。
  • Percent,产生一个字面量“%”。
  • Line Separator,产生一条水平分割线。

下面用一张表格展示可接受参数的符号

符号 参数类型 描述
‘b’, ‘B’ General 如果参数是null或者String.valueOf(arg)返回值为false的值,得到的结果则是false。否则,结果为true。
‘h’, ‘H’ General 如果参数是null则返回“null”。否则返回这个参数的Integer.toHexString()的返回值。
‘c’, ‘C’ Character 返回值为Character。
’s’, ’S’ General 如果参数是null则返回”null”;如果参数是Formattable的实现类,则调用参数的arg.formatTo()方法,并且将方法的返回值作为结果;最后返回的结果为arg.toString()
’d’ Integer 返回值为格式化后的十进制数字。
‘o’ Integer 返回值为格式化后的八进制数字。
‘x’, ‘X’ Integer 返回值为格式化后的十六进制数字。
‘e’, ‘E’ floating point 将浮点数用科学计数法表示出来。
‘f’ floating point 返回值为格式化的浮点数。
‘g’, ‘G’ floating point 返回用科学计数法表示的,固定精度的经过四舍五入的浮点数。
‘a’, ‘A’ floating point 返回指数形式有效位的十六进制浮点数。
’t’, ’T’ Data/Time 将日期和时间转化成字符串。
‘%’ percert 返回一个百分比。
‘n’ line separator 返回一条分割线。