Golang中如何避免对JSON数据进行两次循环遍历(只需一次循环)
Golang中如何避免对JSON数据进行两次循环遍历(只需一次循环) 最近,我决定从一个招聘网站的API获取所有广告,并得到了JSON格式的返回数据。JSON中包含了许多职位广告以及可用的总页数。作为一名实习生,这是一个很好的练习。
我遇到的问题是,需要遍历JSON两次,这导致速度非常慢。第一次遍历是为了获取总页数变量,第二次遍历则是将所有内容存储到结构体中。其次,我重复编写了相同的代码,这非常令人讨厌,因为我讨厌重复自己。我遇到了很长的运行时间,希望能缩短它。我用Python编写了相同的功能,实际上运行得更快。
提前感谢任何回复!我在这里是为了学习和进步。
代码:
package main
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
)
type JsonData struct {
Content []JsonContent `json:"content"`
TotalElements int `json:"totalElements"`
PageNumber int `json:"pageNumber"`
PageSize int `json:"pageSize"`
TotalPages int `json:"totalPages"`
First bool `json:"first"`
Last bool `json:"last"`
Sort string `json:"sort"`
}
type JsonContent struct {
Uuid string `json:"uuid"`
Published string `json:"published"`
Expires string `json:"expires"`
Updated string `json:"updated"`
WorkLoc []WorkLocations `json:"workLocations"`
Title string `json:"title"`
Description string `json:"description"`
SourceUrl string `json:"sourceurl"`
Source string `json:"source"`
ApplicationDue string `json:"applicationDue"`
OccupationCat []OccupationCategories `json:"occupationCategories"`
JobTitle string `json:"jobtitle"`
Link string `json:"link"`
Employ Employer `json:"employer"`
EngagementType string `json:"engagementtype"`
Extent string `json:"extent"`
StartTime string `json:"starttime"`
PositionCount interface{} `json:"positioncount"`
Sector string `json:"sector"`
}
type WorkLocations struct {
Country string `json:"country"`
Address string `json:"address"`
City string `json:"city"`
PostalCode string `json:"postalCode"`
County string `json:"county"`
Municipal string `json:"municipal"`
}
type OccupationCategories struct {
Level1 string `json:"level1"`
Level2 string `json:"level2"`
}
type Employer struct {
Name string `json:"name"`
OrgNr string `json:"orgnr"`
Description string `json:"description"`
Homepage interface{} `json:"homepage"`
}
type TotalPageStruct struct {
TotalPages int `json:"totalPages"`
}
func main() {
fmt.Println("Starting..")
var totalPages TotalPageStruct
var datas JsonData
var keyWords = []string{"Python", "Golang", "Ansible", "Powershell", "Support", "Kundeservice", "Servicekonsulent", "kundebehandling", "Medarbeider"}
getTotalPages(&totalPages)
i := 1
for i < totalPages.TotalPages {
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=" + strconv.Itoa(i) + "&size=5000&published=%2A&county=Oslo"
// Create a Bearer string by appending string access token
var bearer = "Bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwdWJsaWMudG9rZW4udjFAbmF2Lm5vIiwiYXVkIjoiZmVlZC1hcGktdjEiLCJpc3MiOiJuYXYubm8iLCJpYXQiOjE1NTc0NzM0MjJ9.jNGlLUF9HxoHo5JrQNMkweLj_91bgk97ZebLdfx3_UQ"
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
// add authorization header to the req
req.Header.Add("Authorization", bearer)
// Send req using http Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &datas)
if err != nil {
log.Fatal(err)
}
dataMap := lookForWork(&datas, keyWords)
for k, v := range dataMap {
if v > 0 {
fmt.Println(k, v)
}
}
i++
}
fmt.Println("Ended..")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func getTotalPages(totalPages *TotalPageStruct) {
// Getting the totalPages variable
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=1&size=5000&published=%2A&county=Oslo"
// Create a Bearer string by appending string access token
var bearer = "Bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwdWJsaWMudG9rZW4udjFAbmF2Lm5vIiwiYXVkIjoiZmVlZC1hcGktdjEiLCJpc3MiOiJuYXYubm8iLCJpYXQiOjE1NTc0NzM0MjJ9.jNGlLUF9HxoHo5JrQNMkweLj_91bgk97ZebLdfx3_UQ"
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
// add authorization header to the req
req.Header.Add("Authorization", bearer)
// Send req using http Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &totalPages)
if err != nil {
log.Fatal(err)
}
}
func lookForWork(data *JsonData, keyWords []string) map[string]int {
var dataMap = map[string]int{}
for _, v := range data.Content {
dataMap[v.Link] = 0
for _, keyWord := range keyWords {
if strings.Contains(strings.ToLower(v.Description), strings.ToLower(keyWord)) {
//fmt.Println(v.Link, keyWord)
dataMap[v.Link] += 1
}
}
}
return dataMap
}
更多关于Golang中如何避免对JSON数据进行两次循环遍历(只需一次循环)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
外层循环可以写得更简单,例如:
for i =1; i < totalPages.TotalPages; i++ {
...
}
更多关于Golang中如何避免对JSON数据进行两次循环遍历(只需一次循环)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
感谢您的回复。我会再次检查我的代码,看看是否能找出问题所在。
API访问密钥是公开的,对所有人开放,因此不会造成任何损害。
嘿,我现在没时间深入查看,但只是想建议你不要在公开的代码片段中包含访问令牌。
编辑: 我快速看了一下,我不确定你是否真的需要在获取其余数据之前先获取总页数?当我通过上述API检索任何页面时,无论是第1页还是第90页,我都能在响应中获取到“TotalPages”数据。你甚至在你的 JsonData 结构体中包含了这个字段。所以,如果你愿意,你可以直接从第1页开始,并从那里获取总页数,或者直接循环直到响应中没有列表数据为止。
你的代码中有一个小错误。如果你将 i 初始化为 1,那么循环条件应该是 i <= totalPages.TotalPages,而不是 i < totalPages.TotalPages。
另一个错误是,在 body, _ := ioutil.ReadAll(resp.Body) 指令之后,你缺少了一个 resp.Body.Close()。
你可以通过将 var bearer = "Bearer " + "e..." 移出循环来优化你的代码。这个变量只需要初始化一次。对于 client := &http.Client{} 也是一样。你可以为所有调用使用同一个 client。这些只是微小的优化。通过这些改动,你不会看到执行时间有显著的提升,但养成这样的习惯是好的。
正如 Liza 所建议的,你可以避免调用获取总页数的函数。 以下是你可以如何实现循环:
...
var datas JsonData
...
datas.TotalPages = 1
for datas.PageNumber < datas.TotalPages {
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=" + strconv.Itoa(datas.PageNumber+1) + "&size=5000&published=%2A&county=Oslo"
...
err = json.Unmarshal(body, &datas)
...
}
这将允许你摆脱 getTotalPages(&totalPages)。
请注意,上述代码假设:
datas.PageNumber被初始化为 0- 页码从 1 到 datas.TotalPages(包含)
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
)
type JsonData struct {
Content []JsonContent `json:"content"`
TotalPages int `json:"totalPages"`
}
type JsonContent struct {
Description string `json:"description"`
Link string `json:"link"`
}
func main() {
fmt.Println("Starting..")
var datas JsonData
var keyWords = []string{
strings.ToLower("Python"),
strings.ToLower("Golang"),
strings.ToLower("Ansible"),
strings.ToLower("Powershell"),
strings.ToLower("Support"),
strings.ToLower("Kundeservice"),
strings.ToLower("Servicekonsulent"),
strings.ToLower("kundebehandling"),
strings.ToLower("Medarbeider"),
}
buffer := bytes.NewBuffer(make([]byte, 0, 4096))
page := 1
for {
if err := request(page, buffer); err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(buffer.Bytes(), &datas); err != nil {
log.Fatal(err)
}
dataMap := lookForWork(&datas, keyWords)
for k, v := range dataMap {
if v > 0 {
fmt.Println(k, v)
}
}
if page >= datas.TotalPages {
break
}
page++
}
buffer = nil
fmt.Println("Ended..")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func request(page int, buffer *bytes.Buffer) error {
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=" + strconv.Itoa(page) + "&size=5000&published=%2A&county=Oslo"
// Create a Bearer string by appending string access token
var bearer = "Bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwdWJsaWMudG9rZW4udjFAbmF2Lm5vIiwiYXVkIjoiZmVlZC1hcGktdjEiLCJpc3MiOiJuYXYubm8iLCJpYXQiOjE1NTc0NzM0MjJ9.jNGlLUF9HxoHo5JrQNMkweLj_91bgk97ZebLdfx3_UQ"
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
// add authorization header to the req
req.Header.Add("Authorization", bearer)
resp, err := http.DefaultClient.Do(req)
if err != nil {
if resp != nil {
resp.Body.Close()
}
return err
}
defer resp.Body.Close()
buffer.Reset()
_, err = io.Copy(buffer, resp.Body)
return err
}
func lookForWork(data *JsonData, keyWords []string) map[string]int {
var dataMap = map[string]int{}
for _, v := range data.Content {
dataMap[v.Link] = 0
for _, keyWord := range keyWords {
if strings.Contains(strings.ToLower(v.Description), keyWord) {
dataMap[v.Link] += 1
}
}
}
return dataMap
}
我已经简化了你的代码。首先,我简化了结构,只保留了有用的字段,以减少 JSON.Unmarshal 的工作量。我还优化了HTTP请求以读取 resp.Body。我重用了 buffer 以减少内存分配。
我调整了分页请求的方法。首先,我认为至少可以获取一页数据。在请求完第一页数据后,我会验证是否有更多数据需要请求。如果满足条件,我会及时退出循环,程序将结束。
我无法比较性能。受网络状况影响,需要你自己比较效率,以确保程序能达到你期望的效果。
在Go中避免对JSON数据进行两次循环遍历的关键是使用流式JSON处理。通过json.Decoder可以一次性解析JSON并同时提取所需信息。以下是优化后的代码示例:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"sync"
)
// 使用json.RawMessage延迟解析content字段
type JsonData struct {
Content json.RawMessage `json:"content"`
TotalPages int `json:"totalPages"`
PageNumber int `json:"pageNumber"`
// 其他字段...
}
type JsonContent struct {
Description string `json:"description"`
Link string `json:"link"`
// 其他字段...
}
func processPage(page int, keywords []string, wg *sync.WaitGroup, results chan<- map[string]int) {
defer wg.Done()
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=" +
strconv.Itoa(page) + "&size=5000&published=%2A&county=Oslo"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwdWJsaWMudG9rZW4udjFAbmF2Lm5vIiwiYXVkIjoiZmVlZC1hcGktdjEiLCJpc3MiOiJuYXYubm8iLCJpYXQiOjE1NTc0NzM0MjJ9.jNGlLUF9HxoHo5JrQNMkweLj_91bgk97ZebLdfx3_UQ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
// 使用json.Decoder进行流式解析
decoder := json.NewDecoder(resp.Body)
var data JsonData
if err := decoder.Decode(&data); err != nil {
return
}
// 单次遍历处理content
dataMap := make(map[string]int)
// 解析content数组
var contents []JsonContent
if err := json.Unmarshal(data.Content, &contents); err != nil {
return
}
// 单次循环处理所有逻辑
for _, content := range contents {
count := 0
descLower := strings.ToLower(content.Description)
for _, keyword := range keywords {
if strings.Contains(descLower, strings.ToLower(keyword)) {
count++
}
}
if count > 0 {
dataMap[content.Link] = count
}
}
results <- dataMap
}
func main() {
fmt.Println("Starting..")
keywords := []string{"Python", "Golang", "Ansible", "Powershell", "Support",
"Kundeservice", "Servicekonsulent", "kundebehandling", "Medarbeider"}
// 首先获取总页数
url := "https://arbeidsplassen.nav.no/public-feed/api/v1/ads?page=1&size=5000&published=%2A&county=Oslo"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwdWJsaWMudG9rZW4udjFAbmF2Lm5vIiwiYXVkIjoiZmVlZC1hcGktdjEiLCJpc3MiOiJuYXYubm8iLCJpYXQiOjE1NTc0NzM0MjJ9.jNGlLUF9HxoHo5JrQNMkweLj_91bgk97ZebLdfx3_UQ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
var firstPage JsonData
decoder := json.NewDecoder(resp.Body)
decoder.Decode(&firstPage)
resp.Body.Close()
totalPages := firstPage.TotalPages
// 并发处理所有页面
var wg sync.WaitGroup
results := make(chan map[string]int, totalPages)
// 处理第一页(已获取)
wg.Add(1)
go func() {
defer wg.Done()
dataMap := make(map[string]int)
var contents []JsonContent
json.Unmarshal(firstPage.Content, &contents)
for _, content := range contents {
count := 0
descLower := strings.ToLower(content.Description)
for _, keyword := range keywords {
if strings.Contains(descLower, strings.ToLower(keyword)) {
count++
}
}
if count > 0 {
dataMap[content.Link] = count
}
}
results <- dataMap
}()
// 处理剩余页面
for page := 2; page <= totalPages; page++ {
wg.Add(1)
go processPage(page, keywords, &wg, results)
}
// 等待所有goroutine完成
go func() {
wg.Wait()
close(results)
}()
// 收集结果
finalResult := make(map[string]int)
for result := range results {
for k, v := range result {
finalResult[k] = v
}
}
// 输出结果
for k, v := range finalResult {
fmt.Println(k, v)
}
fmt.Println("Ended..")
}
主要优化点:
-
使用
json.RawMessage延迟解析:Content字段使用json.RawMessage类型,允许我们先获取总页数,然后再按需解析内容。 -
单次遍历处理:在
processPage函数中,通过一次循环同时完成关键词匹配和结果统计。 -
并发处理:使用goroutine并发获取和处理所有页面数据,显著减少总运行时间。
-
流式JSON解析:使用
json.Decoder代替json.Unmarshal,减少内存分配。 -
避免重复代码:将页面处理逻辑封装在
processPage函数中,消除代码重复。
这些优化应该能显著提升程序性能,减少运行时间。

