咱們都知道,計算機是處理數據的設備,而數據的主要存儲位置就是磁盤
和內存
,而且對於程序員來說,CPU 和內存是咱們必須瞭解的兩個物理結構,它是你通向高階程序員很重要的橋樑,那麼本篇文章咱們就來介紹一下基本的內存知識。html
什麼是內存
內存(Memory)是計算機中最重要的部件之一,它是程序與CPU進行溝通的橋樑。計算機中全部程序的運行都是在內存中進行的,所以內存對計算機的影響很是大,內存又被稱爲主存
,其做用是存放 CPU 中的運算數據,以及與硬盤等外部存儲設備交換的數據。只要計算機在運行中,CPU 就會把須要運算的數據調到主存中進行運算,當運算完成後CPU再將結果傳送出來,主存的運行也決定了計算機的穩定運行。程序員
內存的物理結構
在瞭解一個事物以前,你首先得先須要見
過它,你纔會有印象,纔會有想要了解的興趣,因此咱們首先須要先看一下什麼是內存以及它的物理結構是怎樣的。編程
內存的內部是由各類IC電路組成的,它的種類很龐大,可是其主要分爲三種存儲器數組
- 隨機存儲器(RAM): 內存中最重要的一種,表示既能夠從中讀取數據,也能夠寫入數據。當機器關閉時,內存中的信息會
丟失
。 - 只讀存儲器(ROM):ROM 通常只能用於數據的讀取,不能寫入數據,可是當機器停電時,這些數據不會丟失。
- 高速緩存(Cache):Cache 也是咱們常常見到的,它分爲一級緩存(L1 Cache)、二級緩存(L2 Cache)、三級緩存(L3 Cache)這些數據,它位於內存和 CPU 之間,是一個讀寫速度比內存
更快
的存儲器。當 CPU 向內存寫入數據時,這些數據也會被寫入高速緩存中。當 CPU 須要讀取數據時,會直接從高速緩存中直接讀取,固然,如須要的數據在Cache中沒有,CPU會再去讀取內存中的數據。
內存 IC 是一個完整的結構,它內部也有電源、地址信號、數據信號、控制信號和用於尋址的 IC 引腳來進行數據的讀寫。下面是一個虛擬的 IC 引腳示意圖緩存
圖中 VCC 和 GND 表示電源,A0 - A9 是地址信號的引腳,D0 - D7 表示的是數據信號、RD 和 WR 都是控制信號,我用不一樣的顏色進行了區分,將電源鏈接到 VCC 和 GND 後,就能夠對其餘引腳傳遞 0 和 1 的信號,大多數狀況下,+5V 表示1,0V 表示 0。markdown
咱們都知道內存是用來存儲數據,那麼這個內存 IC 中能存儲多少數據呢?D0 - D7 表示的是數據信號,也就是說,一次能夠輸入輸出 8 bit = 1 byte 的數據。A0 - A9 是地址信號共十個,表示能夠指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024個地址
。每一個地址都會存放 1 byte 的數據,所以咱們能夠得出內存 IC 的容量就是 1 KB。數據結構
若是咱們使用的是 512 MB 的內存,這就至關因而 512000(512 * 1000) 個內存 IC。固然,一臺計算機不太可能有這麼多個內存 IC ,然而,一般狀況下,一個內存 IC 會有更多的引腳,也就能存儲更多數據。post
內存的讀寫過程
讓咱們把關注點放在內存 IC 對數據的讀寫過程上來吧!咱們來看一個對內存IC 進行數據寫入和讀取的模型學習
來詳細描述一下這個過程,假設咱們要向內存 IC 中寫入 1byte 的數據的話,它的過程是這樣的:url
- 首先給 VCC 接通 +5V 的電源,給 GND 接通 0V 的電源,使用
A0 - A9
來指定數據的存儲場所,而後再把數據的值輸入給D0 - D7
的數據信號,並把WR(write)
的值置爲 1,執行完這些操做後,便可以向內存 IC 寫入數據 - 讀出數據時,只須要經過 A0 - A9 的地址信號指定數據的存儲場所,而後再將 RD 的值置爲 1 便可。
- 圖中的 RD 和 WR 又被稱爲控制信號。其中當WR 和 RD 都爲 0 時,沒法進行寫入和讀取操做。
內存的現實模型
爲了便於記憶,咱們把內存模型映射成爲咱們現實世界的模型,在現實世界中,內存的模型很想咱們生活的樓房。在這個樓房中,1層能夠存儲一個字節的數據,樓層號就是地址
,下面是內存和樓層整合的模型圖
咱們知道,程序中的數據不只只有數值,還有數據類型
的概念,從內存上來看,就是佔用內存大小(佔用樓層數)的意思。即便物理上強制以 1 個字節爲單位來逐一讀寫數據的內存,在程序中,經過指定其數據類型,也能實現以特定字節數爲單位來進行讀寫。
下面是一個以特定字節數爲例來讀寫指令字節的程序的示例
// 定義變量 char a; short b; long c; // 變量賦值 a = 123; b = 123; c = 123;
咱們分別聲明瞭三個變量 a,b,c ,並給每一個變量賦上了相同的 123,這三個變量表示內存的特定區域。經過變量,即便不指定物理地址,也能夠直接完成讀寫操做,操做系統會自動爲變量分配內存地址。
這三個變量分別表示 1 個字節長度的 char,2 個字節長度的 short,表示4 個字節的 long。所以,雖然數據都表示的是 123,可是其存儲時所佔的內存大小是不同的。以下所示
這裏的 123 都沒有超過每一個類型的最大長度,因此 short 和 long 類型爲所佔用的其餘內存空間分配的數值是0,這裏咱們採用的是低字節序列
的方式存儲
低字節序列:將數據低位存儲在內存低位地址。
高字節序列:將數據的高位存儲在內存地位的方式稱爲高字節序列。
內存的使用
指針
指針是 C 語言很是重要的特徵,指針也是一種變量,只不過它所表示的不是數據的值,而是內存的地址。經過使用指針,能夠對任意內存地址的數據進行讀寫。
在瞭解指針讀寫的過程前,咱們先須要瞭解如何定義一個指針,和普通的變量不一樣,在定義指針時,咱們一般會在變量名前加一個 *
號。例如咱們能夠用指針定義以下的變量
char *d; // char類型的指針 d 定義 short *e; // short類型的指針 e 定義 long *f; // long類型的指針 f 定義
咱們以32
位計算機爲例,32位計算機的內存地址是 4 字節,在這種狀況下,指針的長度也是 32 位。然而,變量 d e f 卻表明了不一樣的字節長度,這是爲何呢?
實際上,這些數據表示的是從內存中一次讀取的字節數,好比 d e f 的值都爲 100,那麼使用 char 類型時就可以從內存中讀寫 1 byte 的數據,使用 short 類型就可以從內存讀寫 2 字節的數據, 使用 long 就可以讀寫 4 字節的數據,下面是一個完整的類型字節表
類型 | 32位 | 64位 |
---|---|---|
char | 1 | 1 |
short int | 2 | 2 |
int | 4 | 4 |
unsigned int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
long | 4 | 8 |
long long | 8 | 8 |
unsigned long | 4 | 8 |
咱們能夠用圖來描述一下這個讀寫過程
數組是內存的實現
數組是指多個相同
的數據類型在內存中連續排列的一種形式。做爲數組元素的各個數據會經過下標編號
來區分,這個編號也叫作索引
,如此一來,就能夠對指定索引的元素進行讀寫操做。
首先先來認識一下數組,咱們仍是用 char、short、long 三種元素來定義數組,數組的元素用[value]
擴起來,裏面的值表明的是數組的長度,就像下面的定義
char g[100]; short h[100]; long i[100];
數組定義的數據類型,也表示一次可以讀寫的內存大小,char 、short 、long 分別以 1 、2 、4 個字節爲例進行內存的讀寫。
數組是內存的實現,數組和內存的物理結構徹底一致,尤爲是在讀寫1個字節的時候,當字節數超過 1 時,只能經過逐個字節來讀取,下面是內存的讀寫過程
數組是咱們學習的第一個數據結構,咱們都知道數組的檢索效率是比較快的,至於數組的檢索效率爲何這麼快並非咱們這篇文章討論的重點。
棧和隊列
咱們上面提到數組是內存的一種實現,使用數組可以使編程更加高效,下面咱們就來認識一下其餘數據結構,經過這些數據結構也能夠操做內存的讀寫。
棧
棧(stack)是一種很重要的數據結構,棧採用 LIFO(Last In First Out)即後入先出
的方式對內存進行操做。它就像一個大的收納箱,你能夠往裏面放相同類型的東西,好比書,最早放進收納箱的書在最下面,最後放進收納箱的書在最上面,若是你想拿書的話, 必須從最上面開始取,不然是沒法取出最下面的書籍的。
棧的數據結構就是這樣,你把書籍壓入收納箱的操做叫作壓入(push)
,你把書籍從收納箱取出的操做叫作彈出(pop)
,它的模型圖大概是這樣
入棧至關因而增長操做,出棧至關因而刪除操做,只不過叫法不同。棧和內存不一樣,它不須要指定元素的地址。它的大概使用以下
// 壓入數據 Push(123); Push(456); Push(789); // 彈出數據 j = Pop(); k = Pop(); l = Pop();
在棧中,LIFO 方式表示棧的數組中所保存的最後面的數據(Last In)會被最早讀取出來(First On)。
隊列
隊列
和棧很類似但又不一樣,相同之處在於隊列也不須要指定元素的地址,不一樣之處在於隊列是一種 先入先出(First In First Out)
的數據結構。隊列在咱們生活中的使用很像是咱們去景區排隊買票同樣,第一個排隊的人最早買到票,以此類推,俗話說: 先到先得。它的使用以下
// 往隊列中寫入數據 EnQueue(123); EnQueue(456); EnQueue(789); // 從隊列中讀出數據 m = DeQueue(); n = DeQueue(); o = DeQueue();
向隊列中寫入數據稱爲 EnQueue()
入列,從隊列中讀出數據稱爲DeQueue()
。
與棧相對,FIFO 的方式表示隊列中最早所保存的數據會優先被讀取出來。
隊列的實現通常有兩種:順序隊列
和 循環隊列
,咱們上面的事例使用的是順序隊列,那麼下面咱們看一下循環隊列的實現方式
環形緩衝區
循環隊列通常是以環狀緩衝區(ring buffer)
的方式實現的,它是一種用於表示一個固定尺寸、頭尾相連的緩衝區的數據結構,適合緩存數據流。假如咱們要用 6 個元素的數組來實現一個環形緩衝區,這時能夠從起始位置開始有序的存儲數據,而後再按照存儲時的順序把數據讀出。在數組的末尾寫入數據後,後一個數據就會從緩衝區的頭開始寫。這樣,數組的末尾和開頭就鏈接了起來。
鏈表
下面咱們來介紹一下鏈表
和 二叉樹
,它們都是能夠不用考慮索引的順序就能夠對元素進行讀寫的方式。經過使用鏈表,能夠高效的對數據元素進行添加
和 刪除
操做。而經過使用二叉樹,則能夠更高效的對數據進行檢索
。
在實現數組的基礎上,除了數據的值以外,經過爲其附帶上下一個元素的索引,便可實現鏈表
。數據的值和下一個元素的地址(索引)就構成了一個鏈表元素,以下所示
對鏈表的添加和刪除都是很是高效的,咱們來敘述一下這個添加和刪除的過程,假如咱們要刪除地址爲 p[2]
的元素,鏈表該如何變化呢?
咱們能夠看到,刪除地址爲 p[2] 的元素後,直接將鏈表剔除,並把 p[2] 前一個位置的元素 p[1] 的指針域
指向 p[2] 下一個鏈表元素的數據區便可。
那麼對於新添加進來的鏈表,須要肯定插入位置
,好比要在 p[2] 和 p[3] 之間插入地址爲 p[6]
的元素,須要將 p[6] 的前一個位置 p[2] 的指針域改成 p[6] 的地址,而後將 p[6] 的指針域改成 p[3] 的地址便可。
鏈表的添加不涉及到數據的移動
,因此鏈表的添加和刪除很快,而數組的添加設計到數據的移動,因此比較慢,一般狀況下,使用數組來檢索數據,使用鏈表來進行添加和刪除操做。
二叉樹
二叉樹
也是一種檢索效率很是高的數據結構,二叉樹是指在鏈表的基礎上往數組追加元素時,考慮到數組的大小關係,將其分紅左右兩個方向的表現形式。假如咱們把 50 這個值保存到了數組中,那麼,若是接下來要進行值寫入的話,就須要和50比較,肯定誰大誰小,比50數值大的放右邊,小的放左邊,下圖是二叉樹的比較示例
二叉樹是由鏈表發展而來,所以二叉樹在追加和刪除元素方面也是一樣有效的。
這一切的演變都是之內存爲基礎的。
下面爲本身作個宣傳,歡迎關注公衆號 Java建設者,號主是Java技術棧,熱愛技術,喜歡閱讀,熱衷於分享和總結,但願能把每一篇好文章分享給成長道路上的你。公號回覆002,有你想要的資源。
文章參考:
https://www.computerhope.com/jargon/m/memory.htm
https://baike.baidu.com/item/隊列/14580481?fr=aladdin