C# List源码分析(二)

常用操作的复杂度分析

Contains

该方法是一个O(n)的方法,是根据顺序依次遍历整个列表的,观看源码,跟JAVA还是有不少分别的,在上一篇中就有发现,因为C#对Primitive类型是有处理的,所以跟JAVA代码略有不同

// Contains returns true if the specified element is in the List.
// It does a linear, O(n) search.  Equality is determined by calling
// item.Equals().
//
public bool Contains(T item) {
    if ((Object) item == null) {
        for(int i=0; i<_size; i++)
            if ((Object) _items[i] == null)
                return true;
        return false;
    }
    else {
        EqualityComparer<T> c = EqualityComparer<T>.Default;
        for(int i=0; i<_size; i++) {
            if (c.Equals(_items[i], item)) return true;
        }
        return false;
    }
}

代码中使用了EqualityComparer,就是对Primitive类型进行了额外的处理,否则他们并非继承Object,则没有Equals和Hashcode方法。
以下是JAVA的代码

/**
 * Returns <tt>true</tt> if this list contains the specified element.
 * More formally, returns <tt>true</tt> if and only if this list contains
 * at least one element <tt>e</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
 *
 * @param o element whose presence in this list is to be tested
 * @return <tt>true</tt> if this list contains the specified element
 */
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

Java是通过调用Object的equals方法来实现的,也从侧面说明了为什么JAVA的泛型其实是不支持primitive类型了。
另外还有一个细节,C#对Contains是针对泛型的方法,而JAVA是针对Object的方法。
不过忽略掉Primitive的泛型,JAVA代码的简洁上,比C#确实要好些,用起来麻烦一些

Insert

Java中的Insert是一个重载方法,其实就是对Add方法的重载
C#则是额外抽象了一个方法叫做Insert,因为Insert指定了索引,所以,insert操作对数组是有copy操作的,所以复杂度为O(n)
而且同时有可能因为插入造成数组大量的copy从而进行Capacity的倍增

// Inserts an element into this list at a given index. The size of the list
// is increased by one. If required, the capacity of the list is doubled
// before inserting the new element.
// 
public void Insert(int index, T item) {
    // Note that insertions at the end are legal.
    if ((uint) index > (uint)_size) {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
    }
    Contract.EndContractBlock();
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    if (index < _size) {
        Array.Copy(_items, index, _items, index + 1, _size - index);
    }
    _items[index] = item;
    _size++;            
    _version++;
}

这里使用了ThrowHelper这个静态类,最开始,还以为是他们为了让代码可读,所以使用工厂方法,更易用
但是后来发现不是这门回事,是为了在中间层次优化集合类而做的。

// This file defines an internal class used to throw exceptions in BCL code.
// The main purpose is to reduce code size. 
// 
// The old way to throw an exception generates quite a lot IL code and assembly code.
// Following is an example:
//     C# source
//          throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
//     IL code:
//          IL_0003:  ldstr      "key"
//          IL_0008:  ldstr      "ArgumentNull_Key"
//          IL_000d:  call       string System.Environment::GetResourceString(string)
//          IL_0012:  newobj     instance void System.ArgumentNullException::.ctor(string,string)
//          IL_0017:  throw
//    which is 21bytes in IL.
// 
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
// argument name and resource name in a small integer. The source code will be changed to 
//    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
//
// The IL code will be 7 bytes.
//    IL_0008:  ldc.i4.4
//    IL_0009:  ldc.i4.4
//    IL_000a:  call       void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
//    IL_000f:  ldarg.0
//
// This will also reduce the Jitted code size a lot. 
//
// It is very important we do this for generic classes because we can easily generate the same code 
// multiple times for different instantiation. 
// 
// <

在IL层次上,如果使用throw关键字,那么编译出来的IL代码很多,然后,如果使用静态方法,他们IL大量代码将被重用,从而减少IL代码的大小。

InsertRange

InsertRange 方法,在指定位置插入若干元素,该方法和JAVA中的addAll方法有些类似,不过不同的是,C#中的插入的参数是必须实现IEnumerable< T >接口的,但是Java中,参数是必须实现Collection接口的,这也导致了两者方法中的不同

C# 针对Collection,对操作进行了优化,在内存copy上,只是进行了一次,但是如果是自己的类实现了IEnumerable接口的话,操作复杂度则大大增加,会频繁调用insert操作

自己实现了IEnumerable接口的话,切忌使用insertRange方法,复杂度太高

// Inserts the elements of the given collection at a given index. If
// required, the capacity of the list is increased to twice the previous
// capacity or the new size, whichever is larger.  Ranges may be added
// to the end of the list by setting index to the List's size.
//
public void InsertRange(int index, IEnumerable<T> collection) {
    if (collection==null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    }

    if ((uint)index > (uint)_size) {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
    }
    Contract.EndContractBlock();

    ICollection<T> c = collection as ICollection<T>;
    if( c != null ) {    // if collection is ICollection<T>
        int count = c.Count;
        if (count > 0) {
            EnsureCapacity(_size + count);
            if (index < _size) {
                Array.Copy(_items, index, _items, index + count, _size - index);
            }

            // If we're inserting a List into itself, we want to be able to deal with that.
            if (this == c) {
                // Copy first part of _items to insert location
                Array.Copy(_items, 0, _items, index, index);
                // Copy last part of _items back to inserted location
                Array.Copy(_items, index+count, _items, index*2, _size-index);
            }
            else {
                T[] itemsToInsert = new T[count];
                c.CopyTo(itemsToInsert, 0);
                itemsToInsert.CopyTo(_items, index);                    
            }
            _size += count;
        }                
    }
    else {
        using(IEnumerator<T> en = collection.GetEnumerator()) {
            while(en.MoveNext()) {
                Insert(index++, en.Current);                                    
            }                
        }
    }
    _version++;            
}

SynchronizedList

这个类实现了IList接口,只是所有的方法都加上了同步操作,看代码结构就知道是典型的代理模式啊

internal class SynchronizedList : IList<T> {
    private List<T> _list;
    private Object _root;

    /// other
}

在初始化的时候,使用的lock用的对象则是List中的SyncRoot,这样每次增加了同步操作,保证了线程的安全性
Java的线程安全的List是Vector,和C#不同的是,没有用代理模式,而是所有方法全部重写了,仍然是ArrayList一个结构,不同的是方法上都加了synchronized

以下为List源码地址

List源码
Array源码

原文地址:https://www.cnblogs.com/qitian1/p/6461586.html