Objective-C中的NSMutableArray就是一個動態數組,不用指定數組的長度就能夠放心向裏邊添加元素,不須要考慮溢出的問題。實現動態數據的方式很是多,例如對靜態數組進行封裝擴容或者鏈表,甚至多種方式混合使用,根據數據量的大小來動態改變本身的數據結構。這裏就使用最簡單的根據靜態數據動態擴容的方式,來實現一個動態數組。git
爲了實現一套完整的自定義數據結構,這裏對靜態數組的封裝,使用的是上一篇文章中建立的自定義靜態數組JKRArray。github
JKRArrayList *array = [JKRArrayList new];
for (NSUInteger i = 0; i < 60; i++) {
[array addObject:[Person personWithAge:i]];
}
NSLog(@"添加後 %@", array);
打印:
--- 擴容: 16 -> 24 ---
--- 擴容: 24 -> 36 ---
--- 擴容: 36 -> 54 ---
--- 擴容: 54 -> 81 ---
添加後 size=60, {
<Person: 0x10285ad50>
<Person: 0x10285ae50>
<Person: 0x10285ae70>
...
<Person: 0x10285af30>
<Person: 0x10285af50>
<Person: 0x10285af70>
}
[array removeAllObjects];
NSLog(@"清空後 %@", array);
打印:
<Person: 0x100501070> dealloc
<Person: 0x1005010b0> dealloc
<Person: 0x1005010d0> dealloc
...
<Person: 0x10285b010> dealloc
清空後 size=0, {
}
複製代碼
動態數組的應該提供的功能仿照NSMutableArray來設計,因爲以後還會用多種鏈表來實現動態數組,因此還會有不少相同的處理邏輯和接口,這裏先定義一個動態數組的基類:數組
@interface JKRBaseList<ObjectType> : NSObject {
@protected
// 記錄動態數組的當前長度
NSUInteger _size;
}
- (NSUInteger)count;
- (void)rangeCheckForAdd:(NSUInteger)index;
- (void)rangeCheckForExceptAdd:(NSUInteger)index;
- (void)addObject:(nullable ObjectType)anObject;
- (BOOL)containsObject:(nullable ObjectType)anObject;
- (nullable ObjectType)firstObject;
- (nullable ObjectType)lastObject;
- (void)removeFirstObject;
- (void)removeLastObject;
- (void)removeObject:(nullable ObjectType)anObject;
- (_Nullable ObjectType)objectAtIndexedSubscript:(NSUInteger)idx;
- (void)setObject:(_Nullable ObjectType)obj atIndexedSubscript:(NSUInteger)idx;
@end
@interface JKRBaseList<ObjectType> (JKRBaseList)
- (void)insertObject:(nullable ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(nullable ObjectType)anObject;
- (nullable ObjectType)objectAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfObject:(nullable ObjectType)anObject;
- (void)removeAllObjects;
- (void)enumerateObjectsUsingBlock:(void (^)(_Nullable ObjectType obj, NSUInteger idx, BOOL *stop))block;
@end
複製代碼
JKRBaseList是全部動態數組的基類,之因此有一部分在擴展裏邊寫,是爲了便於區分,擴展內的接口是須要子類實現的,而擴展以外的接口則是不一樣方式實現的動態數組都相同的處理,不須要子類重寫,只要JKRBaseList本身實現便可。把不須要JKRBaseList實現的方法寫在擴展的好處就是不須要在JKRBaseList.m裏邊寫接口的具體實現,若是定義成它的方法聲明而不去實現的話,編譯器會報警告。另外分紅兩部分寫,也便於區分哪些是子類須要實現的。bash
系統的NSArray和NSMutableArray都容許存放nil,這裏爲了擴容功能,因此容許傳入並保存nil到數據中。數據結構
首先先看一下JKRBaseList裏邊的成員變量:NSUInteger _size; 這個變量是全部動態數組都須要的,它負責記錄當前動態數組的長度。由於動態數組對外部可見的長度和內部真實的長度是不必定一致的,好比如今咱們要實現的經過靜態數組封裝的動態數組,剛剛開始初始化的時候,動態數組對象內部保存的靜態數組長度多是16,而外部展現的長度則爲0,由於尚未添加任何元素。若是用鏈表來實現動態數組的話,一樣須要記錄數組長度,不然每次都要遍歷鏈表全部節點來累加計算數組長度。app
下面先看一下JKRBaseList裏邊不須要子類單獨實現的邏輯,依次看一下爲何它們是公用的,以及它們是如何實現的。post
注:有些公共接口須要調用子類須要重寫的接口來實現具體功能,先不要考慮子類如何實現,假設子類已經實現,只須要調用對於接口實現完整功能便可。ui
如下方法所有在JKRBaseList.m中實現,不須要子類重寫。atom
獲取數組的長度spa
由於動態數組的長度保存在成員變量_size中,只須要返回_size便可
時間複雜度: O(1)
- (NSUInteger)count {
return _size;
}
複製代碼
添加元素時的越界檢查
由於動態數組能夠尾部添加元素的特性,添加元素的時越界檢查範圍應該是:index > 0 && index <= _size。index能夠等於數組的長度,此時至關於在數組尾部追加一個元素。
- (void)rangeCheckForAdd:(NSUInteger)index {
// index能夠等於_size,至關於在數組尾部追加元素
if (index < 0 || index > _size) {
[self indexOutOfBounds:index];
}
}
- (void)indexOutOfBounds:(NSUInteger)index {
NSAssert(NO, @"index: %zd, size: %zd", index, _size);
}
複製代碼
除添加以外的越界檢查
除了添加以外,其餘狀況下,操做數組的index應該在數組長度範圍以內:index > 0 && index < _size。
- (void)rangeCheckForExceptAdd:(NSUInteger)index {
if (index < 0 || index >= _size) {
[self indexOutOfBounds:index];
}
}
複製代碼
尾部追加元素
在數組尾部追加元素就至關於在 index=_size 的位置插入一個元素,插入元素的接口由子類實現。
- (void)addObject:(id)anObject {
[self insertObject:anObject atIndex:_size];
}
複製代碼
是否包含元素
是否包含元素,經過查找元素的index,判斷是否找到。indexOfObject由子類實現,未找到則返回NSUIntegerMax,處理同NSArray。
- (BOOL)containsObject:(id)anObject {
return [self indexOfObject:anObject] != NSUIntegerMax;
}
複製代碼
返回第一個/最後一個元素
能夠經過根據objectAtIndex接口得到,不一樣之處在於當數組長度爲0的時候,直接返回nil,而不是調用objectAtIndex來越界報錯(同NSArray處理)。
- (id)firstObject {
if (_size == 0) {
return nil;
}
return [self objectAtIndex:0];
}
- (id)lastObject {
if (_size == 0) {
return nil;
}
return [self objectAtIndex:_size - 1];
}
複製代碼
刪除第一個/最後一個元素
同返回第一次元素同樣,當數組長度爲0時世界返回,不會給removeObjectAtIndex去處理來越界報錯。removeObjectAtIndex由子類實現。
- (void)removeFirstObject {
if (_size == 0) {
return;
}
[self removeObjectAtIndex:0];
}
- (void)removeLastObject {
if (_size == 0) {
return;
}
[self removeObjectAtIndex:_size - 1];
}
複製代碼
刪除某元素
這裏調用分別調用indexOfObject接口獲取元素的index,而後再調用removeObjectAtIndex接口刪除元素。
- (void)removeObject:(id)anObject {
NSUInteger index = [self indexOfObject:anObject];
if (index != NSUIntegerMax) {
[self removeObjectAtIndex:index];
}
}
複製代碼
支持數組的[]運算符
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
return [self objectAtIndex:idx];
}
// 這裏須要區分處理
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
// 若是idx==_size,在數組尾部添加元素
if (idx == _size) {
[self insertObject:obj atIndex:idx];
} else { // 不然替換index位置的元素
[self replaceObjectAtIndex:idx withObject:obj];
}
}
複製代碼
首先建立JKRBaseList的子類JKRArrayList,在JKRArrayList.m中依次完成以下接口的具體實現:
- (void)insertObject:(nullable ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(nullable ObjectType)anObject;
- (nullable ObjectType)objectAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfObject:(nullable ObjectType)anObject;
- (void)removeAllObjects;
- (void)enumerateObjectsUsingBlock:(void (^)(_Nullable ObjectType obj, NSUInteger idx, BOOL *stop))block;
複製代碼
動態數組內部成員變量
動態數組保存這一個靜態數組,而且在初始化的時候建立靜態數據,並指定其長度。
#define JKRARRAY_LIST_DEFAULT_CAPACITY (1<<4)
@interface JKRArrayList ()
@property (nonatomic, strong) JKRArray *array;
@end
@implementation JKRArrayList
+ (instancetype)array {
return [[self alloc] initWithCapacity:JKRARRAY_LIST_DEFAULT_CAPACITY];
}
+ (instancetype)arrayWithCapacity:(NSUInteger)capacity {
return [[self alloc] initWithCapacity:JKRARRAY_LIST_DEFAULT_CAPACITY];
}
- (instancetype)init {
return [self initWithCapacity:JKRARRAY_LIST_DEFAULT_CAPACITY];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity {
self = [super init];
self.array = [JKRArray arrayWithLength:capacity > JKRARRAY_LIST_DEFAULT_CAPACITY ? capacity : JKRARRAY_LIST_DEFAULT_CAPACITY];
return self;
}
複製代碼
插入元素
插入元素不是簡單的在靜態數組對應的index位置將元素放進去這麼簡單,假設如今有一個長度爲6的數組,要將71插入到index爲1的位置,以下圖:
若是直接將71替換到index爲1的位置,那麼數組元素就變成了:
這樣並無增長元素,靜態數組元素數量沒有變化,而且原來index爲1位置存放的32丟失了。
正確的作法應該是,從最後的位置開始到index位置,每個元素都向後移動一位,將index位置空出來,而後將index位置放入新元素:
代碼以下:
// 時間複雜度複雜度O(n)
// 尾部追加元素複雜度O(1)
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
// 越界檢查
[self rangeCheckForAdd:index];
// 若是須要擴容則擴充容量
[self ensureCapacityWithCapacity:_size + 1];
// 添加元素
for (NSUInteger i = _size; i > index; i--) {
self.array[i] = self.array[i - 1];
}
self.array[index] = anObject;
// 添加元素以後,_size要加1
_size++;
}
複製代碼
動態持續添加元素時,確定會出現靜態數組容量沒法知足的狀況,這是就須要擴充靜態數組的容量,下面開始實現擴容操做。
擴容
當插入一個元素的時候,首先須要判斷當前靜態數組是否有足夠的容量存放新添加的元素,當前動態數組內元素的個數爲_size,因此靜態數組須要知足其長度不小於_size + 1,即 _array.length >= _size + 1。若是靜態數組容量不夠,則就要進行擴容,擴容的方式就是建立一個比當前靜態數組長度更大的靜態數據,並將原數組數據所有複製到新數組中,而後再進添加新元素。
具體代碼以下:
// 時間複雜度複雜度O(n)
- (void)ensureCapacityWithCapacity:(NSUInteger)capacity {
NSUInteger oldCapacity = self.array.length;
if (oldCapacity >= capacity) {
return;
}
NSUInteger newCapacity = oldCapacity + (oldCapacity >> 1);
NSLog(@"--- 擴容: %zd -> %zd ---", oldCapacity, newCapacity);
JKRArray *newArray = [JKRArray arrayWithLength:newCapacity];
for (NSUInteger i = 0; i < _size; i++) {
newArray[i] = self.array[i];
}
self.array = newArray;
}
複製代碼
刪除元素
刪除元素一樣須要挪動節點:
// 時間複雜度複雜度O(n)
// 刪除尾部元素複雜度O(1)
- (void)removeObjectAtIndex:(NSUInteger)index {
[self rangeCheckForExceptAdd:index];
for (NSUInteger i = index + 1; i < _size; i++) {
self.array[i - 1] = self.array[i];
}
self.array[--_size] = nil;
}
複製代碼
獲取元素的index
獲取元素的index須要遍歷靜態數組的所有節點,找到匹配相等的元素,並返回對於的index。
// 時間複雜度複雜度O(n)
- (NSUInteger)indexOfObject:(id)anObject {
if (!anObject) {
for (NSUInteger i = 0; i < _size; i++) {
if (self.array[i] == nil) {
return i;
}
}
} else {
for (NSUInteger i = 0; i < _size; i++) {
if ([anObject isEqual:self.array[i]]) {
return i;
}
}
}
return NSUIntegerMax;
}
複製代碼
清空數組
// 時間複雜度 O(n)
- (void)removeAllObjects {
if (_size == 0) return;
for (NSUInteger i = 0; i < _size; i++) {
self.array[i] = nil;
}
_size = 0;
}
複製代碼
經過index獲取元素
// 時間複雜度 O(1)
- (id)objectAtIndex:(NSUInteger)index {
[self rangeCheckForExceptAdd:index];
return self.array[index];
}
複製代碼
重寫打印
- (NSString *)description {
NSMutableString *string = [NSMutableString string];
[string appendString:[NSString stringWithFormat:@"size=%zd, {\n", _size]];
[self enumerateObjectsUsingBlock:^(id _Nullable obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx) {
[string appendString:@"\n"];
}
[string appendString:[NSString stringWithFormat:@"%@", obj]];
}];
[string appendString:@"\n}"];
return string;
}
複製代碼
動態數組在添加和刪除元素時,須要依次挪動元素,這樣顯然費時又費力,而單向鏈表能夠動態的添加和刪除元素,並且不須要進行挪動元素這樣的操做。有了動態數組的比較,能夠更加了解它們各自的優缺點。