海量数据面试题(一)提取大文件中topK个IP

44 阅读2分钟

海量日志数据,提取出访问百度次数最多的TOPK个IP

  1. hash映射:将所有的IP提取出来到一个大文件,将大文件映射成小文件。比如%1000,就会把大文件映射为1000个小文件(这个1000根据实际情况定,设置成每个小文件都小于内存为止)

  2. map存储:对每一个小文件进行IP频次的统计,利用map数据结构,遍历到一个就对应加一,找出该小文件的次数最多的那个IP。

  3. 堆排序:对1000个IP次数大小进行堆排序(此时map假设内存装的下的,如果一次性装不下,得使用多路外部排序对这1000数进行排序),取TopK个IP即可()

为什么这种从文件中取最大的IP是可行的呢?

因为相同IP经过Hash函数后,只会被分到其中一个小文件之中,不会出现在多个文件。

实际实现的代码:

主要难度是文件的读写操作,还有IP字符串的hash操作(利用每一个字符的Ascll值进行操作)

fileToHash("./test.txt")
sort := topKSort(precess(), 2)
fmt.Println(sort)
package BigData

import (
   "bufio"
   "os"
   "sort"
   "strconv"
   "strings"
)


const filePrefix string = "tmp_"
const hashValue int = 1000
type ipAndCount struct {
   ip    string
   count int
}

// 读取那个大的文件
func fileToHash(file string) {
   open, err := os.Open(file)
   if err != nil {

   }
   defer open.Close()

   scanner := bufio.NewScanner(open)
   scanner.Split(bufio.ScanWords)

   for scanner.Scan() {
      cur := scanner.Text()
      //对当前IP进行hash
      writeIpToFile(cur)
   }
}

// 将字符串的ASCLL值相加,再取模
func stringToIntMod(input string, mod int) int {
   sum := 0
   for _, char := range input {
      sum += int(char)
   }
   return sum % mod
}

// 将IP追加写到文件中
func writeIpToFile(IP string) {

   mod := stringToIntMod(IP, hashValue)
   file, err := os.OpenFile(filePrefix+strconv.Itoa(mod), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
   if err != nil {

   }
   defer file.Close()
   file.Write([]byte(IP + "\n"))
}

// 处理小文件列表 返回每个文件的top1的IP
func precess() []ipAndCount {
   files := getSmallFiles("./", filePrefix)
   res := make([]ipAndCount, 0, hashValue)
   for _, entry := range files {
      ipAndCountNode := processOneFile(entry)
      res = append(res, ipAndCountNode)
   }
   return res
}



// 对map进行排序,输出topK个IP
func topKSort(res []ipAndCount, k int) []string {

   sort.Slice(res, func(i, j int) bool {
      return res[i].count > res[j].count
   })

   var ans []string
   if k > len(res) {
      k = len(res)
   }
   for i := 0; i < k; i++ {
      ans = append(ans, res[i].ip)
   }
   return ans
}

// 获取指定目录下的指定前缀的文件列表
func getSmallFiles(path, prefix string) []string {
   var file []string
   dir, err := os.ReadDir(path)
   if err != nil {
      return nil
   }
   for _, entry := range dir {
      if !entry.IsDir() && strings.HasPrefix(entry.Name(), prefix) {
         file = append(file, entry.Name())
      }
   }
   return file
}

// 处理每一个小文件,小文件可以全部载入内存,返回次数最多的一个IP
func processOneFile(file string) ipAndCount {
   oneFileIp2Count := make(map[string]int, 0)
   maxIp := ""
   open, err := os.Open(file)
   if err != nil {

   }
   defer open.Close()

   scanner := bufio.NewScanner(open)
   scanner.Split(bufio.ScanLines)

   for scanner.Scan() {
      cur := scanner.Text()
      //对当前IP进行hash
      oneFileIp2Count[cur]++
      if oneFileIp2Count[cur] > oneFileIp2Count[maxIp] {
         maxIp = cur
      }
   }
   return ipAndCount{ip: maxIp, count: oneFileIp2Count[maxIp]}
}