Go fitsio 怎样向FITS表追加一条结构化记录
来源:17golang原创
时间:2026-10-31 01:10:56 464浏览 收藏
我第一次把观测结果写成 FITS 表时,最容易混淆的是“写入一行”和“把表写进文件”其实是两件事。用 Go fitsio 时,可以先准备一个带 fits 标签的结构体,把它作为表模式,再调用 Table.Write 填入一条记录,最后调用 File.Write 提交这个表 HDU。
官方地址:https://pkg.go.dev/github.com/astrogo/fitsio
本文里的“追加”限定为向新建 FITS 文件中的表追加一条记录。fitsio 的公开文档把 Open 定义为只读入口,所以不要把下面的写法理解成对已有 FITS 文件的原地更新方案。
先判断你手里的是新文件还是已有文件
如果目标是生成一个新的观测文件,处理边界很清楚:os.File 负责文件句柄,fitsio.Create 创建写入模式的 FITS 对象,NewTableFrom 根据结构体建立表,Table.Write 写当前表行,File.Write 把表作为 HDU 写入文件。
如果目标是打开已有文件再改写,先停下来确认库版本和实现是否提供可用的读写路径。官方包文档中 Open 是只读打开,本文不把“追加”包装成原地修改。
用结构体先确定 FITS 表的列布局

我更愿意先写一个专门表示一行数据的结构体,而不是直接手工拼列。这样列名和 Go 字段放在一起,后面排查“写入列顺序不对”时,定位范围会小很多。结构体标签使用 FITS 列名,字段类型则决定写入值的 Go 类型。

type Observation struct {
// fits 标签固定列名,字段类型对应要写入的值类型。
ID int64 `fits:"ID"`
// Flux 保存测量值,使用浮点类型避免把小数截断。
Flux float64 `fits:"FLUX"`
// Band 保存观测波段,列名与结构体字段可以保持同义但不必同名。
Band string `fits:"BAND"`
}
这里的结构体值既可以作为 NewTableFrom 的模式样本,也可以作为后面 Table.Write 的一行数据。模式样本不需要填业务值;真正写入时再创建一个带值的 Observation。
用 NewTableFrom 和 Table.Write 写入一条记录
写表时我会把“表结构”和“当前行数据”分开命名。这样即使后续要连续写多行,也只需要重复构造数据值并调用 Write,不必重新创建表。
package main
import (
"log"
"os"
fitsio "github.com/astrogo/fitsio"
)
type Observation struct {
// 标签确定 FITS 列名,类型决定写入值的形态。
ID int64 `fits:"ID"`
Flux float64 `fits:"FLUX"`
Band string `fits:"BAND"`
}
func writeOne(path string) error {
// 外层文件由调用方创建和关闭,避免把底层句柄交给 fitsio 代管。
out, err := os.Create(path)
if err != nil {
return err
}
defer out.Close()
fits, err := fitsio.Create(out)
if err != nil {
return err
}
defer fits.Close()
// 零值结构体只用来描述表的列布局,不代表要写入的那一行。
table, err := fitsio.NewTableFrom("OBSERVATIONS", Observation{}, fitsio.BINARY_TBL)
if err != nil {
return err
}
defer table.Close()
row := Observation{ID: 1001, Flux: 12.75, Band: "r"}
// Table.Write 把这一组字段写入表的当前行。
if err := table.Write(row); err != nil {
return err
}
// File.Write 负责把已经填充的表 HDU 写入 FITS 文件。
if err := fits.Write(table); err != nil {
return err
}
return nil
}
func main() {
if err := writeOne("observations.fits"); err != nil {
log.Fatal(err)
}
}
这个示例的关键不是把所有 API 都塞进一个函数,而是保留三条可检查的边界:NewTableFrom 定义表,Table.Write 写记录,File.Write 写 HDU。需要多条记录时,创建一次 table,按顺序多次调用 table.Write,最后再调用一次 fits.Write。
写入失败时,先保留可回退的输出
生产任务里我不会直接把正式文件名交给写入流程。因为结构体类型不匹配、表定义失败或底层写入失败时,目标路径可能只得到半成品。更稳妥的做法是先写临时文件,所有写入动作返回成功后再改名。
func publishTable(finalPath string, row Observation) error {
tempPath := finalPath + ".tmp"
// 临时路径失败时只影响待提交文件,不覆盖上一次正式结果。
out, err := os.Create(tempPath)
if err != nil {
return err
}
fits, err := fitsio.Create(out)
if err != nil {
out.Close()
os.Remove(tempPath)
return err
}
table, err := fitsio.NewTableFrom("OBSERVATIONS", Observation{}, fitsio.BINARY_TBL)
if err != nil {
fits.Close()
out.Close()
os.Remove(tempPath)
return err
}
// 先写行,再提交 HDU;任何一步失败都不替换正式文件。
err = table.Write(row)
if err == nil {
err = fits.Write(table)
}
table.Close()
closeErr := fits.Close()
fileErr := out.Close()
if err == nil {
err = closeErr
}
if err == nil {
err = fileErr
}
if err != nil {
os.Remove(tempPath)
return err
}
// 只有临时文件完整关闭后才替换正式文件。
return os.Rename(tempPath, finalPath)
}
回退动作不是“重新调用一次 Write”。失败后应关闭已打开的资源、删除临时文件,并保留原正式文件;确认错误原因已修复后,再以新的临时文件重新生成。
用一张责任表确认是否真的写成功
| 对象 | 负责什么 | 常见误区 |
|---|---|---|
Observation{} | 提供列结构样本 | 把零值模式误认为业务记录 |
Table.Write(row) | 把一行字段写入表 | 以为它已经把整个 HDU 提交到文件 |
File.Write(table) | 写入表 HDU | 漏掉这一步就只是在内存中准备表对象 |
Table.Close / File.Close | 释放 fitsio 对象资源 | 以为 File.Close 会自动关闭底层 os.File |
官方文档特别说明,File.Close 不会关闭底层的 io.Reader 或 io.Writer。所以示例里仍然由 defer out.Close() 或显式关闭 os.File。遇到“文件句柄还占用”或输出无法重命名时,优先检查这条边界。
这套写法适合什么场景
如果你的任务是从 Go 服务或批处理程序生成一个新的 FITS 表,这种结构体驱动的写法足够直接:列布局集中、写行入口明确、失败时可以用临时文件回退。若你的任务是修改已有 FITS 文件、追加到已有 HDU,或者需要 FITS 原地更新,则应先确认当前库版本是否提供对应的读写能力,再决定是否换用专门的 FITS 工具链。
我自己的判断是:当记录结构稳定、输出文件由当前任务负责生成时,NewTableFrom + Table.Write + File.Write 很顺手;当文件生命周期复杂、需要原地增量更新时,不要只因为 API 名字里有 Write 就假定它支持数据库式追加。
相关问题
Table.Write 可以连续调用吗?
可以把同一个表对象作为目标,按记录逐次调用 Table.Write。表的创建和 HDU 提交不要在每一行之间重复进行。
为什么写了 Table.Write 仍然没有输出文件内容?
Table.Write 面向表的当前行;还需要把表对象交给 File.Write,并正常关闭 FITS 对象和底层文件。
fitsio.Create 会关闭 os.File 吗?
不要依赖这种假设。底层 os.File 仍应由创建它的代码负责关闭,临时文件提交流程也应在关闭成功后再改名。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
380 收藏
-
134 收藏
-
101 收藏
-
121 收藏
-
445 收藏
-
227 收藏
-
312 收藏
-
337 收藏
-
377 收藏
-
415 收藏
-
495 收藏
-
246 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习