摘要: 一年的时间里写了一些东西,做个目录方便自己查找。也包含了自己打算写的一些东西。一.网络协议1.字节和字符,对信息进行编码2.分组报文,协议和Socket的概念3.Socket:流,TCP连接,TCP可靠性概述4.TCP和流5.关于TCP的可靠性6.用TCP/IP实现自己简单的应用程序协议:成帧器部分7.用TCP/IP实现自己简单的应用程序协议:其余部分(包括完整代码)8.用TCP/IP实现自己简单的应用程序协议:最后再返回来看HTTP协议9.由TCP的可靠性实现对比WCF中可靠性的实现(未完成)10.由传输过程中需要面对的问题探讨WCF中的“可靠性”二.多线程之旅1.多线程之旅——从概念开始2阅读全文
posted @ 2012-03-26 10:41 浪雪 阅读(30) 评论(0) 编辑
摘要: 1.怎样解题 数学思维的新方法http://book.douban.com/subject/2124114/2.代码大全http://book.douban.com/subject/1477390/3.Windows并发编程指南http://book.douban.com/subject/4214617/4.软件框架设计艺术http://book.douban.com/subject/6003832/5.Effective C++http://book.douban.com/subject/1842426/6.Effective Javahttp://book.douban.com/subje阅读全文
posted @ 2012-01-31 20:23 浪雪 阅读(218) 评论(4) 编辑
 
内核对象(kernel object):
windows操作系统提供的最近本同步机制,这些对象是构建并发程序和基本并发数据结构的基础。事实上,无论在代码中是否直接使用了这些对象,在软件的某个层次中都肯定会依赖它们。
 


内核对象的分类:
1.内核对象有很多种,有5种是专门用于同步的。其他的诸如I/O完成端口后面会有介绍。
分别是:Mutex, Semaphore, Auto-Reset Event , Manual-Reset Event , Waitable Timer

2.这几种内核对象就是对不同情形的分类。
从状态来说,这几种内核对象都分为SignaledNonsignaled状态。简单来说,就是“是” 和“否”允许进入临界域。因为创建这几种内核对象本身是对不同情况的分类,所以这几种内核对象切换”是“和”否“所需要的条件肯定是不同的。
如同前面所说,在等待某件事情发生时,采用自旋操作往往伴随着对CPU运行周期的一种抢夺和浪费,有时候我们就需要线程”什么事都不干“,也就是线程阻塞。
 

“阻塞”和停止的区别:
只有Windows操作体统内核才能停止一个线程的运行。在用户态模式中运行的线程可能会被操作系统抢占,但线程会以最快的速度再次调度。
因此理想的锁应该是在没有竞争的情况下可以快速的执行不会阻塞,但是如果出现了多个线程竞争,那么就应该被操作系统内核阻塞,这样就可以避免CPU时间的浪费。
线程发生阻塞的原因有多种,比如: 执行I/O操作 、睡眠、挂起等。另外一个常见的原因就是等待内核对象切换成Signaled状态。
其实说白了,为了维护次序肯定不能一拥而上,关键就是“等”。当然等这个抽象概念大家都知道,更具体一点来说就是等“一个”还是等“多个”?有可能等待一个条件的成熟,也有可能等待多个条件的成熟。再更具体点,在等多个的时候,是这多个中的任意一个呢,还是要等全部?
 

 
当线程阻塞时,CLR将使用一个通用的等待函数,而并不考虑这个等待是不是由调用了WaitHandler类的 WaitOne、 WaitAny、WaitAll等,或是在用户态的混合锁上的任何阻塞调用,如Monitor,ReadWriteLockSlim等。
 
 
 
内核对象API
当一个线程获得了一个指向内核对象的引用,也就是说在代码中new出来或者其他方式获得一个内核对象时,我们可以调用.NET中的等待API在这个对象等待。多个线程可以同时等待一个内核对象,这样多个线程也许都会进入阻塞状态。根据不同的内核对象,唤醒阻塞的线程时也会有不同的情况,有的内核对象在状态切换时,只唤醒多个等待线程中的一个,比如Pulse,有的是全部都唤醒,比如PulseAll。这两种方式各有自己的好处和不足。比如PulseAll安全,不会造成唤醒遗失现象,但是会造成唤醒的线程重复等待。具体情况后面会有详细说明。
 

