Go 的多層切片
我是一名程式員,我的工作內容是為美國不同地區的海洋天氣預報載入多邊形區域(polygons)。這些多邊形需要儲存在 MongoDB 中,而且還需要作特殊處理。本來也沒什麼難的,但是每個地區有很多個多邊形區域。一個大的多邊形區域還包含著 0-n 個多邊形,並且它們之間需要維護一定的關係。
看了一會兒問題之後,我意識到我需要建立一個海洋預報地區的切片,每個地區包含一個多邊形區域的切片。為了儲存每一個多邊形環線,我需要一個地理座標切片。最後,每個座標需要儲存在二維的 float 陣列中。
一張圖片勝過千言萬語 :
儲存在 Mongodb 中的資料應該是如下格式的 :
只是看著圖表和圖片我就暈了。該圖描述瞭如何將切片和物件組合在一起。
圖中顯示了多邊形是如何在 MongoDB 儲存的。在座標下會有多個元素,而每個元素又都有它自己的點集。
我決定寫一個測試程式來構造和儲存這些資料。
切片用得越多,我就越喜歡它們。這一點我很喜歡:當切片作為函式的引數或者返回值時,我不用親自去處理引用和記憶體。切片是一種輕量級的資料結構,可以在函式中安全地傳入或者返回。
我一直在想,我需要傳遞切片的引用,這樣就不會在堆疊上覆制資料結構。我記得棧上資料結構大小是 24 位元組的,我不需要複製抽象層次較低一級的所有資料。
閱讀下面兩篇文章可以學習到更多關於切片的知識:
- https://www.ardanlabs.com/blog/2013/08/understanding-slices-in-go-programming.html
- https://studygolang.com/articles/14132
讓我們看一下在 MongoDB 中資料是如何儲存和維護的:
// Polygon defines a set of points that complete a ring // around a geographic area type Polygon [][2]float64 // PolygonRings defines a MongoDB Structure for storing multiple polygon rings type PolygonRings struct { Type stringbson:"type" Coordinates []Polygon bson:"coordinates" } // Represents a marine station and its polygons type MarineStation struct { StationId stringbson:"station_id" Polygons PolygonRings bson:"polygons" }
Polygon 型別表示長度為 2 的 float 陣列切片。切片中陣列表示多邊形的各個端點。
如果你要是想通過 MongoDB 來執行不同多邊形區域的地理空間搜尋,那麼在 MongoDB 中儲存多邊形區域資料的結構是必須的。
MarineStation 結構體模擬一個單獨的站點和對應的多邊形區域。
測試程式碼將建立一個帶有兩個多邊形區域結構的站。然後它會顯示一切。讓我們來看看如何用切片建立一個海洋站,並建立一個單一的海洋站進行測試:
// Create a nil slice to store the polygon rings // for the different marine stations var marineStations []MarineStation // Create a marine station for AMZ123 marineStation := MarineStation{ StationId: "AMZ123", Polygons: PolygonRings{ Type: "Polygon", Coordinates: []Polygon{}, }, }
第一行程式碼建立了一個儲存 MarineStation 物件的空切片。然後我們用複合字面量的方式,建立了一個 MarineStation 物件。在這個複合字面量中,我們需要為這個 PolygonRings,建立另一個複合字面量物件 Polygons。在建立的 PolygonRings 物件中,我們為 Coordinates 欄位建立空的切片來儲存 Polygon 物件。
若要了解複合字面量的更多資訊,請檢視此文件:
http://golang.org/ref/spec#Composite_literals
現在是時候向 station 新增幾個區域資料結構:
// Create the points for the second polygon ring point1 = [2]float64{-80.4370117189999, 27.7877197270001} point2 = [2]float64{-80.4376220699999, 27.7885131840001} point3 = [2]float64{-80.4384155269999, 27.7885131840001} point4 = [2]float64{-80.4370117189999, 27.7877197270001} // Create a polygon for this ring polygon = Polygon{point1, point2, point3, point4} // Add the polygon to the slice of polygon coordinates marineStation.Polygons.Coordinates = append(marineStation.Polygons.Coordinates, polygon)
在第二個 polygon 中,有 4 個點而不是 5 個,剩下的最後一件事,就是將 polygon 加入到 stations 切片中,並且展示出來:
// Add the marine station marineStations = append(marineStations, marineStation) Display(marineStations)
Display 函式使用關鍵字 range
來進行遍歷所有的切片。
func Display(marineStations []MarineStation) { for _, marineStation := range marineStations { fmt.Printf("\nStation: %s\n", marineStation.StationId) for index, rings := range marineStation.Polygons.Coordinates { fmt.Printf("Ring: %d\n", index) for _, coordinate := range rings { fmt.Printf("Point: %f,%f\n", coordinate[0], coordinate[1]) } } } }
這個方法需要傳入一個 MarineStation 切片作為引數。記住,在棧上拷貝的僅僅是切片的結構,而不是切片表示的所有物件。
當我們迭代 MarineStation 物件和組成它的所有切片時,我們得到以下結果:
Station: AMZ123 Ring: 0 Point: -79.729119,26.972940 Point: -80.079953,26.969269 Point: -80.080363,26.970533 Point: -80.081051,26.975004 Point: -79.729119,26.972940 Ring: 1 Point: -80.437012,27.787720 Point: -80.437622,27.788513 Point: -80.438416,27.788513 Point: -80.437012,27.787720
使用切片去解決這個問題是快速的、容易的、高效的。我將這份測試程式碼複製了一份放在了 [The Go Playground]7 j7m(https://play.golang.org/)。
http://play.golang.org/p/UYO2HIKggy
通過快速構建這個測試應用,讓我深深地感覺到切片具有很大的優點。它可以使你開發更高效,程式碼更健壯。你不必擔心記憶體管理,你可以通過切片的引用,在函式資料傳遞時傳遞較大的資料。花一些時間去學習在程式碼中使用切片,你會很有收穫。