zoukankan      html  css  js  c++  java
  • 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源码

  • 相关阅读:
    Redis学习--命令执行过程中写AOF日志和同步从库顺序
    MySQL Innodb Engine--MVCC代码瞎猜
    MySQL Innodb Engine--DML操作时先生成Undo Log还是先生成Redo Log
    MySQL InnoDB Engine--自适应哈希索引总结
    MySQL InnoDB Engine--自适应哈希索引代码瞎猜03
    MySQL InnoDB Engine--自适应哈希索引代码瞎猜02
    MySQL InnoDB Engine--自适应哈希索引代码瞎猜01
    expect,传入数组进行SSH
    在底图不变的基础上,切换腾讯地图的文字注记(让底图更“干净”)
    十六进制透明颜色 转换成 RGBA 颜色(腾讯地图 与 微信地图组件所需颜色格式不同)
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461586.html
Copyright © 2011-2022 走看看