1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>(åå)golangå®ç°Parquetæä»¶è¯»å(æ¯ææ³å),åºäºgithub.com/apache/arrow-go</title> 6 <meta name="author" content="yanue"/> 7 <meta name="copyright" content="yanue.net"/> 8 <meta name="keywords" content=""/> 9 <meta name="description" content=""/> 10 <link rel="stylesheet" href="/assets/css/base.css"> 11
11<script src="/assets/js/iconfont.js"></script>
11 12 <style id="extra-fonts-css" media="all"> 13 14 </style> 15 16
17<script src="/assets/theme/marked.min.js"></script>
17 18 19 20<link rel="stylesheet" href="/assets/theme/prism.css">
21<script src="/assets/theme/prism.js"></script>
21 22 23 24<link rel="stylesheet" href="/assets/theme/markdown.css"> 25 26</head> 27<body> 28<div class="main"> 29 <div class="sidebar"> 30 <div class="section"> 31 <div class="navbar-header"> 32 <div class="profile-block text-center"> 33 <div class="avatar"> 34 <img class="img-avatar img-rotate" src="/assets/img/avatar.png"> 35 </div> 36 <div class="name">ð± Yanue's å客</div> 37 <div class="title">Go & Web Developer</div> 38 <div class="location"> 39 <i class="icon icon-map-marker"> 40 </i> 41 Shenzhen China 42 </div> 43 <div class="map-link"> 44 <a href="https://map.yanue.net/" class="link"> 45 <svg class="icon"> 46 <use xlink:href="#icon-a-04dingwei"></use> 47 </svg> 48 <span class="menu-title"> 49 å°å¾ä½å 50 </span> 51 </a> 52 </div> 53 </div> 54 </div> 55 <div id="main-navbar"> 56 <ul class="menu "> 57 <li class="menu-item active"> 58 <a href="/." class="link"> 59 <svg class="icon"> 60 <use xlink:href="#icon-a-29shouye1"></use> 61 </svg> 62 <span class="menu-title"> 63 é¦é¡µ 64 </span> 65 </a> 66 </li> 67 <li class="menu-item "> 68 <a href="/archives" class="link"> 69 <svg class="icon"> 70 <use xlink:href="#icon-a-22"></use> 71 </svg> 72 <span class="menu-title"> 73 彿¡£ 74 </span> 75 </a> 76 </li> 77 <li class="menu-item "> 78 <a href="/categories" class="link"> 79 <svg class="icon"> 80 <use xlink:href="#icon-a-23"></use> 81 </svg> 82 <span class="menu-title"> 83 åç±» 84 </span> 85 </a> 86 </li> 87 <li class="menu-item "> 88 <a href="/tags" class="link"> 89 <svg class="icon"> 90 <use xlink:href="#icon-a-07biaoqian"></use> 91 </svg> 92 <span class="menu-title"> 93 æ ç¾ 94 </span> 95 </a> 96 </li> 97 <li class="menu-item "> 98 <a href="/about" class="link"> 99 <svg class="icon"> 100 <use xlink:href="#icon-a-26wode3"></use> 101 </svg> 102 <span class="menu-title"> 103 å ³äº 104 </span> 105 </a> 106 </li> 107 </ul> 108 </div> 109 <div class="copyright"> ð± yanue.net © 2023 (åå¶å¯ç¾½)</div> 110 </div> 111</div> 112 113 114 <div class="content"> 115 <div class="wrap"> 116 <div class="box"> 117 118 <div class="article-header"> 119 <h2 class="title">(åå)golangå®ç°Parquetæä»¶è¯»å(æ¯ææ³å),åºäºgithub.com/apache/arrow-go</h2> 120 <div class="meta"> 121 <p class="meta-date"> 122 æ¥æ 123 <i class="icon icon-calendar-check"></i> 124 2025å¹´03æ17æ¥ 18:42 125 </p> 126 <p class="meta-cat"> 127 åç±» 128 <i class="icon icon-folder"></i> 129 <a href="/categories/Golang">Golang</a> 130 </p> 131 <p class="meta-tag"> 132 æ ç¾ 133 <i class="icon icon-tags"></i> 134 135 <span class="tag"> 136 <a href="/tags/Parquet">Parquet</a> 137 </span> 138 139 <span class="tag"> 140 <a href="/tags/%e6%b3%9b%e5%9e%8b">æ³å</a> 141 </span> 142 143 <span class="tag"> 144 <a href="/tags/arrow-go">arrow-go</a> 145 </span> 146 147 <span class="tag"> 148 <a href="/tags/%e5%8e%9f%e5%88%9b">åå</a> 149 </span> 150 151 </p> 152 <p class="meta-view"> 153 <i class="icon icon-view"></i> 154 <span>æµè§ 30098</span> 155 </p> 156 <p class="meta-word"> 157 åæ°ç»è®¡: 9956(å) 158 </p> 159 160 </div> 161 </div> 162 163 <div class="markdown-wrap">
164 <div id="markdown-content" class="markdown-body"> 165 <pre>## golangå®ç°Parquetæä»¶è¯»å,æ¯ææ³å 166 167> åºäºgithub.com/apache/arrow-go 168> go get github.com/apache/arrow/go/v18 169 1701. å®ä¹æ³åæ¥å£ - <span style="color: #f11818">ParquetGeneric </span> 171```go 172import ( 173 "context" 174 "fmt" 175 "github.com/apache/arrow/go/v18/arrow" 176 "github.com/apache/arrow/go/v18/arrow/array" 177 "github.com/apache/arrow/go/v18/arrow/memory" 178 "github.com/apache/arrow/go/v18/parquet" 179 "github.com/apache/arrow/go/v18/parquet/compress" 180 "github.com/apache/arrow/go/v18/parquet/file" 181 "github.com/apache/arrow/go/v18/parquet/pqarrow" 182 "os" 183 "os/signal" 184 "path" 185 "sync" 186 "syscall" 187) 188 189// ParquetGeneric å®ä¹æ³åæ¥å£ 190type ParquetGeneric interface { 191 Fields() []arrow.Field 192 Schema() *arrow.Schema 193 BuildRecord(builder *array.RecordBuilder) 194 ParseRecord(column arrow.Array, colIdx int, rowIdx int) 195} 196``` 197 198 1992. å®ç° - <span style="color: #ef0000">ParquetReader</span> 200 201 202```go 203type ParquetReader[T ParquetGeneric] struct { 204 filename string 205 reader pqarrow.RecordReader 206 readBatchSize int // æ¹é读åå¤§å° 207 newT func() T // å建 T å®ä¾ 208} 209 210// NewParquetReader å建 ParquetReader 211// filename: æä»¶å 212// readBatchSize: æ¹é读åå¤§å° 213// newT: å建 T å®ä¾ 214func NewParquetReader[T ParquetGeneric](filename string, readBatchSize int, newT func() T) *ParquetReader[T] { 215 return &ParquetReader[T]{ 216 filename: filename, 217 readBatchSize: readBatchSize, 218 newT: newT, 219 } 220} 221 222func (s *ParquetReader[T]) initReader() { 223 fileReader, err := file.OpenParquetFile(s.filename, false) 224 if err != nil { 225 panic(err) 226 } 227 // é»è®¤å ååé å¨ 228 mem := memory.NewCheckedAllocator(memory.DefaultAllocator) 229 // å¼å¯å¹¶è¡è¯»å 230 reader, err := pqarrow.NewFileReader(fileReader, pqarrow.ArrowReadProperties{BatchSize: int64(s.readBatchSize), Parallel: true}, mem) 231 if err != nil { 232 panic(err) 233 } 234 rr, err := reader.GetRecordReader(context.Background(), nil, nil) 235 if err != nil { 236 panic(err) 237 } 238 s.reader = rr 239} 240 241func (s *ParquetReader[T]) ReadParquet() []T { 242 // åå§å reader 243 s.initReader() 244 // 读å宿åéæ¾èµæº 245 defer s.reader.Release() 246 // è¯»åæ°æ® 247 items := make([]T, 0) 248 for { 249 // æ¹é读å 250 records := s.ReadBatchRecords() 251 if records == nil { 252 break 253 } 254 items = append(items, records...) 255 } 256 return items 257} 258 259func (s *ParquetReader[T]) ReadBatchRecords() []T { 260 if !s.reader.Next() { 261 return nil 262 } 263 rec := s.reader.Record() 264 defer rec.Release() 265 rec.Retain() 266 267 // è·åè¡æ°ååæ° 268 numRows, numCols := int(rec.NumRows()), int(rec.NumCols()) 269 270 // 循ç¯å建 T å®ä¾ 271 records := make([]T, numRows) 272 for rowIdx := 0; rowIdx < numRows; rowIdx++ { 273 records[rowIdx] = s.newT() // Create a new T instance 274 } 275 276 // 循ç¯è§£ææ°æ® 277 for colIdx := 0; colIdx < numCols; colIdx++ { 278 column := rec.Column(colIdx) 279 for rowIdx := 0; rowIdx < numRows; rowIdx++ { 280 records[rowIdx].ParseRecord(column, colIdx, rowIdx) 281 } 282 } 283 284 return records 285} 286``` 287 2883. å®ç° - <span style="color: #ef0000">ParquetWriter</span> 289 290```go 291 292// NewParquetWriter å建 ParquetWriter 293// filename: æä»¶å 294// writeBatchSize: æ¹éåå ¥å¤§å° 295// compression: å缩æ¹å¼: compress.Codecs.{Gzip,Snappy,Lz4} 296// newT: å建 T å®ä¾ 297func NewParquetWriter[T ParquetGeneric](filename string, writeBatchSize int, compression compress.Compression, newT func() T) *ParquetWriter[T] { 298 s := &ParquetWriter[T]{ 299 filename: filename, 300 writeBatchSize: writeBatchSize, 301 compression: compression, 302 schema: newT().Schema(), 303 ch: make(chan T, writeBatchSize*5), 304 } 305 s.Run() 306 return s 307} 308 309// ParquetGeneric å®ä¹æ³åæ¥å£ 310type ParquetGeneric interface { 311 Fields() []arrow.Field 312 Schema() *arrow.Schema 313 BuildRecord(builder *array.RecordBuilder) 314 ParseRecord(column arrow.Array, colIdx int, rowIdx int) 315} 316 317type ParquetWriter[T ParquetGeneric] struct {
318 filename string 319 compression compress.Compression // æ¨è: compress.Codecs.Snappy (æ´å¿«,å缩çéä¸) 320 schema *arrow.Schema 321 writer *pqarrow.FileWriter 322 writeBatchSize int // æ¹éåå ¥å¤§å° 323 ch chan T 324 wg sync.WaitGroup 325} 326 327func (s *ParquetWriter[T]) Run() { 328 s.ch = make(chan T, s.writeBatchSize*5) 329 s.initWriter() 330 s.wg.Add(1) 331 go s.writeParquet() 332} 333 334func (s *ParquetWriter[T]) initWriter() { 335 _ = os.MkdirAll(path.Dir(s.filename), 0777) 336 // å建 Parquet æä»¶ 337 fs, err := os.Create(s.filename) 338 if err != nil { 339 fmt.Printf("å建æä»¶å¤±è´¥: %v\n", err) 340 return 341 } 342 // å缩é ç½® 343 props := parquet.NewWriterProperties(parquet.WithCompression(s.compression)) // æ¨è: compress.Codecs.Snappy 344 // å建 Parquet writer 345 writer, err := pqarrow.NewFileWriter(s.schema, fs, props, pqarrow.DefaultWriterProps()) 346 if err != nil { 347 fmt.Printf("å建 Parquet writer 失败: %v\n", err) 348 return 349 } 350 s.writer = writer 351} 352 353func (s *ParquetWriter[T]) AddRecord(item T) { 354 if s.ch == nil { 355 return 356 } 357 s.ch <- item 358} 359 360func (s *ParquetWriter[T]) writeParquet() { 361 items := make([]T, 0) 362 defer s.wg.Done() // ç¡®ä¿åç¨éåºæ¶éæ¾èµæº 363 defer func() { 364 // å¤çå©ä½æ°æ® 365 if len(items) > 0 { 366 s.writeBatchRecords(items) 367 } 368 // å ³é writer,å¾éè¦(åå ¥footer),å¦åæä»¶æ æ³è¯»å 369 if s.writer != nil { 370 if err := s.writer.Close(); err != nil { 371 fmt.Printf("å ³é writer 失败: %v,%v\n", s.filename, err) 372 } 373 } 374 }() 375 // æ¥æ¶æ°æ®,çclose(s.ch)å,ä¼ç»§ç»æ¥å宿åæéåº 376 for item := range s.ch { 377 items = append(items, item) 378 if len(items) >= s.writeBatchSize { 379 s.writeBatchRecords(items) 380 items = make([]T, 0) 381 } 382 } 383} 384 385func (s *ParquetWriter[T]) writeBatchRecords(items []T) { 386 builder := array.NewRecordBuilder(memory.DefaultAllocator, s.schema) 387 for _, item := range items { 388 item.BuildRecord(builder) 389 } 390 rec := builder.NewRecord() 391 defer rec.Release() // ç¡®ä¿ rec æ»æ¯è¢«éæ¾ 392 if rec.NumRows() == 0 { 393 fmt.Println("æ²¡ææ°æ®å¯åå ¥") 394 return 395 } 396 if err := s.writer.Write(rec); err != nil { 397 fmt.Printf("æ¹éåå ¥ Record 失败: %v\n", err) 398 return 399 } 400} 401 402func (s *ParquetWriter[T]) Close() { 403 // å ³é channel, éç¥åå ¥åç¨éåº 404 if s.ch != nil { 405 close(s.ch) 406 s.ch = nil 407 } 408 // çå¾ writeParquet æ§è¡ç»æ 409 s.wg.Wait() 410} 411``` 412 413 4144. ç¤ºä¾ - <span style="color: #ef0000">Demo</span> 415 416```go 417 418func main() { 419 writeParquet() 420 readParquet() 421} 422 423type ApiLog struct { 424 RequestIp string `json:"request_ip" bson:"request_ip" orc:"request_ip"` 425 Date int `json:"date" bson:"date" orc:"date"` 426 Hour int `json:"hour" bson:"hour" orc:"hour"` 427 Start int64 `json:"start" bson:"start" orc:"start"` 428 End int64 `json:"end" bson:"end" orc:"end"` 429 Spent int64 `json:"spent" bson:"spent" orc:"spent"` 430 Method string `json:"method" bson:"method" orc:"method"` 431 Url string `json:"url" bson:"url" orc:"url"` 432 Req string `json:"req" bson:"req" orc:"req"` 433 Body string `json:"body" bson:"body" orc:"body"` 434 Error string `json:"error" bson:"error" orc:"error"` 435} 436 437var ApiLogFields = []arrow.Field{ 438 {Name: "request_ip", Type: arrow.BinaryTypes.String}, 439 {Name: "date", Type: arrow.PrimitiveTypes.Int32}, 440 {Name: "hour", Type: arrow.PrimitiveTypes.Int32}, 441 {Name: "start", Type: arrow.PrimitiveTypes.Int64}, 442 {Name: "end", Type: arrow.PrimitiveTypes.Int64}, 443 {Name: "spent", Type: arrow.PrimitiveTypes.Int64}, 444 {Name: "method", Type: arrow.BinaryTypes.String}, 445 {Name: "url", Type: arrow.BinaryTypes.String}, 446 {Name: "req", Type: arrow.BinaryTypes.String}, 447 {Name: "body", Type: arrow.BinaryTypes.String}, 448 {Name: "error", Type: arrow.BinaryTypes.String}, 449} 450 451func (s *ApiLog) Schema() *arrow.Schema { 452 return arrow.NewSchema(ApiLogFields, nil) 453} 454 455func (s *ApiLog) Fields() []arrow.Field { 456 return ApiLogFields 457} 458 459func (s *ApiLog) BuildRecord(builder *array.RecordBuilder) { 460 for i := 0; i < len(ApiLogFields); i++ { 461 switch ApiLogFields[i].Name { 462 case "request_ip": 463 builder.Field(i).(*array.StringBuilder).Append(s.RequestIp) 464 case "date": 465 builder.Field(i).(*array.Int32Builder).Append(int32(s.Date)) 466 case "hour": 467 builder.Field(i).(*array.Int32Builder).Append(int32(s.Hour)) 468 case "start": 469 builder.Field(i).(*array.Int64Builder).Append(s.Start) 470 case "end": 471 builder.Field(i).(*array.Int64Builder).Append(s.End) 472 case "spent": 473 builder.Field(i).(*array.Int64Builder).Append(s.Spent) 474 case "method": 475 builder.Field(i).(*array.StringBuilder).Append(s.Method) 476 case "url": 477 builder.Field(i).(*array.StringBuilder).Append(s.Url) 478 case "req": 479 builder.Field(i).(*array.StringBuilder).Append(s.Req) 480 case "body": 481 builder.Field(i).(*array.StringBuilder).Append(s.Body) 482 case "error": 483 builder.Field(i).(*array.StringBuilder).Append(s.Error) 484 } 485 } 486} 487 488func (s *ApiLog) ParseRecord(column arrow.Array, colIdx int, rowIdx int) {
489 fieldName := ApiLogFields[colIdx].Name 490 switch fieldName { 491 case "request_ip": 492 s.RequestIp = column.(*array.String).Value(rowIdx) 493 case "date": 494 s.Date = int(column.(*array.Int32).Value(rowIdx)) 495 case "hour": 496 s.Hour = int(column.(*array.Int32).Value(rowIdx)) 497 case "start": 498 s.Start = column.(*array.Int64).Value(rowIdx) 499 case "end": 500 s.End = column.(*array.Int64).Value(rowIdx) 501 case "spent": 502 s.Spent = column.(*array.Int64).Value(rowIdx) 503 case "method": 504 s.Method = column.(*array.String).Value(rowIdx) 505 case "url": 506 s.Url = column.(*array.String).Value(rowIdx) 507 case "req": 508 s.Req = column.(*array.String).Value(rowIdx) 509 case "body": 510 s.Body = column.(*array.String).Value(rowIdx) 511 case "error": 512 s.Error = column.(*array.String).Value(rowIdx) 513 } 514} 515 516func writeParquet() { 517 w := NewParquetWriter[*ApiLog]("test.parquet", 10000, compress.Codecs.Snappy, func() *ApiLog { 518 return new(ApiLog) 519 }) 520 // çæ 10 䏿¡æ¥å¿æ°æ®ï¼æ¨¡æå¤§æ°æ®éï¼ 521 total := 1000011 522 ts := time.Now() 523 for i := 0; i < total; i++ { 524 // å¡«å æ°æ®å° builder 525 apiLog := ApiLog{ 526 RequestIp: "", 527 Date: 20230101, 528 Hour: 12, 529 Start: 1672531200000, 530 End: 1672531201000, 531 Spent: 1000, 532 Method: "GET", 533 Url: "/api/v1/data", 534 Req: fmt.Sprintf("req%d", i), 535 Body: fmt.Sprintf("body%d", i), 536 Error: "", 537 } 538 w.AddRecord(&apiLog) 539 } 540 // è®°å¾å ³é writer 541 w.Close() 542 fmt.Println("åå ¥æ°æ®ï¼", total, "åå ¥æ°æ®èæ¶ï¼", time.Since(ts)) 543} 544 545func readParquet() { 546 ts := time.Now() 547 records := NewParquetReader[*ApiLog]("test.parquet", 10000, func() *ApiLog { 548 return new(ApiLog) 549 }).ReadParquet() 550 fmt.Println("读åå°æ°æ®ï¼", len(records), "è¯»åæ°æ®èæ¶ï¼", time.Since(ts)) 551 return 552} 553```</pre> 554 </div> 555 </div> 556 <div class="clear"></div> 557 <div class="footer"> 558 <div class="prev"> 559 560 <a href="/post-180.html"> 561 < ä¸ä¸ç¯ PostgreSQL15æµå¼ä¸»ä»é ç½®å主ä»åæ¢ 562 </a> 563 564 </div> 565 <div class="next"> 566 567 <span> 568 ä¸ä¸ç¯ 没æäº > 569 </span> 570 571 </div> 572 </div> 573 <div class="comment"> 574 575<link href="https://artalk.yanue.net/dist/Artalk.css" rel="stylesheet" /> 576 577
578<script src="https://artalk.yanue.net/dist/Artalk.js"></script>
578 579 580 581<div id="Comments"></div>
582<script> 583Artalk.init({ 584 el: '#Comments', 585 pageKey: 'detail-181', 586 server: 'https://artalk.yanue.net/', 587 site: 'yanue.net', 588}) 589</script>
589 590 </div> 591 </div> 592 </div> 593 </div> 594 <div class="aside"> 595 <div class="wrap"> 596 <div style="height: 20px;"></div> 597 598 <div class="widget toc-wrap"> 599 <div class="widget-title toc-title">æç« ç®å½</div> 600 <div id="markdown-toc" class="toc-content"></div> 601 </div> 602 603 <div class="widget"> 604 <h3 class="widget-title">å ¬å</h3> 605 <div class="widget-body"> 606 <p>æ°çå客ä¸çº¿äº~</p> 607 <p> 608 åºäº: golang, gin, gorm, sqlite, 609 <a href="https://nhn.github.io/tui.editor/latest/" target="_blank">tui.editor</a> 610 </p> 611 <p>æºç : <a href="https://github.com/yanue/yanue.net" target="_blank">https://github.com/yanue/yanue.net</a></p> 612 <p>åèui: <a href="https://github.com/cofess/hexo-theme-pure" target="_blank">https://github.com/cofess/hexo-theme-pure</a> 613 </p> 614 </div> 615 </div> 616 617 <div class="widget"> 618 <h3 class="widget-title">åç±»</h3> 619 <div class="widget-body"> 620 <ul class="list-square"> 621 622 <li> 623 <a href="/categories/Web"> 624 <span class="name">Web</span> 625 <label class="num">(15)</label> 626 </a> 627 </li> 628 629 <li> 630 <a href="/categories/SQL"> 631 <span class="name">SQL</span> 632 <label class="num">(10)</label> 633 </a> 634 </li> 635 636 <li> 637 <a href="/categories/Python"> 638 <span class="name">Python</span> 639 <label class="num">(6)</label> 640 </a> 641 </li> 642 643 <li> 644 <a href="/categories/PHP"> 645 <span class="name">PHP</span> 646 <label class="num">(20)</label> 647 </a> 648 </li> 649 650 <li> 651 <a href="/categories/Linux"> 652 <span class="name">Linux</span> 653 <label class="num">(9)</label> 654 </a> 655 </li> 656 657 <li> 658 <a href="/categories/Golang"> 659 <span class="name">Golang</span> 660 <label class="num">(4)</label> 661 </a> 662 </li> 663 664 <li> 665 <a href="/categories/"> 666 <span class="name"></span> 667 <label class="num">(4)</label> 668 </a> 669 </li> 670 671 </ul> 672 </div> 673 </div> 674 675 <div class="widget"> 676 <h3 class="widget-title">æ ç¾</h3> 677 <div class="widget-body"> 678 <ul class="list-square"> 679 680 <li> 681 <a href="/tags/%e7%bb%8f%e7%ba%ac%e5%ba%a6"> 682 <span class="name">ç»çº¬åº¦</span> 683 <label class="num">(1)</label> 684 </a> 685 </li> 686 687 <li> 688 <a href="/tags/%e7%ba%a0%e5%81%8f"> 689 <span class="name">çº å</span> 690 <label class="num">(2)</label> 691 </a> 692 </li> 693 694 <li> 695 <a href="/tags/%e6%b5%ae%e7%82%b9%e6%95%b0"> 696 <span class="name">æµ®ç¹æ°</span> 697 <label class="num">(1)</label> 698 </a> 699 </li> 700 701 <li> 702 <a href="/tags/%e6%b3%9b%e5%9e%8b"> 703 <span class="name">æ³å</span> 704 <label class="num">(1)</label> 705 </a> 706 </li> 707 708 <li> 709 <a href="/tags/%e6%ad%a3%e5%88%99"> 710 <span class="name">æ£å</span> 711 <label class="num">(2)</label> 712 </a> 713 </li> 714 715 <li> 716 <a href="/tags/%e5%8e%9f%e5%88%9b"> 717 <span class="name">åå</span> 718 <label class="num">(2)</label> 719 </a> 720 </li> 721 722 <li> 723 <a href="/tags/svn"> 724 <span class="name">svn</span> 725 <label class="num">(1)</label> 726 </a> 727 </li> 728 729 <li> 730 <a href="/tags/selenium">
731 <span class="name">selenium</span> 732 <label class="num">(1)</label> 733 </a> 734 </li> 735 736 <li> 737 <a href="/tags/pycharm"> 738 <span class="name">pycharm</span> 739 <label class="num">(1)</label> 740 </a> 741 </li> 742 743 <li> 744 <a href="/tags/php"> 745 <span class="name">php</span> 746 <label class="num">(3)</label> 747 </a> 748 </li> 749 750 <li> 751 <a href="/tags/pandas"> 752 <span class="name">pandas</span> 753 <label class="num">(1)</label> 754 </a> 755 </li> 756 757 <li> 758 <a href="/tags/nginx"> 759 <span class="name">nginx</span> 760 <label class="num">(1)</label> 761 </a> 762 </li> 763 764 <li> 765 <a href="/tags/mysql"> 766 <span class="name">mysql</span> 767 <label class="num">(5)</label> 768 </a> 769 </li> 770 771 <li> 772 <a href="/tags/map"> 773 <span class="name">map</span> 774 <label class="num">(1)</label> 775 </a> 776 </li> 777 778 <li> 779 <a href="/tags/linux"> 780 <span class="name">linux</span> 781 <label class="num">(3)</label> 782 </a> 783 </li> 784 785 <li> 786 <a href="/tags/js"> 787 <span class="name">js</span> 788 <label class="num">(3)</label> 789 </a> 790 </li> 791 792 <li> 793 <a href="/tags/jquery"> 794 <span class="name">jquery</span> 795 <label class="num">(2)</label> 796 </a> 797 </li> 798 799 <li> 800 <a href="/tags/golang"> 801 <span class="name">golang</span> 802 <label class="num">(1)</label> 803 </a> 804 </li> 805 806 <li> 807 <a href="/tags/ema"> 808 <span class="name">ema</span> 809 <label class="num">(1)</label> 810 </a> 811 </li> 812 813 <li> 814 <a href="/tags/crontab"> 815 <span class="name">crontab</span> 816 <label class="num">(1)</label> 817 </a> 818 </li> 819 820 <li> 821 <a href="/tags/arrow-go"> 822 <span class="name">arrow-go</span> 823 <label class="num">(1)</label> 824 </a> 825 </li> 826 827 <li> 828 <a href="/tags/Parquet"> 829 <span class="name">Parquet</span> 830 <label class="num">(1)</label> 831 </a> 832 </li> 833 834 <li> 835 <a href="/tags/JetBrains"> 836 <span class="name">JetBrains</span> 837 <label class="num">(1)</label> 838 </a> 839 </li> 840 841 <li> 842 <a href="/tags/Goland"> 843 <span class="name">Goland</span> 844 <label class="num">(1)</label> 845 </a> 846 </li> 847 848 <li> 849 <a href="/tags/GRANT"> 850 <span class="name">GRANT</span> 851 <label class="num">(1)</label> 852 </a> 853 </li> 854 855 </ul> 856 </div> 857 </div> 858</div> 859 </div> 860
861<script> 862 window.onload = function () { 863 864 marked.setOptions({ 865 gfm: true, 866 breaks: true, 867 pedantic: false, 868 smartypants: true, 869 }); 870 871 872 let toc = []; 873 874 875 const renderer = new marked.Renderer(); 876 renderer.heading = (item) => { 877 const id = `heading-${toc.length + 1}`; 878 toc.push(item); 879 return `<h${item.depth} id="${id}">${item.text}</h${item.depth}>`; 880 }; 881 882 883 function generateTocHtml(toc, baseClass = 'toc-level') { 884 if (toc.length === 0) return ''; 885 const stack = [{ depth: toc[0].depth, ul: [] }]; 886 toc.forEach((title, index) => { 887 const d = title.depth; 888 889 while (stack.length > 0 && stack[stack.length - 1].depth > d) { 890 const innerUl = stack.pop().ul.join(''); 891 if (stack.length > 0) { 892 const parentDepth = stack[stack.length - 1].depth; 893 stack[stack.length - 1].ul[stack[stack.length - 1].ul.length - 1] +=`<ul class="toc-list ${baseClass}-${parentDepth + 1}">${innerUl}</ul>`; 894 } 895 } 896 897 if (stack.length === 0 || stack[stack.length - 1].depth < d) { 898 stack.push({ depth: d, ul: [] }); 899 } 900 901 stack[stack.length - 1].ul.push(`<li><a href="#heading-${index + 1}">${title.text}</a></li>`); 902 }); 903 904 905 while (stack.length > 1) { 906 const innerUl = stack.pop().ul.join(''); 907 const parentDepth = stack[stack.length - 1].depth; 908 stack[stack.length - 1].ul[stack[stack.length - 1].ul.length - 1] += `<ul class="toc-list ${baseClass}-${parentDepth + 1}">${innerUl}</ul>`; 909 } 910 911 const topDepth = toc[0].depth; 912 return `<ul class="toc-list ${baseClass}-${topDepth}">${stack[0].ul.join('')}</ul>`; 913 } 914 915 const markdownContent = document.getElementById("markdown-content").innerText; 916 const contentHtml = marked.parse(markdownContent, { renderer }); 917 918 const tocHtml = generateTocHtml(toc); 919 document.getElementById("markdown-content").innerHTML = contentHtml; 920 document.getElementById("markdown-toc").innerHTML = tocHtml 921 922 document.querySelectorAll('pre code').forEach((block) => { 923 block.classList.add('line-numbers'); 924 Prism.highlightElement(block); 925 }); 926 } 927</script>
927 928 929</div> 930 931 932 933
934<script> 935 window.onerror = function (message, source, lineno, colno, error) { 936 console.log('æè·å°å¼å¸¸ï¼', {message, source, lineno, colno, error}); 937 } 938</script>
938 939</body> 940</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.