为什么要使用标志+位掩码而不是一系列布尔值?

33

假设我有一个对象可能处于一个或多个true/false状态,我一直有点糊涂,为什么程序员经常使用标志+位掩码而不是只使用几个布尔值。

.NET框架中到处都是这种情况。 不确定这是否是最好的例子,但.NET框架具有以下内容:

public enum AnchorStyles
{
    None = 0,
    Top = 1,
    Bottom = 2,
    Left = 4,
    Right = 8
}

给定一个锚点样式,我们可以使用位掩码来确定哪些状态被选中。但是,似乎也可以通过定义每个可能值的布尔属性的AnchorStyle类/结构体或单独枚举值的数组来实现同样的功能。

当然,我提出这个问题的主要原因是我想知道是否应该在我的代码中遵循类似的做法。

那么,为什么要使用这种方法呢?

  • 内存消耗更少吗?(它似乎不会比布尔数组/结构体消耗更少)
  • 比结构体或数组更好的堆栈/堆性能?
  • 比较操作更快吗?添加/删除值更快吗?
  • 对编写它的开发人员更方便吗?

4
虽然我不认为这是一个很有力的论据,但它确实消耗更少的内存。每个bool类型变量占用一个字节,而一个int类型变量只占用4个字节。因此,4个bool类型变量所占用的内存与一个int类型变量相同。32个bool类型变量需要占用32个字节,而这些变量可以放在同一枚举类型中。如果你走了不推荐的路线,可以把枚举类型长度设为8个字节(sizeof(long))。 - R. Martinho Fernandes
感谢您澄清这一点。它引导我到这篇文章:https://dev59.com/ynVC5IYBdhLWcg3wcgmd - Winston Fassett
从回复中可以清楚地看出,枚举标志在内存方面比布尔结构/数组更轻量级。然而,似乎也有一些.NET框架类非常适合这个任务,例如BitVector32或BitArray。那么,使用一个使用BitVector32(由uint支持)进行存储并提供获取/设置特定索引处位(作为布尔值)的属性的结构体如何呢?Windows Forms似乎在做这件事。对于开发人员来说需要编写更多代码,但它似乎会表现良好,并且封装性将使下游API消费者更容易使用。嗯? - Winston Fassett
11个回答

25

传统上,它是一种减少内存使用的方法。所以,在C#中,它已经相当过时了 :-)

作为一种编程技术,它可能在今天的系统中已经过时,您完全可以使用bool数组,但是...

使用位掩码存储的值进行比较很快。使用AND和OR逻辑运算符并比较生成的2个整数即可。

它使用的内存较少。将所有4个示例值放入位掩码中将使用半个字节。使用bool数组,则最可能会使用一些字节来存储数组对象以及每个布尔值的长字。如果您必须存储100万个值,您将看到为什么位掩码版本更优。

它更易于管理,您只需要处理单个整数值,而bool数组在数据库中存储方式会有所不同。

由于内存布局原因,它比数组快得多。在每个方面几乎与使用单个32位整数一样快。我们都知道这对于数据操作是尽可能快的。


12
  • 轻松设置任意顺序的多个标志。

  • 轻松将一系列的0101011保存到数据库中并获取。


4
请注意,即使将它们作为单独的列,SQL Server 也会将它们优化成一个单独的字节:http://msdn.microsoft.com/en-us/library/ms177603.aspx - AaronLS

8

除其他外,向位域添加新的位含义比向类添加新的布尔值更容易。另外,复制位域比复制一系列布尔值更容易。


对我来说,将布尔值添加到类中似乎很容易:bool newState;关于复制,复制结构体看起来也同样容易。 - Winston Fassett
2
@Winston:序列化格式的更改以及接受旧数据默认值的良好序列化器不易找到,而旧版本不丢弃未知字段的则更难。二进制接口的更改可能导致一系列必要的更新,并需要完整的版本支持结构(当然,合同必须明确声明“未知位被忽略”或“未知位导致错误”)。此外,在实现层面上,将它们作为整体处理更容易。 - peterchen
2
@Winston 如果你已经创建了一个API呢?那么每个可能升级到你的新版本的人都必须改变他们的代码,因为一个新的布尔值被添加到一个方法中。而如果它是枚举类型,那么他们不需要进行任何更改即可继续使用相同的代码。这就是.NET框架偏爱枚举类型而不是布尔类型的原因。 - David Basarab
@David - 我不是在谈论向方法添加参数,而是向结构体添加bool字段,这不会影响任何调用方法,但正如@Peter所说,会影响序列化。 - Winston Fassett
向 C# 结构体/类添加新字段不是破坏性的更改(除非先前的代码不知道它)。在这两种情况下,现有代码必须更新以了解如何使用新标志。 - user2864740

