自己平時長期積累的java資料可供大家學(xué)習(xí)

java 中數(shù)據(jù)轉(zhuǎn)換

1、如何將字符串String轉(zhuǎn)化為整數(shù)int

   int i = Integer.parseInt(str);
   int i = Integer.valueOf(my_str).intValue();

    注: 字串轉(zhuǎn)成Double, Float, Long的方法大同小異。

2、如何將字符串String轉(zhuǎn)化為Integer

    Integer integer=Integer.valueOf(i)

3、如何將整數(shù) int 轉(zhuǎn)換成字串 String?


答:有三種方法:

   String s = String.valueOf(i);
   String s = Integer.toString(i);
   String s = "" + i;

注:Double, Float, Long 轉(zhuǎn)成字串的方法大同小異。

4、如何將整數(shù)int轉(zhuǎn)化為Integer

   Integer integer=new Integer(i)

5、如何將Integer轉(zhuǎn)化為字符串String

    Integer integer=String()

6、如何將Integer轉(zhuǎn)化為int

    int num=Integer.intValue()


7、如何將String轉(zhuǎn)化為BigDecimal


    BigDecimal d_id=new BigDecimal(str)

 
response設(shè)置文件類型

  contentType

/**不可識別文件:“application/octet-stream”
             * bmp:"application/x-bmp"
             * doc:"application/msword"
             * exe:"application/x-msdownload"
             * jpg:"image/jpeg"
             * mdb:"application/msaccess"
             * mp3:"audio/mp3"
             * pdf:“application/pdf”
             * ppt:"application/vnd.ms-powerpoint"
             * rm:"application/vnd.rn-realmedia"
             * rmvb:"application/vnd.rn-realmedia-vbr"
             * swf:"application/x-shockwave-flash"
             * xls:"application/vnd.ms-excel"
             * */

 
java中this的用法

                    java 中的this關(guān)鍵字的使用
在java程序中this關(guān)鍵字隨處可見,用處也比較多;或許很多人對this理解不夠清楚。下面我將結(jié)合例子程序總結(jié)一下this的用法。 主要分為四個小部分。


第一、調(diào)用類中的屬性(this.屬性)。


package com.javawxs;   
 
public class PersonInformation {   
    String name;   
    String address;   
    int age;   
 
    PersonInformation(String name, String address, int age) {   
        this.name = name;   
        this.address = address;   
        this.age = age;   
    }   
}   
 
在PersonInformation()方法中,可以直接訪問對象的成員變量,但是在同一范圍內(nèi),不允許定義兩個名字相同的局部變量,那么就需要一種方法將成員變量和跟她同名字的方法的參數(shù)區(qū)分出來,這時候就可以使用this;在上邊的例子程序里this在方法體中指向引用當(dāng)前方法的對象實例,用this.屬性來調(diào)用類中的屬性。最為常見的就是在setter()和getter()中的使用。


第二、調(diào)用類中的方法(this.方法())。


package com.javawxs;   
 
import java.util.Random;   
 
public class PersonInformation {   
 
    int i = new Random().nextInt(200);// 產(chǎn)生一個隨機(jī)數(shù)   
 
    public int getI() {   
        return i;   
    }   
 
    public boolean greaterthan100() {   
        if (this.getI() > 100) {   
            return true;   
        }   
        return false;   
    }   
在greaterthan100()方法中通過this.getI()來調(diào)用當(dāng)前方法的對象實例中的getI()方法,當(dāng)然這個例子中的this也可以省略。


第三、表示當(dāng)前對象:this。


class A {   
    public A() {   
        new B(this);   
    }   
}   
 
class B {   
    A a;   
 
    public B(A a) {   
        this.a = a;   
    }   
}   
這個小程序中,B的構(gòu)造方法的參數(shù)為A的實例;在A中創(chuàng)建B的實例時將this作為A的的實例傳給B的構(gòu)造方法。new B(this)相當(dāng)于new B(new A()) 。


第四、調(diào)用當(dāng)前類中的構(gòu)造方法:this()。


在構(gòu)造方法使用this(參數(shù)表)就會調(diào)用同一個類中的另一個相對應(yīng)的構(gòu)造方法。以下是一個很典型的例子程序。


package com.javawxs;   
 
public class Tester {   
    int var;   
 
    // 構(gòu)造方法一   
    Tester() {   
        System.out.println(“good-bye”);   
    }   
 
    // 構(gòu)造方法二   
    Tester(String s) {   
        this();   
        System.out.println(s);   
    }   
 
    // 構(gòu)造方法三   
    Tester(int var) {   
        this(“hello”);   
    }   
 
    // 構(gòu)造方法四   
    Tester(double var) {   
        this.var = (int) var;   
    }   
 
    public static void main(String[] args) {   
        Tester t = new Tester(5);   
    }   
}   
 


以上程序的執(zhí)行結(jié)果是:


                            good-bye


                             hello


          在構(gòu)造方法中使用this(參數(shù)表)還需要注意三個問題:


1、錯誤示例:

Tester(String s) {   
        System.out.println(s);   
        this();   
           
    }   
調(diào)用另一個構(gòu)造函數(shù)的語句必須在最起始的位置。


2、錯誤示例:


public void mytest(){   
     this(“hello”);   
 }   
不能在構(gòu)造函數(shù)以外的任何函數(shù)內(nèi)調(diào)用構(gòu)造函數(shù)。


3、錯誤示例:

Tester(String s) {   
        this();   
        this(“hello”);   
        System.out.println(s);   
    }   
在一個構(gòu)造函數(shù)內(nèi)只能調(diào)用一個構(gòu)造函數(shù)。

 
toString() parse() value()的區(qū)別

1.parse()是SimpleDateFomat里面的方法,
你說的應(yīng)該是parseInt()或parsefloat()這種方法吧,顧名思義
比如說parseInt()就是把String類型轉(zhuǎn)化為int類型。如 String a= "123";
      int b = Integer.parseInt(a);這樣b就等于123了。
2.ValueOf()方法比如說 Integer.valueOf() 是把String類型轉(zhuǎn)化為
Integer類型(注意:是Integer類型,而不是int類型,
int類型是表示數(shù)字的簡單類型,Integer類型是一個引用的復(fù)雜類型)如:
String a= "123";Integer c =Integer.valueOf(a);
//Integer類型可以用intValue方法轉(zhuǎn)化為int類型int b =c.intValue();
這時候這個b就等于123了3. toString()
可以把一個引用類型轉(zhuǎn)化為String字符串類型。
下面舉個例子與2相反,把Integer轉(zhuǎn)化為String類型:
Integer a = new Integer(123);String b =a.toString()
;這時候b就是 "123" 了

 
多線程基礎(chǔ)

多線程基礎(chǔ)知識去評論?
時間:2012-12-15  分類:Java SE  標(biāo)簽:MLDN, 多線程  
在多進(jìn)程的處理中,在同一個時間段上有多個程序運行,但在同一個時間點上只能有一個程序運行。Java本身屬于多線程的語言,提供了線程的處理機(jī)制。


1、線程實現(xiàn)的兩種方式
在Java中有兩種方式實現(xiàn)多線程,一是繼承Thread類,二是實現(xiàn)Runnable接口。



1、Thread類
Thread類在java.lang包中定義


一個類只要繼承了Thread類,同時覆寫了本類中的run()方法,則就可以實現(xiàn)多線程的操作。


package com.javawxs.threaddemo;   
public class MyThread extends Thread {   
    private String name;   
    public MyThread(String name) {   
        this.name = name;   
    }   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            System.out.println(“Thread運行:” + name + this.name + “,i=” + i);   
        }   
    }   
}   
package org.wxs.threaddemo;   
public class ThreadDemo01 {   
    public static void main(String[] args) {   
        MyThread m1 = new MyThread(“線程1″);   
        MyThread m2 = new MyThread(“線程2″);   
        m1.run();   
        m2.run();   
    }   
}   
       此時,先執(zhí)行m1,再執(zhí)行m2;沒有實現(xiàn)交互的運行。


從JDK的文檔可以發(fā)現(xiàn),調(diào)用start()方法,則會通過JVM找到run()方法。


package com.javawxs.threaddemo;   
public class ThreadDemo01 {   
    public static void main(String[] args) {   
        MyThread m1 = new MyThread(“線程1″);   
        MyThread m2 = new MyThread(“線程2″);   
        m1.start();   
        m2.start();   
    }   
}   
       此時,程序已經(jīng)可以正常地交互運行。


使用start()方法啟動線程的原因:


在JDK的安裝路徑下,src.zip是全部的java源程序,通過此代碼找到Thread類中的start()方法的定義(JDK1.7版本):


public synchronized void start() {   
        if (threadStatus != 0)   
            throw new IllegalThreadStateException();   
        group.add(this);   
        boolean started = false;   
        try {   
            start0();   
            started = true;   
        } finally {   
            try {   
                if (!started) {   
                    group.threadStartFailed(this);   
                }   
            } catch (Throwable ignore) {   
            }   
        }   
    }   
    private native void start0();   
       多線程要進(jìn)行CPU資源搶占,即要等待CPU調(diào)度,這些調(diào)度都是由各個操作系統(tǒng)的底層實現(xiàn),所以在Java程序中沒有辦法實現(xiàn),所以Java的設(shè)計者定義了native關(guān)鍵字,使用此關(guān)鍵字表示可以調(diào)用操作系統(tǒng)的底層函數(shù),即JNI技術(shù)(Java Native Interface)。


2、Runnable接口
在實際開發(fā)中一個多線程操作很少用Thread類完成,而是通過Runnable接口完成。


Runnable接口的定義:


public interface Runnable{   
   public void run();   
}   
所以,一個類只要實現(xiàn)了此接口,并覆寫run()方法。
package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    private String name;   
    public MyThread(String name) {   
        this.name = name;   
    }   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            System.out.println(“Thread運行:” + name + this.name + “,i=” + i);   
        }   
    }   
}   
       在使用Runnable定義的子類中沒有start()方法,而只有Thread類才有start()類中才有。


在thread類中存在以下構(gòu)造方法:


public Thread(Runnable target)  
此構(gòu)造方法接收runnable的子類實例,也就是可以通過Thread類啟動Runnable實現(xiàn)多線程。
package com.javawxs.threaddemo;   
public class RunnableDemo01 {   
    public static void main(String[] args) {   
        MyThread m1 = new MyThread(“線程1″);   
        MyThread m2 = new MyThread(“線程2″);   
        new Thread(m1).start();   
        new Thread(m2).start();   
    }   
}   
3、兩種實現(xiàn)的區(qū)別
在程序的開發(fā)中要實現(xiàn)多線程最好實現(xiàn)Runnable接口,實現(xiàn)Runnable接口相對繼承Thread類有如下好處:


·避免單繼承的局限,一個類可以同時實現(xiàn)多個接口


·適合于資源的共享


以賣票為例,通過Thread類完成。


package com.javawxs.threaddemo;   
public class MyThread extends Thread{   private int ticket = 5;// 一共5張票   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            if (this.ticket > 0) {   
                System.out.println(“賣票:ticket =” + this.ticket–);   
            }   
        }   
    }   
}   
       建立三個線程,同時賣票。


package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread m1 = new MyThread();   
        MyThread m2 = new MyThread();   
        MyThread m3 = new MyThread();   
        m1.start();   
        m2.start();   
        m3.start();   
    }   
}   
       發(fā)現(xiàn)一共賣了15張票,但實際只有5張票,所以每個線程在賣自己的票,不能達(dá)到資源共享的目的。


下面通過實現(xiàn)Runnable接口來完成??梢詫崿F(xiàn)資源共享。


package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    private int ticket = 5;// 一共5張票   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            if (this.ticket > 0) {   
                System.out.println(“賣票:ticket =” + this.ticket–);   
            }   
        }   
    }   
}   
建立三個線程,同時賣票。


package org.wxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread m = new MyThread();   
        new Thread(m).start();   
        new Thread(m).start();   
        new Thread(m).start();   
    }   
}   
三個線程一共賣出5張票,Runnable實現(xiàn)的多線程可以達(dá)到資源共享的目的。


Thread類和Runnable接口的聯(lián)系:


public class Thread extends Object implements Runnable  
Thread是Runnable的子類。
2、線程的操作方法
對于線程所有的操作都在Thread類中定義的。


1、設(shè)置和取得名字
在Thread類中存在以下幾個方法可以設(shè)置和取得名字:


·設(shè)置名字:


|-set:public final void setName(String name)


|-構(gòu)造:


|-public Thread(Runnable target String name)


|-public Thread(String name)


·取得名字:


|-public final String getName()


在線程的操作中提供了一個方法,取得當(dāng)前操作線程:


public static Thread current Thread()


對于線程的名字一般是在啟動前進(jìn)行設(shè)置。


package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            System.out.println(Thread.currentThread().getName() + “正在運行”);   
        }   
    }   
}   
       測試是否取得了名字。


package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread m = new MyThread();   
        Thread t1 = new Thread(m, “線程1″);   
        Thread t2 = new Thread(m, “線程2″);   
        Thread t3 = new Thread(m, “線程3″);   
        t1.start();   
        t2.start();   
        t3.start();   
    }   
}   
       看以下代碼:

package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread m = new MyThread();   
        Thread t1 = new Thread(m, “線程1″);   
        t1.start();   
        m.run();   
    }   
}   
 
在程序運行時主方法就是一個主線程。


2、線程的休眠


在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)


·public static void sleep(long millis, int nanos) throws InterruptedException

package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    public void run() {   
        for (int i = 0; i < 100; i++) {   
            try {   
                Thread.sleep(500);   
            } catch (InterruptedException e) {   
                e.printStackTrace();   
            }   
            System.out.println(Thread.currentThread().getName() + “正在運行”);   
        }   
    }   
}   
       主程序:

package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread m = new MyThread();   
        Thread t1 = new Thread(m, “線程1″);   
        Thread t2 = new Thread(m, “線程2″);   
        t1.start();   
        t2.start();   
    }   
}   
3、線程的中斷
在sleep()方法存在InterruptException,造成此異常的方法就是中斷:


·public void interrupt()

package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    public void run() {   
        System.out.println(“1、進(jìn)入run()方法體”);   
        try {   
            System.out.println(“2、線程休眠20秒鐘”);   
            Thread.sleep(20000); // 每個休眠20秒   
            System.out.println(“3、線程正常休眠20秒鐘”);   
        } catch (InterruptedException e) {   
            System.out.println(“4、線程休眠被中斷”);   
            return;// 返回方法調(diào)用處   
        }   
        System.out.println(“5、正常結(jié)束run()方法體”);   
    }   
}   
 

package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread mt = new MyThread();   
        Thread thread = new Thread(mt, “線程A”);   
        thread.start();// 啟動線程   
        try {   
            Thread.sleep(2000); // 保證程序至少執(zhí)行2秒   
        } catch (InterruptedException e) {   
            e.printStackTrace();   
        }   
        thread.interrupt(); // 中斷   
    }   
}   
4、線程的優(yōu)先級
使用以下方法設(shè)置優(yōu)先級:


·public final void setPriority(int newPriority)


線程中有3種優(yōu)先級:


·最高:public static final int MAX_PRIORITY


·默認(rèn):public static final int NORM_PRIORITY


·最低:public static final int MAX_PRIORITY


package com.javawxs.threaddemo;   
public class MyThread implements Runnable {   
    public void run() {   
        for (int i = 0; i < 20; i++) {   
            System.out.println(Thread.currentThread().getName() + “正在運行”);   
        }   
    }   
}   
&nbsp:


package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        MyThread mt = new MyThread();   
        Thread thread1 = new Thread(mt, “線程A”);   
        Thread thread2 = new Thread(mt, “線程B”);   
        Thread thread3 = new Thread(mt, “線程C”);   
        thread3.setPriority(Thread.MAX_PRIORITY);   
        thread2.setPriority(Thread.NORM_PRIORITY);   
        thread1.setPriority(Thread.MIN_PRIORITY);   
        thread1.start();   
        thread2.start();   
        thread3.start();   
    }   
}
       取得優(yōu)先級:


package com.javawxs.threaddemo;   
public class Test {   
    public static void main(String[] args) {   
        System.out.println(Thread.currentThread().getPriority());   
        System.out.println(“MIN_PRIORITY = ” + Thread.MIN_PRIORITY);   
        System.out.println(“MAX_PRIORITY = ” + Thread.MAX_PRIORITY);   
        System.out.println(“NOM_PRIORITY = ” + Thread.NORM_PRIORITY);   
    }   
}   
從運行結(jié)果發(fā)現(xiàn):


5


MIN_PRIORITY = 1


MAX_PRIORITY = 10


NOM_PRIORITY = 5

 
枚舉基礎(chǔ)

枚舉基礎(chǔ)去評論?
時間:2012-12-28  分類:Java SE  標(biāo)簽:MLDN  
1、枚舉的作用
在C語言中存在一個枚舉類型,通過此類型可以限制一個內(nèi)容的取值范圍,但在JDK1.5之前,java并不存在枚舉類型,所以很多之前已經(jīng)習(xí)慣使用枚舉的開發(fā)人員感到不適應(yīng),為了解決該問題,在JDK1.5之后加入了枚舉類型


2、 enum關(guān)鍵字
在JDK1.5之后,可以直接使用enum關(guān)鍵字定義一個枚舉的類型。


public enum Color {   
    RED, GREEN, BLUE;   
}   
此時已經(jīng)完成了一個枚舉類型的定義,那么在使用的時候直接通過
public class TestColor {   
    public static void main(String[] args) {   
        Color c = Color.BLUE;   
        System.out.println(c);   
    }   
}   
而且枚舉在JDK1.5之后也可以直接在switch語句中使用,switch判斷的時候只能判斷字符或數(shù)字,但是在JDK1.5之后卻可以判斷枚舉類型


public class TestColor {   
    public static void main(String[] args) {   
        switch (Color.RED) {   
        case RED: {   
            System.out.println(“紅色”);   
            break;   
        }   
        case GREEN: {   
            System.out.println(“綠色”);   
            break;   
        }   
        case BLUE: {   
            System.out.println(“藍(lán)色”);   
            break;   
        }   
        }   
    }   
}   
還可以通過方法得到全部的枚舉的取值范圍


public class TestColor {   
    public static void main(String[] args) {   
        for (Color c : Color.values()) {   
            System.out.print(c + “、”);   
        }   
    }   
}   
3、Enum和enum關(guān)鍵字的區(qū)別
使用enum關(guān)鍵字可以定義一個枚舉類型,那么實際就相當(dāng)于定義了一個類,此類繼承了Enum類而已。


在Enum類的構(gòu)造方法中是受保護(hù)的,實際上對于每一個枚舉對象一旦聲明之后,就表示自動調(diào)用此構(gòu)造方法;所有的編號采用自動編號的方式進(jìn)行。


public class TestColor {   
    public static void main(String[] args) {   
        for (Color c : Color.values()) {   
            System.out.println(c.name() + “->” + c.ordinal());   
        }   
    }   
}   
還可以通過此類找到枚舉中的一個指定的內(nèi)容使用valueOf()方法。


public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)  
傳入的時候直接傳入Color.class即可。


public class TestColor {   
    public static void main(String[] args) {   
        Color c = Color.valueOf(Color.class, “RED”);   
        System.out.println(c);   
    }   
}   
通過以上代碼可以發(fā)現(xiàn),直接通過枚舉取得指定內(nèi)容更加簡單。


4、 類集對枚舉的支持
在JDK1.5之后,增加了兩個類集的操作類:EnumSet、EnumMAp。


1、EnumMap
EnumMap是Map接口的子類,操作形式與Map一致。


import java.util.EnumMap;   
import java.util.Map;   
public class TestColor {   
    public static void main(String[] args) {   
        EnumMap<Color, String> eMap = new EnumMap<Color,String>(Color.class);   
        eMap.put(Color.RED, “紅”);   
        eMap.put(Color.BLUE, “藍(lán)”);   
        eMap.put(Color.GREEN, “綠”);   
        for (Map.Entry<Color, String> me : eMap.entrySet()) {   
            System.out.println(me.getKey() + “ ” + me.getValue());   
        }   
    }   
}   
2、EnumSet
EnumSet本身是Set接口的子類,但是在此類中并沒有任何的構(gòu)造方法定義,表示構(gòu)造方法私有化了,此類的方法都是靜態(tài)的操作。

import java.util.EnumSet;   
import java.util.Iterator;   
public class EnumSetDemo {   
    public static void main(String[] args) {   
        EnumSet<Color> eSet = EnumSet.allOf(Color.class);   
        Iterator<Color> it = eSet.iterator();   
        while (it.hasNext()) {   
            System.out.println(it.next());   
        }   
    }   
}   
以上是將全部的內(nèi)容設(shè)置到集合中,也可以設(shè)置一個指定類型的空集合。


