说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛。但必要的基础还是需要了解掌握。
排序的目的是为了让无序的数据,变得“有序”。此处的有序指的是,满足当前使用需求的顺序,除了自带的API,我们还可以自定义比较器对象、使用LINQ语句、Lambda表达式等方式完成排序。本文将逐一介绍十大排序,并着重介绍分析基于C#的LINQ常用语句和Lambda表达式,二者对排序的实现。
【# 请先阅读注意事项】
【注:(1)以下提到的复杂度仅为算法本身,不计入算法之外的部分(如,待排序数组的空间占用)且时间复杂度为平均时间复杂度
(2)除特殊标识外,测试环境与代码均为.NET 6/C#
(3)默认情况下,所有解释与用例的目标数据均为升序
(4)默认情况下,图片与文字的关系:图片下方,是该幅图片的解释
(5)本文篇幅较长,和主标题(排序)契合度较低,建议分期阅读;也可能存在较多错误,欢迎指出并提出意见,请见谅】
这十大排序对于有基础的程序员并不陌生,只是一些经常不用的小细节可能记忆模糊,该部分内容会对排序方法简要分析,旨在作为笔记,需要的时候帮助回忆相关内容。
名字的起源十分生动形象:气泡从水底向上浮,随气压变化气泡体积逐渐变大最终破裂;在某一时刻让时间静止,可观察到从水面到水底,气泡体积依次减小,体积排列有序,故得此名。
1. 原理:两两比较,按照比较器对象进行交换。
2. 复杂度:时间O(n2) 空间O(1)
3. 实现:(C++14 GCC 9)

(指针形式)
1. 原理:选择——选定一个数,确定当前该数的位置后,再选择下一个数。注意其和冒泡排序的区别。
2. 复杂度:时间O(n2) 空间O(1)
3. 实现:(C++14 GCC 9)

二者区别在于:冒泡是相邻的数比较;选择是每次固定一个数与其他数比较。
其操作方式类似于我们再打牌时,抽出一些牌放入合适的位置。
1. 原理:选定一个数,将该数插入合适的位置。即,若排序结果为升序,则将数b插入某一位置,使得a < b < c。
2. 复杂度:时间O(n2) 空间O(1)
3. 实现:(C++14 GCC 9)

此排序由希尔提出,是对插入排序的改进。改进的思想是:将一整个待排序序列分割为多个组,每一次对每个组的同一位置进行插入排序。即,将第一、二、三…组的第一个元素看为一个新的序列进行插排,第二个元素看为一个新的序列进行插排……以此类推。实验证明,该思想在一定程度上有助于加快插入排序的运行。
1. 原理:分割式插排
2. 复杂度:时间O(n1.3) 空间O(1)
3. 实现:(C++14 GCC 9)

大量数据表明,当h /= 3时可达到最优效果。
快速排序是最常用的一个排序,在之前的介绍.NET数组源码的排序方法([数据结构-线性表1.1] 数组 (.NET源码学习))中也出现过,虽然其内部有三种排序方式,但主体还是快速排序。之所以称之为快速,是因为其时间复杂度不再是n2级,而是nlog2n级。一般地,在1s的时间范围内,总时间复杂级数需要小于等于108,快速排序的出现,解决了许多大量数据排序的问题,提高了不少效率。
1. 原理:类二分法
2. 复杂度:时间O(nlog2n) 空间O(log2n)
3. 实现:(C++14 GCC 9)

快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,递归在运行的过程中会在栈上消耗一定的空间,在一般情况下的空间复杂度为O(logn),在最差的情况下,若每次只完成了一个元素,那么空间复杂度为O(n)。

这是该方法中的快速排序,和我们一般的快速排序不同,其运用了SwapIfGreater()方法、三数取中法对快速排序进行了优化。

根据传入的比较器对象,对两个元素进行相应处理。
在排序前,其先把索引为0和mid的两个元素、索引为0的元素和最后一个元素、索引为mid的元素和最后一个元素进行处理,优先保证这三个位置上的元素符合要求。
该方法即为三数取中法。
1. 方法:是知道无序数列的首和尾,便可以求出其中间位置的数,此时只需在首、中、尾这三个数中,选择中间的这个数作为基准值,进行快速排序,即可进一步提高快速排序的效率。
最初的快速排序是选择第一个或最后一个元素为基准值,然后将整个序列分为两部分。但当数组本身有序度较高或完全有序时,快排会达到O(n2)的时间复杂度,因为可能会导致分完后的两部分中,某一部分为空集。即,相当于没有二分就开始排序。
2. 优化原理:将首、中、尾排序后,选取中间位置的元素,能有效避免“一边倒”的问题,从而真正利用上二分思想的加速性。
该源码和本人写的代码格式不同,本人运用递归的方法,将序列无限二分至无法再分,先左后右逐步有序;源码利用迭代方法,三数取中 + 双向搜索,从整体到局部。虽然平均复杂度均为log级,但长远看来源码具有较高的优越性。
当最外层while循环结束后,i的左侧一定全都是小于基准值的元素,num3的右侧一定全都是大于基准值的元素。此时,相对于基准值而言,i的左侧是有序的,num3的右侧是有序的。

上图是本人手写的一份基本流程草图,该快速排序有别于一般形式的快速排序,但整体思想依旧不变:类二分法,从子集有序到整体有序,加上两种方法的优化,效率已经是很高的了。
归并,即合并,既然要合并,那之前就得拆开。所以,归并排序的方法就是先把元素二分,二分后每段再二分,直到无法二分。再根据比较器对象,按一定顺序合二为一,逐层回调,最后完成排序。
1. 原理:类二分法
2. 复杂度:时间O(nlog2n) 空间O(n)
3. 实现:(C++14 GCC 9)

快速排序是先排序,使得整个序列以基准值有序,再二分;归并排序是直接二分到底,再逐层往回排序,从局部到整体。
堆的结构类似于二叉树,而二叉树的相关操作(如,查询、插入、删除等)的时间复杂度一般都在常数级和log级,所以堆排序的效率也比较高。
1. 原理:根据数组索引和二叉树位置的关系,构建大/小顶堆(二叉树)并还原。
2. 复杂度:时间O(nlog2n) 空间O(1)
3. 实现:(C++14 GCC 9)

一般地,目标为升序数组则构建大顶堆,目标为降序数组则构建小顶堆。

该源码与一般的堆排序写法如出一辙,上方的HeapSort()方法用于控制每次调整堆的范围,保证有序元素不再被调整;下方的DownHeap()方法用于构建/调整堆,将合适的元素放到合适的位置。
之后的过程和本人以C++语言所写的过程大体相似。
理论上这两者排序的思想是类似的:新建数组,以索引代表元素,统计每个元素出现次数,最后再输出。
1. 原理:按索引顺序,以索引值代表数组值,统计出现次数。
2. 复杂度:时间O(n + k) 空间O(n + k)(其中,k为max – min)
3. 实现:(C++14 GCC 9)

当然,从标准出发,这二者肯定是不一样的。
对于桶排序,将待排序元素划分到不同的痛。先扫描一遍序列求出max和min ,设桶的个数为 k ,则把区间 [min, max] 均匀划分成 k 个区间,每个区间就是一个桶,将序列中的元素分配到各自的桶。
对于计数排序,每个数值为一个桶。计数排序本质上是一种特殊的桶排序,当桶排序的桶的个数最大的时候,就是计数排序。
1. 原理:按照元素的每一位进行比较来排序。
2. 复杂度:时间O(n * k) 空间O(n + k) (其中,k为max – min)
3. 实现:(C++)来自(1.10 基数排序 | 菜鸟教程 (runoob.com))
基数排序与计数排序、桶排序这三种排序算法都利用了桶的概念,但对桶的使用上有一定差异:
(1)桶排序:每个桶存储一定范围的数值;
(2)计数排序:每个桶只存储单一键值;
(3)基数排序:根据键值的每位数字来分配桶;
基数排序不是直接根据元素整体的大小进行元素比较,而是将原始列表元素分成多个部分,对每一部分按一定的规则进行排序,进而形成最终的有序列表。
LINQ(Language-Integrated Query)语言集成查询,常用于对数据的查询。一般地,完整的查询操作包括创建数据源、定义查询表达式和在foreach语句中执行查询。

该部分将分析相关方法的源码,以及内部的orerby与orderbyDescending排序方法。本人会尽可能的讲述清楚过程的细节,但之前也没有详细读过源码,该文章也是边读边学边写,可能存在解读错误,也可能会有与读者相矛盾的观点,还望各位指出,请谅解。
确定序列的所有元素是否都满足条件。

对于这两个参数,可以发现:


