PageSourceSearch

https://yanue.net/post-181.html

html yanue.net collected 2026-10-05 22:11:26 UTC 27,632 bytes, 940 lines download raw bytes

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&gt; 基于github.com/apache/arrow-go 
168&gt; go get github.com/apache/arrow/go/v18
169
1701.  定义泛型接口 - &lt;span style=&#34;color: #f11818&#34;&gt;ParquetGeneric &lt;/span&gt;
171```go
172import (
173	&#34;context&#34;
174	&#34;fmt&#34;
175	&#34;github.com/apache/arrow/go/v18/arrow&#34;
176	&#34;github.com/apache/arrow/go/v18/arrow/array&#34;
177	&#34;github.com/apache/arrow/go/v18/arrow/memory&#34;
178	&#34;github.com/apache/arrow/go/v18/parquet&#34;
179	&#34;github.com/apache/arrow/go/v18/parquet/compress&#34;
180	&#34;github.com/apache/arrow/go/v18/parquet/file&#34;
181	&#34;github.com/apache/arrow/go/v18/parquet/pqarrow&#34;
182	&#34;os&#34;
183	&#34;os/signal&#34;
184	&#34;path&#34;
185	&#34;sync&#34;
186	&#34;syscall&#34;
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. 实现 - &lt;span style=&#34;color: #ef0000&#34;&gt;ParquetReader&lt;/span&gt;
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 &amp;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 &lt; numRows; rowIdx&#43;&#43; {
273		records[rowIdx] = s.newT() // Create a new T instance
274	}
275
276	// 循环解析数据
277	for colIdx := 0; colIdx &lt; numCols; colIdx&#43;&#43; {
278		column := rec.Column(colIdx)
279		for rowIdx := 0; rowIdx &lt; numRows; rowIdx&#43;&#43; {
280			records[rowIdx].ParseRecord(column, colIdx, rowIdx)
281		}
282	}
283
284	return records
285}
286```
287
2883. 实现 - &lt;span style=&#34;color: #ef0000&#34;&gt;ParquetWriter&lt;/span&gt;
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 := &amp;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(&#34;创建文件失败: %v\n&#34;, 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(&#34;创建 Parquet writer 失败: %v\n&#34;, 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 &lt;- 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) &gt; 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(&#34;关闭 writer 失败: %v,%v\n&#34;, 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) &gt;= 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(&#34;没有数据可写入&#34;)
394		return
395	}
396	if err := s.writer.Write(rec); err != nil {
397		fmt.Printf(&#34;批量写入 Record 失败: %v\n&#34;, 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. 示例 - &lt;span style=&#34;color: #ef0000&#34;&gt;Demo&lt;/span&gt;
415
416```go
417
418func main() {
419	writeParquet()
420	readParquet()
421}
422
423type ApiLog struct {
424	RequestIp string `json:&#34;request_ip&#34; bson:&#34;request_ip&#34; orc:&#34;request_ip&#34;`
425	Date      int    `json:&#34;date&#34; bson:&#34;date&#34; orc:&#34;date&#34;`
426	Hour      int    `json:&#34;hour&#34; bson:&#34;hour&#34; orc:&#34;hour&#34;`
427	Start     int64  `json:&#34;start&#34; bson:&#34;start&#34; orc:&#34;start&#34;`
428	End       int64  `json:&#34;end&#34; bson:&#34;end&#34; orc:&#34;end&#34;`
429	Spent     int64  `json:&#34;spent&#34; bson:&#34;spent&#34; orc:&#34;spent&#34;`
430	Method    string `json:&#34;method&#34; bson:&#34;method&#34; orc:&#34;method&#34;`
431	Url       string `json:&#34;url&#34; bson:&#34;url&#34; orc:&#34;url&#34;`
432	Req       string `json:&#34;req&#34; bson:&#34;req&#34; orc:&#34;req&#34;`
433	Body      string `json:&#34;body&#34; bson:&#34;body&#34; orc:&#34;body&#34;`
434	Error     string `json:&#34;error&#34; bson:&#34;error&#34; orc:&#34;error&#34;`
435}
436
437var ApiLogFields = []arrow.Field{
438	{Name: &#34;request_ip&#34;, Type: arrow.BinaryTypes.String},
439	{Name: &#34;date&#34;, Type: arrow.PrimitiveTypes.Int32},
440	{Name: &#34;hour&#34;, Type: arrow.PrimitiveTypes.Int32},
441	{Name: &#34;start&#34;, Type: arrow.PrimitiveTypes.Int64},
442	{Name: &#34;end&#34;, Type: arrow.PrimitiveTypes.Int64},
443	{Name: &#34;spent&#34;, Type: arrow.PrimitiveTypes.Int64},
444	{Name: &#34;method&#34;, Type: arrow.BinaryTypes.String},
445	{Name: &#34;url&#34;, Type: arrow.BinaryTypes.String},
446	{Name: &#34;req&#34;, Type: arrow.BinaryTypes.String},
447	{Name: &#34;body&#34;, Type: arrow.BinaryTypes.String},
448	{Name: &#34;error&#34;, 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 &lt; len(ApiLogFields); i&#43;&#43; {
461		switch ApiLogFields[i].Name {
462		case &#34;request_ip&#34;:
463			builder.Field(i).(*array.StringBuilder).Append(s.RequestIp)
464		case &#34;date&#34;:
465			builder.Field(i).(*array.Int32Builder).Append(int32(s.Date))
466		case &#34;hour&#34;:
467			builder.Field(i).(*array.Int32Builder).Append(int32(s.Hour))
468		case &#34;start&#34;:
469			builder.Field(i).(*array.Int64Builder).Append(s.Start)
470		case &#34;end&#34;:
471			builder.Field(i).(*array.Int64Builder).Append(s.End)
472		case &#34;spent&#34;:
473			builder.Field(i).(*array.Int64Builder).Append(s.Spent)
474		case &#34;method&#34;:
475			builder.Field(i).(*array.StringBuilder).Append(s.Method)
476		case &#34;url&#34;:
477			builder.Field(i).(*array.StringBuilder).Append(s.Url)
478		case &#34;req&#34;:
479			builder.Field(i).(*array.StringBuilder).Append(s.Req)
480		case &#34;body&#34;:
481			builder.Field(i).(*array.StringBuilder).Append(s.Body)
482		case &#34;error&#34;:
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 &#34;request_ip&#34;:
492		s.RequestIp = column.(*array.String).Value(rowIdx)
493	case &#34;date&#34;:
494		s.Date = int(column.(*array.Int32).Value(rowIdx))
495	case &#34;hour&#34;:
496		s.Hour = int(column.(*array.Int32).Value(rowIdx))
497	case &#34;start&#34;:
498		s.Start = column.(*array.Int64).Value(rowIdx)
499	case &#34;end&#34;:
500		s.End = column.(*array.Int64).Value(rowIdx)
501	case &#34;spent&#34;:
502		s.Spent = column.(*array.Int64).Value(rowIdx)
503	case &#34;method&#34;:
504		s.Method = column.(*array.String).Value(rowIdx)
505	case &#34;url&#34;:
506		s.Url = column.(*array.String).Value(rowIdx)
507	case &#34;req&#34;:
508		s.Req = column.(*array.String).Value(rowIdx)
509	case &#34;body&#34;:
510		s.Body = column.(*array.String).Value(rowIdx)
511	case &#34;error&#34;:
512		s.Error = column.(*array.String).Value(rowIdx)
513	}
514}
515
516func writeParquet() {
517	w := NewParquetWriter[*ApiLog](&#34;test.parquet&#34;, 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 &lt; total; i&#43;&#43; {
524		// 填充数据到 builder
525		apiLog := ApiLog{
526			RequestIp: &#34;&#34;,
527			Date:      20230101,
528			Hour:      12,
529			Start:     1672531200000,
530			End:       1672531201000,
531			Spent:     1000,
532			Method:    &#34;GET&#34;,
533			Url:       &#34;/api/v1/data&#34;,
534			Req:       fmt.Sprintf(&#34;req%d&#34;, i),
535			Body:      fmt.Sprintf(&#34;body%d&#34;, i),
536			Error:     &#34;&#34;,
537		}
538		w.AddRecord(&amp;apiLog)
539	}
540	// 记得关闭 writer
541	w.Close()
542	fmt.Println(&#34;写入数据:&#34;, total, &#34;写入数据耗时:&#34;, time.Since(ts))
543}
544
545func readParquet() {
546	ts := time.Now()
547	records := NewParquetReader[*ApiLog](&#34;test.parquet&#34;, 10000, func() *ApiLog {
548		return new(ApiLog)
549	}).ReadParquet()
550	fmt.Println(&#34;读取到数据:&#34;, len(records), &#34;读取数据耗时:&#34;, 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                  &lt; 上一篇 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.