import java.util.EnumSet;   
import java.util.Iterator;   
public class EnumSetDemo {   
    public static void main(String[] args) {   
        EnumSet<Color> eSet = EnumSet.noneOf(Color.class);   
        Iterator<Color> it = eSet.iterator();   
        while (it.hasNext()) {   
            System.out.println(it.next());   
        }   
    }   
5、枚舉深入
 

1定義構(gòu)造方法
枚舉的使用非常靈活,可以在枚舉中直接定義構(gòu)造方法,但是一旦構(gòu)造方法定義之后,則所有的枚舉對象都必須明確的調(diào)用此構(gòu)造方法。


public enum Color {   
    RED(“紅”), GREEN(“綠”), BLUE(“藍(lán)”);   
    private String name;   
    private Color(String name) {   
        this.name = name;   
    }   
    public String getName() {   
        return name;   
    }   
    public void setName(String name) {   
        this.name = name;   
    }   
}   
枚舉中的構(gòu)造不能聲明為public,以為外部是不能調(diào)用枚舉的構(gòu)造方法的。


此時每個枚舉對象都有了自己的name屬性


public class TestColor {   
    public static void main(String[] args) {   
        for (Color c : Color.values()) {   
            System.out.println(c.ordinal() + “–>” + c.name() + “:”  
                    + c.getName());   
        }   
    }   
}   
2、讓枚舉實現(xiàn)一個接口
一個枚舉實現(xiàn)一個接口之后,各個枚舉對象必須分別實現(xiàn)接口中定義的抽象方法。


public interface Info {   
    public String getName();   
}   
下面使用枚舉實現(xiàn)接口


public enum Color implements Info {   
    RED {   
        public String getName() {   
            return “紅色”;   
        }   
    },   
    GREEN {   
        public String getName() {   
            return “綠色”;   
        }   
    },   
    BLUE {   
        public String getName() {   
            return “藍(lán)色”;   
        }   
    }   
}   
通過代碼進(jìn)行測試


public class TestColor {   
    public static void main(String[] args) {   
        for (Color c : Color.values()) {   
            System.out.println(c.ordinal() + “–>” + c.name() + “:”  
                    + c.getName());   
        }   
    }   
}   
3、在枚舉中定義抽象方法
在一個枚舉中可以定義多個抽象方法,枚舉中的每個對象都必須單獨實現(xiàn)測方法。


public enum Color  {   
    RED {   
        public String getColor() {   
            return “紅色”;   
        }   
    },   
    GREEN {   
        public String getColor() {   
            return “綠色”;   
        }   
    },   
    BLUE {   
        public String getColor() {   
            return “藍(lán)色”;   
        }   
    };   
    public abstract String getColor();   
}  

 
排序

插入排序
插入排序是最簡單的排序算法;它由N-1趟排序組成。對于p-1到N-1趟,插入排序保證從位置0到位置p的元素已經(jīng)完成排序。在第p趟,將位置p的元素向左移動,直到它在前p+1個元素中的正確位置被找到的地方。


位置p上的元素儲存于tmp,而(在位置p之前)所有更大的元素都被向右移動一個位置,然后tmp被置于正確的位置上;以下為代碼:


package com.javawxs;   
 
public class InsertionSort {   
    public static void main(String[] args) {   
        Integer[] arr = { 71, 45, 32, 76, 142, 43, 98, 54, 14, 333, 12, 31, 3 };   
        insertionSort(arr);   
        for (int i = 0; i < arr.length; i++)   
            System.out.print(arr[i] + “ ”);   
 
    }   
 
    public static <T extends Comparable<? super T>> void insertionSort(T[] a) {   
        int j;   
        for (int p = 1; p < a.length; p++) {   
            T tmp = a[p];   
            for (j = p; j > 0 && tmp.compareTo(a[j - 1]) < 0; j–)   
                a[j] = a[j - 1];   
            a[j] = tmp;   
        }   
    }   
}   
 

希爾排序
希爾排序(Shellsort)的名字源于的它的發(fā)明者Donald Shell,它通過比較相距一定間隔的元素來實現(xiàn);各趟比較所用的距離隨著算法的進(jìn)行而減小,直到只比較相鄰元素的最后一趟為止;所以希爾排序也叫縮減增量排序。


希爾排序使用一個序列h1,h2,h3……,ht,叫做增量序列,只要h1=1,那么任何增量序列都是可行的。在使用hk的一趟排序之后,對于每個i,都有a[i]<=a[i+hk];所有間隔hk的元素都被排序。


希爾排序的一個重要性質(zhì):一個hk排序的文件保持它的hk排序性;實際上,假如不這樣的話,那么希爾排序也就沒什么意義了,因為前面各趟排序會被后面的各趟排序打亂。


hk排序的一般做法是,對于hk,hk+1,……,N-1中的每一個位置i,把其上的元素放到i,i-hk,i-2hk,……中的正確位置上。雖然不影響最終結(jié)果,但是通過觀察可以發(fā)現(xiàn)一趟hk的排序作用就是對hk個獨立的子數(shù)組執(zhí)行一次插入排序。


以下為代碼:


package com.javawxs;   
 
public class ShellSort {   
    public static void main(String[] args) {   
        Integer[] arr = { 71, 45, 32, 76, 142, 43, 98, 54, 14, 333, 12, 31, 3 };   
        shellSort(arr);   
        for (int i = 0; i < arr.length; i++)   
            System.out.print(arr[i] + “ ”);   
 
    }   
 
    public static <T extends Comparable<? super T>> void shellSort(T[] a) {   
        int j;   
        for (int gap = a.length / 2; gap > 0; gap /= 2)   
            for (int i = gap; i < a.length; i++) {   
                T tmp = a[i];   
                for (j = i; j >= gap && tmp.compareTo(a[j - gap]) < 0; j -= gap)   
                    a[j] = a[j - gap];   
                a[j] = tmp;   
            }   
    }   
}   
堆排序
首先建立N個元素的二叉堆,花費時間(O(N)),然后執(zhí)行N次的deleteMin操作。按照順序,最小的元素先離開堆。通過將這些元素記錄到第二個數(shù)組然后再將數(shù)組拷貝回來,得到N個元素的排序。由于每個deleteMin花費時間O(log N),因此運行總時間為O(N log N)。


該算法的主要問題在于它使用了一個附加數(shù)組,增大大儲存需求;可以這樣來避免使用第二個數(shù)組:在每次deleteMin之后,堆縮小1;所以位于堆中最后的單元正好可以存放剛才刪除的元素;最后一次deleteMin后,正好得到數(shù)組的遞減排序。要想得到遞增排序通過改變有序的特性使父親的關(guān)鍵字的值大于兒子的關(guān)鍵字的值;就得到了(max)堆。


第一步以線性時間建立一個堆。然后通過每次將堆中的最后元素與第一個元素交換,執(zhí)行N-1次deleteMin操作,每次將堆的大小縮減1并進(jìn)行下濾。結(jié)束時,數(shù)組排序完成。


以下為代碼:

package com.javawxs;   
 
public class HeapSort {   
    private static int leftChild(int i) {   
        return 2 * i + 1;   
    }   
 
    private static <T extends Comparable<? super T>> void perDown(T[] a, int i,   
            int n) {   
        int child;   
        T tmp;   
        for (tmp = a[i]; leftChild(i) < n; i = child) {   
            child = leftChild(i);   
            if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0)   
                child++;   
            if (tmp.compareTo(a[child]) < 0)   
                a[i] = a[child];   
            else  
                break;   
        }   
 
        a[i] = tmp;   
    }   
 
    private static <T extends Comparable<? super T>> void swapReferences(T[] a,   
            int i, int n) {   
        T temp = a[i];   
        a[i] = a[n];   
        a[n] = temp;   
    }   
 
    public static <T extends Comparable<? super T>> void heapSort(T[] a) {   
        for (int i = a.length / 2; i >= 0; i–)   
            perDown(a, i, a.length);   
        for (int i = a.length - 1; i > 0; i–) {   
            swapReferences(a, 0, i);   
            perDown(a, 0, i);   
        }   
 
    }   
 
}
前兩篇談了插入排序、希爾排序、堆排序和歸并排序,都還是比較容易掌握的,這篇將主要說說快速排序算法;因為快速排序比較復(fù)雜涉及的算法較多,精煉而且高度優(yōu)化的內(nèi)部循環(huán);使得快速排序運行非常迅速,但也加大了理解難度。它的平均運行時間是O(N log N),最壞情形是O(N^2);不過這種情況很少出現(xiàn)。通過快速排序和堆排序的結(jié)合使用,我們可以基本上所有的輸入都達(dá)到快速排序的快速運行時間。和歸并排序一樣,快速排序也是一種分治的遞歸算法。將數(shù)組S排序的基本算法由以下步驟組成:


1、  如果S中元素個數(shù)是0或1,則返回。


2、  取S中任一元素v,作為樞紐元。


3、  將S-{v}(S中其余元素)分為兩個不相交集合:S1={x∈S-{v}|x<=v}和S2={x∈S-{v}|x>=v}.


4、  返回{quicksort(S1)后跟v,繼而返回quicksort(S2)}。


由于對那些等于樞紐元的元素的處理上,第三步分割的描述不是唯一的,因此這就成為了一種設(shè)計決策。我們最好能有效的解決這個問題;我們希望把等于樞紐元的大約一半的關(guān)鍵字分到S1中,剩余的分到S2中,就像二叉查找樹那樣保持平衡。


關(guān)于選擇樞紐元的選取問題:


·選擇第一個元素作為樞紐元,如果輸入的隨機(jī)的,那么不會存在問題,但是如果輸入是預(yù)排序的或者是反序,就會出現(xiàn)嚴(yán)重的問題,所有元素不是被分到S1就是都被分到S2中,更嚴(yán)重的是這種情況會出現(xiàn)在所有的遞歸調(diào)用中。例如,在一個預(yù)排序的輸入中,把第一個元素作為樞紐元,那么快速排序花費的時間是二次的,可實際毫無意義。所以選取第一個 元素作為樞紐元是非常不可取的。


·隨機(jī)選擇樞紐元,這種方法比較安全,但是生成隨機(jī)數(shù)會開銷很大,會影響算法的運行時間。


·選擇數(shù)組中的中值(第[N/2]個最大數(shù));不過,這很難算出并且會明顯減慢排序速度。一般情況下是使用左端、右端和中心位置上的三個元素的中值作為樞紐元。使用三數(shù)中值分割法消除了預(yù)排序輸入的最壞情形,并減少了比較次數(shù)。


分割策略:例如輸入為9,2,5,10,7,4,6,3,8,1的數(shù)據(jù),第一步通過將樞紐元與最后的元素交換使其離開被分割的數(shù)據(jù)段,i從第一個元素開始,j從倒數(shù)第二個元素開始。下圖為當(dāng)前狀態(tài):


在分割階段就是要把所有小于樞紐元的元素放到數(shù)組左邊,把大于樞紐元的元素放到數(shù)組右邊。


當(dāng)i在j左邊的時候,我們將i右移過所有小于樞紐元的元素,并將j左移,移過大于樞紐元的元素。當(dāng)i和j停止時,i指向一個大元素而j指向一個小元素。如果i在j的左邊,那么兩個元素互換,把一個大元素推到右邊而把一個小元素推到左邊在上面的例子中,i不移動而j劃過一個位置,如下圖:


然后交換i和j指向的元素,重復(fù)該過程直到i和j彼此交錯為止。


現(xiàn)在i和j已經(jīng)交錯,不用再交換,最后將樞紐元與i指向的元素交換。


在最后一步當(dāng)樞紐元與i所指向的元素交換時,在p<i的每一個元素必然都是小元素,在p>i上的元素必然都是大元素。


還有一個重要細(xì)節(jié)必須考慮-如何處理那些等于樞紐元的元素。當(dāng)i或j遇到一個等于樞紐元的元素時是否應(yīng)該停止移動。i和j應(yīng)該做同時移動,否則分割將會向一個方向偏;例如可能出現(xiàn)所有的元素都被劃分到S2中。


現(xiàn)在假設(shè)數(shù)組中所有元素都相等,如果i和j都停止,那么相等元素間會有多次無意義的交換,通過歸并排序分析,這時運行時間為O(N log N)。


如果i和j都不停止,那必須防止i和j越界,不再進(jìn)行交換操作。也許不錯,但是要把樞紐元交換到i最后到過的位置,這個位置是倒數(shù)第二個位置(或者最后的位置)。這樣將會產(chǎn)生兩個非常不均衡的子數(shù)組,如果所有元素都相同,那么花費的時間為O(N^2)。對于預(yù)排序的輸入來說,與使用第一個元素作為樞紐元沒有區(qū)別。


所以,進(jìn)行不必要的交換建立兩個均衡的子數(shù)組比得到兩個不均衡子數(shù)組好得多。但i和j遇到等于樞紐元的關(guān)鍵字時,i和j停止,從而避免了話費二次時間。


對于很小的數(shù)組(N<=20),快速排序的性能相對較低,倒不如使用插入排序。



以下是程序代碼:



package com.javawxs;   
 
public class QuickSort {   
    public static final int CUTOFF = 3;// 當(dāng)截取元素數(shù)量大于此值是使用快速排序   
 
    public static <T extends Comparable<? super T>> void quickSort(T[] a) {   
        quickSort(a, 0, a.length - 1);   
    }   
 
    private static <T extends Comparable<? super T>> T median3(T[] a, int left,   
            int right) {   
        int center = (left + right) / 2;   
        if (a[center].compareTo(a[left]) < 0) {   
            swapReferences(a, left, center);   
        }   
        if (a[right].compareTo(a[left]) < 0) {   
            swapReferences(a, left, right);   
        }   
        if (a[right].compareTo(a[center]) < 0) {   
            swapReferences(a, center, right);   
        }   
 
        swapReferences(a, center, right - 1);   
        return a[right - 1];   
    }   
 
    private static <T extends Comparable<? super T>> void quickSort(T[] a,   
            int left, int right) {   
        if (left + CUTOFF <= right) {   
            T pivot = median3(a, left, right);   
            int i = left, j = right - 1;   
            for (;;) {   
                while (a[i].compareTo(pivot) < 0)   
                    i++;   
                while (a[j].compareTo(pivot) > 0)   
                    j–;   
                if (i < j) {   
                    swapReferences(a, i, j);   
 
                } else {   
                    break;   
                }   
 
            }   
 
            swapReferences(a, i, right - 1);   
            quickSort(a, left, i - 1);   
            quickSort(a, i + 1, right);   
 
        } else {   
            insertionSort(a, left, right);   
        }   
 
    }   
 
    private static <T extends Comparable<? super T>> void swapReferences(T[] a,   
            int i, int n) {   
        T temp = a[i];   
        a[i] = a[n];   
        a[n] = temp;   
    }   
 
}   
 
數(shù)組的增刪改查

雖然數(shù)組是由固定容量創(chuàng)建的,但是在需要的時候可以用雙倍的容量創(chuàng)建一個不同的數(shù)組。解決了由于使用數(shù)組而產(chǎn)生的問題-需要對數(shù)組的大小進(jìn)行估計;下面通過代碼程序演示數(shù)組在必要的時候進(jìn)行擴(kuò)充,并實現(xiàn)“添加”、“刪除”、“查找”等操作。


代碼:


package com.javawxs;   
 
public class MyArrayList<T> {   
    private T[] object;   
    private int length;// 長度   
    private int capacity;// 容量   
    private static final int BASE_LENGTH = 10;// 初始長度   
 
    public MyArrayList() {   
        this(BASE_LENGTH);   
    }   
 
    // 初始長度的構(gòu)造   
    public MyArrayList(int intLength) {   
        if (intLength > 0) {   
            this.length = intLength;   
            this.capacity = intLength;   
            object = (T[]) new Object[intLength];   
        } else {   
            this.length = BASE_LENGTH;   
            this.capacity = BASE_LENGTH;   
            object = (T[]) new Object[BASE_LENGTH];   
        }   
    }   
 
    // 添加元素   
    public boolean add(T anEntry) {   
        try {   
            if (length == capacity) {   
                copyEntry();   
            }   
            object[length] = anEntry;   
            length++;   
            return true;   
        } catch (Exception e) {   
            return false;   
        }   
    }   
 
    private void copyEntry() {   
        T[] newEntry = object;   
        this.capacity *= 2;   
        object = (T[]) new Object[this.capacity];   
        for (int index = 0; index < newEntry.length; ++index) {   
            object[index] = newEntry[index];   
        }   
    }   
 
    // 指定位置添加元素   
    public boolean add(int index, T anEntry) {   
        try {   
            while (index >= capacity) {   
                copyEntry();   
            }   
            if (index < length) {   
                moveEntry(index, anEntry);   
                length++;   
                return true;   
            } else {   
                object[index] = anEntry;   
                int sub = index - length + 1;   
                length += sub;   
                return true;   
            }   
        } catch (Exception e) {   
            return false;   
        }   
    }   
 
    // 當(dāng)插入位置在length之前時移動元素   
    private boolean moveEntry(int aPosition, T anEntry) {   
        if (aPosition < length) {   
            for (int index = length - 1; index >= aPosition; ++index) {   
                object[index + 1] = object[index];   
            }   
            object[aPosition] = anEntry;   
            return true;   
        }   
        return false;   
    }   
 
    public boolean contains(T anEntry) {   
        for (T t : object) {   
            if (t.equals(anEntry)) {   
                return true;   
            }   
        }   
        return false;   
    }   
 
    public T get(int index) {   
        if (index < length) {   
            return object[index];   
        }   
        return null;   
    }   
 
    public boolean isEmpty() {   
        return (length == 0);   
    }   
 
    public boolean isFull() {   
 
        return (length == capacity);   
 
    }   
 
    // 移除元素   
    public T remove(int index) {   
 
        if (index < length) {   
            T newEntry = object[index];   
            for (int i = index; i < length; ++i) {   
                object[i] = object[i + 1];   
            }   
            length–;   
            return newEntry;   
        }   
        return null;   
    }   
 
    // 替換元素   
    public T replace(int index, T anEntry) {   
        if (index < length) {   
            T newEntry = object[index];   
            object[index] = anEntry;   
            return newEntry;   
        }   
        return null;   
    }   
 
    public int getLength() {   
        return length;   
    }   
 
    public int getCapacity() {   
        return this.capacity;   
    }   
 
    public void show() {   
        for (int index = 0; index < length; ++index) {   
            if (object[index] != null) {   
                System.out.println(index + “:” + object[index].toString());   
            } else {   
                System.out.println(index + “:” + “null”);   
            }   
        }   
    }   
}  

 
序列化

一個對象序列化的接口,一個類只有實現(xiàn)了Serializable接口,它的對象才是可序列化的。因此如果要序列化某些類的對象,這些類就必須實現(xiàn)Serializable接口。而實際上,Serializable是一個空接口,沒有什么具體內(nèi)容,它的目的只是簡單的標(biāo)識一個類的對象可以被序列化。
追問
那進(jìn)行序列化有什么好處呢?
回答
什么情況下需要序列化
a)當(dāng)你想把的內(nèi)存中的對象寫入到硬盤的時候;
b)當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對象的時候;
c)當(dāng)你想通過RMI傳輸對象的時候;
再稍微解釋一下:a)比如說你的內(nèi)存不夠用了,那計算機(jī)就要將內(nèi)存里面的一部分對象暫時的保存到硬盤中,等到要用的時候再讀入到內(nèi)存中,硬盤的那部分存儲空間就是所謂的虛擬內(nèi)存。在比如過你要將某個特定的對象保存到文件中,我隔幾天在把它拿出來用,那么這時候就要實現(xiàn)Serializable接口;
b)在進(jìn)行java的Socket編程的時候,你有時候可能要傳輸某一類的對象,那么也就要實現(xiàn)Serializable接口;最常見的你傳輸一個字符串,它是JDK里面的類,也實現(xiàn)了Serializable接口,所以可以在網(wǎng)絡(luò)上傳輸。
c)如果要通過遠(yuǎn)程的方法調(diào)用(RMI)去調(diào)用一個遠(yuǎn)程對象的方法,如在計算機(jī)A中調(diào)用另一臺計算機(jī)B的對象的方法,那么你需要通過JNDI服務(wù)獲取計算機(jī)B目標(biāo)對象的引用,將對象從B傳送到A,就需要實現(xiàn)序列化接口。
沒有人說的話能全部準(zhǔn)確,批判性的參考。
提問者評價
thank you !
評論(128)|437


longturong |五級采納率44%
擅長:編程語言數(shù)據(jù)庫DBJAVA相關(guān)
按默認(rèn)排序|按時間排序
其他1條回答
2011-09-16 20:05ymiqplgao|七級
序列化。主要解決就是完成如何將JVM中的對象保存起來,當(dāng)需要這個對象的時候,再把它讀入內(nèi)存。為了實現(xiàn)這個功能,便有這個序列化的方法。
當(dāng)然有序列化,就有反序列化。見于你不明白,詳細(xì)的也就不多說了,參考相關(guān)資料先了解一下


舉個例子,你編寫了一款游戲,保存記錄時把所有狀態(tài)一一保存非常麻煩,這時就可以使用Serializable(序列化接口),它的作用是可以將一個對象實例序列化,序列化后你可以選擇將它保存在你需要的位置。相對的,讀取后生成的對象所有屬性(除了設(shè)置為瞬時值的屬性)將和原對象的屬性相同(只是內(nèi)存地址不同)。這樣可以方便的將一個java對象寫入到磁盤中,保存該對象的所有狀態(tài)!值得注意的是序列化的對象中包含的屬性和其他對象都需要實現(xiàn)序列化接口,不然無法正常序列化!在hibernate里,并非所有的實體類必須實現(xiàn)序列化接口,因為在hibernate中我們通常是將基本類型的數(shù)值映射為數(shù)據(jù)庫中的字段。而基礎(chǔ)類型都實現(xiàn)了序列化接口(String也實現(xiàn)了)。所以,只有在想將一個對象完整存進(jìn)數(shù)據(jù)庫(存儲為二進(jìn)制碼),而不是將對象的屬性分別存進(jìn)數(shù)據(jù)庫,讀取時再重新構(gòu)建的話,就可以不用實現(xiàn)序列化接口。

 
正則表達(dá)式

