【注:本篇文章源码内容较少,分析度较浅,请酌情选择阅读】
关键词:链表(数据结构) C#中的链表(源码) 可空类型与特性(底层原理 源码) 迭代器的实现(底层原理) 接口IEqualityCompare<T>(源码) 相等判断(底层原理)
链表,一种元素彼此之间具有相关性的数据结构,主要可分为三大类:单向链表、双向链表、循环链表。其由“链”和“表”组成,“链”指当前元素到其他元素之间的路径(指针);“表”指当前单元存储的内容(数据)。本文主要对 C# 中 LinkedList 的源码进行简要分析。
【# 请先阅读注意事项】
【注:
(1) 文章篇幅较长,可直接转跳至想阅读的部分。
(2) 以下提到的复杂度仅为算法本身,不计入算法之外的部分(如,待排序数组的空间占用)且时间复杂度为平均时间复杂度。
(3) 除特殊标识外,测试环境与代码均为 .NET 6/C# 10。
(4) 默认情况下,所有解释与用例的目标数据均为升序。
(5) 默认情况下,图片与文字的关系:图片下方,是该幅图片的解释。
(6) 文末“ [ # … ] ”的部分仅作补充说明,非主题(算法)内容,该部分属于 .NET 底层运行逻辑,有兴趣可自行参阅。
(7) 本文内容基本为本人理解所得,可能存在较多错误,欢迎指出并提出意见,谢谢。】
【注:该部分在网络上已有很多资料,故不作为重点】
数组作为一个最初的顺序储存方式的数据结构,其可通过索引访问的灵活性,使用为我们 的程序设计带来了大量的便利。但是,数组最大的缺点就是:为了保证在存储空间上的连续性,在插入和删除时需要移动大量的元素,造成大量的消耗时间,以及高冗余度。为了避免这样的问题,因此引入了另一种数据结构链表。
链表通过不连续的储存方式、动态内存大小,以及灵活的指针使用(此处的指针是广义上的指针,不仅仅只代表 C/C++ 中的指针,还包括一些标记等),巧妙的简化了上述的缺点。其基本思维是,利用结构体或类的设置,额外开辟出一份内存空间去作“表”本身,其内部包含本身的值,以及指向下一个结点的指针,一个个结点通过指针相互串联,就形成了链表。但优化了插入与删除的额外时间消耗,随之而来的缺点就是:链表不支持索引访问。
以下仅简单写一下基本构成和方法。

首先定义“表”,即每个结点。其包含自身数据与指向下一个表的指针。其中一个默认构造方法,一个带参构造方法用于两种不同形式的初始化。
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

再定义一下“链“
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
接下来定义常用方法





双向链表在单向的基础上增加了指向前的指针
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
“链“的构造方法及相关字段和属性不变,只是在方法的实现时,需要增加对 prev 的赋值

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

一般地,先修改新结点的信息,再修改原结点的信息。
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——


循环,就是把尾部结点的 next 指针继续指向下,指向 head。大同小异,本节内容在此不作演示,详细请参阅:(理论基础 —— 线性表 —— 循环链表 - 老程序员111 - 博客园 (cnblogs.com))
(1) 对于数组:
(2) 对于集合:
(3) 对于链表:
(1) 数组:
(2) 集合:
(3) 链表:

C# 中的LinkedList 为双向链表(双重链接链表),位于程序集 System.Collections.dll中的命名空间 System.Collections.Generic 之下。
简单解释一下其拥有的特性:【注:特性基本介绍请参阅本人的文章([算法2-数组与字符串的查找与匹配] (.NET源码学习) - PaperHammer - 博客园 (cnblogs.com))】

这里解释一下“上下文”:
上下文并不是一个具体的东西,就和阅读小说一样,需要结合前后进行理解。
在计组中也出现过上下文的概念,CPU 在用户态与内核态相互切换时,需要保留当前任务的上下文信息,并挂起该任务,直到优先级更高的任务结束后,再根据上下文信息,继续原任务。这里的上下文信息相当于对某个进程当前的状态描述。
根据这样的方式,那么此处对于LinkedList 的该特性可以解释为对其当前状态描述的可空性。
【注:有关特性 Nullable 的详细介绍会在文末进行补充说明】
说人话就是,在调试过程中,若要查看变量内部的元素,则会显示代理类型的相关成员,不会显示原本类型的相关成员。其主要作用是,在调试时得到最希望最关心的信息。
【更多有关该特性的内容会在今后专门发文详解】
解释一下“序列化”:
有时为了使用介质转移对象,并且把对象的状态保持下来,就需要把对象所有信息保存下来,这个过程就叫做序列化。通俗点,就是把人的魂(对象)收伏成一个石子(可传输的介质)。各种序列化类各自有各自的做法,这些序列化类只是读取这个标签,之后就按照自己的方式去序列化。
【注:下一篇会对序列化与反序列化进行补充说明】



注意,由于 ArrayList 内部存储的元素并不是同一个类型,因此其并未继承泛型接口 IEnumerable<T>,其只继承了普通接口 IEnumerable,因此不能将其通过构造函数直接转化为链表。

只读属性,返回链表长度。

只读属性,返回链表头结点,若不存在则根据特性 Nullable 返回空。每个数字对应修饰的对象,此处 2表示可为空,对应 Linkedlist;1表示不能为空,对应 <T>。
一般地,被 Nullable 修饰的变量可以为空。以 Nullable 作为特性,可以修饰方法、属性等,拓宽了数据可为空的范围。

只读属性,返回链表尾结点。因为LinkedList 默认是双向链表,因此 tail == head.prev。
注:这三个属性为非公共属性,只限于类自己内部调用。


只读属性,分别表示 LinkedList 的非只读、对堆栈的访问不同步 (线程安全)、获取可用于同步对 ICollection 的访问的对象。其中,符号<!0>可能表示非 NULL
这三个属性在此处似乎只有定义,并没有在其他地方调用,推测其作用是作为该对象的一种标识属性,在进行某些操作时,供 CLR 检测访问是否允许执行该操作。
【注:碍于篇幅,后两个属性将在之后的文章进行补充说明】


初始化内部字段,为之后的迭代器遍历与结点做准备。


将指向当前结点对象的引用,向后移动到下一个结点。配合迭代器访问,逐一向后遍历元素。
在枚举器的构造方法里,已将 _list.version 赋值给 version,理论上每次进行枚举迭代均会更新 version。因此该判断语句的目的可能是防止经过浅层拷贝的两个集合,在职u型某些操作后,调用另一个不属于自身的迭代器。
【有关浅层拷贝请参阅([数据结构1.1-线性表] 数组 (.NET源码学习) - PaperHammer - 博客园 (cnblogs.com))】

在每次调用完枚举器后,需要恢复原有的信息,以便下一次继续调用。可以理解为回溯。
【注:有关迭代器的实现原理,会在文末进行补充说明】
首先是方法 AddFirst()。其有两个重载方法,不同之处体现在返回值类型与参数。


该方法的作用是:在空表中创建一个结点,其既是头结点,也是尾结点。

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
方法 AddLast() 与 AddFirst() 大同小异,在此不做过多分析

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
接下来是方法 AddBefore(),其和前两个方法一样,均有两个重载方法,主要是返回类型与参数不同。
(1) 对于无返回值、待添加元素为链表(结点)的方法



是否为空;是否为该链表的成员(目的可能是防止多线程同步访问出现的错误调用)

就是普通的双向链表的插入。
(2) 对于返回类型为 LinkedList<T>,添加元素为某个值的方法

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
方法 AddAfter() 大同小异,不做过多分析


方法套方法,Find 可以用来查找元素,其在其他的方法(如:Remove())中也可以使用,为了减少冗余,因此此处直接复用了方法 Find() 来实现。
复用的思想在竞赛、开发中十分常用,既能有效减少工程量,还能提高代码可读性。


【思考:为什么需要定义该类型的变量,调用该类中的比较方法,而不直接用判断运算符 “==” 或 Object.Equals() 方法?文末进行解释】
从方法名来看,其作用是找到值 value 在链表中出现的最后一次所对应的结点。

相比方法 Find(),只是将 next 换成了 prev,其他地方大同小异,主要来分析一下这个 prev 的作用。
当 linkedListNode == prev 时,说明已经遍历完了一遍,此时还未找到目标元素,返回 null。

其基本思路说判断合法性,然后执行操作。其中 ValidateNode 在之前提到过,用于判断结点合法性。
下面看一下方法 InternalRemoveNode()

和添加方法中的那个 InternalInsert…() 基本一致,改变结点的指向即可,记得最后要修改结点数量。

包含四个字段:list 结点所在的链表;next 结点的下一个节点;prev 结点的上一个结点;item 结点内部存储的元素。

两种初始化的方式,没有默认构造器。也就是说,在定义节点时必须对其赋值(默认为 null)。但链表 LinkedList 是有默认构造器的。
对结点包括以下三种查询操作(Next、Previous、Value):


单个数据结构其实没有太多要讲的内容,既然 C# 与 Java 是两大较为著名的 OO 语言,那在此就对比一下二者在实现链表结构时的异同。

可以知道,Java 中的链表结构也是双向链表。

Java 中的 LinkedList 有两个构造方法。不过差别不大,只不过 C# 中的 LinekdList<T> 还多了一个可列化的元素集。

注:在 Java 中此类变量称作属性,没有字段的概念;在 C# 中此类变量称作字段,而属性是针对字段而设计的访问器,用于增强安全性。
除去 C# LinkedList 中的三个特殊字段,其余与 Java LinkedList 一致。


可以看到,C# 中的添加方法内置方法 InternalInsert…();Java 中也和 C# 类似,均调用其他来实现。鉴于相似性,下面就只展示一下方法 linkLast() 不做分析。

区别不大。硬要说,就可能是运行时 CLR(公共语言运行库) 于 JVM(Java 虚拟机) 优化性能的差异了。
众所周知,在C# 中,值类型时不可空的,引用类型是可空的:

过了一段时间,大概在 C# 5 时期,引入了可空值类型,长这样:

然后又又又过了一段时间,应该是在 C# 8 引入了,可空引用类型:

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
调用一下反汇编,看看加了一个问号会不会对数据类型产生影响


string? 依然是 string,int? 不再是 int,而是变成了 Nullable 类型,相当于:

分析一下这个有意思的 CS8600 警告:
|
严重性 |
代码 |
说明 |
|
警告 |
CS8600 |
将 null 文本或可能的 null 值转换为不可为 null 类型。 |
嘿!我大引用类型什么时候变成不可为 null 类型了?根据微软文档的解释:该警告的目的是将应用程序在运行时引发 System.NullReferenceException 的可能性降至最低。简单说就是降低代码在运行时引发空引用异常的概率,这一做法会让程序在运行时带来一些效率上的提高。至于是怎么提高的,应该是避免了异常的频繁发生,导致程序频繁终止。(个人观点,仅供参考)。
[注意:Nullable<T> 和 Nullable 是两个不同的概念,前一个是结构体,后一个是类;当然二者都是数据类型]


上述文本摘自 Reference Source .NET Framework 4.8。直译:这个特性用于表示特定成员的实现或结构布局不能以不兼容的方式在给定的平台进行更改。这允许跨模块内联方法和数据结构,这些方法和结构的实现在ReadyToRun的本机映像中永远不会改变,对这些成员或类型的任何更改都将破坏对ReadyToRun的更改。说人话大概就是,不允许在某些平台上乱改被其修饰的对象,以此保证在本机映像和实际使用时的一致性,避免在不同的环境下同一个内容出现不同的形式。

判断是否存储了元素以及返回存储的元素。

hasValue 用于表示当前对象是否存储了某个值;value 表示存储的值。

implicit 用于声明隐式的自定义类型转换运算符,实现2个不同类型的隐式转换。使用隐式转换操作符之后,在编译时会跳过异常检查,可能会出现某些异常或信息丢失。
explicit 用于声明必须通过显示转换来调用的自定义的类型转换运算符。不同于隐式转换,显式转换运算符必须通过转换的方式来调用,如果缺少了显式转换,在编译时会产生错误。
简单来说,这两个关键字用于声明类型转换的运算符,针对自定义类型间的转换,一种为隐式转换,另一种为显示转换。

其包含的方法和其他类型中的方法大致相同,在此不作解释。
单个问号在 C# 中是三元表达式的结构之一,也是定于可空类型的符号。而两个问号被定义为合并运算符,其工作原理如下:对于表达式 <par> = <par1> ?? <par2> 如果左操作数 par1 的值不为 null,则合并运算符返回该值,即 par1;否则,它会计算右操作数并返回其结果。如果左操作数的计算结果为不为 null,则 ?? 运算符不会计算其右操作数。
仅当左操作数的计算结果为 null 时,Null 合并赋值运算符 ??= 才会将其右操作数的值赋值给其左操作数。 如果左操作数的计算结果为非 null,则 ??= 运算符不会计算其右操作数。其中 ??= 运算符的左操作数必须是变量、属性或索引器元素。
举例如下:

此时 a 为 null,因此返回 5
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

此时 a 不为 null,因此返回 a 的值。
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——


同理可得,a 为空返回11;a 不为空返回 a。

其实这是一个补充,在代码中如果我们判断出某个变量在使用时一定不为null,但是编译器会在可空上下文中抛出警告,这是一个不太正常的行为,可空容忍可以消除这种警告,将不可为空的引用类型转换成可为空的引用类型。


假如我们知道 obj 和 obj.ToString() 在这里一定不为空,那么就可以在 obj 与 ToString() 的结果后加上可空容忍运算符,将其转换为不可空类型,以此消除警告。

这样操作后,obj 不可再被赋值为 null。
【注:
1. 特性一般用来解决警告问题,并不能解决错误或进行强制类型转换。
2. 特性的修饰更多地,只起到标识告知的作用。】
需要引入命名空间

性质:前置条件,即放在修饰对象前。
作用:将不可为 null 的参数、字段或属性使其可以为 null。【注意,这里的“不可为”指的是警告内容,不是数据类型上的不可为空】
举例:

现在有一个字段,当通过属性获取字段值的时候,一定不会获得到 null,因为在 set 里面指定了非 null 的默认值。然而在方法 Set() 里是允许设置 null 到这个属性,但属性 Msg 是不可为空的。于是,为了解决警告的出现,要么将字段定义为可空,要么将这个加上特性 [AllowNull]。这样,获取此字段的时候会得到非 null 值,但设置的时候却可以传递 null。


即,将不可为空的属性 Msg 标记为可为空(可以传入空值),但传入空时会保持其默认值。
大多数情况下,属性或 in、out 和 ref 参数需要此特性。 当变量通常为非 null 时,[AllowNull] 是最佳选择,但需要允许 null 作为前提条件。
特性一般主要用于处理警告方面的问题,使得程序更加规范化,在此不作过多演示,更多内容,请参考下表(来自:C# 编译器解释的属性:可为 null 的静态分析 | Microsoft Learn)
|
Attribute |
Category |
含义 |
|
不可为 null 的参数、字段或属性可以为 null。 |
||
|
可为 null 的参数、字段或属性应永不为 null。 |
||
|
不可为 null 的参数、字段、属性或返回值可能为 null。 |
||
|
可为 null 的参数、字段、属性或返回值将永不为 null。 |
||
|
当方法返回指定的 bool 值时,不可为 null 的参数可以为 null。 |
||
|
当方法返回指定的 bool 值时,可以为 null 的参数不会为 null。 |
||
|
如果指定参数的自变量不为 null,则返回值、属性或自变量不为 null。 |
||
|
当方法返回时,列出的成员不会为 null。 |
||
|
当方法返回指定的 bool 值时,列出的成员不会为 null。 |
||
|
方法或属性永远不会返回。 换句话说,它总是引发异常。 |
||
|
如果关联的 bool 参数具有指定值,则此方法或属性永远不会返回。 |
特性,在之前的文章中也讲述过,主要是进行修饰,使得对象具有某些额外性质。
Nullable,属于内部密封类 NullableAttribute,派生自类 Attribute。



根据 C# 的编译器roslyn的GitHub页面(roslyn/nullable-metadata.md at main · dotnet/roslyn · GitHub):Each type reference in metadata may have an associated NullableAttribute with a byte[] where each byte represents nullability: 0 for oblivious, 1 for not annotated, and 2 for annotated. 也就是说,该数组中的有效值仅为0、1、2,且具有不同的含义。【由于无法找到相关文档,也无法进行相关实验操作,数值所代表的含义在此暂不做分析,后续可能会补上】
在讲 LINQ 时,提到过迭代器 Iterator,也分析了其源码,感兴趣的可参阅本人的文章([算法1-排序](.NET源码学习)& LINQ & Lambda - PaperHammer - 博客园 (cnblogs.com))[# 有关Iterator的源码]
在 C# 中,用来比较元素/对象是否相等有以下几种方式:运算符 “==”,方法 Equals(),方法 SequenceEqual(),接口 IEquatable<T>,接口 IEqualityComparer<T>。
其比较规则较为简单:“==” 是对方法 Equals() 的重载,对于值类型,比较的是值内容(即,栈中的内容);对于引用类型,比较的是引用地址(即,堆/堆栈中的内容)。

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——

—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
虽然在之前的文章中提到过,这里还是再解释一下即使窗口里的信息。此处显示了两个地址:一个是 &x 后的地址;另一个时 *&x 后的地址。
对于 C# 而言总会将变量本身存储在栈中,变量内部的值存储在相应的位置(值类型在栈中,引用类型在堆/堆栈中)。对于获取到的第一个地址,是变量在栈中存储的位置,也就是说:C# 中的 &x 获取的是变量在栈中的位置。
而 *&x 的含义是:解析地址 &x,即读取其中的值。对于值类型而言,其值就存储在栈中,因此解析后直接得到对应的值;对于引用类型而言,其值存储在堆/堆栈中,因此解析后会得到一个堆中的位置,这个位置就是存储实际值的位置。

类 Object:

类 RuntimeHelpers:

方法 Equals() 最初被定义在类 RuntimeHelpers 中,再由类 Object 与类 ValueType 进行扩展,而 C# 中其他所有类型(包括值类型、引用类型、自定义类型)均派生自这两个类,并重写了这个方法,因此对于任意一个变量均可使用这个方法;且均有各自的比较据。
【关于方法 Equals() 的更多说明与应用,请参阅C# 有关List<T>的Contains与Equals方法 - PaperHammer - 博客园 (cnblogs.com)】
在 C# 中,对于值类型的比较不管是用 “==” 还是 Equals 都是对于其内容的比较,也就是说对于其值的比较,相等则返回 true 不相等则返回 false;
但是对于除 string 类型以外的引用类型 “==” 比较的是对象在栈上的引用是否相同,而 Equals 则比较的是对象在堆上的内容是否相同。
该方法主要用于字符串间的比较,比较的是字符串的大小,根据字符对应的 ASCII 码进行比较。
对于 Compare(s1, s2):s1 == s2 返回 0;s1 > s2 返回 1;s1 < s2 返回 -1。



该类是一个抽象类,位于程序集 CoreLib.dll 中的 命名空间 System.Collections.Generic

包含 8 个方法,一个属性。
—— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— —— ——
此处主要关注方法 IEqualityComparer.Equals(object x, object y)

其实该方法的比较依旧主要过运算符 “==” 完成。
对于比较,其实并不复杂,只需要区分开值类型与引用类型的比较规则就可以。虽然有许多工具都可以用来比较,但其比较的本质依旧是不变的。
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
是的,我知道最好使用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
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最