Прошло много времени с тех пор, как я написал это, и я планирую сделать серию Dig101 в этом году и углубиться в историю технологии.
также может двигатьсяWeChat версияЧитайте, формат кода будет лучше
Dig101: dig more, simplified more and know more
В golang есть два распространенных метода обхода: for и for-range. А некоторые ямки часто встречаются при использовании for-range, сегодня мы их и рассмотрим.
0x01 Traversal не может получить все указатели на элементы?
Следующий код хочет получить коллекцию срезов элементов указателя из обхода массива.
arr := [2]int{1, 2}
res := []*int{}
for _, v := range arr {
res = append(res, &v)
}
//expect: 1 2
fmt.Println(*res[0],*res[1])
//but output: 2 2
Ответ [не могу получить]
тот же код для среза[]int{1, 2}
илиmap[int]int{1:1, 2:2}
Пересечение также не так, как ожидалось.
в чем проблема?
просмотревиди компилируй исходный кодМожно понять, что for-range на самом деле синтаксический сахар.Внутренний вызов по-прежнему является циклом for.Инициализация будет копировать список с обходом (таким как массив, срез, карта), а затем каждый раз обходиться.v
Все они являются обходными присваиваниями одному и тому же элементу.
То есть, если вы прямоv
Взяв адрес, в итоге будет получен только один адрес, и соответствующее значение — это элемент, прикрепленный к последнему пройденному элементу.v
значение . Соответствующий псевдокод выглядит следующим образом:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
Итак, как его изменить? Есть два
- Использовать копию локальной переменной
v
for _, v := range arr {
//局部变量v替换了v,也可用别的局部变量名
v := v
res = append(res, &v)
}
- Получить исходный элемент по прямому индексу
//这种其实退化为for循环的简写
for k := range arr {
res = append(res, &arr[k])
}
После устранения этой проблемы ямки за ней в принципе легко найти, давайте быстро пройдемся по ней.
0x02 Прекратится ли обход?
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
Ответ [да], потому что перед пересечениемv
сделал копию, так что точка на оригиналv
изменения не будут отражены в обходе
0x03 Что не так с таким обходом больших массивов?
//假设值都为1,这里只赋值3个
var arr = [102400]int{1, 1, 1}
for i, n := range arr {
//just ignore i and n for simplify the example
_ = i
_ = n
}
Ответ [есть проблема]! Копирование перед обходом — огромная трата памяти. Как оптимизировать? Есть два
- обход адреса массива
for i, n := range &arr
- срез ссылки на массив
for i, n := range arr[:]
Вопрос для размышления: почему не существует проблемы нехватки памяти при обходе большого количества элементов по срезам и картам? (подсказка, копируется ли базовая структура данных)
Эффективно ли сбрасывать 0x04 для больших массивов?
//假设值都为1,这里只赋值3个
var arr = [102400]int{1, 1, 1}
for i, _ := range &arr {
arr[i] = 0
}
Ответ [высокий]. Это нужно понимать, чтобы знать, что go оптимизировал обход этого значения элемента сброса до значения по умолчанию. Подробнее см.источник: memclrrange
// Lower n into runtime·memclr if possible, for
// fast zeroing of slices and arrays (issue 5373).
// Look for instances of
//
// for i := range a {
// a[i] = zero
// }
//
// in which the evaluation of a is side-effect-free.
0x05 Можно ли удалять элементы при обходе карты?
var m = map[int]int{1: 1, 2: 2, 3: 3}
//only del key once, and not del the current iteration key
var o sync.Once
for i := range m {
o.Do(func() {
for _, key := range []int{1, 2, 3} {
if key != i {
fmt.Printf("when iteration key %d, del key %d\n", i, key)
delete(m, key)
break
}
}
})
fmt.Printf("%d%d ", i, m[i])
}
Ответ - нет]
Внутренняя реализация карты представляет собой цепочку хэш-таблиц, чтобы гарантировать, что каждый раз не по порядку, она будет инициализирована.Случайная начальная позиция обхода,
Таким образом, если удаляемый элемент сначала не будет пройден (вышеonce.Do
Функция гарантированно удалит непройденный элемент при первом выполнении), тогда он не появится в дальнейшем.
0x06 Можно ли обходить вновь добавленные элементы при обходе карты?
var m = map[int]int{1:1, 2:2, 3:3}
for i, _ := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}
Ответ [может быть], в выводе могут быть некоторые44
. Причина та же, что и предыдущая, что можно проверить с помощью следующего кода.
var createElemDuringIterMap = func() {
var m = map[int]int{1: 1, 2: 2, 3: 3}
for i := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}
}
for i := 0; i < 50; i++ {
//some line will not show 44, some line will
createElemDuringIterMap()
fmt.Println()
}
0x07 Можно ли запустить горутину в этом обходе?
var m = []int{1, 2, 3}
for i := range m {
go func() {
fmt.Print(i)
}()
}
//block main 1ms to wait goroutine finished
time.Sleep(time.Millisecond)
Ответ - нет]. Ожидается вывод некоторой комбинации 0,1,2, например 012, 210.. Результат 222. Такая же проблема с копированием Как с этим бороться
- передается как параметр
for i := range m {
go func(i int) {
fmt.Print(i)
}(i)
}
- Использовать копию локальной переменной
for i := range m {
i := i
go func() {
fmt.Print(i)
}()
}
После тщательного анализа вы обнаружили, что простой for-range имеет много интересных моментов. Я надеюсь, что анализ даст вам лучшее понимание. Если у вас есть какие-либо вопросы, пожалуйста, обратите внимание на обмен сообщениями.
Начальный адрес статьи:блог Кем я был s.com/2020/01/03/…
Ссылаться на