1. 引子
  目前,正則表達(dá)式已經(jīng)在很多軟件中得到廣泛的應(yīng)用,包括*nix(Linux, Unix等),HP等操作系統(tǒng),PHP,C#,Java等開發(fā)環(huán)境,以及很多的應(yīng)用軟件中,都可以看到正則表達(dá)式的影子。


  正則表達(dá)式的使用,可以通過簡單的辦法來實現(xiàn)強(qiáng)大的功能。為了簡單有效而又不失強(qiáng)大,造成了正則表達(dá)式代碼的難度較大,學(xué)習(xí)起來也不是很容易,所以需要付出一些努力才行,入門之后參照一定的參考,使用起來還是比較簡單有效的。


例子: ^.+@.+\\..+$


  這樣的代碼曾經(jīng)多次把我自己給嚇退過??赡芎芏嗳艘彩潜贿@樣的代碼給嚇跑的吧。繼續(xù)閱讀本文將讓你也可以自由應(yīng)用這樣的代碼。


  注意:這里的第7部分跟前面的內(nèi)容看起來似乎有些重復(fù),目的是把前面表格里的部分重新描述了一次,目的是讓這些內(nèi)容更容易理解。

上一頁    下一頁
2. 正則表達(dá)式的歷史


  正則表達(dá)式的“祖先”可以一直上溯至對人類神經(jīng)系統(tǒng)如何工作的早期研究。Warren McCulloch 和 Walter Pitts 這兩位神經(jīng)生理學(xué)家研究出一種數(shù)學(xué)方式來描述這些神經(jīng)網(wǎng)絡(luò)。
  1956 年, 一位叫 Stephen Kleene 的數(shù)學(xué)家在 McCulloch 和 Pitts 早期工作的基礎(chǔ)上,發(fā)表了一篇標(biāo)題為“神經(jīng)網(wǎng)事件的表示法”的論文,引入了正則表達(dá)式的概念。正則表達(dá)式就是用來描述他稱為“正則集的代數(shù)”的表達(dá)式,因此采用“正則表達(dá)式”這個術(shù)語。


  隨后,發(fā)現(xiàn)可以將這一工作應(yīng)用于使用 Ken Thompson 的計算搜索算法的一些早期研究,Ken Thompson 是 Unix 的主要發(fā)明人。正則表達(dá)式的第一個實用應(yīng)用程序就是 Unix 中的 qed 編輯器。


  如他們所說,剩下的就是眾所周知的歷史了。從那時起直至現(xiàn)在正則表達(dá)式都是基于文本的編輯器和搜索工具中的一個重要部分。


3. 正則表達(dá)式定義
  正則表達(dá)式(regular expression)描述了一種字符串匹配的模式,可以用來檢查一個串是否含有某種子串、將匹配的子串做替換或者從某個串中取出符合某個條件的子串等。


列目錄時, dir *.txt或ls *.txt中的*.txt就不是一個正則表達(dá)式,因為這里*與正則式的*的含義是不同的。
  正則表達(dá)式是由普通字符(例如字符 a 到 z)以及特殊字符(稱為元字符)組成的文字模式。正則表達(dá)式作為一個模板,將某個字符模式與所搜索的字符串進(jìn)行匹配。


3.1 普通字符
  由所有那些未顯式指定為元字符的打印和非打印字符組成。這包括所有的大寫和小寫字母字符,所有數(shù)字,所有標(biāo)點符號以及一些符號。


3.2 非打印字符 字符  含義
\cx  匹配由x指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個原義的 'c' 字符。
\f  匹配一個換頁符。等價于 \x0c 和 \cL。
\n  匹配一個換行符。等價于 \x0a 和 \cJ。
\r  匹配一個回車符。等價于 \x0d 和 \cM。
\s  匹配任何空白字符,包括空格、制表符、換頁符等等。等價于 [ \f\n\r\t\v]。
\S  匹配任何非空白字符。等價于 [^ \f\n\r\t\v]。
\t  匹配一個制表符。等價于 \x09 和 \cI。
\v  匹配一個垂直制表符。等價于 \x0b 和 \cK。


3.3 特殊字符


   所謂特殊字符,就是一些有特殊含義的字符,如上面說的"*.txt"中的*,簡單的說就是表示任何字符串的意思。如果要查找文件名中有*的文件,則需要對*進(jìn)行轉(zhuǎn)義,即在其前加一個\。ls \*.txt。正則表達(dá)式有以下特殊字符。


特別字符 說明
$ 匹配輸入字符串的結(jié)尾位置。如果設(shè)置了 RegExp 對象的 Multiline 屬性,則 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,請使用 \$。
( ) 標(biāo)記一個子表達(dá)式的開始和結(jié)束位置。子表達(dá)式可以獲取供以后使用。要匹配這些字符,請使用 和


