当前位置: 首页 > 知识库问答 >
问题:

为什么切片值有时会过时,但永远不会映射值?

公孙宸
2023-03-14

我发现切片映射函数和通道经常作为引用类型一起提到。然而,我注意到有些东西表现出非引用行为,就像它们会过时一样:

   var s []int
   //must update slice value
   s = append(s, ...) 

   //must use pointer if we want to expose the change
   func foo(s *[]int) error  
   //or change the function signature to return it like _append_
   func foo(s []int) (r slice, err error)

通常我是通过记住slice描述器实现的内部组件来理解这一点的:slice值可以被看作len、cap和数据指针的结构。

但是地图值永远不需要像

   m := make(map[string]int)
   ...
   // don't know how to express with insertion, but you know what i mean.
   m = delete(m, "well")  

为什么?映射值只是映射描述符的指针吗?如果是这样,为什么不这样做呢?

共有2个答案

单凯捷
2023-03-14

Slice是对连续内存块的薄如纸的包装,部分或全部重用该内容通常是有益的(避免复制数据)。地图没有这些特征。这是一个具有复杂行为的复杂数据结构,您不能重用它的存储(就像您对切片所做的那样)。

易刚捷
2023-03-14

在Go中没有像C中那样的引用类型。在围棋中,一切都是按价值传递的。在Go中使用术语“reference type”时,它表示引用它们应该表示的数据的类型(通过指针)。

切片是由类型反映的小的、类似结构的数据结构。SliceHeader

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

它包含一个指针,指向底层数组中切片的第一个元素(SliceHeader.Datafield)。该结构很小,作为值传递很有效,无需传递其地址(并取消引用以间接访问其任何字段)。切片的元素不存储在切片标头中,而是存储在标头内存区域之外的数组中。“修改”原始元素的“将”表示修改原始元素的“将”。

当您将(超过0)个元素附加到切片时,标头中的Len字段必须更改,因此使用附加元素描述切片的新切片必须与附加之前的切片不同,这就是为什么您需要指定内置append()函数的返回值。(其他值也可能会更改,但Lensure必须更改。)

映射被实现为指向运行时的指针。hmap结构:

type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

    extra *mapextra // optional fields
}

正如您所看到的,这是一个比切片头复杂得多的数据结构,而且要大得多,将其作为值传递是没有效率的。

从映射中添加/删除元素(键值对)存储在由该结构的字段引用的桶中,但是由于映射在引擎盖下作为指针处理,因此您不需要分配此类操作的结果。

为了完成,通道也被实现为指针,指向runtime包的hchan类型:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

这也是一个“fat”结构,处理方式类似于映射值。

见相关问题:

要在参数中使用的切片与贴图

使用值接收器追加到具有足够容量的片

golang切片是否按值传递?

Go中的“值语义”和“指针语义”是什么意思?

 类似资料:
  • 问题内容: 我发现切片图功能和通道经常一起作为 参考类型 提及。但是我注意到,切片的东西不会表现出参考行为,就像它们会过时一样: 要么 通常,我会通过牢记切片描述符实现的内部组件来理解这一点:切片值可以视为len,cap和data指针的结构。 但是地图值永远不需要像 为什么?映射值仅仅是指向映射描述符的指针吗?如果是这样,为什么还不这样做呢? 问题答案: 在Go中,没有像C ++中那样的引用类型。

  • 问题内容: 抱歉,这听起来太简单了。我是Java的新手。 这是我用来检查的一些简单代码。当我运行它时,我无法停止它。我以为如果不写任何输入并按,就可以避免循环。 有人可以向我解释在这种情况下如何工作吗? 问题答案: 从System.in读取时,默认情况下是从键盘读取的,这是一个无限的输入流……它的行数与用户希望输入的行数相同。我认为发送EOF的控制序列可能会起作用,例如CTL-Z(或者是CTL-D

  • 我有一个ColdFusion事件网关,有时会在以下行产生错误(其中Local.cur线程是数字1-5): 错误是: 消息:超时值为负。类型:java。lang.IllegalArgumentException 这是StackTrace: Java语言lang.IllegalArgumentException:java的超时值为负。lang.Object。在coldfusion上等待(本机方法)。运

  • 问题内容: 我正在尝试在JPA中使用悲观锁定,而不是针对Postgres数据库使用Hibernate 3。我无法超时锁定-它似乎永远挂着。 这是一个例子: 据我了解,em2应该尝试长达五秒钟(5000毫秒)来获取锁,然后应该抛出异常。而是代码陷入僵局。 如果我在两个不同的线程中运行它,那么我会看到线程2(带有em2)在线程1(em1)释放它后立即获得了锁。因此锁定正在发生,只是永不超时。 我用PE

  • 问题内容: 我正进入(状态 : 当我尝试运行以下 (尽管不完整) 代码段时: 在如下所示: 我正在尝试和班级之间建立联系。 发生此异常的原因是什么? 我创建了两个表并使用以下sql命令: POJO 人 地址 问题答案: 对于多对多关系交换,您需要专用的映射表 6.2.4。价值和多对多关联的集合 即,您需要类似PersonAddress表的内容 其中p_id是对人员表的FK引用,而a_id是对地址表

  • 问题内容: 我正进入(状态 : 当我尝试运行以下 (尽管不完整) 代码段时: 在如下所示: 我正在尝试和班级之间建立联系。 发生此异常的原因是什么? 我创建了两个表并使用以下sql命令: POJO 人 地址 问题答案: 对于ManyToMany Relationshhip,您需要专用的映射表 6.2.4。价值和多对多关联的集合 即,您需要类似PersonAddress表的内容 其中p_id是对人员