6
它还可以使方法更清晰。想象一下有10个布尔值的方法和一个位掩码的方法。

3
从领域模型的角度来看,在某些情况下,它可以更好地模拟现实。如果您有三个布尔值,如AccountIsInDefault、IsPreferredCustomer和RequiresSalesTaxState,则将它们添加到单个标记装饰的枚举中是没有意义的,因为它们不是同一领域模型元素的三个不同值。
但是,如果您有一组布尔值:
 [Flags] enum AccountStatus {AccountIsInDefault=1, 
         AccountOverdue=2 and AccountFrozen=4}

或者
  [Flags] enum CargoState {ExceedsWeightLimit=1,  
         ContainsDangerousCargo=2, IsFlammableCargo=4, 
         ContainsRadioactive=8}

因此,能够将账户(或货物)的整个状态存储在一个变量中是非常有用的...这个变量代表了一个领域元素,其值可以表示任何可能的状态组合。


2
实际上,如果您的枚举从字节派生而来,它可以有更好的性能。在这种极端情况下,每个枚举值将由一个字节表示,包含所有组合,最多256个。使用布尔值会导致256个字节,因为有太多可能的组合。
但即使如此,我认为这不是真正的原因。我喜欢使用枚举的原因是C#赋予我的处理枚举的能力。我可以使用单个表达式添加多个值。我也可以删除它们。我甚至可以使用枚举一次比较多个值。使用布尔值,代码可能会变得更加冗长。

4
共有256种组合,但只有8个旗帜,请勿混淆。 - Dykam
1
使用bool?可以得到256种组合。这是8个布尔值。但8个布尔值并不等于256字节。 - CoperNick

2

我建议除非你处理一些相当严重的内存限制(不太可能),否则永远不要使用枚举标志。你应该始终编写优化维护的代码。

拥有多个布尔属性可以更轻松地阅读和理解代码,更改值,并提供智能感知注释,更不用说减少错误的可能性。如果必要,您始终可以在内部使用枚举标志字段,只需确保使用布尔属性公开设置/获取值即可。


2

Raymond Chen在这个主题的博客文章中有所提及。

当然,位域可以节省数据内存,但是你必须权衡代码大小、调试性和减少多线程的成本。

正如其他人所说,它的时代已经过去了。虽然进行位操作很有趣且看起来很酷,但它不再更加高效,且在维护方面存在严重缺陷,与数据库不兼容,除非你在嵌入式领域工作,否则你拥有足够的内存。


3
Raymond 讨论的是位域(bitfields),而不是位掩码(bitmasks)。 - gbjbaanb

1
  1. 空间效率 - 1位
  2. 时间效率 - 由硬件快速处理比较位。
  3. 语言独立性 - 当数据可能被多个不同的程序处理时,您无需担心在不同的语言/平台上实现布尔值。

大多数情况下,这些都不值得以维护为代价。但是,在以下几种情况下它们确实很有用:

  1. 网络协议 - 缩小消息大小将大大节省。
  2. 遗留软件 - 有一次我必须将一些信息添加到一些旧有的软件中以进行跟踪。

修改标题的成本:数百万美元和数年的努力。 将信息塞入头文件中未使用的2个字节的成本:0。

当然,访问和操作此信息的代码也需要额外的成本,但这些都由函数完成,因此一旦定义了访问器,就不会比使用布尔值更难以维护。


2
  1. 空间效率仅适用于非常密集的包装或极其有限的环境;
  2. 时间效率取决于有效使用掩码(而单独比较一个位与比较一个布尔值肯定更快);
  3. 不适用,错误地使用布尔类型就是错误地使用布尔类型。
- user2864740

-1

这是为了速度和效率。基本上,你所处理的只是一个整数。

if ((flags & AnchorStyles.Top) == AnchorStyles.Top)
{
    //Do stuff
} 

这是一个相当高层次的回答。你能具体说明哪些操作更快/更有效,以及为什么吗?或者提供一篇文章来证明你的说法吗? - Winston Fassett
1
我真的需要给你证明,使用本地类型和简单逻辑表达式是快速高效的吗? - ChaosPandion
不要忘记操作顺序。你必须在位运算周围加上括号。 - Sam Harwell
很棒的发现,我习惯了Visual Studio一直支持我。 - ChaosPandion
3
未提供“速度和效率”的理由,在这种情况下,我怀疑它既不快也不高效。考虑采用反向风格/建议:if (AnchorsTop) { .. } - user2864740

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接