* 匹配前面的子表達(dá)式零次或多次。要匹配 * 字符,請使用 \*。
+ 匹配前面的子表達(dá)式一次或多次。要匹配 + 字符,請使用 \+。
. 匹配除換行符 \n之外的任何單字符。要匹配 .,請使用 \。
[  標(biāo)記一個中括號表達(dá)式的開始。要匹配 [,請使用 \[。
? 匹配前面的子表達(dá)式零次或一次,或指明一個非貪婪限定符。要匹配 ? 字符,請使用 \?。
\ 將下一個字符標(biāo)記為或特殊字符、或原義字符、或向后引用、或八進(jìn)制轉(zhuǎn)義符。例如, 'n' 匹配字符 'n'。'\n' 匹配換行符。序列 '\\' 匹配 "\",而 '\(' 則匹配 "("。
^ 匹配輸入字符串的開始位置,除非在方括號表達(dá)式中使用,此時它表示不接受該字符集合。要匹配 ^ 字符本身,請使用 \^。
{ 標(biāo)記限定符表達(dá)式的開始。要匹配 {,請使用 \{。
| 指明兩項之間的一個選擇。要匹配 |,請使用 \|。


  構(gòu)造正則表達(dá)式的方法和創(chuàng)建數(shù)學(xué)表達(dá)式的方法一樣。也就是用多種元字符與操作符將小的表達(dá)式結(jié)合在一起來創(chuàng)建更大的表達(dá)式。正則表達(dá)式的組件可以是單個的字符、字符集合、字符范圍、字符間的選擇或者所有這些組件的任意組合。
 


3.4 限定符


   限定符用來指定正則表達(dá)式的一個給定組件必須要出現(xiàn)多少次才能滿足匹配。有*或+或?或{n}或{n,}或{n,m}共6種。
*、+和?限定符都是貪婪的,因為它們會盡可能多的匹配文字,只有在它們的后面加上一個?就可以實現(xiàn)非貪婪或最小匹配。
   正則表達(dá)式的限定符有:
 


字符  描述
*  匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價于{0,}。
+  匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價于 {1,}。
?  匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價于 {0,1}。
{n}  n 是一個非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。
{n,}  n 是一個非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價于 'o+'。'o{0,}' 則等價于 'o*'。
{n,m}  m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價于 'o?'。請注意在逗號和兩個數(shù)之間不能有空格。


3.5 定位符


   用來描述字符串或單詞的邊界,^和$分別指字符串的開始與結(jié)束,\b描述單詞的前或后邊界,\B表示非單詞邊界。不能對定位符使用限定符。


3.6 選擇


   用圓括號將所有選擇項括起來,相鄰的選擇項之間用|分隔。但用圓括號會有一個副作用,是相關(guān)的匹配會被緩存,此時可用?:放在第一個選項前來消除這種副作用。
   其中?:是非捕獲元之一,還有兩個非捕獲元是?=和?!,這兩個還有更多的含義,前者為正向預(yù)查,在任何開始匹配圓括號內(nèi)的正則表達(dá)式模式的位置來匹配搜索字符串,后者為負(fù)向預(yù)查,在任何開始不匹配該正則表達(dá)式模式的位置來匹配搜索字符串。


3.7 后向引用


   對一個正則表達(dá)式模式或部分模式兩邊添加圓括號將導(dǎo)致相關(guān)匹配存儲到一個臨時緩沖區(qū)中,所捕獲的每個子匹配都按照在正則表達(dá)式模式中從左至右所遇到的內(nèi)容存儲。存儲子匹配的緩沖區(qū)編號從 1 開始,連續(xù)編號直至最大 99 個子表達(dá)式。每個緩沖區(qū)都可以使用 '\n' 訪問,其中 n 為一個標(biāo)識特定緩沖區(qū)的一位或兩位十進(jìn)制數(shù)。
   可以使用非捕獲元字符 '?:', '?=', or '?!' 來忽略對相關(guān)匹配的保存。



上一頁    下一頁


4. 各種操作符的運算優(yōu)先級


   相同優(yōu)先級的從左到右進(jìn)行運算,不同優(yōu)先級的運算先高后低。各種操作符的優(yōu)先級從高到低如下:
 


操作符  描述
\  轉(zhuǎn)義符
(), (?:), (?=), []  圓括號和方括號
*, +, ?, {n}, {n,}, {n,m}  限定符
^, $, \anymetacharacter  位置和順序
|  “或”操作


上一頁    下一頁


5. 全部符號解釋


字符  描述
\  將下一個字符標(biāo)記為一個特殊字符、或一個原義字符、或一個 向后引用、或一個八進(jìn)制轉(zhuǎn)義符。例如,'n' 匹配字符 "n"。'\n' 匹配一個換行符。序列 '\\' 匹配 "\" 而 "\(" 則匹配 "("。
^  匹配輸入字符串的開始位置。如果設(shè)置了 RegExp 對象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$  匹配輸入字符串的結(jié)束位置。如果設(shè)置了RegExp 對象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置。
*  匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價于{0,}。
+  匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價于 {1,}。
?  匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價于 {0,1}。
{n}  n 是一個非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。
{n,}  n 是一個非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價于 'o+'。'o{0,}' 則等價于 'o*'。
{n,m}  m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價于 'o?'。請注意在逗號和兩個數(shù)之間不能有空格。
?  當(dāng)該字符緊跟在任何一個其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對于字符串 "oooo",'o+?' 將匹配單個 "o",而 'o+' 將匹配所有 'o'。
.  匹配除 "\n" 之外的任何單個字符。要匹配包括 '\n' 在內(nèi)的任何字符,請使用象 '[.\n]' 的模式。
(pattern)  匹配 pattern 并獲取這一匹配。所獲取的匹配可以從產(chǎn)生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中則使用 $0…$9 屬性。要匹配圓括號字符,請使用 '\(' 或 '\)'。
(?:pattern)  匹配 pattern 但不獲取匹配結(jié)果,也就是說這是一個非獲取匹配,不進(jìn)行存儲供以后使用。這在使用 "或" 字符 (|) 來組合一個模式的各個部分是很有用。例如, 'industr(?:y|ies) 就是一個比 'industry|industries' 更簡略的表達(dá)式。
(?=pattern)  正向預(yù)查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始。
(?!pattern)  負(fù)向預(yù)查,在任何不匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始
x|y  匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。
[xyz]  字符集合。匹配所包含的任意一個字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz]  負(fù)值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。
[a-z]  字符范圍。匹配指定范圍內(nèi)的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范圍內(nèi)的任意小寫字母字符。
[^a-z]  負(fù)值字符范圍。匹配任何不在指定范圍內(nèi)的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范圍內(nèi)的任意字符。
\b  匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B  匹配非單詞邊界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\cx  匹配由 x 指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個原義的 'c' 字符。
\d  匹配一個數(shù)字字符。等價于 [0-9]。
\D  匹配一個非數(shù)字字符。等價于 [^0-9]。
\f  匹配一個換頁符。等價于 \x0c 和 \cL。
\n  匹配一個換行符。等價于 \x0a 和 \cJ。
\r  匹配一個回車符。等價于 \x0d 和 \cM。
\s  匹配任何空白字符,包括空格、制表符、換頁符等等。等價于 [ \f\n\r\t\v]。
\S  匹配任何非空白字符。等價于 [^ \f\n\r\t\v]。
\t  匹配一個制表符。等價于 \x09 和 \cI。
\v  匹配一個垂直制表符。等價于 \x0b 和 \cK。
\w  匹配包括下劃線的任何單詞字符。等價于'[A-Za-z0-9_]'。
\W  匹配任何非單詞字符。等價于 '[^A-Za-z0-9_]'。
\xn  匹配 n,其中 n 為十六進(jìn)制轉(zhuǎn)義值。十六進(jìn)制轉(zhuǎn)義值必須為確定的兩個數(shù)字長。例如,'\x41' 匹配 "A"。'\x041' 則等價于 '\x04' & "1"。正則表達(dá)式中可以使用 ASCII 編碼。.
\num  匹配 num,其中 num 是一個正整數(shù)。對所獲取的匹配的引用。例如,'(.)\1' 匹配兩個連續(xù)的相同字符。
\n  標(biāo)識一個八進(jìn)制轉(zhuǎn)義值或一個向后引用。如果 \n 之前至少 n 個獲取的子表達(dá)式,則 n 為向后引用。否則,如果 n 為八進(jìn)制數(shù)字 (0-7),則 n 為一個八進(jìn)制轉(zhuǎn)義值。
\nm  標(biāo)識一個八進(jìn)制轉(zhuǎn)義值或一個向后引用。如果 \nm 之前至少有 nm 個獲得子表達(dá)式,則 nm 為向后引用。如果 \nm 之前至少有 n 個獲取,則 n 為一個后跟文字 m 的向后引用。如果前面的條件都不滿足,若 n 和 m 均為八進(jìn)制數(shù)字 (0-7),則 \nm 將匹配八進(jìn)制轉(zhuǎn)義值 nm。
\nml  如果 n 為八進(jìn)制數(shù)字 (0-3),且 m 和 l 均為八進(jìn)制數(shù)字 (0-7),則匹配八進(jìn)制轉(zhuǎn)義值 nml。
\un  匹配 n,其中 n 是一個用四個十六進(jìn)制數(shù)字表示的 Unicode 字符。例如, \u00A9 匹配版權(quán)符號 (?)。



上一頁    下一頁


6. 部分例子


正則表達(dá)式 說明
/\b([a-z]+) \1\b/gi 一個單詞連續(xù)出現(xiàn)的位置
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/  將一個URL解析為協(xié)議、域、端口及相對路徑
/^(?:Chapter|Section) [1-9][0-9]{0,1}$/ 定位章節(jié)的位置
/[-a-z]/ A至z共26個字母再加一個-號。
/ter\b/ 可匹配chapter,而不能terminal
/\Bapt/ 可匹配chapter,而不能aptitude
/Windows(?=95 |98 |NT )/ 可匹配Windows95或Windows98或WindowsNT,當(dāng)找到一個匹配后,從Windows后面開始進(jìn)行下一次的檢索匹配。



上一頁    下一頁
7. 正則表達(dá)式匹配規(guī)則
7.1 基本模式匹配


   一切從最基本的開始。模式,是正規(guī)表達(dá)式最基本的元素,它們是一組描述字符串特征的字符。模式可以很簡單,由普通的字符串組成,也可以非常復(fù)雜,往往用特殊的字符表示一個范圍內(nèi)的字符、重復(fù)出現(xiàn),或表示上下文。例如:


^once


  這個模式包含一個特殊的字符^,表示該模式只匹配那些以once開頭的字符串。例如該模式與字符串"once upon a time"匹配,與"There once was a man from NewYork"不匹配。正如如^符號表示開頭一樣,$符號用來匹配那些以給定模式結(jié)尾的字符串。


bucket$


  這個模式與"Who kept all of this cash in a bucket"匹配,與"buckets"不匹配。字符^和$同時使用時,表示精確匹配(字符串與模式一樣)。例如:


^bucket$


  只匹配字符串"bucket"。如果一個模式不包括^和$,那么它與任何包含該模式的字符串匹配。例如:模式


once


與字符串


There once was a man from NewYork
Who kept all of his cash in a bucket.


是匹配的。


   在該模式中的字母(o-n-c-e)是字面的字符,也就是說,他們表示該字母本身,數(shù)字也是一樣的。其他一些稍微復(fù)雜的字符,如標(biāo)點符號和白字符(空格、制表符等),要用到轉(zhuǎn)義序列。所有的轉(zhuǎn)義序列都用反斜杠(\)打頭。制表符的轉(zhuǎn)義序列是:\t。所以如果我們要檢測一個字符串是否以制表符開頭,可以用這個模式:


^\t


類似的,用\n表示“新行”,\r表示回車。其他的特殊符號,可以用在前面加上反斜杠,如反斜杠本身用\\表示,句號.用\.表示,以此類推。


7.2 字符簇


在INTERNET的程序中,正規(guī)表達(dá)式通常用來驗證用戶的輸入。當(dāng)用戶提交一個FORM以后,要判斷輸入的電話號碼、地址、EMAIL地址、信用卡號碼等是否有效,用普通的基于字面的字符是不夠的。


所以要用一種更自由的描述我們要的模式的辦法,它就是字符簇。要建立一個表示所有元音字符的字符簇,就把所有的元音字符放在一個方括號里:


[AaEeIiOoUu]


這個模式與任何元音字符匹配,但只能表示一個字符。用連字號可以表示一個字符的范圍,如:


[a-z] //匹配所有的小寫字母
[A-Z] //匹配所有的大寫字母
[a-zA-Z] //匹配所有的字母
[0-9] //匹配所有的數(shù)字
[0-9\.\-] //匹配所有的數(shù)字,句號和減號
[ \f\r\t\n] //匹配所有的白字符


同樣的,這些也只表示一個字符,這是一個非常重要的。如果要匹配一個由一個小寫字母和一位數(shù)字組成的字符串,比如"z2"、"t6"或"g7",但不是"ab2"、"r2d3" 或"b52"的話,用這個模式:


^[a-z][0-9]$


盡管[a-z]代表26個字母的范圍,但在這里它只能與第一個字符是小寫字母的字符串匹配。


前面曾經(jīng)提到^表示字符串的開頭,但它還有另外一個含義。當(dāng)在一組方括號里使用^是,它表示“非”或“排除”的意思,常常用來剔除某個字符。還用前面的例子,我們要求第一個字符不能是數(shù)字:


^[^0-9][0-9]$


這個模式與"&5"、"g7"及"-2"是匹配的,但與"12"、"66"是不匹配的。下面是幾個排除特定字符的例子:


[^a-z] //除了小寫字母以外的所有字符
[^\\\/\^] //除了(\)(/)(^)之外的所有字符
[^\"\'] //除了雙引號(")和單引號(')之外的所有字符


特殊字符"." (點,句號)在正規(guī)表達(dá)式中用來表示除了“新行”之外的所有字符。所以模式"^.5$"與任何兩個字符的、以數(shù)字5結(jié)尾和以其他非“新行”字符開頭的字符串匹配。模式"."可以匹配任何字符串,除了空串和只包括一個“新行”的字符串。


PHP的正規(guī)表達(dá)式有一些內(nèi)置的通用字符簇,列表如下:


字符簇 含義
[[:alpha:]] 任何字母
[[:digit:]] 任何數(shù)字
[[:alnum:]] 任何字母和數(shù)字
[[:space:]] 任何白字符
[[:upper:]] 任何大寫字母
[[:lower:]] 任何小寫字母
[[:punct:]] 任何標(biāo)點符號
[[:xdigit:]] 任何16進(jìn)制的數(shù)字,相當(dāng)于[0-9a-fA-F]


7.3 確定重復(fù)出現(xiàn)


到現(xiàn)在為止,你已經(jīng)知道如何去匹配一個字母或數(shù)字,但更多的情況下,可能要匹配一個單詞或一組數(shù)字。一個單詞有若干個字母組成,一組數(shù)字有若干個單數(shù)組成。跟在字符或字符簇后面的花括號({})用來確定前面的內(nèi)容的重復(fù)出現(xiàn)的次數(shù)。


字符簇 含義
^[a-zA-Z_]$ 所有的字母和下劃線
^[[:alpha:]]{3}$ 所有的3個字母的單詞
^a$ 字母a
^a{4}$ aaaa
^a{2,4}$ aa,aaa或aaaa
^a{1,3}$ a,aa或aaa
^a{2,}$ 包含多于兩個a的字符串
^a{2,} 如:aardvark和aaab,但apple不行
a{2,} 如:baad和aaa,但Nantucket不行
\t{2} 兩個制表符
.{2} 所有的兩個字符


這些例子描述了花括號的三種不同的用法。一個數(shù)字,{x}的意思是“前面的字符或字符簇只出現(xiàn)x次”;一個數(shù)字加逗號,{x,}的意思是“前面的內(nèi)容出現(xiàn)x或更多的次數(shù)”;兩個用逗號分隔的數(shù)字,{x,y}表示“前面的內(nèi)容至少出現(xiàn)x次,但不超過y次”。我們可以把模式擴(kuò)展到更多的單詞或數(shù)字:


^[a-zA-Z0-9_]{1,}$ //所有包含一個以上的字母、數(shù)字或下劃線的字符串
^[0-9]{1,}$ //所有的正數(shù)
^\-{0,1}[0-9]{1,}$ //所有的整數(shù)
^\-{0,1}[0-9]{0,}\.{0,1}[0-9]{0,}$ //所有的小數(shù)


最后一個例子不太好理解,是嗎?這么看吧:與所有以一個可選的負(fù)號(\-{0,1})開頭(^)、跟著0個或更多的數(shù)字([0-9]{0,})、和一個可選的小數(shù)點(\.{0,1})再跟上0個或多個數(shù)字([0-9]{0,}),并且沒有其他任何東西($)。下面你將知道能夠使用的更為簡單的方法。


特殊字符"?"與{0,1}是相等的,它們都代表著:“0個或1個前面的內(nèi)容”或“前面的內(nèi)容是可選的”。所以剛才的例子可以簡化為:


^\-?[0-9]{0,}\.?[0-9]{0,}$


特殊字符"*"與{0,}是相等的,它們都代表著“0個或多個前面的內(nèi)容”。最后,字符"+"與 {1,}是相等的,表示“1個或多個前面的內(nèi)容”,所以上面的4個例子可以寫成:


^[a-zA-Z0-9_]+$ //所有包含一個以上的字母、數(shù)字或下劃線的字符串
^[0-9]+$ //所有的正數(shù)
^\-?[0-9]+$ //所有的整數(shù)
^\-?[0-9]*\.?[0-9]*$ //所有的小數(shù)


當(dāng)然這并不能從技術(shù)上降低正規(guī)表達(dá)式的復(fù)雜性,但可以使它們更容易閱讀。
 
字符編碼

今天中午,我突然想搞清楚Unicode和UTF-8之間的關(guān)系,于是就開始在網(wǎng)上查資料。


結(jié)果,這個問題比我想象的復(fù)雜,從午飯后一直看到晚上9點,才算初步搞清楚。


下面就是我的筆記,主要用來整理自己的思路。但是,我盡量試圖寫得通俗易懂,希望能對其他朋友有用。畢竟,字符編碼是計算機(jī)技術(shù)的基石,想要熟練使用計算機(jī),就必須懂得一點字符編碼的知識。


1. ASCII碼


我們知道,在計算機(jī)內(nèi)部,所有的信息最終都表示為一個二進(jìn)制的字符串。每一個二進(jìn)制位(bit)有0和1兩種狀態(tài),因此八個二進(jìn)制位就可以組合出256種狀態(tài),這被稱為一個字節(jié)(byte)。也就是說,一個字節(jié)一共可以用來表示256種不同的狀態(tài),每一個狀態(tài)對應(yīng)一個符號,就是256個符號,從0000000到11111111。


上個世紀(jì)60年代,美國制定了一套字符編碼,對英語字符與二進(jìn)制位之間的關(guān)系,做了統(tǒng)一規(guī)定。這被稱為ASCII碼,一直沿用至今。


ASCII碼一共規(guī)定了128個字符的編碼,比如空格“SPACE”是32(二進(jìn)制00100000),大寫的字母A是65(二進(jìn)制01000001)。這128個符號(包括32個不能打印出來的控制符號),只占用了一個字節(jié)的后面7位,最前面的1位統(tǒng)一規(guī)定為0。


2、非ASCII編碼


英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。比如,在法語中,字母上方有注音符號,它就無法用ASCII碼表示。于是,一些歐洲國家就決定,利用字節(jié)中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進(jìn)制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。


但是,這里又出現(xiàn)了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0—127表示的符號是一樣的,不一樣的只是128—255的這一段。


至于亞洲國家的文字,使用的符號就更多了,漢字就多達(dá)10萬左右。一個字節(jié)只能表示256種符號,肯定是不夠的,就必須使用多個字節(jié)表達(dá)一個符號。比如,簡體中文常見的編碼方式是GB2312,使用兩個字節(jié)表示一個漢字,所以理論上最多可以表示256x256=65536個符號。


中文編碼的問題需要專文討論,這篇筆記不涉及。這里只指出,雖然都是用多個字節(jié)表示一個符號,但是GB類的漢字編碼與后文的Unicode和UTF-8是毫無關(guān)系的。


3.Unicode


正如上一節(jié)所說,世界上存在著多種編碼方式,同一個二進(jìn)制數(shù)字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現(xiàn)亂碼。為什么電子郵件常常出現(xiàn)亂碼?就是因為發(fā)信人和收信人使用的編碼方式不一樣。


可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那么亂碼問題就會消失。這就是Unicode,就像它的名字都表示的,這是一種所有符號的編碼。


Unicode當(dāng)然是一個很大的集合,現(xiàn)在的規(guī)模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字“嚴(yán)”。具體的符號對應(yīng)表,可以查詢unicode.org,或者專門的漢字對應(yīng)表。


4. Unicode的問題


需要注意的是,Unicode只是一個符號集,它只規(guī)定了符號的二進(jìn)制代碼,卻沒有規(guī)定這個二進(jìn)制代碼應(yīng)該如何存儲。


比如,漢字“嚴(yán)”的unicode是十六進(jìn)制數(shù)4E25,轉(zhuǎn)換成二進(jìn)制數(shù)足足有15位(100111000100101),也就是說這個符號的表示至少需要2個字節(jié)。表示其他更大的符號,可能需要3個字節(jié)或者4個字節(jié),甚至更多。


這里就有兩個嚴(yán)重的問題,第一個問題是,如何才能區(qū)別unicode和ascii?計算機(jī)怎么知道三個字節(jié)表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經(jīng)知道,英文字母只用一個字節(jié)表示就夠了,如果unicode統(tǒng)一規(guī)定,每個符號用三個或四個字節(jié)表示,那么每個英文字母前都必然有二到三個字節(jié)是0,這對于存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。


它們造成的結(jié)果是:1)出現(xiàn)了unicode的多種存儲方式,也就是說有許多種不同的二進(jìn)制格式,可以用來表示unicode。2)unicode在很長一段時間內(nèi)無法推廣,直到互聯(lián)網(wǎng)的出現(xiàn)。


5.UTF-8


互聯(lián)網(wǎng)的普及,強(qiáng)烈要求出現(xiàn)一種統(tǒng)一的編碼方式。UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種unicode的實現(xiàn)方式。其他實現(xiàn)方式還包括UTF-16和UTF-32,不過在互聯(lián)網(wǎng)上基本不用。重復(fù)一遍,這里的關(guān)系是,UTF-8是Unicode的實現(xiàn)方式之一。


UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節(jié)表示一個符號,根據(jù)不同的符號而變化字節(jié)長度。


UTF-8的編碼規(guī)則很簡單,只有二條:


1)對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個符號的unicode碼。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。


2)對于n字節(jié)的符號(n>1),第一個字節(jié)的前n位都設(shè)為1,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位,全部為這個符號的unicode碼。


下表總結(jié)了編碼規(guī)則,字母x表示可用編碼的位。


Unicode符號范圍 | UTF-8編碼方式
(十六進(jìn)制) | (二進(jìn)制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx


下面,還是以漢字“嚴(yán)”為例,演示如何實現(xiàn)UTF-8編碼。


已知“嚴(yán)”的unicode是4E25(100111000100101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800-0000 FFFF),因此“嚴(yán)”的UTF-8編碼需要三個字節(jié),即格式是“1110xxxx10xxxxxx 10xxxxxx”。然后,從“嚴(yán)”的最后一個二進(jìn)制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,“嚴(yán)”的UTF-8編碼是“1110010010111000 10100101”,轉(zhuǎn)換成十六進(jìn)制就是E4B8A5。


6. Unicode與UTF-8之間的轉(zhuǎn)換


通過上一節(jié)的例子,可以看到“嚴(yán)”的Unicode碼是4E25,UTF-8編碼是E4B8A5,兩者是不一樣的。它們之間的轉(zhuǎn)換可以通過程序?qū)崿F(xiàn)。


在Windows平臺下,有一個最簡單的轉(zhuǎn)化方法,就是使用內(nèi)置的記事本小程序Notepad.exe。打開文件后,點擊“文件”菜單中的“另存為”命令,會跳出一個對話框,在最底部有一個“編碼”的下拉條。


里面有四個選項:ANSI,Unicode,Unicode big endian 和 UTF-8。


1)ANSI是默認(rèn)的編碼方式。對于英文文件是ASCII編碼,對于簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會采用Big5碼)。


2)Unicode編碼指的是UCS-2編碼方式,即直接用兩個字節(jié)存入字符的Unicode碼。這個選項用的little endian格式。


3)Unicode big endian編碼與上一個選項相對應(yīng)。我在下一節(jié)會解釋little endian和big endian的涵義。


4)UTF-8編碼,也就是上一節(jié)談到的編碼方法。