既然说了各种内核对象只是为了应对不同的状况而被人们分类的时候,我们先讨论这几种状况:
 
1.当我们只允许一个线程进入临界区的时候:
 
Mutex
在win32中,等待一个Mutex对象的API如下:
 
DWORD WINAPI WaitForSingleObject(HANDLE hHandle , DWORD dwMilliseconds)
 
使用方式是:
HANDLE hMutex = CreateMutex(...);
...
DWORD WINAPI WaitForSingleObject(hMutant  , IFINITE);
 
由于方法参数用的是一个句柄,因此也可以用该方法在其他内核对象上等待。换句话说,就是用方法来抽象了等的概念。
 
.NET中对应的方法如下
Mutex mutex = new Mutex();
...
mutex.WaitOne();

 WaitHandle 
      EventWaitHandle 
         AutoResetEvent 
         ManualResetEvent 
      Semaphore 
      Mutex
 
是用对象间的关系来表达相应的逻辑。用一个WaitHandle 类来抽象出”等待“的概念。
 
可以看出这是C++中过程式风格和C#面向对象风格的区别
public abstract class WaitHandle : MarshalByRefObject, IDisposable { 
   public virtual void Close(); 
   public void Dispose(); 

   public virtual Boolean WaitOne(); 
   public virtual Boolean WaitOne(Int32 millisecondsTimeout); 

   public static Int32 WaitAny(WaitHandle[] waitHandles); 
   public static Int32 WaitAny(WaitHandle[] waitHandles, Int32 millisecondsTimeout); 

   public static Boolean WaitAll(WaitHandle[] waitHandles); 
   public static Boolean WaitAll(WaitHandle[] waitHandles, Int32 millisecondsTimeout); 

   public static Boolean SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn); 
   public static Boolean SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, 
      Int32 millisecondsTimeout, Boolean exitContext) 

   public SafeWaitHandle SafeWaitHandle { get; set; } 

   // Returned from WaitAny if a timeout occurs 
   public const Int32 WaitTimeout = 0x102;  
}

 


 
2.当我们只允许指定数量的线程进入临界区的情况以及应用
 
Semaphore
和mutex不同的是,Semaphore不应该被认为是由某个特定的线程所“拥有”。例如,一个线程可以在信号量上执行插入操作,而另一个线程可以在同一个线程上执行取走操作。通常,信号量用于保护在容量上有限的资源。比如一个固定大小的数据库连接池需要被定期访问,这样同时请求的连接数量不应该超过可用连接的数量。同样,还可能有一个共享的内存缓冲区,缓冲区的大小是变动的,但要确保同时访问缓冲区的线程数量与缓冲区中的可用项数量一样多。
因为允许多个线程进入临界区,因此信号量并不能避免并发带来的危害,通常需要配合其他的数据同步机制。Semaphore本质上就是内核维护的一个计数器,计数值大于1的Semaphore并不能保证互斥行为。
 
public sealed class SimpleWaitLock : IDisposable { 
   private Semaphore m_AvailableResources; 

   public SimpleWaitLock(Int32 maximumConcurrentThreads) { 
      m_AvailableResources =  
         new Semaphore(maximumConcurrentThreads, maximumConcurrentThreads); 
   } 

   public void Enter() { 
      // Wait efficiently in the kernel for resource access, then return 
      m_AvailableResources.WaitOne(); 
   } 

   public void Leave() { 
      // This thread doesn’t need access anymore; another thread can have it 
      m_ AvailableResources.Release(); 
   } 

