導航:首頁 > 編程語言 > python實現雙向鏈表

python實現雙向鏈表

發布時間:2023-03-29 20:05:09

㈠ 在python中, list index out of range是什麼意思

在python中,list index out of range意思是列表的索引分配超出列范圍。

對於有序序列: 字元串 str 、列表 list 、元組 tuple進行按索引取值的時候,默認范圍為 0 ~ len(有序序列)-1,計數從0開始,而不是從1開始,最後一位索引則為總長度減去1。

當然也可以使用負數指喊表示從倒數第幾個,計數從-1開始,則對於有序序列,總體范圍為 -len(有序序列) ~ len(有序序列)-1,如果輸入的取值結果不在這個范圍內,則報這個錯。

解決辦法:檢查慧仔索引是否在-len(有序序列) ~ len(有序序列)-1范圍內,修改正確。如下圖,len(a)=2,則對於有序序列,總體范圍為 0~ 1。

(1)python實現雙向鏈表擴展閱讀:

在Python編程語言中List 是標准類庫中的一個類,可以簡單視之為雙向鏈表,以線性列的方式管理物件集合。

list 的特色是在集合的任何位置增加或刪除元素都很快,但是不支持隨機存取。list 是類庫提供的眾多容器(container)之一,除此之外還有vector、set、map、…等等。

list() 方法用於將元組轉換為列表。註:元組與列表是非常類似的,區別在於元組的元素值不能修改,元組是放在括弧中,列表是放於方括弧中。

list 以模板方式實現(即泛型),可以處理任意型別的變數,包括使用者自定義的資料型態例如:它可以是一個放置整數(int)型態的 list、也可以是放置字串(char 或 string)型態的 list、或者放置使用者自定類別前逗汪(user-defined class)的 list。

㈡ python的內存管理機制

論壇

活動

招聘

專題

打開CSDN APP
Copyright © 1999-2020, CSDN.NET, All Rights Reserved

登錄

XCCS_澍
關注
Python 的內存管理機制及調優手段? 原創
2018-08-05 06:50:53

XCCS_澍

碼齡7年

關注
內存管理機制:引用計數、垃圾回收、內存池。
一、引用計數:
    引用計數是一種非常高效的內存管理手段, 當一個 Python 對象被引用時其引用計數增加 1, 當其不再被一個變數引用時則計數減 1. 當引用計數等於 0 時對象被刪除。
二、垃圾回收 :
1. 引用計數
      引用計數也是一種垃圾收集機制,而且也是一種最直觀,最簡單的垃圾收集技術。當 Python 的某個對象的引用計數降為 0 時,說明沒有任何引用指向該對象,該對象就成為要被回收的垃圾了。比如某個新建對象,它被分配給某個引用,對象的引用計數變為 1。如果引用被刪除,對象的引用計數為 0,那麼該對象就可以被垃圾回收。不過如果出現循環引用的話,引用計數機制就不再起有效的作用了
2. 標記清除
     如果兩個對象的引用計數都為 1,但是僅僅存在他們之間的循環引用,那麼這兩個對象都是需要被回收的,也就是說,它們的引用計數雖然表現為非 0,但實際上有效的引用計數為 0。所以先將循環引用摘掉,就會得出這兩個對象的有效計數。
3. 分代回收
     從前面「標記-清除」這樣的垃圾收集機制來看,這種垃圾收集機制所帶來的額外操作實際上與系統中總的內存塊的數量是相關的,當需要回收的內存塊越多時,垃圾檢測帶來的額外操作就越多,而垃圾回收帶來的額外操作就越少;反之,當需回收的內存塊越少時,垃圾檢測就將比垃圾回收帶來更少的額外操作。

㈢ python有鏈表嗎

python中的鏈表(linked list)是一組數據項的集合,其中每個數據項都是一個節點的一部分,每個節點還包含指向下一個節點的鏈接。鏈表有兩種類型:單鏈表和雙鏈表。
鏈表的數據結構
在鏈表中刪除操作可以通過修改指針來實現,
插入則是調整,插入點的前後兩個指針的指向關系,
在python中每個變數都是指針,例如:

用內置數據結構(list,dict,tuple等)的嵌套/組合,它們隱式地包含了指向/嵌套關系,如graph[u][v]={w0,w1..}類的成員變數、嵌套類可能包含了指向/嵌套關系;
引用表示指向關系,只不過引用不能像指針一樣運算,比如p+1指向下一個元素,所以可能限制頗多。因此,要實現鏈表的操作,不能和c一樣直接對指針進行操作。
python學習網,大量的免費python視頻教程,歡迎在線學習!

㈣ 「干貨」redis面試題

Redis 的全稱是:Remote Dictionary.Server,本質上是一個 Key-Value 類型的內存資料庫,很像

memcached,整個資料庫統統載入在內存當中進行操作,定期通過非同步操作把資料庫數據 flush 到硬碟

上進行保存。

因為是純內存操作,Redis 的性能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的

Key-Value DB。

Redis 的出色之處不僅僅是性能,Redis 最大的魅力是支持保存多種數據結構,此外單個 value 的最大限

制是 1GB,不像 memcached 只能保存 1MB 的數據,因此 Redis 可以用來實現很多有用的功能。

比方說用他的 List 來做 FIFO 雙向鏈表,實現一個輕量級的高性 能消息隊列服務,用他的 Set 可以做高

性能的 tag 系統等等。

另外 Redis 也可以對存入的 Key-Value 設置 expire 時間,因此也可以被當作一 個功能加強版的

memcached 來用。 Redis 的主要缺點是資料庫容量受到物理內存的限制,不能用作海量數據的高性能

讀寫,因此 Redis 適合的場景主要局限在較小數據量的高性能操作和運算上。

1.memcached 所有的值均是簡單的字元串,redis 作為其替代者,支持更為豐富的數據類型

2.redis 的速度比 memcached 快很多 redis 的速度比 memcached 快很多

3.redis 可以持久化其數據 redis 可以持久化其數據

String、List、Set、Sorted Set、hashes

內存。

1.noeviction:返回錯誤當內存限制達到,並且客戶端嘗試執行會讓更多內存被使用的命令

2.allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。

3.volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存

放。

4.allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。

5.volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。

6.volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間

存放。

因為目前 Linux 版本已經相當穩定,而且用戶量很大,無需開發 windows 版本,反而會帶來兼容性等問

題。

512M

Redis 為了達到最快的讀寫速度將數據都讀到內存中,並通過非同步的方式將數據寫入磁碟。

所以 redis 具有快速和數據持久化的特徵,如果不將數據放在內存中,磁碟 I/O 速度為嚴重影響 redis 的

性能。

在內存越來越便宜的今天,redis 將會越來越受歡迎, 如果設置了最大使用的內存,則數據已有記錄數達

到內存限值後不能繼續插入新值。

1.codis 2.目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在節點數量改變情況下,舊節點

數據可恢復到新 hash 節點。

redis cluster3.0 自帶的集群,特點在於他的分布式演算法不是一致性 hash,而是 hash 槽的概念,以及自

身支持節點設置從節點。具體看官方文檔介紹。

3.在業務代碼層實現,起幾個毫無關聯的 redis 實例,在代碼層,對 key 進行 hash 計算,然後去對應的

redis 實例操作數據。這種方式對 hash 層代碼要求比較高,考慮部分包括,節點失效後的替代演算法方

案,數據震盪後的自動腳本恢復,實例的監控,等等。

有 A,B,C 三個節點的集群,在沒有復制模型的情況下,如果節點 B 失敗了,那麼整個集群就會以為缺少

5501-11000 這個范圍的槽而不可用。

redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。

(1)會話緩存(Session Cache)

最常用的一種使用 Redis 的情景是會話緩存(sessioncache),用 Redis 緩存會話比其他存儲(如

Memcached)的優勢在於:Redis 提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的

購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?

幸運的是,隨著 Redis 這些年的改進,很容易找到怎麼恰當的使用 Redis 來緩存會話的文檔。甚至廣為

人知的商業平台 Magento 也提供 Redis 的插件。

(2)全頁緩存(FPC)

除基本的會話 token 之外,Redis 還提供很簡便的 FPC 平台。回到一致性問題,即使重啟了 Redis 實

例,因為有磁碟的持久化,用戶也不會看到頁面載入速度的下降,這是一個極大改進,類似 php 本地

FPC。

再次以 Magento 為例,Magento 提供一個插件來使用 Redis 作為全頁緩存後端。

此外,對 WordPress 的用戶來說,Pantheon 有一個非常好的插件 wp-redis,這個插件能幫助你以最快

速度載入你曾瀏覽過的頁面。

(3)隊列

Reids 在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得 Redis 能作為一個很好的消息隊列

平台來使用。Redis 作為隊列使用的操作,就類似於本地程序語言(如 Python)對 list 的 push/pop

操作。

如果你快速的在 Google 中搜索「Redis queues」,你馬上就能找到大量的開源項目,這些項目的目的

就是利用 Redis 創建非常好的後端工具,以滿足各種隊列需求。例如,Celery 有一個後台就是使用

Redis 作為 broker,你可以從這里去查看。

(4)排行榜/計數器 Redis 在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(SortedSet)也使

得我們在執行這些操作的時候變的非常簡單,Redis 只是正好提供了這兩種數據結構。

所以,我們要從排序集合中獲取到排名最靠前的 10 個用戶–我們稱之為「user_scores」,我們只需要像

下面一樣執行即可:

當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執

行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games 就是一個很好的例子,用 Ruby 實現的,它的排行榜就是使用 Redis 來存儲數據的,你可

以在這里看到。

立聊天系統!

Redisson、Jedis、lettuce 等等,官方推薦使用 Redisson。

Redisson 是一個高級的分布式協調 Redis 客服端,能幫助用戶在分布式環境中輕松實現一些 Java 的對

象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap,

List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock,

ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

Jedis 是 Redis 的 Java 實現的客戶端,其 API 提供了比較全面的 Redis 命令的支持;

Redisson 實現了分布式和可擴展的 Java 數據結構,和 Jedis 相比,功能較為簡單,不支持字元串操作,

Redis 集群沒有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 個哈希槽,每個 key 通

過 CRC16 校驗後對 16384 取模來決定放置哪個槽,集群的每個節點負責一部分 hash 槽。

為了使在部分節點失敗或者大部分節點無法通信的情況下集群仍然可用,所以集群使用了主從復制模型,

每個節點都會有 N-1 個復製品.

Redis 並不能保證數據的強一致性,這意味這在實際中集群在特定的條件下可能會丟失寫操作。

非同步復制

16384 個

Redis 集群目前無法做資料庫選擇,默認在 0 資料庫。

一次請求/響應伺服器能實現處理新的請求即使舊的請求還未被響應,這樣就可以將多個命令發送到服務

器,而不用等待回復,最後在一個步驟中讀取該答復。

這就是管道(pipelining),是一種幾十年來廣泛使用的技術。例如許多 POP3 協議已經實現支持這個功

能,大大加快了從伺服器下載新郵件的過程。

事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行,事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

MULTI、EXEC、DISCARD、WATCH

EXPIRE 和 PERSIST 命令

盡可能使用散列表(hashes),散列表(是說散列表裡面存儲的數少)使用的內存非常小,所以你應該盡可能的將你的數據模型抽象到一個散列表裡面。

比如你的 web 系統中有一個用戶對象,不要為這個用戶的名稱,姓氏,郵箱,密碼設置單獨的 key,而是應該把這個用戶的所有信息存儲到一張散列表裡面。

一個客戶端運行了新的命令,添加了新的數據。Redi 檢查內存使用情況,如果大於 maxmemory 的限制, 則根據設定好的策略進行回收。一個新的命令被執行,等等。

所以我們不斷地穿越內存限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。

如果一個命令的結果導致大量內存被使用(例如很大的集合的交集保存到一個新的鍵),不用多久內存限制就會被這個內存使用量超越。

咱們來看上面那張圖,現在某個客戶端要加鎖。如果該客戶端面對的是一個 redis cluster 集 群,他首先會根據 hash 節點選擇一台機器。這里注意,僅僅只是選擇一台機器!這點很關 鍵!緊接著,就會發送一段 lua 腳本到 redis 上,那段 lua 腳本如下所示:

為啥要用 lua 腳本呢?因為一大坨復雜的業務邏輯,可以通過封裝在 lua 腳本中發送給 redis, 保證這段復雜業務邏輯執行的原子性。

那麼,這段 lua 腳本是什麼意思呢?這里 KEYS[1]代表的是你加鎖的那個 key,比如說:RLoc

k lock = redisson.getLock("myLock");這里你自己設置了加鎖的那個鎖 key 就是「myLock」。

ARGV[1]代表的就是鎖 key 的默認生存時間,默認 30 秒。ARGV[2]代表的是加鎖的客戶端的 I D,類似於下面這樣:8743c9c0-0795-4907-87fd-6c719a6b4586:1

給大家解釋一下,第一段 if 判斷語句,就是用「exists myLock」命令判斷一下,如果你要加鎖 的那個鎖 key 不存在的話,你就進行加鎖。如何加鎖呢?很簡單,用下面的命令:hset myLoc k 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1,通過這個命令設置一個 hash 數據結構,這行 命令執行後,會出現一個類似下面的數據結構:

上述就代表「8743c9c0-0795-4907-87fd-6c719a6b4586:1」這個客戶端對「myLock」這個鎖 key 完 成了加鎖。接著會執行「pexpire myLock 30000」命令,設置 myLock 這個鎖 key 的生存時間 是 30 秒。好了,到此為止,ok,加鎖完成了。

那麼在這個時候,如果客戶端 2 來嘗試加鎖,執行了同樣的一段 lua 腳本,會咋樣呢?很簡 單,第一個 if 判斷會執行「exists myLock」,發現 myLock 這個鎖 key 已經存在了。接著第二 個 if 判斷,判斷一下,myLock 鎖 key 的 hash 數據結構中,是否包含客戶端 2 的 ID,但是明 顯不是的,因為那裡包含的是客戶端 1 的 ID。

所以,客戶端 2 會獲取到 pttl myLock 返回的一個數字,這個數字代表了 myLock 這個鎖 key 的剩餘生存時間。比如還剩 15000 毫秒的生存時間。此時客戶端 2 會進入一個 while 循環,不 停的嘗試加鎖。

客戶端 1 加鎖的鎖 key 默認生存時間才 30 秒,如果超過了 30 秒,客戶端 1 還想一直持有這把 鎖,怎麼辦呢?

簡單!只要客戶端 1 一旦加鎖成功,就會啟動一個 watch dog 看門狗,他是一個後台線程,會 每隔 10 秒檢查一下,如果客戶端 1 還持有鎖 key,那麼就會不斷的延長鎖 key 的生存時間。

31.可重入加鎖機制

那如果客戶端 1 都已經持有了這把鎖了,結果可重入的加鎖會怎麼樣呢?比如下面這種代碼:

這時我們來分析一下上面那段 lua 腳本。第一個 if 判斷肯定不成立,「exists myLock」會顯示鎖 key 已經存在了。第二個 if 判斷會成立,因為 myLock 的 hash 數據結構中包含的那個 ID,就 是客戶端 1 的那個 ID,也就是「8743c9c0-0795-4907-87fd-6c719a6b4586:1」 此時就會執行可重入加鎖的邏輯,他會用:

incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1 ,通過這個命令,對客戶端 1 的加鎖次數,累加 1。此時 myLock 數據結構變為下面這樣:

大家看到了吧,那個 myLock 的 hash 數據結構中的那個客戶端 ID,就對應著加鎖的次數