選擇完”編碼方式“后,點擊”保存“按鈕,文件的編碼方式就立刻轉(zhuǎn)換好了。


7. Little endian和Big endian


上一節(jié)已經(jīng)提到,Unicode碼可以采用UCS-2格式直接存儲。以漢字”嚴(yán)“為例,Unicode碼是4E25,需要用兩個字節(jié)存儲,一個字節(jié)是4E,另一個字節(jié)是25。存儲的時候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。


這兩個古怪的名稱來自英國作家斯威夫特的《格列佛游記》。在該書中,小人國里爆發(fā)了內(nèi)戰(zhàn),戰(zhàn)爭起因是人們爭論,吃雞蛋時究竟是從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。為了這件事情,前后爆發(fā)了六次戰(zhàn)爭,一個皇帝送了命,另一個皇帝丟了王位。


因此,第一個字節(jié)在前,就是”大頭方式“(Big endian),第二個字節(jié)在前就是”小頭方式“(Little endian)。


那么很自然的,就會出現(xiàn)一個問題:計算機(jī)怎么知道某一個文件到底采用哪一種方式編碼?


Unicode規(guī)范中定義,每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做”零寬度非換行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個字節(jié),而且FF比FE大1。


如果一個文本文件的頭兩個字節(jié)是FEFF,就表示該文件采用大頭方式;如果頭兩個字節(jié)是FF FE,就表示該文件采用小頭方式。


8. 實例


下面,舉一個實例。


打開”記事本“程序Notepad.exe,新建一個文本文件,內(nèi)容就是一個”嚴(yán)“字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8編碼方式保存。


然后,用文本編輯軟件UltraEdit中的”十六進(jìn)制功能“,觀察該文件的內(nèi)部編碼方式。


1)ANSI:文件的編碼就是兩個字節(jié)“D1 CF”,這正是“嚴(yán)”的GB2312編碼,這也暗示GB2312是采用大頭方式存儲的。


2)Unicode:編碼是四個字節(jié)“FF FE 25 4E”,其中“FF FE”表明是小頭方式存儲,真正的編碼是4E25。


3)Unicode big endian:編碼是四個字節(jié)“FE FF4E 25”,其中“FE FF”表明是大頭方式存儲。


4)UTF-8:編碼是六個字節(jié)“EF BB BF E4 B8 A5”,前三個字節(jié)“EF BB BF”表示這是UTF-8編碼,后三個“E4B8A5”就是“嚴(yán)”的具體編碼,它的存儲順序與編碼順序是一致的。


9. 延伸閱讀


* The AbsoluteMinimum Every Software Developer Absolutely, Positively Must Know About Unicodeand Character Sets(關(guān)于字符集的最基本知識)


* 談?wù)刄nicode編碼


* RFC3629:UTF-8, a transformation format ofISO 10646(如果實現(xiàn)UTF-8的規(guī)定)


關(guān)于URL編碼


一、問題的由來


URL就是網(wǎng)址,只要上網(wǎng),就一定會用到。


一般來說,URL只能使用英文字母、阿拉伯?dāng)?shù)字和某些標(biāo)點符號,不能使用其他文字和符號。比如,世界上有英文字母的網(wǎng)址“http://www.abc.com”,但是沒有希臘字母的網(wǎng)址“http://www.aβγ.com”(讀作阿爾法-貝塔-伽瑪.com)。這是因為網(wǎng)絡(luò)標(biāo)準(zhǔn)RFC 1738做了硬性規(guī)定:


"...Only alphanumerics [0-9a-zA-Z], the special characters"$-_.+!*'()," [not including the quotes - ed], and reservedcharacters used for their reserved purposes may be used unencoded within aURL."


“只有字母和數(shù)字[0-9a-zA-Z]、一些特殊符號“$-_.+!*'(),”[不包括雙引號]、以及某些保留字,才可以不經(jīng)過編碼直接用于URL。”


這意味著,如果URL中有漢字,就必須編碼后使用。但是麻煩的是,RFC 1738沒有規(guī)定具體的編碼方法,而是交給應(yīng)用程序(瀏覽器)自己決定。這導(dǎo)致“URL編碼”成為了一個混亂的領(lǐng)域。


下面就讓我們看看,“URL編碼”到底有多混亂。我會依次分析四種不同的情況,在每一種情況中,瀏覽器的URL編碼方法都不一樣。把它們的差異解釋清楚之后,我再說如何用Javascript找到一個統(tǒng)一的編碼方法。


二、情況1:網(wǎng)址路徑中包含漢字


打開IE(我用的是8.0版),輸入網(wǎng)址“http://zh.wikipedia.org/wiki/春節(jié)”。注意,“春節(jié)”這兩個字此時是網(wǎng)址路徑的一部分。


查看HTTP請求的頭信息,會發(fā)現(xiàn)IE實際查詢的網(wǎng)址是“http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82”。也就是說,IE自動將“春節(jié)”編碼成了“%E6%98%A5%E8%8A%82”。


我們知道,“春”和“節(jié)”的utf-8編碼分別是“E6 98A5”和“E8 8A 82”,因此,“%E6%98%A5%E8%8A%82”就是按照順序,在每個字節(jié)前加上%而得到的。(具體的轉(zhuǎn)碼方法,請參考我寫的《字符編碼筆記》。)


在Firefox中測試,也得到了同樣的結(jié)果。所以,結(jié)論1就是,網(wǎng)址路徑的編碼,用的是utf-8編碼。


三、情況2:查詢字符串包含漢字


在IE中輸入網(wǎng)址“http://www.baidu.com/s?wd=春節(jié)”。注意,“春節(jié)”這兩個字此時屬于查詢字符串,不屬于網(wǎng)址路徑,不要與情況1混淆。


查看HTTP請求的頭信息,會發(fā)現(xiàn)IE將“春節(jié)”轉(zhuǎn)化成了一個亂碼。


切換到十六進(jìn)制方式,才能清楚地看到,“春節(jié)”被轉(zhuǎn)成了“B4 BA BDDA”。


我們知道,“春”和“節(jié)”的GB2312編碼(我的操作系統(tǒng)“WindowsXP”中文版的默認(rèn)編碼)分別是“B4 BA”和“BD DA”。因此,IE實際上就是將查詢字符串,以GB2312編碼的格式發(fā)送出去。


Firefox的處理方法,略有不同。它發(fā)送的HTTP Head是“wd=%B4%BA%BD%DA”。也就是說,同樣采用GB2312編碼,但是在每個字節(jié)前加上了%。


所以,結(jié)論2就是,查詢字符串的編碼,用的是操作系統(tǒng)的默認(rèn)編碼。


四、情況3:Get方法生成的URL包含漢字


前面說的是直接輸入網(wǎng)址的情況,但是更常見的情況是,在已打開的網(wǎng)頁上,直接用Get或Post方法發(fā)出HTTP請求。


根據(jù)臺灣中興大學(xué)呂瑞麟老師的試驗,這時的編碼方法由網(wǎng)頁的編碼決定,也就是由HTML源碼中字符集的設(shè)定決定。


  <meta http-equiv="Content-Type"content="text/html;charset=xxxx">


如果上面這一行最后的charset是UTF-8,則URL就以UTF-8編碼;如果是GB2312,URL就以GB2312編碼。


舉例來說,百度是GB2312編碼,Google是UTF-8編碼。因此,從它們的搜索框中搜索同一個詞“春節(jié)”,生成的查詢字符串是不一樣的。


百度生成的是%B4%BA%BD%DA,這是GB2312編碼。


Google生成的是%E6%98%A5%E8%8A%82,這是UTF-8編碼。


所以,結(jié)論3就是,GET和POST方法的編碼,用的是網(wǎng)頁的編碼。


五、情況4:Ajax調(diào)用的URL包含漢字


前面三種情況都是由瀏覽器發(fā)出HTTP請求,最后一種情況則是由Javascript生成HTTP請求,也就是Ajax調(diào)用。還是根據(jù)呂瑞麟老師的文章,在這種情況下,IE和Firefox的處理方式完全不一樣。


舉例來說,有這樣兩行代碼:


  url = url + "?q=" +document.myform.elements[0].value; // 假定用戶在表單中提交的值是“春節(jié)”這兩個字


  http_request.open('GET', url, true);


那么,無論網(wǎng)頁使用什么字符集,IE傳送給服務(wù)器的總是“q=%B4%BA%BD%DA”,而Firefox傳送給服務(wù)器的總是“q=%E6%98%A5%E8%8A%82”。也就是說,在Ajax調(diào)用中,IE總是采用GB2312編碼(操作系統(tǒng)的默認(rèn)編碼),而Firefox總是采用utf-8編碼。這就是我們的結(jié)論4。


六、Javascript函數(shù):escape()


好了,到此為止,四種情況都說完了。


假定前面你都看懂了,那么此時你應(yīng)該會感到很頭痛。因為,實在太混亂了。不同的操作系統(tǒng)、不同的瀏覽器、不同的網(wǎng)頁字符集,將導(dǎo)致完全不同的編碼結(jié)果。如果程序員要把每一種結(jié)果都考慮進(jìn)去,是不是太恐怖了?有沒有辦法,能夠保證客戶端只用一種編碼方法向服務(wù)器發(fā)出請求?


回答是有的,就是使用Javascript先對URL編碼,然后再向服務(wù)器提交,不要給瀏覽器插手的機(jī)會。因為Javascript的輸出總是一致的,所以就保證了服務(wù)器得到的數(shù)據(jù)是格式統(tǒng)一的。


Javascript語言用于編碼的函數(shù),一共有三個,最古老的一個就是escape()。雖然這個函數(shù)現(xiàn)在已經(jīng)不提倡使用了,但是由于歷史原因,很多地方還在使用它,所以有必要先從它講起。


實際上,escape()不能直接用于URL編碼,它的真正作用是返回一個字符的Unicode編碼值。比如“春節(jié)”的返回結(jié)果是%u6625%u8282,也就是說在Unicode字符集中,“春”是第6625個(十六進(jìn)制)字符,“節(jié)”是第8282個(十六進(jìn)制)字符。


它的具體規(guī)則是,除了ASCII字母、數(shù)字、標(biāo)點符號“@ * _ + - . /”以外,對其他所有字符進(jìn)行編碼。在\u0000到\u00ff之間的符號被轉(zhuǎn)成%xx的形式,其余符號被轉(zhuǎn)成%uxxxx的形式。對應(yīng)的解碼函數(shù)是unescape()。


所以,“Hello World”的escape()編碼就是“Hello%20World”。因為空格的Unicode值是20(十六進(jìn)制)。


還有兩個地方需要注意。


首先,無論網(wǎng)頁的原始編碼是什么,一旦被Javascript編碼,就都變?yōu)閡nicode字符。也就是說,Javascipt函數(shù)的輸入和輸出,默認(rèn)都是Unicode字符。這一點對下面兩個函數(shù)也適用。


其次,escape()不對“+”編碼。但是我們知道,網(wǎng)頁在提交表單的時候,如果有空格,則會被轉(zhuǎn)化為+字符。服務(wù)器處理數(shù)據(jù)的時候,會把+號處理成空格。所以,使用的時候要小心。


七、Javascript函數(shù):encodeURI()


encodeURI()是Javascript中真正用來對URL編碼的函數(shù)。


它著眼于對整個URL進(jìn)行編碼,因此除了常見的符號以外,對其他一些在網(wǎng)址中有特殊含義的符號“; / ?: @ & = + $ , #”,也不進(jìn)行編碼。編碼后,它輸出符號的utf-8形式,并且在每個字節(jié)前加上%。


它對應(yīng)的解碼函數(shù)是decodeURI()。


需要注意的是,它不對單引號'編碼。


八、Javascript函數(shù):encodeURIComponent()


最后一個Javascript編碼函數(shù)是encodeURIComponent()。與encodeURI()的區(qū)別是,它用于對URL的組成部分進(jìn)行個別編碼,而不用于對整個URL進(jìn)行編碼。


因此,“; / ? : @ & = + $ , #”,這些在encodeURI()中不被編碼的符號,在encodeURIComponent()中統(tǒng)統(tǒng)會被編碼。至于具體的編碼方法,兩者是一樣。


它對應(yīng)的解碼函數(shù)是decodeURIComponent()。




 

 

 作者:chen.yu
深信服三年半工作經(jīng)驗,目前就職游戲廠商,希望能和大家交流和學(xué)習(xí),
微信公眾號:編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進(jìn)階人工智能(鏈接)