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

6 回复

外层循环可以写得更简单,例如:

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..")
}

主要优化点:

  1. 使用json.RawMessage延迟解析Content字段使用json.RawMessage类型,允许我们先获取总页数,然后再按需解析内容。

  2. 单次遍历处理:在processPage函数中,通过一次循环同时完成关键词匹配和结果统计。

  3. 并发处理:使用goroutine并发获取和处理所有页面数据,显著减少总运行时间。

  4. 流式JSON解析:使用json.Decoder代替json.Unmarshal,减少内存分配。

  5. 避免重复代码:将页面处理逻辑封装在processPage函数中,消除代码重复。

这些优化应该能显著提升程序性能,减少运行时间。

回到顶部