如果執行 lock.unlock(),就可以釋放分布式鎖,此時的業務邏輯也是非常簡單的。其實說白 了,就是每次都對 myLock 數據結構中的那個加鎖次數減 1。如果發現加鎖次數是 0 了,說明 這個客戶端已經不再持有鎖了,此時就會用:「del myLock」命令,從 redis 里刪除這個 key。 然後呢,另外的客戶端 2 就可以嘗試完成加鎖了。這就是所謂的分布式鎖的開源 Redisson 框 架的實現機制。

一般我們在生產系統中,可以用 Redisson 框架提供的這個類庫來基於 redis 進行分布式鎖的加 鎖與釋放鎖。

其實上面那種方案最大的問題,就是如果你對某個 redis master 實例,寫入了 myLock 這種鎖 key 的 value,此時會非同步復制給對應的 master slave 實例。但是這個過程中一旦發生 redis m aster 宕機,主備切換,redis slave 變為了 redis master。

接著就會導致,客戶端 2 來嘗試加鎖的時候,在新的 redis master 上完成了加鎖,而客戶端 1 也以為自己成功加了鎖。此時就會導致多個客戶端對一個分布式鎖完成了加鎖。這時系統在業 務語義上一定會出現問題,導致各種臟數據的產生。

所以這個就是 redis cluster,或者是 redis master-slave 架構的主從非同步復制導致的 redis 分布 式鎖的最大缺陷:在 redis master 實例宕機的時候,可能導致多個客戶端同時完成加鎖。

先拿 setnx 來爭搶鎖,搶到之後,再用 expire 給鎖加一個過期時間防止鎖忘記了釋放。

如果在 setnx 之後執行 expire 之前進程意外 crash 或者要重啟維護了,那會怎麼樣?

set 指令有非常復雜的參數,這個應該是可以同時把 setnx 和 expire 合成一條指令來用的!

緩存穿透

一般的緩存系統,都是按照 key 去緩存查詢,如果不存在對應的 value,就應該去後端系統查找(比如DB)。一些惡意的請求會故意查詢不存在的 key,請求量很大,就會對後端系統造成很大的壓力。這就叫做緩存穿透。

如何避免?

1:對查詢結果為空的情況也進行緩存,緩存時間設置短一點,或者該 key 對應的數據 insert 了之後清理緩存。

2:對一定不存在的 key 進行過濾。可以把所有的可能存在的 key 放到一個大的 Bitmap 中,查詢時通過該 bitmap 過濾。

緩存雪崩

當緩存伺服器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給後端系統帶來很大壓力。導致系統崩潰。

如何避免?

1:在緩存失效後,通過加鎖或者隊列來控制讀資料庫寫緩存的線程數量。比如對某個 key 只允許一個線程查詢數據和寫緩存,其他線程等待。

2:做二級緩存,A1 為原始緩存,A2 為拷貝緩存,A1 失效時,可以訪問 A2,A1 緩存失效時間設置為短期,A2 設置為長期

3:不同的 key,設置不同的過期時間,讓緩存失效的時間點盡量均勻

㈤ 數組鏈表的概念及區別

python中的list並不是我們傳統意義上的列表
傳統列表——通常也叫作鏈表(linked list)是由一系列節點來實現的,其中每個節點都持有一個指向下一節點的引用
簡枯團單實現起來應該就像下面那樣:

接下來,我們就可以將所有的節點構造成一個列表了:

這是消彎一個所謂的單向鏈表,雙向鏈表的各節點中還需要持有一個指向前一個節點的引用
但python中的list則與此有所不同,它不是由若干個獨立的節點相互引用而成的,而是一整塊單一連拿敗悶續的內存區塊,我們通常稱之為「數組」(array),這直接導致了它與鏈表之間的一些重要區別。
例如如果我們要按既定的索引值對某一元素進行直接訪問的話,顯然使用數組會更有效率。因為,在數組中,我們通常可以直接計算出目標元素在內存中的位置,並對其進行直接訪問。而對於鏈表來說,我們必須從頭開始遍歷整個鏈表。
但是具體到insert操作上,情況又會有所不同。對於鏈表而言,只要知道了要在哪裡執行insert操作,其操作成本是非常低的,無論該列表中有多少元素,其操作時間大致上是相同的。而數組就不一樣了,它每次執行insert操作都需要移動插入點右邊的所有元素,甚至在必要的時候,我們可能還需要將這些列表元素整體搬到一個更大的數組中去。

數組數據是連續的,一般需要預先設定數據長度,不能適應數據動態的增減,當數據增加是可能超過預設值,需要要重新分配內存,當數據減少時,預先申請的內存未使用,造成內存浪費。鏈表的數據因為是隨機存儲的,所以鏈表可以動態的分配內存,適應長度的動態變化
1.數組的元素是存放在棧中的,鏈表的元素在堆中
2.讀取操作:數組時間復雜度為O(1),鏈表為O(n)
3.插入或刪除操作:數據時間復雜度為O(n),鏈表為O(1)

㈥ 160. 相交鏈表(Python)

難度:★★☆☆☆
類型:鏈表

編寫一個程序,找到兩個單鏈表相交的起始節點。

如下面的兩個鏈表:

在節點 c1 開始相交。

示例 1:

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值為 8 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [4,1,8,4,5],鏈表 B 為 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

示例2:

輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
輸出:Reference of the node with value = 2
輸入解釋:相交節點的值為 2 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [0,9,1,2,4],鏈表 B 為 [3,2,4]。在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。

示例3:

輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
輸出:null
輸入解釋:從各自的表頭開始算起,鏈表 A 為 [2,6,4],鏈表 B 為 [1,5]。由於這兩個鏈表不相交,所以 intersectVal 必須為 0,而 skipA 和 skipB 可以是任意值。
解釋:這兩個鏈表不相交,因此返回 null。

注意:

如果兩個鏈表沒有交點,返回 null.
在返回結果後,兩個鏈表仍須保持原有的結構。
可假定整個喊派舉鏈表結構中沒有循環。
程序盡量滿足 O(n) 時間復雜度,且僅用 O(1) 內存。

這道題與 【題目141. 環形鏈表】 屬於同一題型,環形鏈表可以使用快慢指針判斷,這里我們同樣使用雙羨陵指針進行判別,不過步長都是一步,讓兩個指針分別從兩個鏈表頭結點開始向後移動,當其中一個指針走到鏈表末尾後,換到另一個鏈表的頭結點上,另一個指針也是如此,這樣如果兩個鏈表相交,鄭碧則一定可以相遇,且根據數量關系可知,首次相遇的結點即為相交結點。

如有疑問或建議,歡迎評論區留言~

㈦ Python對象

眾所周知,Python是一門面向對象的語言,在Python無論是數值、字元串、函數亦或是類型、類,都是對象。
對象是在 堆 上分配的結構,我們定義的所有變數、函數等,都存儲於堆內存,而變數名、函數名則是一個存儲於 棧 中、指向堆中具體結構的引用。

要想深入學習Python,首先需要知道Python對象的定義。

我們通常說的Python都是指CPython,底層由C語言實現,源碼地址: cpython [GitHub]
Python對象的定義位於 Include/object.h ,是一個名為 PyObject 的結構體:

Python中的所有對象都繼承自PyObejct,PyObject包含一個用於垃圾回收的雙向鏈表,一個引用計數變數 ob_refcnt 和 一個類型對象指針 ob_type

從PyObejct的注釋中,我們可以看到這樣一句:每個指向 可變大小Python對象 的指針也可以轉換為 PyVarObject* (可變大小的Python對象會在下文中解釋)。 PyVarObejct 就是在PyObject的基礎上多了一個 ob_size 欄位,用於存儲元素個數:

在PyObject結構中,還有一個類型對象指針 ob_type ,用於表示Python對象是什麼類型,定義Python對象類型的是一個 PyTypeObject 介面體

實際定義是位於 Include/cpython/object.h 的 _typeobject :

在這個類型對象中,不僅包含了對象的類型,還包含了如分配內存大小、對象標准操作等信息,主要分為:

以Python中的 int類型 為例,int類型對象的定義如下:

從PyObject的定義中我們知道,每個對象的 ob_type 都要指向一個具體的類型對象,比如一個數值型對象 100 ,它的ob_type會指向 int類型對象PyLong_Type 。

PyTypeObject結構體第一行是一個PyObject_VAR_HEAD宏,查看宏定義可知PyTypeObject是一個變長對象

也就是說,歸根結底 類型對象也是一個對象 ,也有ob_type屬性,那 PyLong_Type 的 ob_type 是什麼呢?
回到PyLong_Type的定義,第一行 PyVarObject_HEAD_INIT(&PyType_Type, 0) ,查看對應的宏定義

由以上關系可以知道, PyVarObject_HEAD_INIT(&PyType_Type, 0) = { { _PyObject_EXTRA_INIT 1, &PyType_Type } 0} ,將其代入 PyObject_VAR_HEAD ,得到一個變長對象:

這樣看就很明確了,PyLong_Type的類型就是PyType_Typ,同理可知, Python類型對象的類型就是PyType_Type ,而 PyType_Type對象的類型是它本身

從上述內容中,我們知道了對象和對象類型的定義,那麼根據定義,對象可以有以下兩種分類

Python對象定義有 PyObject 和 PyVarObject ,因此,根據對象大小是否可變的區別,Python對象可以劃分為 可變對象(變長對象) 和 不可變對象(定長對象)

原本的對象a大小並沒有改變,只是s引用的對象改變了。這里的對象a、對象b就是定長對象

可以看到,變數l仍然指向對象a,只是對象a的內容發生了改變,數據量變大了。這里的對象a就是變長對象

由於存在以上特性,所以使用這兩種對象還會帶來一種區別:
聲明 s2 = s ,修改s的值: s = 'new string' ,s2的值不會一起改變,因為只是s指向了一個新的對象,s2指向的舊對象的值並沒有發生改變
聲明 l2 = l ,修改l的值: l.append(6) ,此時l2的值會一起改變,因為l和l2指向的是同一個對象,而該對象的內容被l修改了

此外,對於 字元串 對象,Python還有一套內存復用機制,如果兩個字元串變數值相同,那它們將共用同一個對象:

對於 數值型 對象,Python會默認創建0~2 8 以內的整數對象,也就是 0 ~ 256 之間的數值對象是共用的:

按照Python數據類型,對象可分為以下幾類:

Python創建對象有兩種方式,泛型API和和類型相關的API

這類API通常以 PyObject_xxx 的形式命名,可以應用在任意Python對象上,如:

使用 PyObjecg_New 創建一個數值型對象:

這類API通常只能作用於一種類型的對象上,如:

使用 PyLong_FromLong 創建一個數值型對象:

在我們使用Python聲明變數的時候,並不需要為變數指派類型,在給變數賦值的時候,可以賦值任意類型數據,如:

從Python對象的定義我們已經可以知曉造成這個特點的原因了,Python創建對象時,會分配內存進行初始化,然後Python內部通過 PyObject* 變數來維護這個對象,所以在Python內部各函數直接傳遞的都是一種泛型指針 PyObject* ,這個指針所指向的對象類型是不固定的,只能通過所指對象的 ob_type 屬性動態進行判斷,而Python正是通過 ob_type 實現了多態機制

Python在管理維護對象時,通過引用計數來判斷內存中的對象是否需要被銷毀,Python中所有事物都是對象,所有對象都有引用計數 ob_refcnt 。
當一個對象的引用計數減少到0之後,Python將會釋放該對象所佔用的內存和系統資源。
但這並不意味著最終一定會釋放內存空間,因為頻繁申請釋放內存會大大降低Python的執行效率,因此Python中採用了內存對象池的技術,是的對象釋放的空間會還給內存池,而不是直接釋放,後續需要申請空間時,優先從內存對象池中獲取。

㈧ redis隊列什麼意思

Redis隊列功能介紹

List

常用命令:

Blpop刪除,並獲得該列表中的第一元素,或阻塞,直到有一個可用

Brpop刪除,並獲得該列表中的最後一個元素,或阻塞,直到有一個可用

Brpoplpush

Lindex獲取一個元素,通過其索引列表

Linsert在列表中的另一個元素之前或之後插入一個元素

Llen獲得隊列(List)的長度

Lpop從隊列的左邊出隊一個元素

Lpush從隊列的左邊入隊一個或多個元素

Lpushx當隊列存在時,從隊到左邊入隊一個元素

Lrange從列表中獲取指定返回的元素

Lrem從列表中刪除元素

Lset設置隊列裡面一個元素的值

Ltrim修剪到指定范圍內的清單

Rpop從隊列的右邊出隊一個元素

Rpoplpush刪除列表中的最後一個元素,將其追加到另一個列表

Rpush從隊列的右邊入隊一個元素

Rpushx從隊列的右邊入隊一個元素,僅隊列存在時有效

Redis支持php、python、c等介面

應用場景:

Redislist的應用場景非常多,也是Redis最重要的數據結構之一,比如twitter的關注列表,粉絲列表等都可以用Redis的list結構來實現。

Lists就是鏈表,相信略有數據結構知識的人都應該能理解其結構。使用Lists結構,我們可以輕松地實現最新消息排行等功能。

Lists的另一個應用就是消息隊列,

可以利用Lists的PUSH操作,將任務存在Lists中,然後工作線程再用POP操作將任務取出進行執行。Redis還提供了操作Lists中某一段的api,你可以直接查詢,刪除Lists中某一段的元素。

如果需要還可以用redis的Sorted-Sets數據結構來做優先隊列.可以給每條消息加上一個唯一的序號。這里就不詳細介紹了。

實現方式:

Redislist的實現為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括發送緩沖隊列等也都是用的這個數據結構。

示意圖:

1)入隊

㈨ python實現鏈表隊列棧

實現如下

㈩ 總結redis在節省內存開銷方面做過哪些設計

Redis常用數據類型
Redis最為常用的數據類型主要有以下五種:
String
Hash
List
Set
Sorted set
在具體描述這幾種數據類型之前,我們先通過一張圖了解下Redis內部內存管理中是如何描述這些不同數據類型的:

首先Redis內部使用一個redisObject對象來表示所有的key和value,redisObject最主要的信息如上圖所示:type代表一個value對象具體是何種數據早滲類型,encoding是不同數據類型在redis內部的存儲方式,比如:type=string代表value存儲的是一個普通字元串,那麼對應的encoding可以是raw或者是int,如果是int則代表實際redis內部是按數值型類存儲和表示這個字元串的,當然前提是這個字元串本身可以用數值表示,比如:"123" "456"這樣的字元串。
這里需要特殊說明一下vm欄位,只有打開了Redis的虛擬內存功能,此欄位才會真正的分配內存,該功能默認是關閉狀態的,該功能會在後面具體描述。通過上圖我們可以發現Redis使用redisObject來表示所有的key/value數據是比較浪費內存的,當然這些內存管理成本的付出主要也是為了給Redis不同數據類型提供一個統一的管理介面,實際作者也提供了多種方法幫助我們盡量節省內存使用,我們隨後會具體討論。
下面我們先來逐一的分析下這五種數據類型的使用和內部實現方式:
String
常用命令:
set,get,decr,incr,mget 等。
應用場景:
String是最常用的一種數據類型,普通的key/value存儲都可以歸為此類,這里就不所做解釋了。
實現方式:
String在redis內部存儲默認就是一個字元串,被redisObject所引用,當遇到incr,decr等操作時會轉成數值型進行計算,此時redisObject的encoding欄位為int。
Hash
常用命令:
hget,hset,hgetall 等。
應用場景:
我們簡單舉個實例來描述下Hash的應用場景,比如我們要存儲一個用戶信息對象數據,包含以下信息:
用戶ID為查找的key,存儲的value用戶對象包含姓名,年齡,罩消生日等信息,如果用普通的key/value結構來存儲,主要有以下2種存儲方式:

常用內存優化手段與參數
通過我們上面的一些實現上的分析可以看出redis實際上的內存管理成本非常高,即佔用了過多的內存,作者對這點也非常清楚,所以提供了一系列的參數和手段來控制和節省內存,我們分別來討論下。
首先最重要的一點是不要開啟Redis的VM選項,即虛擬內存功能,這個本來是作為Redis存儲超出物理內存數據的一種數據在內存與磁碟換入換出的一個持久化策略,但是其內存管理成本也非常的高,並且我們後續會分析此種持久化策略並不成熟,所以要關閉VM功能,請檢查你的redis.conf文件中 vm-enabled 為 no。
其次最好設置下redis.conf中的maxmemory選項,該選項是告訴Redis當使用了多少物理內存後就開始拒絕後續的寫入請求,該參數能很好的保護好你的Redis不會因為使用了過多的物理內存而導致swap,最終嚴重影響性能甚至崩潰。
另外Redis為不同數據類型分別提供了一組參數來控制內存使用,我們在前面詳細分析過Redis Hash是value內部為一個HashMap,如果該Map的成員數比較少,則會採用類似一維線性的緊湊格式來存儲該Map, 即省去了大量指針的內存開銷,這個參數控制對應在redis.conf配置文件中下面2項:
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-entries

含義是當value這個Map內部不超過多少個成員時會採用線性緊湊格式存儲,默認是64,即value內部有64個以下的成員就是使用線性緊湊存儲,超過該值自動轉成真正的HashMap。
hash-max-zipmap-value 含義是當 value這個Map內部的物睜知每個成員值長度不超過多少位元組就會採用線性緊湊存儲來節省空間。
以上2個條件任意一個條件超過設置值都會轉換成真正的HashMap,也就不會再節省內存了,那麼這個值是不是設置的越大越好呢,答案當然是否定的,HashMap的優勢就是查找和操作的時間復雜度都是O(1)的,而放棄Hash採用一維存儲則是O(n)的時間復雜度,如果
成員數量很少,則影響不大,否則會嚴重影響性能,所以要權衡好這個值的設置,總體上還是最根本的時間成本和空間成本上的權衡。
同樣類似的參數還有:
list-max-ziplist-entries 512

說明:list數據類型多少節點以下會採用去指針的緊湊存儲格式。
list-max-ziplist-value 64

說明:list數據類型節點值大小小於多少位元組會採用緊湊存儲格式。
set-max-intset-entries 512

說明:set數據類型內部數據如果全部是數值型,且包含多少節點以下會採用緊湊格式存儲。
最後想說的是Redis內部實現沒有對內存分配方面做過多的優化,在一定程度上會存在內存碎片,不過大多數情況下這個不會成為Redis的性能瓶頸,不過如果在Redis內部存儲的大部分數據是數值型的話,Redis內部採用了一個shared integer的方式來省去分配內存的開銷,即在系統啟動時先分配一個從1~n 那麼多個數值對象放在一個池子中,如果存儲的數據恰好是這個數值范圍內的數據,則直接從池子里取出該對象,並且通過引用計數的方式來共享,這樣在系統存儲了大量數值下,也能一定程度上節省內存並且提高性能,這個參數值n的設置需要修改源代碼中的一行宏定義REDIS_SHARED_INTEGERS,該值默認是10000,可以根據自己的需要進行修改,修改後重新編譯就可以了。
Redis的持久化機制
Redis由於支持非常豐富的內存數據結構類型,如何把這些復雜的內存組織方式持久化到磁碟上是一個難題,所以Redis的持久化方式與傳統資料庫的方式有比較多的差別,Redis一共支持四種持久化方式,分別是:
在設計思路上,前兩種是基於全部數據都在內存中,即小數據量下提供磁碟落地功能,而後兩種方式則是作者在嘗試存儲數據超過物理內存時,即大數據量的數據存儲,截止到本文,後兩種持久化方式仍然是在實驗階段,並且vm方式基本已經被作者放棄,所以實際能在生產環境用的只有前兩種,換句話說Redis目前還只能作為小數據量存儲(全部數據能夠載入在內存中),海量數據存儲方面並不是Redis所擅長的領域。下面分別介紹下這幾種持久化方式:
定時快照方式(snapshot):
該持久化方式實際是在Redis內部一個定時器事件,每隔固定時間去檢查當前數據發生的改變次數與時間是否滿足配置的持久化觸發的條件,如果滿足則通過操作系統fork調用來創建出一個子進程,這個子進程默認會與父進程共享相同的地址空間,這時就可以通過子進程來遍歷整個內存來進行存儲操作,而主進程則仍然可以提供服務,當有寫入時由操作系統按照內存頁(page)為單位來進行-on-write保證父子進程之間不會互相影響。
該持久化的主要缺點是定時快照只是代表一段時間內的內存映像,所以系統重啟會丟失上次快照與重啟之間所有的數據。
基於語句追加方式(aof):
aof方式實際類似mysql的基於語句的binlog方式,即每條會使Redis內存數據發生改變的命令都會追加到一個log文件中,也就是說這個log文件就是Redis的持久化數據。
aof的方式的主要缺點是追加log文件可能導致體積過大,當系統重啟恢復數據時如果是aof的方式則載入數據會非常慢,幾十G的數據可能需要幾小時才能載入完,當然這個耗時並不是因為磁碟文件讀取速度慢,而是由於讀取的所有命令都要在內存中執行一遍。另外由於每條命令都要寫log,所以使用aof的方式,Redis的讀寫性能也會有所下降。
虛擬內存方式:
虛擬內存方式是Redis來進行用戶空間的數據換入換出的一個策略,此種方式在實現的效果上比較差,主要問題是代碼復雜,重啟慢,復制慢等等,目前已經被作者放棄。
diskstore方式:
diskstore方式是作者放棄了虛擬內存方式後選擇的一種新的實現方式,也就是傳統的B-tree的方式,目前仍在實驗階段,後續是否可用我們可以拭目以待。
Redis持久化磁碟IO方式及其帶來的問題
有Redis線上運維經驗的人會發現Redis在物理內存使用比較多,但還沒有超過實際物理內存總容量時就會發生不穩定甚至崩潰的問題,有人認為是基於快照方式持久化的fork系統調用造成內存佔用加倍而導致的,這種觀點是不準確的,因為fork 調用的-on-write機制是基於操作系統頁這個單位的,也就是只有有寫入的臟頁會被復制,但是一般你的系統不會在短時間內所有的頁都發生了寫入而導致復制,那麼是什麼原因導致Redis崩潰的呢?
答案是Redis的持久化使用了Buffer IO造成的,所謂Buffer IO是指Redis對持久化文件的寫入和讀取操作都會使用物理內存的Page Cache,而大多數資料庫系統會使用Direct IO來繞過這層Page Cache並自行維護一個數據的Cache,而當Redis的持久化文件過大(尤其是快照文件),並對其進行讀寫時,磁碟文件中的數據都會被載入到物理內存中作為操作系統對該文件的一層Cache,而這層Cache的數據與Redis內存中管理的數據實際是重復存儲的,雖然內核在物理內存緊張時會做Page Cache的剔除工作,但內核很可能認為某塊Page Cache更重要,而讓你的進程開始Swap ,這時你的系統就會開始出現不穩定或者崩潰了。我們的經驗是當你的Redis物理內存使用超過內存總容量的3/5時就會開始比較危險了。
定時快照方式(snapshot)
基於語句追加文件的方式(aof)
虛擬內存(vm)
Diskstore方式
第一種方式將用戶ID作為查找key,把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化/反序列化的開銷,並且在需要修改其中一項信息時,需要把整個對象取回,並且修改操作需要對並發進行保護,引入CAS等復雜問題。

第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應屬性的名稱作為唯一標識來取得對應屬性的值,雖然省去了序列化開銷和並發問題,但是用戶ID為重復存儲,如果存在大量這樣的數據,內存浪費還是非常可觀的。
那麼Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部存儲的Value為一個HashMap,並提供了直接存取這個Map成員的介面,如下圖:

也就是說,Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取都可以直接通過其內部Map的Key(Redis里稱內部Map的key為field), 也就是通過 key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和並發修改控制的問題。很好的解決了問題。
這里同時需要注意,Redis提供了介面(hgetall)可以直接取到全部的屬性數據,但是如果內部Map的成員很多,那麼涉及到遍歷整個內部Map的操作,由於Redis單線程模型的緣故,這個遍歷操作可能會比較耗時,而另其它客戶端的請求完全不響應,這點需要格外注意。
實現方式:
上面已經說到Redis Hash對應Value內部實際就是一個HashMap,實際這里會有2種不同實現,這個Hash的成員比較少時Redis為了節省內存會採用類似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding為zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding為ht。
List
常用命令:
lpush,rpush,lpop,rpop,lrange等。
應用場景:
Redis list的應用場景非常多,也是Redis最重要的數據結構之一,比如twitter的關注列表,粉絲列表等都可以用Redis的list結構來實現,比較好理解,這里不再重復。
實現方式:
Redis list的實現為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括發送緩沖隊列等也都是用的這個數據結構。
Set
常用命令:
sadd,spop,smembers,sunion 等。
應用場景:
Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要介面,這個也是list所不能提供的。
實現方式:
set 的內部實現是一個 value永遠為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。
Sorted set
常用命令:
zadd,zrange,zrem,zcard等
使用場景:
Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先順序(score)的參數來為成員排序,並且是插入有序的,即自動排序。當你需要一個有序的並且不重復的集合列表,那麼可以選擇sorted set數據結構,比如twitter 的public timeline可以以發表時間作為score來存儲,這樣獲取時就是自動按時間排好序的。
實現方式:
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表裡存放的是所有的成員,排序依據是HashMap里存的score,使用跳躍表的結構可以獲得比較高的查找效率,並且在實現上比較簡單。

閱讀全文

與python實現雙向鏈表相關的資料

熱點內容
excel表格單列數據加密 瀏覽:646
給同事的解壓話語 瀏覽:990
linux關閉網卡命令行 瀏覽:452
史上最漂亮程序員 瀏覽:768
java實現excel的導入 瀏覽:758
光遇賬號如何轉移安卓 瀏覽:266
5分之13除以26的演算法 瀏覽:342
蘭州安寧區買解壓包子 瀏覽:641
php接收圖片代碼 瀏覽:668
hci命令 瀏覽:662
福建伺服器大區雲空間 瀏覽:840
筆桿子程序員 瀏覽:745
手機軟體易驗證加密 瀏覽:589
文檔加密只讀模式也不能看到 瀏覽:431
把jpg轉換成pdf的軟體 瀏覽:874
linuxeth0mac 瀏覽:192
windows編程知乎 瀏覽:442
壓縮工期超過40 瀏覽:249
Android怎麼優化內存 瀏覽:106
linuxetcsysconfig 瀏覽:396