詳解數據結構中的「數組」與編程語言中的「數組」的區別和聯繫

在大部分數據結構和算法書籍中,數組做爲最基礎的數據類型,是最早被介紹的。通常咱們都是這麼定義數組的。javascript

數組這種數據結構,是存儲相同數據類型的一塊連續的存儲空間。
解讀一下這個定義的話,那就是:數組中的數據必須是相同類型的,數組中的數據必須是連續存儲的。
只有這樣,數組才能實現根據下標快速地(時間複雜度是O(1))定位一個元素。前端

可是,若是你是一名比較喜歡鑽研的程序員,你會發現,在你所熟悉的編程語言中,」數組「這種數據類型,並不必定徹底符合上面的定義。好比Javascript這種語言中,數組中的數據不必定是連續存儲的,也不必定非得是相同類型,甚至數組能夠是變長的。java

var arr = new Array(4,'hello', new Date());
複製代碼

除此以外,大部分數據結構和算法書籍中,在講到二維或者多維數組中數據的存儲方式的時候,通常都會這麼說:程序員

二維數組中的數據,是先按行再按列(或者先按列後按行),依次存儲在連續的存儲空間中。
若是二維數組定義爲a[n][m],那a[i][j]的尋址公式爲下面這樣(先按行後按列存儲):
address_a[i][j] = address_base + (i*m+j) * data_size;
算法

可是,在有些編程語言中,二維數組並不知足上面的說法和尋址公式。好比,Java中的二維數組,第二維能夠是不一樣長度的,並且第二維的三個數組(arr[0]、arr[1]、arr[2])並非連續存儲。編程

int arr[][] = new int[3][];
arr[0] = new int[1];
arr[1] = new int[2];
arr[2] = new int[3];
複製代碼

是否是看的一頭霧水?難道數據結構和算法書籍裏的講解脫離實踐?難道編程語言中的數組沒有徹底按照數組的定義來設計?哪一個對哪一個錯呢?數組

實際上,兩個都沒錯。編程語言中的」數組「並不徹底等同於,咱們在講數據結構和算法的時候,提到的」數組「。編程語言在實現本身的」數組「類型的時候,並非徹底遵循數據結構」數組「的定義,而是針對編程語言自身的特色,作了調整。微信

在不一樣的編程語言中,數組這種數據類型的實現方式都不大相同,我就拿幾個比較典型的編程語言:C/C++、Java、Javascript,來給你展現一下,幾種比較有表明性的數組實現方式。前端工程師

1. C/C++中數組的實現方式

C/C++中的數組,是很是標準的數據結構中的數組,也就是連續存儲相同類型的數據的一塊內存空間。在C/C++中,不論是基本類型數據,好比int、long、char,仍是結構體、對象,在數組中都是連續存儲的。我舉了一下例子,你能夠看下。數據結構