   public void Dispose() { m_AvailableResources.Close(); } 
}

 

 
阻塞列队
在我们操作一个列队的时候,最先想到的就是从列队中提取一个元素的时候如果没有元素应该怎么办。我们可以用一个等待条件使得列队中有元素了再提取,否则就阻塞。看样子就应该这样的
public class BlockingQueueWithAutoResetEvents <T>
    {
        private Queue <T> m_queue = new Queue <T>();
        private Mutex m_mutex = new Mutex ();
        private AutoResetEvent m_event = new AutoResetEvent (false );
 
        public void Enqueue(T obj)
        {
            // Enter the critical region and insert into our queue.
            m_mutex.WaitOne();
            try
            {
                m_queue.Enqueue(obj);
            }
            finally
            {
                m_mutex.ReleaseMutex();
            }
 
            // Note that an item is available, possibly waking a consumer.
            m_event.Set();
        }
 
        public T Dequeue()
        {
            // Dequeue the item from within our critical region.
            T value;
            bool taken = true ;
            m_mutex.WaitOne();
            try
            {
                // If the queue is empty, we will need to exit the
                // critical region and wait for the event to be set.
                while (m_queue.Count == 0)
                {
                    taken = false ;
                    WaitHandle .SignalAndWait(m_mutex, m_event);
                    m_mutex.WaitOne();
                    taken = true ;
                }
 
                value = m_queue.Dequeue();
            }
            finally
            {
                if (taken)
                {
                    m_mutex.ReleaseMutex();
                }
            }
 
            return value;
        }
    }

 


 
阻塞/有界列队
但是如果对于一个动态的生产者/消费者的情况中,这样的只对消费者限制,对生产者没限制的列队,随着时间的推移,生产者和消费者之间的比率就会不平衡。
我们想要实现的功能是:如果试图从一个空的队列中取数据,那么线程将阻塞,知道有新的元素加入。试图将数据放入到一个容量已满的列队同样也会导致线程阻塞,直到有空间腾出来。
 
现在我们采用Semaphore+Mutex的方式实现这个数据结构。Mutex用来实现临界域的互斥行为,确保对状态的修改能够安全的进行;Semaphore用于实现控制同步。Semaphore使得这个工作变得相对容易,因为对容量有限的资源进行保护正是当初为什么要划分出这么一个类的原因。值得注意的是,管理Semaphore和Mutex产生的内核切换开销可能是这个结构最大的性能问题。
 
 
   public class BlockingBoundedQueue<T>
    {
        private Queue <T> m_queue = new Queue <T>();
        private Mutex m_mutex = new Mutex ();
        private Semaphore m_producerSemaphore;
        private Semaphore m_consumerSemaphore;
 
        public BlockingBoundedQueue(int capacity)
        {
            m_producerSemaphore = new Semaphore (capacity, capacity);
            m_consumerSemaphore = new Semaphore (0, capacity);
        }
 
        public void Enqueue(T obj)
        {
            // Ensure the buffer hasn't become full yet. If it has, we will
            // be blocked until a consumer takes an item.
            m_producerSemaphore.WaitOne();
 
            // Now enter the critical region and insert into our queue.
            m_mutex.WaitOne();
            try
            {
                m_queue.Enqueue(obj);
            }
            finally
            {
                m_mutex.ReleaseMutex();
            }
 
            // Note that an item is available, possibly waking a consumer.
            m_consumerSemaphore.Release();
        }
 
        public T Dequeue()
        {
            // This call will block if the queue is empty.
            m_consumerSemaphore.WaitOne();
 
            // Dequeue the item from within our critical region.
            T value;
            m_mutex.WaitOne();
            try
            {
                value = m_queue.Dequeue();
            }
            finally
            {
                m_mutex.ReleaseMutex();
            }
 
            // Note that we took an item, possibly waking producers.
            m_producerSemaphore.Release();
 
            return value;
        }
    }

 

 
因为其实Semaphore说白了就是一个计数器,因此我们用一个int类型来计数+mutex也同样能实现这个功能。后面我会只用混合构造锁Monitor这一个类来实现。
其实在一些情况下,如AutoResteEvent和计数为1的Semaphore, 感觉起来就和Mutex差不多。
 
如果单纯的使用内核对象作为同步手段因为需要付出内核态切换的代价所以并不是一个好的选择。
我们接下来就看看结合自旋机制和内核对象的抽象层度更高的混合构造锁。



多线程之旅

1.多线程之旅——从概念开始

2.多线程之旅二——线程

3.多线程之旅之三——Windows内核对象同步机制