(一) 不管是在源码还是在从元数据中,该方法均有两个参数。但在使用时,却只需要传入一个参数。
(二) TSource和泛型T不是一个东西,泛型T可以容纳所有类型,但需要手动指定数据类型;TSource可以容纳所有类型,但可自动识别类型。

当要访问的元素集是什么类型时,TSource会自动分配对应的类型。
(三) 待传入的参数Func<TSource, bool> predicate是一个方法(称为此处过滤器),将方法作为参数,无疑用到了委托。

关键字in和out一般用在泛型interface和delegate中, 用来进行逆变和协变(in逆变,out协变)。(协变、逆变将在下方介绍)
Func()方法是一个有返回值的泛型委托,可包含0~16个参数T,并在最后多加一个参数,表示返回值类型。我们也可以自己定义该方法,如:

根据我们自己写的调用的操作步骤,可以发现,我们只需传入待处理参数,不用管最后的返回参数。

其基本语法格式为:方法名(临时变量 => 条件表达式)。其中,括号中的式子称为Lambda表达式,会在后文介绍。
判断是否包含任何元素、是否存在元素满足指定的条件。


(1)首先是判断源数据是否包含任何元素。


对于IListProvider,其内部包含了三个方法:转换为数组、转换为泛型集合、GetCount()方法。

其中,前两个方法的实现均是将元素放入到新的数据结构中,最后返回。内部的字段_source[]指的是源数据及,_predicate()方法指的是传入的条件表达式;LargeArrayBuilder可以近似看作StringBuilder。

对于GetCount()方法,其原理也是通过遍历源数据集,在满足相应条件的情况下记录元素个数。
(2)判断源数据是否包含任意一个符合条件的元素

通过遍历的方式逐一检查,存在一个满足条件就返回true,一个都不满足就返回false;
(1)Count()方法
其包含两个重载形式:一个是纯计数,另一个是带条件计数(记录满足条件的元素个数)。


可以发现,其原理和Any()方法中的GetCount()方法基本类似。
(2)Max()、Min()、Sum()方法
这三种方法,前两种每种包含21个重载函数,Sum()方法包含19个重载函数,因为需要针对不同的数据类型。但最大不同依旧在于是否带有条件。


其实现原理基本类似,且没有什么新的东西,所以在此不做过多论述。
Where子句用在查询表达式中,用于指定将在查询表达式中返回数据源中的哪些元素。它将一个布尔条件应用于每个源元素(由范围变量引用),并返回满足指定条件的元素。一个查询表达式可以包含多个Where子句,一个子句可以包含多个条件表达式。简而言之,Where起到一个筛选器的作用,筛选出符合条件的元素。

并且可以发现,筛选出的元素默认情况下共同构成一个可枚举的集合。
Where方法的过滤功能主要通过迭代器实现,其源代码包含7个迭代器。按照功能划分如下:
(1)索引参与过滤操作运算的迭代器WhereIterator,容器(可迭代对象)包含Enmuerable,List和Array。
(2)索引不参与过滤运算
1. Enmuerable:WhereEnumerableIterator和WhereSelectEnumerableIterator
2. List:WhereListIterator和WhereSelectListIterator
3. Array:WhereArrayIterator和WhereSelectArrayIterator

其中Enmuerable迭代器,List迭代器和Array迭代器在实现上相差不大,都是通过设计模式中的迭代器模式实现具体的功能。区别是Enmuerable迭代器,List迭代器都是调用容器自身的迭代器实现逐个元素的比较和过滤,而Array迭代器是通过数组索引操作实现元素比较和过滤。
除了WhereIterator,其余六个迭代器均派生自迭代器类Iterator


这两个迭代器均不参与过滤运算,两个虚方法主要用于具体容器的复用或重写。如果调用Where的迭代器,属于刚才提到的不参与过滤运算的六个迭代器对象,则会覆盖父类中的某些方法;如果是其它迭代器,例如Distinct迭代器,则会调用父类的Where和Select方法。


(1)如果是Where相关的迭代器,如调用形式为XX.Where().Where()。此处iterator.Where(predicate)的Iterator是Where相关的迭代器对象(WhereListIterator、WhereArrayIterator),此时调用的Where方法是派生类WhereXXXIterator重写后的Where方法,返回的是WhereXXXIterator对象,XXX表示List或Array或Enumerable。
(2)如果是其他迭代器,如调用形式为XX.Distinct().Where(),此处iterator.Where(predicate)的Iterator是Distinct的迭代器对象,此时调用的Where方法是父类Iterator种的Where方法,返回的是默认的WhereEnumerableIterator对象。