int arr[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
複製代碼

數組arr中存儲的是int基本類型的數據,對應的內存存儲方式,若是用畫圖的方式表示出來的話,就是下面這樣子。從圖中能夠看出,數據是存儲在一片連續的內存空間中的。

剛剛講的是用數組存儲基本類型數據的例子,咱們再來看:用數組存儲struct結構體(或者class對象)的例子。

struct Dog {
  char a;
  char b;
};
struct Dog arr[3];
// 爲了節省頁面,放到了一行裏了
arr[0].a = '0'; arr[0].b = '1'; 
arr[1].a = '2'; arr[1].b = '3';
arr[2].a = '4'; arr[2].b = '5';
複製代碼

若是咱們把這個結構體數組,用畫圖的方式表示出來,就是下面這個樣子。咱們發現,結構體數組中的元素,也是存儲在一片連續的內存空間中的。

剛剛講的都是一維數組的數據存儲方式,咱們再來看下,二維數組的數據存儲方式。注意,多維數組跟二維數組大同小異,咱們就拿二維數組來說解。咱們來看下面這段代碼。

struct Dog {
  char a;  
  char b;
};
struct Dog arr[3][2];
複製代碼

咱們把上面的struct Dog arr[3][2]對應的數據存儲方式,用圖畫出來的話,就是下面這樣子的。從圖中,咱們發現,C/C++的二維數組,跟數據結構中二維數組是同樣的,數據是先按行後按列,而且是連續存儲的。

剛剛咱們分析了C/C++的基本數據類型數組、結構體或對象數組、以及二維數組。它們的數據存儲方式,徹底符合數據結構和算法中數組的定義。

你還知道,在其餘哪些編程語言中,數組的定義徹底符合數據結構中數組的定義嗎?

2. Java中數組的實現方式

看完了C/C++中的數組,咱們再來看下,Java中的數組。Java中的數組就有點跟數據結構中數組的定義不同了。咱們仍是分三種狀況來分析。這三種狀況分別是:基本數據類型數組、對象數組、二維數組(或多維數組)。

首先,咱們先來看下基本數據類型數組,也就是說,數組中存儲的是int、long、char等基本數據類型的數據。咱們仍是拿一段代碼來舉例。

int arr[] = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
複製代碼

若是咱們把arr中數據在內存中的存儲方式,用圖畫出來的話,就是下面這個樣子的。注意,new申請的空間在堆上,arr存儲在棧上。arr存儲的是數組空間的首地址。

從圖中來看,在Java中,基本數據類型數組仍是符合數據結構中數組的定義的。數組中數據是相同類型的、而且存儲在一片連續的內存空間中。

看完了基本數據類型數組,咱們再來看下對象數組,也就是說,數組中存儲的不是int、long、char這種基本類型數據了,而是對象。咱們仍是拿一個例子來講明。

class Person {  
  private String name;  
  public Person(String name) {
    this.name = name;  
  }
}

Person arr[] = new Person[3];
arr[0] = new Person("0");
arr[1] = new Person("1");
arr[2] = new Person("2");
複製代碼

在上面的代碼中,數組arr中存儲是Person對象。一樣,咱們仍是把數組中數據在內存中的存儲方式,用畫圖的方式表示出來。

從圖中,你有沒有發現,在Java中,對象數組的存儲方式,已經跟C/C++中對象數組的存儲方式,不大同樣了。在Java中,對象數組中存儲的是對象在內存中的地址,而非對象自己。對象自己在內存中並非連續存儲的,而是散落在各個地方的。

瞭解了一維數組的存儲方式,咱們再來看下,Java中的二維數組或者多維數組。前面也提到了,由於多維數組跟二維數組相似,咱們仍是隻拿二維數組來說解。

Java中的二維數組,跟數據結構中二維數組,有很大區別。在Java中,二維數組中的第二維,能夠是不一樣長度的。這句話有點很差理解。我舉個例子說明一下。

int arr[][] = new int[3][];
arr[0] = new int[1];
arr[1] = new int[2];
arr[2] = new int[3];
複製代碼

在上面的代碼中,arr是一個二維數組,第一維長度是3,第二維的長度各不相同:arr[0]長度是1,arr[1]長度是2,arr[2]長度是3。若是咱們把這個數組在內存中的存儲方式,用圖畫出來的話,就是下面這個樣子。

剛剛這個二維數組存儲的是基本數據類型,咱們再來看下,若是二維數組中存儲的是對象,那又會是怎麼的數據存儲方式呢?咱們仍是拿個例子來講明。

Person arr[][] = new Person[3][];
arr[0] = new Person[1];
arr[1] = new Person[2];
arr[2] = new Person[3];

arr[0][0] = new Person("0");
arr[1][1] = new Person("1");
複製代碼

在上面的代碼中,Person arr[][]是一個二維對象數組。對於它在內存中存儲方式,你能夠在紙上先畫下,或者在本身腦海中想下,而後,再來對比一下我畫的下面這張圖。

我總結一下。在Java這種編程語言中,數組這種數據類型,除了存儲基本數據類型的一維數組以外,對象數組、二維數組,都跟數據結構中數組的定義,有很大區別了。

3. JavaScript中數組的實現方式

若是咱們說,Java中的數組,只是根據語言本身的特色,在數據結構數組基礎之上,作的改造的話,那JavaScript這種動態腳本語言中的數組,徹底就被改的「面目全非」了。

在開頭的時候,咱們已經提到過,JavaScript中的數組,能夠存儲不一樣類型的數據,數組中的數據也不必定是連續存儲的(按照下標隨機訪問的效率不高),而且還能支持變長數組。這徹底就是跟數據結構中數組的定義反着的。若是你是一名Web前端工程師,你應該會對此很困惑吧?

實際上,JavaScript中數組的底層實現原理,已經不是依賴數據結構中的數組了。也就是說,JavaScript中的數組只不過是名字叫數組而已,跟數據結構中數組沒啥太大關係。

接下來,咱們就來看下,JavaScript中的數組,底層是如何實現的呢?實際上,JavaScript中的數組,會根據你存儲數據的不一樣,選擇不從的實現方式。

若是數組中存儲的是相同類型的數據,那JavaScript就真的用數據結構中數組來實現。也就是說,會分配一塊連續的內存空間來存儲數據。

若是數組中存儲的是非相同類型的數據,那JavaScript就用相似散列表的結構來存儲數據。也就是說,數據並非連續存儲在內存中的。這也是JavaScript數組支持存儲不一樣類型數據的緣由。

若是你往一個存儲了相同類型數據的數組中,插入一個不一樣類型的數據,那JavaScript會將底層的存儲結構,從數組變成散列表。

若是你熟悉JavaScript,你應該知道,JavaScript爲了照顧一些底層應用的開發者,還提供了另一種數據類型,叫作ArrayBuffer。而ArrayBuffer才符合標準的數據結構中數組的定義。它分配一片連續的內存空間,僅僅用來存儲相同類型的數據。

最後,總結

數據結構和算法先於編程語言出現。編程語言中是一些數據類型,並不能跟數據結構和算法書籍中講到的經典數據結構,徹底一一對應。好比咱們今天講到的數組,不少編程語言中,都會有數組這種數據類型,而它們每每會根據本身語言的特色,在實現上作了調整。

歡迎留言說說,在你熟悉的語言中,數組這種數據類型符不符合標準的數據結構中數組的定義?或者,說一說,還有哪些數據類型,雖然名字跟數據結構中講到的同樣,但在實現上卻有很大不一樣呢?

關注個人微信公衆號:小爭哥,獲取更多、更新的技術、非技術分享。
做者:前Google工程師,5萬人訂閱《數據結構和算法之美》專欄做者。
但願經過我加速你的技術、職場進步。

相關文章
相關標籤/搜索