4.多线程之旅之四——浅谈内存模型和用户态同步机制

5.多线程之旅之五——线程池与I/O完成端口

6.多线程之旅六——异步编程模式,自己实现IAsyncResult

7.多线程之旅七——再谈内存模型

8.多线程之旅八——多线程下的数据结构

9.多线程之旅九——最后再回头总结一下



posted @ 2012-04-24 10:42 浪雪 阅读(1152) 评论(1) 编辑
不知道是不是我引起的话题,老赵出了个O1的面试题

// Please write an sequence list implements the interface with the required
// time complexity described in the comments. The users can add the same
// element as many times as they want, but it doesn't support the null item.
// You can use any types in .NET BCL but cannot use any 3rd party libraries.
// PS: You don't need to consider the multi-threaded environment.
interface IMyList<T> : IEnumerable<T>
{
    // O(1)
    // Add an item at the beginning of the list.
    void AddFirst(T item);
    
    // O(1)
    // Add an item at the end of the list.
    void AddLast(T itme);
    
    // O(1)
    // Remove the item from the list. If the list contains multiple
    // copies/references of the item, remove one of them.
    void Remove(T item);
    
    // O(1)
    // Reverse the list.
    void Reverse();
}
 

这道题有实用性吗?当然有,因为这题是采用了缓存的思想,在很多代码中经常见到。
 
这道题我当时的第一反应就是:
 
insert要O1,那么需要该集合是链表结构。
 
reverse要O1,那么该集合要符合双向链表的结构。
 
remove O1,那么需要hash结构保持对相应结点的引用,在删除的时候还要注意不要使得指针断裂。
 
 
但是老赵要求能在30分钟内完成,一般15分钟就要完成。
 
因此我面对的问题是,作为冒牌.NET程序员,我对.NET中的双向链表结构的实现不是很熟悉。
 
这时有两种选择,自己创建双链表结构,或者查找msdn找到.NET中双链表的实现。
 
我觉得在30分钟内实现一个双链表完成这道题目对我来说基本上完不成,因为要考虑的事情比较多。因此使用内置数据结构看似是更好的做法。
 
查找MSDN,我找到了LinkedListNode和LinkedList这两个数据结构,但是我在第一步就发现了问题:
 
LinkedListNode的Previous属性和Next属性是只读的。我顿时疑惑产生,这样子怎么生成双链表呢?
 
这时就需要LinkedList了。
 
 
这时最开始的实现:

public class MyClass<T> : IMyList<T>

    {

        bool isReversed = false;

        LinkedList<T> list = new LinkedList<T>();

        Dictionary<T, List<T>> dic = new Dictionary<T, List<T>>();

 

        public void AddFirst(T item)

        {

            if (!isReversed)

            {

                list.AddFirst(item);

            }

            else

            {

                list.AddLast(item);

            }

 

            CacheItemInDic(item);

        }

 

        private void CacheItemInDic(T item)

        {

            List<T> sameValueItems;

            if (!dic.TryGetValue(item,out sameValueItems))

            {

                sameValueItems = new List<T>();

            }

            sameValueItems.Add(item);

        }

 

        public void AddLast(T item)

        {

            if (!isReversed)

            {

                list.AddLast(item);

            }

            else

            {

                list.AddFirst(item);

            }

 

            CacheItemInDic(item);

        }

 

        public void Remove(T item)

        {           

            T cachedItem;

            List<T> sameValueItems;

            if (!dic.TryGetValue(item, out sameValueItems))

            {

                return;

            }

            else

            {

                cachedItem = sameValueItems[0];

            }
          //写到这里的时候,突然发现问题

            list.Remove(item);

        }

 

        public void Reverse()

        {

            isReversed = !isReversed; ;

        }

 

        public IEnumerator<T> GetEnumerator()

        {

            throw new NotImplementedException();

        }

 

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

        {

            return GetEnumerator();

        }

    }
 
 
