برنامه نویسی

برو برش و زیر مجموعه ها: درک حافظه مشترک و جلوگیری از `ضخامت ()

مقدمه

هیا به وبلاگ من خوش آمدید. 😁 اگر اینجا هستید ، احتمالاً در Golang یا باتجربه تازه وارد هستید و می خواهید کارهای داخلی برش ها را کشف کنید. پس بیایید سوار شویم.

GO اغلب به دلیل سادگی و کارآیی آن مورد ستایش قرار می گیرد – همانطور که می گویند ، “برو فقط کار را تمام می کند”. برای کسانی از ما که از زبانهایی مانند C ، C ++ یا Java آمده اند ، نحو ساده و سهولت استفاده از آن طراوت است. با این حال ، حتی در GO ، برخی از سؤالات خاص می توانند توسعه دهندگان را به ویژه در مورد برش ها و زیرمجموعه ها سفر کنند. بیایید از این ظرافت ها پرده برداریم تا بهتر درک کنیم که چگونه می توان از مشکلات مشترک جلوگیری کرد append() و حافظه مشترک در برش ها.

برش های موجود در GO چیست؟

به طور معمول ، هنگامی که برای ذخیره دنباله ای از مقادیر به یک ساختار داده نیاز دارید ، برش ها گزینه Go-to در Go هستند. انعطاف پذیری آنها از این واقعیت ناشی می شود که طول آنها به عنوان بخشی از نوع آنها ثابت نیست. این ویژگی بر محدودیت های آرایه ها غلبه می کند و به ما امکان می دهد یک عملکرد واحد ایجاد کنیم که بتواند برش هایی با هر اندازه را کنترل کند و باعث می شود برش ها در صورت لزوم رشد یا گسترش پیدا کنند.

در حالی که برش ها برخی از شباهت ها را با آرایه هایی مانند شاخص بودن و داشتن طول به اشتراک می گذارند ، در نحوه مدیریت داده ها متفاوت هستند. یک برش به عنوان مرجع یک آرایه زیرین عمل می کند ، که در واقع داده های برش را ذخیره می کند. در اصل ، یک برش منظره ای را به برخی یا تمام عناصر این آرایه ارائه می دهد. بنابراین ، هنگامی که یک برش ایجاد می کنید ، به طور خودکار به ایجاد آرایه زیربنایی که عناصر/داده های برش را در خود نگه می دارد ، استفاده می کند.

حافظه مشترک در برش

یک آرایه یک بلوک حافظه متناوب است اما آنچه که برش ها را جالب می کند این است که چگونه آنها به این حافظه مراجعه می کنند. بیایید آناتومی یک برش را تجزیه کنیم:

type slice struct {
    array unsafe.Pointer // Pointer to the underlying array
    len   int           // Number of elements in the slice
    cap   int           // Capacity of the underlying array
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

وقتی یک برش ایجاد می کنید ، از سه مؤلفه تشکیل شده است:

  1. یک اشاره گر به آرایه زیرین

  2. len طول برش (چند عنصر موجود در آن)

  3. cap ظرفیت (چند عنصر می تواند قبل از نیاز به رشد داشته باشد)

اینجا جایی است که همه چیز جالب می شود. اگر چندین برش حاصل از همان آرایه داشته باشید ، تغییراتی که از طریق یک برش ایجاد شده است در سایر برش ها قابل مشاهده خواهد بود زیرا آنها همان آرایه زیر را دارند.

بیایید به یک مثال زیر نگاه کنیم:

package main

import "fmt"

func main() {
    // Create a slice with some initial values
    original := []int{1, 2, 3, 4, 5}

    // Create a subslice - both slices share the same underlying array!
    subslice := original[1:3]

    fmt.Println("Unmodified Subslice:", subslice)  // Output => Umodified Subslice: [2 3]

    // Modify the subslice
    subslice[0] = 42

    fmt.Println("Original:", original) // Output => Original: [1 42 3 4 5]
    fmt.Println("Modified Subslice:", subslice)  // Output => Modified Subslice: [42 3]
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

درک ظرفیت برش

قبل از اینکه بیشتر شیرجه بزنیم ، سعی کنیم ظرفیت برش را درک کنیم cap()بشر هنگامی که یک زیر مجموعه را از یک قطعه موجود در Go می گیرید ، ظرفیت زیر مجموعه جدید با ظرفیت باقیمانده برش اصلی از نقطه ای که زیر مجموعه شروع می شود تعیین می شود. بیایید این را کمی خراب کنیم:

هنگامی که یک برش از یک آرایه ایجاد می کنید ، طول برش تعداد عناصری است که در ابتدا در آن قرار دارد و ظرفیت آن تعداد کل عناصری است که می تواند قبل از نیاز به رشد داشته باشد.

گرفتن یک زیرمجموع

هنگامی که یک زیر مجموعه را از یک قطعه موجود می گیرید:

  • در طول از زیر مجموعه تعداد عناصری است که شما مشخص می کنید.

  • در ظرفیت به عنوان ظرفیت برش اصلی منهای شاخص شروع زیر مجموعه محاسبه می شود.

بیایید نگاهی به یک مثال مفصل بیندازیم:

func main() {
    // Original slice
    original := []int{1, 2, 3, 4, 5}

    // Create a subslice
    subslice := original[1:4] // Refers to elements 2, 3, 4

    fmt.Println("Subslice:", subslice)    // Output => Subslice: [2 3 4]
    fmt.Println("Length of subslice:", len(subslice)) // Output => Length of subslice: 3
    fmt.Println("Capacity of subslice:", cap(subslice)) // Output => Capacity of subslice: 4
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • در original برش دارای 5 عنصر با طول و ظرفیت 5 است.

  • وقتی می گیرید subslice := original[1:4]، این به عناصر از شاخص 1 تا 3 اشاره دارد (2, 3, 4).

  • در طول از subslice است ، 4 - 1 = 3بشر

  • در ظرفیت از subslice است ، 5 - 1 = 4 از آنجا که از فهرست 1 شروع می شود و عناصر را تا انتهای برش اصلی شامل می شود.

در append() گرده

در اینجا جایی است که توسعه دهندگان اغلب از آن محافظت می شوند. در append() عملکرد در GO می تواند منجر به رفتار غیر منتظره هنگام کار با زیر مجموعه ها شود.

اشتراک ظرفیت استفاده نشده

در ظرفیت از subslice عناصری را شامل می شود که جزئی از طول آن نیستند اما در محدوده ظرفیت برش اصلی قرار دارند. این بدان معنی است که در صورت رشد ، زیر مجموعه می تواند به آن عناصر دسترسی پیدا یا اصلاح کند.

بیایید این مثال را در نظر بگیریم:

func main() {
    original := []int{1, 2, 3, 4, 5}
    subslice := original[1:3] // Refers to elements 2, 3

    fmt.Println("Original slice before append:", original) // Outputs => [1 2 3 4 5]
    fmt.Println("Subslice before append:", subslice)       // Outputs => [2 3]
    fmt.Println("Capacity of subslice:", cap(subslice))    // Outputs => 4

    // Appending to subslice within capacity
    subslice = append(subslice, 60, 70)

    // Printing after appending to subslice
    fmt.Println("Original slice after append:", original)  // Outputs => [1 2 3 60 70]
    fmt.Println("Subslice after append:", subslice)        // Outputs => [2 3 60 70]
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • subslice در اصل به 2, 3 با ظرفیت 4 (می تواند تا انتهای برش اصلی رشد کند).

  • وقتی پیوسته می شوید 60, 70 به subslice، از ظرفیت باقیمانده برش اصلی استفاده می کند.

  • هر دو original وت subslice تغییرات را منعکس کنید زیرا آنها همان آرایه اساسی را به اشتراک می گذارند.

تعجب؟ در append() عملیات برش اصلی را اصلاح کرد زیرا ظرفیت کافی در آرایه زیرین وجود داشت. با این حال ، اگر ما از ظرفیت فراتر برویم یا عناصر بیشتری را از آنچه ظرفیت اجازه می دهد اضافه کنیم ، GO آرایه جدیدی را برای زیر مجموعه اختصاص می دهد و اشتراک گذاری را با برش اصلی می شکند:

func main() {
    original := []int{1, 2, 3, 4, 5}
    subslice := original[1:3] // Refers to elements 2, 3

    // Appending beyond the capacity of subslice
    subslice = append(subslice, 60, 70, 80)

    fmt.Println("Original slice after large append:", original) // Outputs =>[1 2 3 4 5]
    fmt.Println("Subslice after large append:", subslice)       // Outputs => [2 3 60 70 80]
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در این حالت ، append() یک آرایه زیرین جدید ایجاد کرد زیرا از ظرفیت اصلی فراتر رفته است.

بهترین روشها برای جلوگیری از مشکلات

  • در مورد ظرفیت صریح باشد
// Let's say we start with this
original := []int{1, 2, 3, 4, 5}
subslice := original[1:3]  // subslice points to original's backing array

// This is our solution:
newSlice := make([]int, len(subslice))  // Step 1: Create new backing array
copy(newSlice, subslice)               // Step 2: Copy values over
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مزایای اصلی این است:

من make([]int, len(subslice)) یک برش جدید با آرایه پشتی جداگانه خود ایجاد می کند. این بسیار مهم است – این فقط یک هدر برش جدید نیست ، بلکه یک آرایه کاملاً جدید در حافظه است.

ii. copy() سپس فقط مقادیر را منتقل می کند ، نه مرجع حافظه. این مانند فتوکپی یک سند است نه به اشتراک گذاشتن اصلی.

  • از عبارات برش کامل استفاده کنید
// Specify the capacity limit to prevent sharing beyond what you intend
limited := original[1:3:3] // third number limits the capacity
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • هنگام انتقال برش به توابع که نباید داده های اصلی را تغییر دهند ، تغییر ناپذیری را در نظر بگیرید
func processSlice(data []int) []int {
    // Create a new slice with its own backing array
    result := make([]int, len(data))
    copy(result, data)
    // Process result...
    return result
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مزایای اصلی این است:

من حفاظت از داده ها: داده های اصلی بدون تغییر باقی می ماند و از عوارض جانبی غیر منتظره جلوگیری می کند

ii. رفتار قابل پیش بینی: توابع تأثیرات پنهان بر روی ورودی های آنها ندارند

iii ایمنی همزمان: استفاده از داده های اصلی در سایر goroutines هنگام پردازش بی خطر است

به یاد داشته باشید:

  • برش ها منابع مربوط به آرایه های اساسی هستند

  • زیر مجموعه ها حافظه را با برش والدین خود به اشتراک می گذارند

  • append() بسته به ظرفیت ممکن است یک آرایه پشتی جدید ایجاد کند یا ایجاد نکند

  • هنگام افزودن به زیر مجموعه با ظرفیت موجود ، داده های برش والدین را تغییر می دهد.

  • هنگامی که می خواهید از اشتراک گذاری خودداری کنید ، از مدیریت حافظه صریح استفاده کنید

  • هنگام کار با زیر مجموعه ها ، یا:

1. Create a new slice with its own backing array using `make()` and `copy()`

2. Use full slice expressions with capacity limits: `parent[1:3:3]`
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

برنامه نویسی مبارک و به یاد داشته باشید ، با قدرت عالی مسئولیت بزرگی به وجود می آید ، به خصوص وقتی که حافظه مشترک می آید! 😉


تبریک می گویم برای رسیدن به پایان این مقاله.

آیا این منبع را مفید می دانید؟ آیا سوالی دارید یا اشتباه یا تایپی را کشف کرده اید؟ لطفاً نظرات خود را در نظرات بگذارید.

فراموش نکنید که این منبع را با دیگران به اشتراک بگذارید که ممکن است از آن نیز بهره مند شوند. برای اطلاعات بیشتر مرا دنبال کن

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا