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;
}
复制代码
动态数组在添加和删除元素时,须要依次挪动元素,这样显然费时又费力,而单向链表能够动态的添加和删除元素,并且不须要进行挪动元素这样的操做。有了动态数组的比较,能够更加了解它们各自的优缺点。