该方法为带索引参数的扩展方法,其没有对线程对资源的占用情况进行检查,而是直接调用了WhereIterator方法

WhereIterator方法中维护了一个计数器,每循环一次,计数器加1,计数器中如果出现整型数字溢出情况,则抛出异常。yield return将结果以值的形式返回给枚举器对象,可一次返回一个元素;yield break将控制权无条件地交给迭代器的调用方,该调用方为枚举器对象的IEnumerator.MoveNext方法(或其对应的泛型System.Collections.Generic.IEnumerable<T>)或Dispose方法。

其包含三个字段:源数据、过滤器、迭代器对象。

GetCount()方法在上文提到过,用于计算源数据集中,符合条件的元素个数,默认传入true。

包含两个类型转换方法,分别转换为数组类型和泛型集合类型。转换原理均是通过建立相应对象,并写入数据完成。

一个构造方法,初始化源数据集和过滤器对象。

继承的类与接口。

由于其继承了许多接口和类,所以此处重写了接口中的方法,包括创建并返回新的(克隆)迭代器对象、释放对象、向后移动到下一个元素。

重写了IEnumerable<TSource>接口中的Select()和Where()方法,用于当调用Where的迭代器不属于六大类型时,调用上一级的方法。
【Select()方法和Where()方法原理类似,在此不作叙述】
将了这么多题外话,终于拉回了主题。Linq中也有用于排序的方法,包括OrderBy、OrderByDescending、ThenBy、ThenByDescending,在一个语句中,以OrderBy型开头,之后的只能用ThenBy型,但ThenBy型可多次使用。一般地,O/T型共同存在的排序多用于多关键字排序。
基础语法如下:



OrderBy()方法有两个重载方法,均返回一个OrderedEnumerable类型的对象。


其内置的排序方法,位于类EnumerableSorter<TElement, TKey>中,分别为PartialQuickSort()和QuickSort()。
在排序前,
(1) 对于QuickSort()方法


其重写了父类EnumerableSorter<TElement>中的同名抽象方法,并调用类ArraySortHelper<T>中的IntrospectiveSort()方法,这与前一篇文章中提到的数组排序方法Array.Sort(),方法一致。
(2) 对于PartialQuickSort()方法
该方法直接定义在类EnumerableSorter<TElement, TKey>中,针对源数据集的某一部分进行排序。

从左往右,找到第一个大于等于中间位置的元素。

其方法内部的四个紫色字段均在类EnumerableSorter中

【注:下方有关五个参数的解释为推断得出】
_keySelector表示委托方法Func(),过滤器;_compare表示比较器对象;_descending表示是否降序;_next下一个迭代排序器对象;_keys表示经过滤器筛选出的待排序数据集;
此时,内层循环结束,完成了以中间位置元素为基准值的排序,保证基准值左侧小、右侧大。
整个排序过程以递归的方式进行,类似于快速排序。
【注:以下内容属于推断得出】

数据集在调用OrderBy()等一类排序方法后,会先将源数据转换为泛型Buffer类型

之后,再调用类OrderedEnumerable中的SortedMap()方法


在调用真正开始排序前,首先调用ComputeMap()方法,根据过滤器,筛选出要排序的元素,并保存在_keys中。再根据不同的参数,调用不同的方法进行排序。
以上为OrderBy一类排序方法的“前摇”和过程,其余的OrderByDescenidng()、ThenBy()、ThenByDescending()方法与OrderBy()类似。
流程图如下:

综合来看,就对于OrderBy一类排序算法本身,其时间复杂度和Array中的Sort()方法相差不大,但实际运行效果却要比Array.Sort()方法慢。原因应该在于其需要频繁创建EnumerableSorter对象、将数据类型转换为Buffer再转换为数组、排序后从IOrderByEnumerable类型转换为源数据类型,这些过程大大延长了总时间,尤其是在数据量较大的时候,所需时间将会产生较大差异。



Lambda表达式用来创建匿名函数,常用于委托、回调,且可以访问到外部变量。使用Lambda运算符“=>”,从其主体中分离 lambda 参数列表,可采用以下任意一种形式:

一般地,输入的参数不需要显示指定类型。但当编译器无法判断其类型时,可显示指明各个参数的类型:

(1)匿名类型
该类型可用来将一组只读属性封装到单个对象中,而无需显式定义一个类型。类型由编译器在编译阶段生成,并且不能在源代码级使用。可结合使用new运算符和对象初始值设定项创建匿名类型。



其中,这里的var被定义为匿名类型(AnonymousType),v被定义为类型 `a 。

在反编译程序中,查询到20中相关的方法

根据IL DASM工具可以发现,其包含的主要信息:类<>f__AnonymousType0`2<’<参数para>j__TPar’,’<Message>j__TPar’>;私有只读字段<Amount>i__Field和<Message>i__Field;三个非静态重写方法Equals()、GetHashCode()、ToString()。
以此为例,在源码中找到相关信息,其位于程序集PresentationFramwork.dll中



(2)匿名方法
委托是用于引用与其具有相同标签的方法。即,可以使用委托对象调用可由委托引用的方法。匿名方法提供了一种传递代码块作为委托参数的技术,其没有名称只有方法体;没有返回值类型,类型根据具体方法中的return语句推断。如,Func()方法、Action()方法、Predicate()方法。

所以在OrderBy一类排序中,其内部需要传入一个匿名方法,以赋值给Func()方法,故使用Lambda表达式。
Lambda表达式本身没有类型,因为CLS(通用类型系统)没有“Lambda 表达式”这一固有概念。不过,有时以非正式的方式谈论 Lambda 表达式的“类型”会很方便。该非正式“类型”是指委托类型或 Lambda 表达式所转换到的Expression类型。
从C# 10开始,Lambda表达式可能具有自然类型。编译器不会强制为 Lambda 表达式声明委托类型(如Func<>或Action<>),而是根据 Lambda 表达式推断委托类型。

也就是说,一开始的Lambda表达式只能赋值给委托类型。而在此之后,Lambda表达式可以根据具体的情况,赋值给具体的类型。
【注:更多Lambda表达式内容请参阅Lambda 表达式 - C# 引用 | Microsoft Docs】
据官方解释,协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型;逆变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。
在谈论协变与逆变之前先来看一下泛型集合中的对象转换。

此处有三个类。其中Student与Worker派生自Person,那么可以将Person称为Student和Worker的上层数据类型;Student和Worker称为Person的下层类型。

可以发现,虽然Person时Student和Worker的父类,但List<Person>不是List<Student>和List<Worker>的父类,所以后两行的赋值会报错。
在C# 4之前,类似于上述的赋值操作是不被允许的。因为假设其能够赋值,即List<Person> p = new List<Student>();成立,那么虽然p的类型为List<Person>但其实例对象使List<Student>,在调用方法时,调用的也就是Student的方法。如果现在实现这个语句:p.Add(new Person());其实质上是用Student中的Add方法,而Student又是Person的子类,Person无法安全(直接)转换为Studnt对象,所以这样的集合定义没有意义,因此不被允许。
从C# 4开始,类似的操作,在泛型委托、泛型接口中,允许发生。但上述操作依旧是无法实现的,因为其违反类型类型转换的基本流程。
定义一个无参泛型委托。


我们尝试在上层类型中存放下层类型。不出意外,依旧报错。根据刚才的分析,要想解决这个错误,需要解决两个问题:
(1) 在调用p()方法时,实际上调用的是s()方法,所以需要s执行的结果能转换为p执行后所返回的类型。即,s能够转换为p类型。
(2) 解除编译器的检查限制,在此处允许将Work<Student>类型的对象赋值给Work<Person>类型的变量。
对于(1)已经满足,由隐式转换直接完成;而条件(2)就需要在委托中加上out关键字。


尝试在上层类型中存放下层类型。解决这个错误,也需要解决两个问题:
(1)在调用s()方法时,实际上调用的是p()方法,即,p能够转换为s类型。
(2)解除编译器的检查限制,在此处允许将Work<Person>类型的对象赋值给Work<Student>类型的变量。
对于(1)因为Student为Person的子类,所以二者存在联系,通过强制类型转换可以实现;而条件(2)需要在委托中加上in关键字。

【注:如果没有子父类的关系,加上in/out关键字,也无法实现】
简而言之:协变可以在上层数据类型中存放下层对象;逆变可以在下层的数据类型中存放上层对象(这里的上层与下层是相对而言),这两个过程本质上是参数的类型转换。

据微软官方的说法,协变于逆变只发生在数组、委托、泛型参数之上,对于类的上下转型而言不算做协变于逆变。
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou