面向?qū)ο蟮囊恍┲R(shí)暫時(shí)告一段落,從本文開(kāi)始,進(jìn)入java語(yǔ)法的重學(xué)階段~
初識(shí)數(shù)組
什么是數(shù)組
數(shù)組:相同類(lèi)型的、用一個(gè)標(biāo)識(shí)符名稱封裝到一起的一個(gè)對(duì)象序列或基本類(lèi)型數(shù)據(jù)序列。
數(shù)組的定義及初始化
定義及動(dòng)態(tài)初始化
- 方式一,java推薦用法: type [] 變量名 = new type[數(shù)組中元素的個(gè)數(shù)];
//舉例
int[] a = new int[10];
for (int i = 0; i < a.length; i++){
a[i] = new Random().nextInt();
}
- 方式二,c語(yǔ)言用法(不推薦): type 變量名 [] = new type[數(shù)組中元素的個(gè)數(shù)];
int a[] = new int[10];
for (int i = 0; i < a.length; i++){
a[i] = new Random().nextInt();
}
以上兩種方式都叫做動(dòng)態(tài)初始化,也就是說(shuō),只有當(dāng)程序運(yùn)行以后,你才能知道數(shù)組里到底存了哪些數(shù)據(jù)。方式二的命名方式c和c++程序員比較熟悉,但是java官方推薦使用第一種,一看就能知道,這是一個(gè)int型的數(shù)組,叫a。
靜態(tài)初始化
int[] b = new int[]{1,2,3};
在定義數(shù)組的時(shí)候直接初始化,大括號(hào)里的值就是數(shù)組的值。
隱式初始化
int[] c = {1,2,3};
可以不寫(xiě)new,直接使用大括號(hào)初始化,但是本質(zhì)上還是調(diào)用了new的,只是可以不寫(xiě)出來(lái)而已,所以叫隱式初始化。
最后,我們回過(guò)頭來(lái)仔細(xì)的研究一下下面這一句代碼:
int[] a = new int[10];
這句代碼做了哪些事呢?
- int[] a: 定義了一個(gè)int型數(shù)組的引用,名字叫做a,存放在棧中。
- new int[10]:初始化一個(gè)長(zhǎng)度為10的int型數(shù)組,在堆中開(kāi)辟相應(yīng)大小的內(nèi)存。
- int[] a = new int[10]:將堆中開(kāi)辟的數(shù)組的內(nèi)存地址賦給數(shù)組引用a。
這樣就可以通過(guò)a這個(gè)變量,來(lái)操作這個(gè)數(shù)組了。
是不是覺(jué)得這個(gè)過(guò)程很熟悉?沒(méi)錯(cuò)!我們創(chuàng)建一個(gè)對(duì)象的過(guò)程也是這樣的!那這是不是證明,數(shù)組其實(shí)是一個(gè)對(duì)象呢?我們后面會(huì)詳細(xì)分析。
數(shù)組的使用
數(shù)組自身的使用
數(shù)組是使用方式大家應(yīng)該都很清楚了,我這里簡(jiǎn)單的提一下。
數(shù)組的遍歷
- 方式一:for循環(huán)
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i] + " ");
}
- 方式二:foreach循環(huán)
for (int element: myList) {
System.out.println(element);
}
數(shù)組長(zhǎng)度
int length = myList.length;
java中的每個(gè)數(shù)組都有一個(gè)名為length的屬性,表示數(shù)組的長(zhǎng)度。
length屬性我們后面會(huì)詳細(xì)分析。
數(shù)組元素不為基本數(shù)據(jù)類(lèi)型
數(shù)組是可以存放任意類(lèi)型的數(shù)據(jù)的,不一定非得是基本數(shù)據(jù)類(lèi)型。數(shù)組元素不為基本原生數(shù)據(jù)類(lèi)型時(shí),存放的是引用類(lèi)型,而不是對(duì)象本身。當(dāng)生成對(duì)象之后,引用才指向?qū)ο?,否則引用為null。
Person[] p = new Person[3];
//未生成對(duì)象時(shí),引用類(lèi)型均為空
System.out.println(p[0]);
//生成對(duì)象之后,引用指向?qū)ο? p[0] = new Person(10);
p[1] = new Person(20);
p[2] = new Person(30);
for(int i = 0; i < p.length; i++){
System.out.println(p[i].age);
}
數(shù)組作為方法的參數(shù)
public void printArray(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
數(shù)組作為方法的返回值
public int[] reverse(int[] list) {
int[] result = new int[list.length];
for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {
result[j] = list[i];
}
return result;
}
數(shù)組內(nèi)容的輸出
首先,這樣寫(xiě)是不對(duì)的。
public static void main(String[] args) {
int a[]={1,9};
System.out.println(a.toString());
}
//[I@61bbe9ba
這輸出的是什么奇怪的東西?我們先不管,后面會(huì)詳細(xì)說(shuō)。那怎么輸出數(shù)組呢?
方式一:
public static void main(String [] args){
int a[]={1,9};
for (int i : a){
System.out.println(i);
}
}
方式二:
public static void main(String [] args){
int a[]={1,9};
System.out.println(Arrays.toString(a));
}
數(shù)組內(nèi)容的比較
數(shù)組內(nèi)容的比較可以使用equals()方法嗎?
看代碼:
public class ArrayTest{
public static void main(String[] args){
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a.equals(b));
//結(jié)果是false。
}
}
所以證明不能直接用equals()方法比較數(shù)組內(nèi)容,因?yàn)闆](méi)有override Object中的實(shí)現(xiàn),所以仍采用其實(shí)現(xiàn),即采用==實(shí)現(xiàn)equals()方法,比較是否為同一個(gè)對(duì)象。
Object類(lèi)中的equals方法默認(rèn)使用==實(shí)現(xiàn)的,至于為什么數(shù)組也能使用equals方法,我們后面再分析。
怎么比較呢?一種解決方案是自己寫(xiě)代碼,另一種方法是利用java.util.Arrays。
java.util.Arrays中的方法全是static的。其中包括了equals()方法的各種重載版本。
代碼如下:
import java.util.Arrays;
public class ArrayEqualsTest{
public static boolean isEquals(int[] a, int[] b){
if( a == null || b == null ){
return false;
}
if(a.length != b.length){
return false;
}
for(int i = 0; i < a.length; ++i ){
if(a[i] != b[i]){
return false;
}
}
return true;
}
public static void main(String[] args){
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(isEquals(a,b));
System.out.println(Arrays.equals(a,b));
}
}
Arrays類(lèi)的使用
java.util.Arrays 類(lèi)能方便地操作數(shù)組,它提供的所有方法都是靜態(tài)的。
具有以下功能:
- 給數(shù)組賦值:通過(guò) fill 方法。
- 對(duì)數(shù)組排序:通過(guò) sort 方法,按升序。
- 比較數(shù)組:通過(guò) equals 方法比較數(shù)組中元素值是否相等。
- 查找數(shù)組元素:通過(guò) binarySearch 方法能對(duì)排序好的數(shù)組進(jìn)行二分查找法操作。
- .......
我覺(jué)得這些大家也都知道,我就不細(xì)說(shuō)了,重點(diǎn)在后面。
數(shù)組的高級(jí)應(yīng)用
二維數(shù)組
二維數(shù)組是數(shù)組的數(shù)組。其實(shí)java只有一維數(shù)組,但是由于數(shù)組可以存放任意類(lèi)型的數(shù)據(jù),當(dāng)然也就可以存放數(shù)組了,這個(gè)時(shí)候,就可以模擬多維數(shù)組了。
基本的定義方式同樣有兩種,如:
type[][] i = new type[2][3];//(推薦)
type i[][] = new type[2][3];
變長(zhǎng)的二維數(shù)組
二維數(shù)組的每個(gè)元素都是一個(gè)一維數(shù)組,這些數(shù)組不一定都是等長(zhǎng)的。
聲明二維數(shù)組的時(shí)候可以只指定第一維大小,空缺出第二維大小,之后再指定不同長(zhǎng)度的數(shù)組。但是注意,第一維大小不能空缺(不能只指定列數(shù)不指定行數(shù))。
public class ArrayTest4{
public static void main(String[] args){
//二維變長(zhǎng)數(shù)組
int[][] a = new int[3][];
a[0] = new int[2];
a[1] = new int[3];
a[2] = new int[1];
//Error: 不能空缺第一維大小
//int[][] b = new int[][3];
}
}
二維數(shù)組也可以在定義的時(shí)候初始化,使用花括號(hào)的嵌套完成,這時(shí)候不指定兩個(gè)維數(shù)的大小,并且根據(jù)初始化值的個(gè)數(shù)不同,可以生成不同長(zhǎng)度的數(shù)組元素。
int[][] c = new int[][]{{1, 2, 3},{4},{5, 6, 7, 8}};
可變參數(shù)
有的時(shí)候,你需要一個(gè)方法,但是你在調(diào)用它之前不知道要傳遞幾個(gè)參數(shù)給他,這個(gè)時(shí)候你就需要可變參數(shù)了。
public static void main(String [] args){
System.out.println(add(2,3));
System.out.println(add(2,3,5));
}
public static int add(int x,int ...args){
int sum=x;
for(int i=0;i<args.length;i++){
sum+=args[i];
}
return sum;
}
那個(gè)奇怪的int ...args就是可變參數(shù),這樣你就可以傳遞任意個(gè)你想傳遞的數(shù)據(jù)了。
java把可變參數(shù)當(dāng)做數(shù)組處理。
注意:可變參數(shù)必須位于最后一項(xiàng)。當(dāng)可變參數(shù)個(gè)數(shù)多余一個(gè)時(shí),必將有一個(gè)不是最后一項(xiàng),所以只支持有一個(gè)可變參數(shù)。因?yàn)閰?shù)個(gè)數(shù)不定,所以當(dāng)其后邊還有相同類(lèi)型參數(shù)時(shí),java無(wú)法區(qū)分傳入的參數(shù)屬于前一個(gè)可變參數(shù)還是后邊的參數(shù),所以只能讓可變參數(shù)位于最后一項(xiàng)。
可變參數(shù)實(shí)質(zhì)上是一個(gè)數(shù)組,所以下面這樣重載是不可以的!
private int sumUp(int... values) {
}
private int sumUp(int[] values) {
}
盡管在背地里,編譯器會(huì)把能匹配不確定個(gè)實(shí)參的形參,轉(zhuǎn)化為數(shù)組形參;而且也可以用數(shù)組包了實(shí)參,再傳遞給實(shí)參個(gè)數(shù)可變的方法;但是,這并不表示“能匹配不確定個(gè)實(shí)參的形參”和“數(shù)組形參”完全沒(méi)有差異。
一個(gè)明顯的差異是,如果按照調(diào)用實(shí)參個(gè)數(shù)可變的方法的形式,來(lái)調(diào)用一個(gè)最后一個(gè)形參是數(shù)組形參的方法,只會(huì)導(dǎo)致一個(gè)“cannot be applied to”的編譯錯(cuò)誤。
比如:
private static void testOverloading(int[] i) {
System.out.println("A");
}
public static void main(String[] args) {
testOverloading(1, 2, 3);//編譯出錯(cuò)
}
這樣是不行的。
除此之外,可變參數(shù)是不可以使用泛型的,關(guān)于泛型,我們下一篇文章會(huì)詳細(xì)講解。
可變參數(shù)還有許多其他的坑,感興趣的可以詳細(xì)了解一下,我就不多說(shuō)了。畢竟。。打字好累啊。
數(shù)組復(fù)制
int[] a = new int[]{1,2};
int[] b = a;
b [1] = 5;
這個(gè)時(shí)候a[1]也變成了5,為什么會(huì)這樣?就不用我多說(shuō)了吧,所以,要拷貝一個(gè)數(shù)組,還是需要些技巧的:
方式一:System.arraycopy的用法
int[] src = {1,3,5,7,9,11,13,15,17};
int[] dest = {2,4,6,8,10,12,14,16,18,20};
//從src中的第一個(gè)元素起復(fù)制三個(gè)元素,即1,3,5復(fù)蓋到dest第2個(gè)元素開(kāi)始的三個(gè)元素
System.arraycopy(src, 0, dest, 1, 3);
System.out.println(Arrays.toString(dest));
//[2, 1, 3, 5, 10, 12, 14, 16, 18, 20]
方式二:Arrays.copyOf的用法
int[] src = {1,3,5,7,9,11,13,15,17};
int[] dest = {2,4,6,8,10,12,14,16,18,20};
//copyOf(是復(fù)制src數(shù)組從0開(kāi)始的兩個(gè)元素到新的數(shù)組對(duì)象)
int[] copyof=Arrays.copyOf(src, 2);
System.out.println(Arrays.toString(copyof));
//[1, 3]
方式三:Arrays.copyOfRange的用法
nt[] src = {1,3,5,7,9,11,13,15,17};
int[] dest = {2,4,6,8,10,12,14,16,18,20};
//copyRange(從src數(shù)組中從0開(kāi)始的第二個(gè)元素到第五個(gè)元素復(fù)制到新數(shù)組,含頭不含尾)
int[] copyofRange=Arrays.copyOfRange(src, 2,6);
System.out.println(Arrays.toString(copyofRange));
//[5, 7, 9, 11]
數(shù)組到底是什么
說(shuō)了那么多,那么,數(shù)組究竟是個(gè)什么東西呢?
我們來(lái)看看數(shù)組有沒(méi)有什么可以用的方法:
喲,還真有?怎么看著這么像Object類(lèi)里那幾個(gè)方法啊!這其中,必有蹊蹺。
來(lái)看這段代碼:
public class Test {
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("array的父類(lèi)是:" + array.getClass().getSuperclass());
System.out.println("array的類(lèi)名是:" + array.getClass().getName());
}
}
//array的父類(lèi)是:class java.lang.Object
//array的類(lèi)名是:[I
從上面示例可以看出,數(shù)組的是Object的直接子類(lèi),它屬于“第一類(lèi)對(duì)象”,但是它又與普通的java對(duì)象存在很大的不同,從它的類(lèi)名就可以看出:[I,這是什么東東??
我們?cè)倏慈缦率纠?/p>
public class Test {
public static void main(String[] args) {
int[] array_00 = new int[10];
System.out.println("一維數(shù)組:" + array_00.getClass().getName());
int[][] array_01 = new int[10][10];
System.out.println("二維數(shù)組:" + array_01.getClass().getName());
int[][][] array_02 = new int[10][10][10];
System.out.println("三維數(shù)組:" + array_02.getClass().getName());
}
}
//一維數(shù)組:[I
//二維數(shù)組:[[I
//三維數(shù)組:[[[I
通過(guò)這個(gè)實(shí)例我們知道:[代表了數(shù)組的維度,一個(gè)[表示一維,兩個(gè)[表示二維。可以簡(jiǎn)單的說(shuō)數(shù)組的類(lèi)名由若干個(gè)'['和數(shù)組元素類(lèi)型的內(nèi)部名稱組成。不清楚我們?cè)倏矗?/p>
public class Test {
public static void main(String[] args) {
System.out.println("Object[]:" + Object[].class);
System.out.println("Object[][]:" + Object[][].class);
System.err.println("Object[][][]:" + Object[][][].class);
System.out.println("Object:" + Object.class);
}
}
//Object[]:class [Ljava.lang.Object;
//Object[][]:class [[Ljava.lang.Object;
//Object[][][]:class [[[Ljava.lang.Object;
//Object:class java.lang.Object
從這個(gè)實(shí)例我們可以看出數(shù)組的“廬山真面目”。同時(shí)也可以看出數(shù)組和普通的Java類(lèi)是不同的,普通的java類(lèi)是以全限定路徑名+類(lèi)名來(lái)作為自己的唯一標(biāo)示的,而數(shù)組則是以若干個(gè)[+L+數(shù)組元素類(lèi)全限定路徑+類(lèi)來(lái)最為唯一標(biāo)示的。這個(gè)不同也許在某種程度上說(shuō)明了數(shù)組也普通java類(lèi)在實(shí)現(xiàn)上存在很大的區(qū)別,也許可以利用這個(gè)區(qū)別來(lái)使得JVM在處理數(shù)組和普通java類(lèi)時(shí)作出區(qū)分。
我們?cè)趈dk中并沒(méi)有找到一個(gè)可以代表數(shù)組的類(lèi),但是數(shù)組的的確確是Object類(lèi)的一個(gè)子類(lèi),那么,它究竟是從哪冒出來(lái)的呢?
數(shù)組是對(duì)象
首先,數(shù)組是對(duì)象!
但是這個(gè)數(shù)組對(duì)象并不是從某個(gè)類(lèi)實(shí)例化來(lái)的,而是由JVM直接創(chuàng)建的,因此查看類(lèi)名的時(shí)候會(huì)發(fā)現(xiàn)是很奇怪的樣子,這個(gè)直接創(chuàng)建的對(duì)象的父類(lèi)就是Object,所以可以調(diào)用Object中的所有方法,包括你用到的toString()。
所以我們之前的輸出問(wèn)題就很明顯了,因?yàn)檎{(diào)用的toString()方法是來(lái)自于Object的,這個(gè)方法的實(shí)現(xiàn)是
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
所以就打出了類(lèi)似于[I@61bbe9ba這樣的稀奇古怪的數(shù)字。
如果要輸出“{1,9}”這樣的內(nèi)容,可以寫(xiě)一個(gè)循環(huán)逐個(gè)輸出,或者使用Arrays.toString()輸出。
數(shù)組的length屬性也是jvm添加的,數(shù)組一初始化,jvm就會(huì)給它一個(gè)固定的length【屬性】,在它的生命周期中不可變。
數(shù)組的協(xié)變
java中數(shù)組為什么要設(shè)計(jì)為協(xié)變的?
比如:
Number[] num = new Integer[10];
num[0] = 2.1;
這樣的語(yǔ)句可以通過(guò)編譯,而在運(yùn)行時(shí)會(huì)錯(cuò)誤。
那為何不禁止數(shù)組協(xié)變,在編譯期間就指出錯(cuò)誤呢?
因?yàn)镾E5之前還沒(méi)有泛型,但很多代碼迫切需要泛型來(lái)解決問(wèn)題。
舉個(gè)例子,比較兩個(gè)數(shù)組是否“值相等“的Arrays.equals( )方法。因?yàn)榈讓訉?shí)現(xiàn)調(diào)用的是Object.equals( )方法,和數(shù)組中元素的具體類(lèi)型無(wú)關(guān)。
for (int i=0; i<length; i++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
所以不想讓每個(gè)類(lèi)型都要重新定義Arrays.equals( )方法。而是”泛化“地接受任何元素類(lèi)型的數(shù)組為參數(shù),就像現(xiàn)在這樣:
public static boolean equals(Object[] a, Object[] a2) {
... ...
}
要讓Object[]能接受所有數(shù)組類(lèi)型,那個(gè)時(shí)候又沒(méi)有泛型,最簡(jiǎn)單的辦法就是讓數(shù)組接受協(xié)變,把String[],Integer[]都定義成Object[]的派生類(lèi),然后多態(tài)就起作用了。
但為什么數(shù)組設(shè)計(jì)成”協(xié)變“不會(huì)有大問(wèn)題呢?這是基于數(shù)組的一個(gè)獨(dú)有特性:
數(shù)組記得它內(nèi)部元素的具體類(lèi)型,并且會(huì)在運(yùn)行時(shí)做類(lèi)型檢查。
這就是上面的代碼能通過(guò)編譯,但運(yùn)行時(shí)報(bào)錯(cuò)的原因:
Number[] num = new Integer[10];
num[0] = 2.1; //Error
num變量記得它內(nèi)部元素是Integer。所以運(yùn)行時(shí)給它插入double型的時(shí)候不讓執(zhí)行。
這反而是數(shù)組的優(yōu)點(diǎn),也是當(dāng)初”敢于“把數(shù)組設(shè)計(jì)成協(xié)變的原因。雖然向上轉(zhuǎn)型以后,編譯期類(lèi)型檢查放松了,但因?yàn)閿?shù)組運(yùn)行時(shí)對(duì)內(nèi)部元素類(lèi)型看得緊,不匹配的類(lèi)型還是插不進(jìn)去的。
這也是為什么容器Collection不能設(shè)計(jì)成協(xié)變的原因。Collection不做運(yùn)行時(shí)類(lèi)型檢查,比較耿直。還是題主Number的例子,如果Collection接受”協(xié)變“,List<Integer>的引用能傳給List<Number>:
List<Integer> integerList = new ArrayList<Integer>();
List<Number> num = integerList; // 假設(shè)現(xiàn)在容器接受”協(xié)變“
這時(shí)候我想往List<Number>里插入一個(gè)Double。它不會(huì)像數(shù)組這樣”堅(jiān)貞“,它將”安靜“地接受。
num.add(new Double(2.1));
然后當(dāng)我們從原先的integerList里面取東西,才會(huì)發(fā)現(xiàn)出問(wèn)題了。雖然看上去從integerList里取Integer,我們的操作無(wú)可指責(zé)。但取出來(lái)的卻是Double型。
Integer itg=integerList.get(0); //BOOM!
于其到拿出來(lái)之后才發(fā)現(xiàn)不對(duì),那還不如當(dāng)初就不讓插入。這就是數(shù)組的好處。
而且,在引入了通配符(Wildcard)之后,協(xié)變的功能也已經(jīng)被實(shí)現(xiàn)了。而且配合通配符的”上界“和”下界“一起用,容器內(nèi)元素的類(lèi)型還是受到嚴(yán)格控制的,雖然有點(diǎn)復(fù)雜。
List<? extends Number> derivedNum=new ArrayList<Integer>();
所以總的來(lái)說(shuō),雖然數(shù)組的協(xié)變不是一個(gè)完美的設(shè)計(jì),但也不能算非常爛。起碼還能用,沒(méi)有捅出大簍子。而且數(shù)組又不支持泛型,底層類(lèi)庫(kù)到處是Object[],現(xiàn)在也不可能改了。
數(shù)組不支持泛型
比如:
List<String>[] l = new ArrayList<String>[10];
會(huì)報(bào)錯(cuò),無(wú)法編譯通過(guò)
根本的原因是:數(shù)組在創(chuàng)建的時(shí)候必須知道內(nèi)部元素的類(lèi)型,而且一直都會(huì)記得這個(gè)類(lèi)型信息,每次往數(shù)組里添加元素,都會(huì)做類(lèi)型檢查。
但因?yàn)镴ava泛型是用擦除(Erasure)實(shí)現(xiàn)的,運(yùn)行時(shí)類(lèi)型參數(shù)會(huì)被擦掉。所以對(duì)于泛型數(shù)組,編譯器看不到泛型的String類(lèi)型參數(shù)。數(shù)組由于無(wú)法確定所持有元素的類(lèi)型,所以不允許初始化。
具體我們會(huì)在下一篇《泛型》中詳細(xì)說(shuō)明。
內(nèi)存中的數(shù)組
數(shù)組的內(nèi)存模型
- 一維數(shù)組:
int arr[] = new int[3];
- 二維數(shù)組:
int[ ][ ] arr = new int[3][ ];
arr[0] = new int[3];
arr[1] = new int[5];
arr[2] = new int[4];
總結(jié)
- 數(shù)組的定義推薦使用
int[] a方式。 - 數(shù)組的長(zhǎng)度是不可變的。
- 數(shù)組是特殊的對(duì)象,父類(lèi)是Object類(lèi)。
- java不支持泛型數(shù)組。
- 數(shù)組是協(xié)變的。
- 數(shù)組中可以保存任意類(lèi)型的數(shù)據(jù),從而可以創(chuàng)建多維數(shù)組。
本篇文章就到這里。如果文章內(nèi)容有什么錯(cuò)誤或者更好的理解,請(qǐng)及時(shí)與我聯(lián)系。
本文首發(fā)自我的個(gè)人博客:
地址:http://wpblog.improvecfan.cn/
同步更新于csdn:
地址:http://blog.csdn.net/qq_31655965
同步更新于簡(jiǎn)書(shū):
地址:http://www.itdecent.cn/u/8dc5811b228f
轉(zhuǎn)載請(qǐng)注明出處?。。。。?!
看完了,如果對(duì)你有用,隨心后點(diǎn)個(gè)贊唄~
引用:
《java編程思想》
《java核心卷一》
https://www.zhihu.com/question/21394322
http://www.cnblogs.com/jjdcxy/p/5870524.html
http://blog.csdn.net/renfufei/article/details/15503469
http://www.cnblogs.com/chenssy/p/3463719.html