ios程序員如何快速寫出鏈表

鏈表這種數(shù)據(jù)結(jié)構(gòu)和數(shù)組一樣是線性表一罩。在我們開發(fā)中我們可能經(jīng)常會(huì)在不知不覺中使用它廊驼。是一種非秤糖郏基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)熙涤。與數(shù)組相比鏈表的優(yōu)點(diǎn)在于它不要求必須要連續(xù)的內(nèi)存空間阁苞。因此可以最大限度的利用內(nèi)存空間,因?yàn)椴恍枰B續(xù)的內(nèi)存空間鏈表的插入與刪除的時(shí)間復(fù)雜度都要優(yōu)與數(shù)組祠挫。那么怎么才能寫出最標(biāo)準(zhǔn)的鏈表了那槽?嘿嘿。蘋果官方有標(biāo)準(zhǔn)的參考哦等舔。
NSCache我們都使用過骚灸,它存儲(chǔ)數(shù)據(jù)的方式就是使用的雙向鏈表。github地址:https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSCache.swift

下面我也把蘋果官方NSCache的源碼貼出來慌植。并做簡單的源碼解析方便查看甚牲。打開github有時(shí)候確實(shí)有點(diǎn)慢。


private class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
    var key: KeyType
    var value: ObjectType
    var cost: Int
    var prevByCost: NSCacheEntry?
    var nextByCost: NSCacheEntry?
    init(key: KeyType, value: ObjectType, cost: Int) {
        self.key = key
        self.value = value
        self.cost = cost
    }
}

NSCacheEntry 是常見的雙向鏈表存儲(chǔ)結(jié)構(gòu)蝶柿,但是其中多了一個(gè)cost屬性丈钙,是為了為了在內(nèi)存達(dá)到預(yù)警時(shí)自動(dòng)刪除比對的。

fileprivate class NSCacheKey: NSObject {
    
    var value: AnyObject
    
    init(_ value: AnyObject) {
        self.value = value
        super.init()
    }
    
    override var hash: Int {
        switch self.value {
        case let nsObject as NSObject:
            return nsObject.hashValue
        case let hashable as AnyHashable:
            return hashable.hashValue
        default: return 0
        }
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = (object as? NSCacheKey) else { return false }
        
        if self.value === other.value {
            return true
        } else {
            guard let left = self.value as? NSObject,
                let right = other.value as? NSObject else { return false }
            
            return left.isEqual(right)
        }
    }
}

NSCacheKey這個(gè)不用說交汤,用在存儲(chǔ)的key雏赦。

下面是NSCache的源碼。
在NSCache源碼中我們能看到它同時(shí)有_entries與_head。一個(gè)是字典另一個(gè)是鏈表的head星岗。為什么會(huì)同時(shí)用兩個(gè)了填大。這里是因?yàn)殒湵淼牟樵儠r(shí)間復(fù)雜度在o(n)但是字典作為hash表它的查詢復(fù)雜度是o(1)。并且NSCacheEntry是class 是引用類型伍茄。因此對內(nèi)存的增加也是很小的栋盹。_entries就是對緩存數(shù)據(jù)做的一層索引。
totalCostLimit與countLimit都是做淘汰算法使用的敷矫,在setObject方法中有使用。

open func object(forKey key: KeyType) 緩存讀取方法汉额,直接讀取_entries字典中的數(shù)據(jù)曹仗,時(shí)間復(fù)雜度o(1)

   open func object(forKey key: KeyType) -> ObjectType? {
        var object: ObjectType?
        
        let key = NSCacheKey(key)
        
        _lock.lock()
        if let entry = _entries[key] {
            object = entry.value
        }
        _lock.unlock()
        
        return object
    }

private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>)與private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>)是鏈表的插入與刪除方法。

  private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
      let oldPrev = entry.prevByCost
      let oldNext = entry.nextByCost
      
      oldPrev?.nextByCost = oldNext
      oldNext?.prevByCost = oldPrev
      
      if entry === _head {
          _head = oldNext
      }
  }

  private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
      guard var currentElement = _head else {
          // The cache is empty
          entry.prevByCost = nil
          entry.nextByCost = nil
          
          _head = entry
          return
      }
      
      guard entry.cost > currentElement.cost else {
          // Insert entry at the head
          entry.prevByCost = nil
          entry.nextByCost = currentElement
          currentElement.prevByCost = entry
          
          _head = entry
          return
      }
      
      while let nextByCost = currentElement.nextByCost, nextByCost.cost < entry.cost {
          currentElement = nextByCost
      }
      
      // Insert entry between currentElement and nextElement
      let nextElement = currentElement.nextByCost
      
      currentElement.nextByCost = entry
      entry.prevByCost = currentElement
      
      entry.nextByCost = nextElement
      nextElement?.prevByCost = entry
  }

setObject方法添加緩存對象到NSCache中蠕搜。
1.首先從_entries中查找是否已經(jīng)有當(dāng)前插入的元素怎茫,如果有在判斷cost是否有更新,如果有更新就更新鏈表中的數(shù)據(jù)妓灌。如果沒有更新直接插入到鏈表中轨蛤。
2.然后判斷purgeAmount。如果purgeAmount大于零就執(zhí)行淘汰算法虫埂,刪除鏈表中最先插入進(jìn)來的數(shù)據(jù)祥山。同時(shí)刪除_entries字典中的數(shù)據(jù)。調(diào)用回調(diào)方法掉伏。
3.然后判斷purgeCount缝呕。和purgeAmount一樣執(zhí)行同樣的淘汰機(jī)制。

 open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
        let g = max(g, 0)
        let keyRef = NSCacheKey(key)
        
        _lock.lock()
        
        let costDiff: Int
        
        if let entry = _entries[keyRef] {
            costDiff = g - entry.cost
            entry.cost = g
            
            entry.value = obj
            
            if costDiff != 0 {
                remove(entry)
                insert(entry)
            }
        } else {
            let entry = NSCacheEntry(key: key, value: obj, cost: g)
            _entries[keyRef] = entry
            insert(entry)
            
            costDiff = g
        }
        
        _totalCost += costDiff
        
        var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
        while purgeAmount > 0 {
            if let entry = _head {
                delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeAmount -= entry.cost
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[NSCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
        while purgeCount > 0 {
            if let entry = _head {
                delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeCount -= 1
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[NSCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        _lock.unlock()
    }


下面是完整的NSCache源碼斧散。

private class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
    var key: KeyType
    var value: ObjectType
    var cost: Int
    var prevByCost: NSCacheEntry?
    var nextByCost: NSCacheEntry?
    init(key: KeyType, value: ObjectType, cost: Int) {
        self.key = key
        self.value = value
        self.cost = cost
    }
}

fileprivate class NSCacheKey: NSObject {
    
    var value: AnyObject
    
    init(_ value: AnyObject) {
        self.value = value
        super.init()
    }
    
    override var hash: Int {
        switch self.value {
        case let nsObject as NSObject:
            return nsObject.hashValue
        case let hashable as AnyHashable:
            return hashable.hashValue
        default: return 0
        }
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = (object as? NSCacheKey) else { return false }
        
        if self.value === other.value {
            return true
        } else {
            guard let left = self.value as? NSObject,
                let right = other.value as? NSObject else { return false }
            
            return left.isEqual(right)
        }
    }
}

open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
    
    private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
    private let _lock = NSLock()
    private var _totalCost = 0
    private var _head: NSCacheEntry<KeyType, ObjectType>?
    
    open var name: String = ""
    open var totalCostLimit: Int = 0 // limits are imprecise/not strict
    open var countLimit: Int = 0 // limits are imprecise/not strict
    open var evictsObjectsWithDiscardedContent: Bool = false

    public override init() {}
    
    open weak var delegate: NSCacheDelegate?
    
    open func object(forKey key: KeyType) -> ObjectType? {
        var object: ObjectType?
        
        let key = NSCacheKey(key)
        
        _lock.lock()
        if let entry = _entries[key] {
            object = entry.value
        }
        _lock.unlock()
        
        return object
    }
    
    open func setObject(_ obj: ObjectType, forKey key: KeyType) {
        setObject(obj, forKey: key, cost: 0)
    }
    
    private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
        let oldPrev = entry.prevByCost
        let oldNext = entry.nextByCost
        
        oldPrev?.nextByCost = oldNext
        oldNext?.prevByCost = oldPrev
        
        if entry === _head {
            _head = oldNext
        }
    }
   
    private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
        guard var currentElement = _head else {
            // The cache is empty
            entry.prevByCost = nil
            entry.nextByCost = nil
            
            _head = entry
            return
        }
        
        guard entry.cost > currentElement.cost else {
            // Insert entry at the head
            entry.prevByCost = nil
            entry.nextByCost = currentElement
            currentElement.prevByCost = entry
            
            _head = entry
            return
        }
        
        while let nextByCost = currentElement.nextByCost, nextByCost.cost < entry.cost {
            currentElement = nextByCost
        }
        
        // Insert entry between currentElement and nextElement
        let nextElement = currentElement.nextByCost
        
        currentElement.nextByCost = entry
        entry.prevByCost = currentElement
        
        entry.nextByCost = nextElement
        nextElement?.prevByCost = entry
    }
    
    open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
        let g = max(g, 0)
        let keyRef = NSCacheKey(key)
        
        _lock.lock()
        
        let costDiff: Int
        
        if let entry = _entries[keyRef] {
            costDiff = g - entry.cost
            entry.cost = g
            
            entry.value = obj
            
            if costDiff != 0 {
                remove(entry)
                insert(entry)
            }
        } else {
            let entry = NSCacheEntry(key: key, value: obj, cost: g)
            _entries[keyRef] = entry
            insert(entry)
            
            costDiff = g
        }
        
        _totalCost += costDiff
        
        var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
        while purgeAmount > 0 {
            if let entry = _head {
                delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeAmount -= entry.cost
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[NSCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
        while purgeCount > 0 {
            if let entry = _head {
                delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeCount -= 1
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[NSCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        _lock.unlock()
    }
    
    open func removeObject(forKey key: KeyType) {
        let keyRef = NSCacheKey(key)
        
        _lock.lock()
        if let entry = _entries.removeValue(forKey: keyRef) {
            _totalCost -= entry.cost
            remove(entry)
        }
        _lock.unlock()
    }
    
    open func removeAllObjects() {
        _lock.lock()
        _entries.removeAll()
        
        while let currentElement = _head {
            let nextElement = currentElement.nextByCost
            
            currentElement.prevByCost = nil
            currentElement.nextByCost = nil
            
            _head = nextElement
        }
        
        _totalCost = 0
        _lock.unlock()
    }    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末供常,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鸡捐,更是在濱河造成了極大的恐慌栈暇,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箍镜,死亡現(xiàn)場離奇詭異源祈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鹿寨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門新博,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脚草,你說我怎么就攤上這事赫悄。” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵埂淮,是天一觀的道長姑隅。 經(jīng)常有香客問我,道長倔撞,這世上最難降的妖魔是什么讲仰? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮痪蝇,結(jié)果婚禮上淀零,老公的妹妹穿的比我還像新娘。我一直安慰自己咕晋,他們只是感情好且预,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著给僵,像睡著了一般毫捣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帝际,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天蔓同,我揣著相機(jī)與錄音,去河邊找鬼蹲诀。 笑死斑粱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侧甫。 我是一名探鬼主播珊佣,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼披粟!你這毒婦竟也來了咒锻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤守屉,失蹤者是張志新(化名)和其女友劉穎惑艇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拇泛,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滨巴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俺叭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恭取。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖熄守,靈堂內(nèi)的尸體忽然破棺而出蜈垮,到底是詐尸還是另有隱情耗跛,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布攒发,位于F島的核電站调塌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惠猿。R本人自食惡果不足惜羔砾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偶妖。 院中可真熱鬧姜凄,春花似錦、人聲如沸餐屎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腹缩。三九已至,卻和暖如春空扎,著一層夾襖步出監(jiān)牢的瞬間藏鹊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工转锈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盘寡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓撮慨,卻偏偏與公主長得像竿痰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子砌溺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • SDWebImage影涉,有內(nèi)存和磁盤兩個(gè)緩存。內(nèi)存緩存中规伐,SDWebImage自己提供了默認(rèn)的內(nèi)存緩存類 SDMem...
    ryan0313閱讀 545評論 0 0
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,334評論 8 265
  • Socket:http://www.mamicode.com/info-detail-877996.html We...
    多情刀客無情刀閱讀 3,962評論 1 18
  • 鏈表(Linked list)是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)蟹倾,是一種線性表,但并不會(huì)按線性的順序存儲(chǔ)數(shù)據(jù)猖闪,而是在一個(gè)節(jié)點(diǎn)...
    pro648閱讀 1,245評論 0 4
  • 前言 經(jīng)典操作系統(tǒng)的虛擬內(nèi)存為什么要有虛擬內(nèi)存鲜棠?尋址方式地址空間分頁缺頁處理虛擬內(nèi)存帶來的好處地址翻譯如何索引提高...
    南梔傾寒閱讀 6,697評論 11 46