Analyze the C # Set internally -- List <T>,
Let's discuss some of the mechanisms of C # list <T> today. If there is anything wrong with it, please feel grateful even if you spend it ..
List <T> Introduction
1. List <T> is a non-thread-safe set.
When I look at the List <T> source code, I find the SynchronizedList class. From this name and its code implementation, it is a thread-safe set, but it is unclear how the SynchronizedList and List <T> work together to implement thread security, or is this method only used for the FCL class library? Anyone who knows this can be confused ..
[Serializable ()] internal class SynchronizedList: IList <T> {private MyList <T> _ list; private Object _ root; internal SynchronizedList (MyList <T> list) {_ list = list; _ root = (System. collections. ICollection) list ). syncRoot;} public int Count {get {lock (_ root) {return _ list. count ;}} public bool IsReadOnly {get {return (ICollection <T>) _ list ). isReadOnly ;}} public void Add (T item) {lock (_ root) {_ list. add (item) ;}} public void Clear () {lock (_ root) {_ list. clear () ;}} public bool Contains (T item) {lock (_ root) {return _ list. contains (item) ;}} public void CopyTo (T [] array, int arrayIndex) {lock (_ root) {_ list. copyTo (array, arrayIndex) ;}} public bool Remove (T item) {lock (_ root) {return _ list. remove (item) ;}} System. collections. IEnumerator System. collections. IEnumerable. getEnumerator () {lock (_ root) {return _ list. getEnumerator () ;}} IEnumerator <T> IEnumerable <T>. getEnumerator () {lock (_ root) {return (IEnumerable <T>) _ list ). getEnumerator () ;}} public T this [int index] {get {lock (_ root) {return _ list [index] ;}} set {lock (_ root) {_ list [index] = value ;}} public int IndexOf (T item) {lock (_ root) {return _ list. indexOf (item) ;}} public void Insert (int index, T item) {lock (_ root) {_ list. insert (index, item) ;}} public void RemoveAt (int index) {lock (_ root) {_ list. removeAt (index );}}}View Code
2. List <T> the internal data structure is relatively simple. The hash algorithm is used without hashtable or Dictionary. The default internal array length is 4 and the maximum length is 2146435071, list <T> does not have a load factor similar to Hashtable. It can also be understood that the load factor of List <T> is 1. When the add method is executed, you must determine whether the current list capacity is equal to the used capacity. If the list capacity is equal, you can scale up. The scaling rule is to double the current capacity.
3. The elements added to the list are ordered, and there is an internal _ size variable. Each time the add operation is executed, _ size plus one.
2. Performance Comparison Between List <T> and Dictionary.
Although their comparability was not too strong, it is still commonly used to add, search, and delete methods to make a comparison.
1) Add new elements
Theoretically, the ADD () of List <T> is faster than that of Dictionary, because List <T> does not have hash computing. This is also true in actual experiments.
Static void Main (string [] args) {for (int I = 0; I <10; I ++) {List <int> myList = new List <int> (); dictionary <int, int> myDict = new Dictionary <int, int> (); Stopwatch swatch = new Stopwatch (); swatch. restart (); for (int j = 0; j <100000; j ++) {myList. add (j);} swatch. stop (); Console. writeLine ("myList:" + swatch. elapsedTicks); swatch. restart (); for (int j = 0; j <100000; j ++) {myDict. add (j, j);} swatch. stop (); Console. writeLine ("myDict:" + swatch. elapsedTicks); Console. writeLine ("------------------------------");} Console. readKey ();}View Code
Execution result:
It can be seen that List <T> is an order of magnitude faster than Dictionary.
2) Search for elements
The query speed of a Dictionary is much faster than that of a list. In extreme cases, the query speed is several orders of magnitude,
Through the previous article, we know that Dictionary calculates the hash value of the key first, and then performs index locating through the hash value. list is a for loop first, and then compares it with the input expression value, the following code:
Public T Find (Predicate <T> match) {if (match = null) {ThrowHelper. throwArgumentNullException (ExceptionArgument. match);} Contract. endContractBlock (); for (int I = 0; I <_ size; I ++) {if (match (_ items [I]) {return _ items [I] ;}} return default (T );}View Code
Next we will test the performance comparison in two extreme cases:
Scenario 1:
Static void Main (string [] args) {for (int I = 0; I <10; I ++) {List <int> myList = new List <int> (); dictionary <int, int> myDict = new Dictionary <int, int> (); Stopwatch swatch = new Stopwatch (); for (int j = 0; j <10000000; j ++) {myList. add (j);} swatch. start (); var myListResult = myList. find (p => {return p = 9999999;}); swatch. stop (); Console. writeLine ("myList:" + swatch. elapsedTicks); for (int j = 0; j <10000000; j ++) {myDict. add (j, j);} swatch = new Stopwatch (); swatch. start (); var myDictResult = myDict [9999999]; swatch. stop (); Console. writeLine ("myDict:" + swatch. elapsedTicks); Console. writeLine ("------------------------------");} Console. readKey ();}View Code
Result:
Scenario 2:
Static void Main (string [] args) {for (int I = 0; I <10; I ++) {List <int> myList = new List <int> (); dictionary <int, int> myDict = new Dictionary <int, int> (); Stopwatch swatch = new Stopwatch (); for (int j = 0; j <10000000; j ++) {myList. add (j);} swatch. start (); var myListResult = myList. find (p => {return p = 0;}); swatch. stop (); Console. writeLine ("myList:" + swatch. elapsedTicks); for (int j = 0; j <10000000; j ++) {myDict. add (j, j);} swatch = new Stopwatch (); swatch. start (); var myDictResult = myDict [0]; swatch. stop (); Console. writeLine ("myDict:" + swatch. elapsedTicks); Console. writeLine ("------------------------------");} Console. readKey ();}View Code
Result:
In the second extreme case, List <T> is faster than Dictionary. In combination with the previous article, I think you already know the reason,
3) remove elements
The Remove Method of List <T> calculates the index value of the element before removing the element, and then transmits the index value to the RemoveAt method to Remove the element. Therefore, we can see that when removing an element, it is also a List of all the items that have been replaced by a Dictionary <T>. If you do not need to talk about anything else, go to the source code directly.
Static void Main (string [] args) {for (int I = 0; I <10; I ++) {MyList <int> myList = new MyList <int> (); dictionary <int, int> myDict = new Dictionary <int, int> (); Stopwatch swatch = new Stopwatch (); for (int j = 0; j <10000000; j ++) {myList. add (j);} swatch. start (); myList. remove (55555); swatch. stop (); Console. writeLine ("myList:" + swatch. elapsedTicks); for (int j = 0; j <10000000; j ++) {myDict. add (j, j);} swatch = new Stopwatch (); swatch. start (); var myDictResult = myDict. remove (55555); swatch. stop (); Console. writeLine ("myDict:" + swatch. elapsedTicks); Console. writeLine ("------------------------------");} Console. readKey ();}View Code
Running result:
I personally think that the most fundamental reason for their performance difference in element insertion and deletion is that the hash used by Dictionary does not exist in the List, even if the hash computing of Dictionary has a certain performance loss, the overall performance is still complete.
The next section describes the implementation of ArrayList. In fact, ArrayList and list are more comparable.