写到这里,我意识到怎么能保证Remove是O1的呢?在链表中要remove一个元素很简单,只要交换一下指针即可。但是.NET中Node的指针是只读的。并且,Remove一个元素要先检查这个元素是否在链表中,而检查这个动作如果只有value的情况下无疑使O(N)复杂度的, 在这里我陷入了思考中……
除非我们缓存的是一个节点而不是value,并且这个节点要能在O(1)时间内判断自己是否属于这个链表。问题来了,怎么判断呢?一个个遍历肯定是不行的,只有加入这个节点的时候,同时赋给这个节点一个指向这个节点的所属链表的指针才行。.NET中的LinkedList是不是这个样子的呢?作为一个冒牌.NET程序员还是不清楚,查MSDN吧,万幸,果然每个Node在插入LinkedList的时候都附上了相应的指针,也就是Node的list属性。
 
 
这样我之前写的代码就错误了,做出如下修改一下:
public class MyClass<T> : IMyList<T>
    {
        bool isReversed = false;
        LinkedList<T> list = new LinkedList<T>();
        Dictionary<T, List<LinkedListNode<T>>> dic = new Dictionary<T, List<LinkedListNode<T>>>();
        
        public void AddFirst(T item)
        {
            LinkedListNode<T> node;
            if (!isReversed)
            {
                node = list.AddFirst(item);
            }
            else
            {
                node = list.AddLast(item);
            }
 
            CacheItemInDic(item,node);
        }
 
        private void CacheItemInDic(T item,LinkedListNode<T> node)
        {
            List<LinkedListNode<T>> sameValueNodes;
            if (!dic.TryGetValue(item,out sameValueNodes))
            {
                sameValueNodes = new List<LinkedListNode<T>>();
            }
            sameValueNodes.Add(node);
        }
 
        public void AddLast(T item)
        {
            LinkedListNode<T> node;
            if (!isReversed)
            {
                node = list.AddLast(item);
            }
            else
            {
                node = list.AddFirst(item);
            }
 
            CacheItemInDic(item, node);
        }
 
        public void Remove(T item)
        {
            LinkedListNode<T> cachedNode;
            List<LinkedListNode<T> > sameValueNodes;
            if (!dic.TryGetValue(item, out sameValueNodes))
            {
                return;
            }
            else
            {
                cachedNode = sameValueNodes[0];
            }
            if (cachedNode == null) return;
            list.Remove(cachedNode);
 
        }
 
        public void Reverse()
        {
            isReversed = !isReversed; ;
        }
 
        public IEnumerator<T> GetEnumerator()
        {
            LinkedListNode<T> node;
            if (!isReversed)
                node = list.First;
            else
                node = list.Last;
 
            while (true)
            {
                if (node == null)
                    yield break;
 
                yield return node.Value;
 
                if (!isReversed)
                    node = node.Next;
                else
                    node = node.Previous;
            }
        }
 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
 
写了点代码测试下:
        static void Main()
        {
            MyClass<int> collection = new MyClass<int>();
            for (int i = 1; i < 11; i++)
            {
                if(i%2 ==0)
                    collection.AddFirst(i);
                else
                    collection.AddLast(i);
            }
 
            foreach (var i in collection)
            {
                Console.WriteLine(i);
                
            }
 
            collection.Reverse();
            foreach (var i in collection)
            {
                Console.WriteLine(i);
 
            }
           
        }
 
最终,边查边改,我花了一个多小时才完成这道简单的题目。按老赵的说法,就是“薪水是0"的水平。好吧,被打击了。但是博客还是要继续写下去的。
posted @ 2012-03-30 12:13 浪雪 阅读(3476) 评论(18) 编辑

一年的时间里写了一些东西,做个目录方便自己查找。也包含了自己打算写的一些东西。

 

一.网络协议

1.字节和字符,对信息进行编码

2.分组报文,协议和Socket的概念

3.Socket:流,TCP连接,TCP可靠性概述

4.TCP和流

5.关于TCP的可靠性

6.用TCP/IP实现自己简单的应用程序协议:成帧器部分

7.用TCP/IP实现自己简单的应用程序协议:其余部分(包括完整代码)

8.用TCP/IP实现自己简单的应用程序协议:最后再返回来看HTTP协议

9.由TCP的可靠性实现对比WCF中可靠性的实现(未完成)

10.由传输过程中需要面对的问题探讨WCF中的“可靠性”

 

二.多线程之旅

1.多线程之旅——从概念开始

2.多线程之旅二——线程

3.多线程之旅之三——Windows内核对象同步机制

4.多线程之旅之四——浅谈内存模型和用户态同步机制

5.多线程之旅之五——线程池与I/O完成端口

6.多线程之旅六——异步编程模式,自己实现IAsyncResult

7.多线程之旅七——再谈内存模型

8.多线程之旅八——多线程下的数据结构

9.多线程之旅九——最后再回头总结一下

 

三.数据库漫谈

1.B+树,聚集索引,非聚集索引(辅助索引) 之一

2.SQL SERVER中的聚集索引和非聚集索引

3.SQL SERVER对索引的利用分析

4.和SQL SERVER优化器斗智斗勇,如何写出更好的SQL语句

5.多个中间查询结果集中的各种类型连接

 

四、数据结构与算法

 

五、静态编译语言与动态语言的比较

posted @ 2012-03-26 10:41 浪雪 阅读(30) 评论(0) 编辑

1.可以转移到方法名,字符串,xml中。

2.并没有使编程变得更简单,只是把一部分变得更抽象,显得更强壮了而已。

3.IOC容器管理了对象的依赖关系,因此要在程序运行前启动。因为没有具体依赖的程序什么都不是。

posted @ 2012-03-16 14:11 浪雪 阅读(25) 评论(0) 编辑

1.观察者模式主要是为了解耦,注意并不是立刻通知。

2.在MVC的教科书定义中,Model采用的是观察者模式,也就是Model是被观察者,View是观察者,Model有任何改变的情况下,View都会接受到通知。


但是在WEB环境中,View不需要实时的改变,只有客户端发送request时,View才可能需要改变。
换句话说,只有当我们需要生成一个页面作为响应返回给客户端的时候,创建一个View并使用Model有意义。
因此其他时候Model的改变对于View来说都是没有意义的,也就没必要通知View
所以View就不再直接观察Model,而是通过Controller来作为中间人。因此Controller是一次Request就调用一次。

3.从"一连接一线程"到"一请求一线程"
为了满足服务器端能同时接受多个连接发送的请求,通常采用的方法是在accept获取socket后,将此socket放入一个线程中处理,通常将此做法称为“一连接一线程”。这样服务器端就可以接受多个连接发送请求了。这种方式的缺点是无论连接上是否有真实的请求,都要耗费一个线程。为了避免创建过多的线程导致服务端资源耗尽,需要限制创建的线程数。从这里可以看出,线程和连接耦合在了一起。

线程是分配cpu的单位,如果没有线程是无法调用cpu资源处理请求的。但是上述的做法由于把线程和连接耦合在了一起,导致了就算没有真实的请求,一个连接也会占用线程。我们这时很自然地就会想到,那么直到有真实的请求到来我们再处理才是最经济的方式。
这种情况特别符合订阅者模式。
posted @ 2012-03-16 13:53 浪雪 阅读(24) 评论(0) 编辑

把引用对象作为参数传进一个方法,实际上是在栈上新分配了一块内存保存传入的地址。

 

如图所示,把局部变量name传入M2方法后,栈上新开了一块内存S,用来保存"joe"的地址。

其实拿string来说明不太合适,因为string类是不可变的。但是为借用CLR VIA C#中现成的图咱们就将就一下了。

这时如果能够直接修改s所指向的内容的话(在C#中string是不可变类,无法演示),外部的局部变量name由于和形参s指向的是同一个对象,因此name也会改变。(图1)

但是如果把s指向一个新的的string时,比如:

void M2(string s){

  s = "new string";

}

那么这时再对s改变,name的内容不会改变。因为两个变量分别指向了堆上的不同string (图2)

用图来说明就是:

 

如果用上ref,在这种情况下,编译器将不会为形参s在栈上新分配一块内存,编译器将使用由调用者所指定的那个引用地址,按上图来说是使用name的地址。

在C中,为了解决这种栈上的变量复制问题,我们可以采用指向指针的指针,如 char[]** s 。

这时再对s修改,指向一个新的对象,外部的name也会改变。

 

由于用string的话,无法演示直接修改的情况,因为改变string实际上也是生成了新的string。因此我随便弄了个student类来演示。

代码示例如下:

 class Student
{
  public string name = "a";
}

  class
Program
{
static void Main(string[] args)
{
var s1 = new Student();
var s2 = new Student();
       var
s3 = new Student();

ModifyWithRef(ref s1);
ModifyWithoutRef(s2);
       DirectlyModifyWithoutRef(s3);

       Console.WriteLine("Directly modify without ref: "+s3.name); //结果被直接修改为c,对应图1
       Console.WriteLine("without ref: "+s2.name); //结果仍为a,对应图2
       Console.WriteLine("with ref: "+s1.name); //结果为c

Console.ReadLine();
}

static void ModifyWithRef(ref Student s)
{
s = new Student();
s.name = "c";
}

static void ModifyWithoutRef(Student s)
{
s = new Student();
s.name = "c";
}
    
        static void DirectlyModifyWithoutRef(Student s)
{
s.name = "c";
}

}



你理解了吗?

posted @ 2012-03-11 21:43 浪雪 阅读(713) 评论(2) 编辑
摘要: 这篇随笔和上篇随笔《从两个数组中查找相同的数字谈Hashtable》都是为了下面分析Dictionary的实现做的铺垫一.两个逻辑上相等的实例对象。两个对象相等,除了指两个不同变量引用了同一个对象外,更多的是指逻辑上的相等。什么是逻辑上相等呢?就是在一定的前提上,这两个对象是相等的。比如说某男生叫刘益红,然后也有另外一个女生叫刘益红,虽然这两个人身高,爱好,甚至性别上都不相同,但是从名字上来说,两者是相同的。Equals方法通常指的就是逻辑上相等。二.Object的GetHashcode方法。计算Hashcode的算法中,应该至少包含一个实例字段。Object中由于没有有意义的实例字段,也对其阅读全文
posted @ 2012-02-26 13:28 浪雪 阅读(1174) 评论(2) 编辑
摘要: private void Insert(TKey key, TValue value, bool add){ if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (this.buckets == null) { this.Initialize(0); } int num = this.comparer.GetHashCode(key) & 2147483647; int num2 = num % t...阅读全文
posted @ 2012-02-21 20:28 浪雪 阅读(58) 评论(0) 编辑
摘要: 提纲:1.在一次比较过程中,我们能确定位置的只有一个数。但是我们能使得数组中的元素“基本有序"基于比较的排序算法,一次只能得出一个结果。最好的情况就是把问题分解成两个1/2的规模,而不是一个1/4,另一个3/4。因为你无法知道你是否处于另一个糟糕的状况之中。因为一次比较只能得出 “大于” 或者 “小于”两个结果,所以底数为log2,而问题的所有解的规模最初是n,当第一轮排序完成后,未排序的元素是n-1个,同理,第二轮排序完成后剩下的元素是n-2个,以此类推。这种等差数列相乘用符号表示就是n!。所以基于比较的排序最好的情况就是log(n!),近似于O(nlogn)2.拿快速排序法举例。阅读全文
posted @ 2012-02-16 20:37 浪雪 阅读(33) 评论(0) 编辑
摘要: 提纲1.n和n+1的关系结论必须由一个整数n决定结论的定义必须明确,这样我们才可能检验由第n项过渡到下一项也就是n+1的时候,结论是否还能成立。我们期望得到这样一个结果:只要这个结论对n成立,那么对n+1也成立。所以我们要做的有两件事,一是证实结论在取某个值时是正确的,二就是证实取下一个值也是正确的。我们考虑,如果取某个值,那么这个值取什么数好呢?很显然,什么事情我们都希望从最简单的情况出发,因此取1就是我们的选择,也就是n=1。因此n=1时成立,所以n+=1,也就是n=2的时候也成立,同理,n=3的时候也成立。从任意整数过渡到下一个,我们就普遍的证明了这个结论。普遍的证明说明已经遍历了所有情阅读全文
posted @ 2012-02-16 20:34 浪雪 阅读(47) 评论(0) 编辑