Ответ
Если бы мне нужно было реализовать собственную функцию append
для слайсов, я бы сосредоточился на эффективности управления памятью и обработке capacity
(ёмкости) слайса. Вот пример реализации с использованием дженериков Go.
// AppendSlices добавляет все элементы из 'src' в 'dst'.
// Возвращает новый слайс, который может указывать на новый базовый массив.
func AppendSlices[T any](dst, src []T) []T {
totalLen := len(dst) + len(src)
// 1. Проверяем, достаточно ли ёмкости в dst.
if totalLen <= cap(dst) {
// Ёмкости достаточно, переиспользуем существующий массив.
// Расширяем слайс dst до нужной длины.
dst = dst[:totalLen]
} else {
// 2. Ёмкости недостаточно, нужно выделить новый массив.
// Рассчитываем новую ёмкость с запасом, чтобы избежать частых аллокаций.
// Стратегия роста может быть разной, здесь для простоты удваиваем требуемую длину.
newCap := totalLen
if newCap < 2*len(dst) {
newCap = 2 * len(dst)
}
if newCap < totalLen {
newCap = totalLen
}
newDst := make([]T, totalLen, newCap)
copy(newDst, dst) // Копируем старые данные
dst = newDst
}
// 3. Копируем элементы из src в свободное место в dst.
copy(dst[len(dst)-len(src):], src)
return dst
}
Ключевые моменты реализации:
Проверка ёмкости (
capacity
): Это самый важный шаг. Если в базовом массивеdst
достаточно места, мы избегаем дорогостоящей операции выделения новой памяти и копирования старых элементов.Стратегия роста: Если нужна новая аллокация, ёмкость нового массива должна быть больше, чем просто необходимая длина. Стандартная библиотека Go использует сложную стратегию (удвоение для маленьких слайсов, затем рост на ~25% для больших), чтобы найти баланс между использованием памяти и количеством аллокаций.
Использование
copy
: Встроенная функцияcopy
— самый эффективный способ копирования данных между слайсами.Возврат нового слайса: Функция обязательно должна возвращать слайс, так как в случае аллокации нового массива указатель на него изменится. Вызывающий код должен всегда использовать результат:
slice = AppendSlices(slice, anotherSlice)
.