跨页表格 (longtable)

放进 table 环境的表格,无论有多少行都 不会跨页断开——而且溢出时不会报错。在 TeX Live 2024 上把一张 119 行的表格包进浮动体,输出是 一页而不是三页,其余的行悄悄滑出了纸面下沿。打破这堵墙的是 LaTeX 的 longtable 宏包。但 longtable 真正的工作并不是分页,而是 让每一页的列宽保持一致——为此它把实测值写进 .aux 文件,并用 Table widths have changed. Rerun LaTeX. 这条警告要求你再编译一次。本页跟踪这第二次编译到底买到了什么,并与跳过它的 supertabular 做实测对比。

为什么 table 环境里的表格不会跨页

因为 tablefigure 一样是 浮动体,而 LaTeX 把浮动体当作「能放进一页的、不可分割的一个盒子」。盒子会整体被搬到页首或页尾;既然要整体搬运,就不能从中间切开。所以内部的 tabular 无论多高都不会被拆分,放不下就直接跑出纸面。麻烦的是 这并不会产生警告。用同样的 119 行数据实验,包进浮动体的版本输出 1 页,不包的版本输出 3 页,一条错误信息也没有。这是那种要把 PDF 翻到最后才会发现的事故。

也就是说,「跨页表格」与浮动体机制本身根本不兼容。需要的是把表格 放进与正文相同的流中,像段落一样交给 TeX 自己的分页算法 处理。这就是 longtable。它由 David Carlisle 编写——他也是 arraytabularxdcolumn 的作者——随每个 LaTeX 安装附带的 tools bundle 一起发行,无需另行安装。

longtable 怎么写 — 与 tabular 有什么不同

写法几乎与 tabular 相同。载入 \usepackage{longtable},用 \begin{longtable}{l l r} 开始——列指定是必选引数——用 & 分列、用 \\ 结束行。lcrp{width} 等列类型以及 \hline\multicolumn 都照常可用。只有三点不同。第一,绝不要用 table 环境包住它:那会让它退回浮动体,重现上文的一页问题。第二,列指定 之前 可以写位置用的可选引数 [c][r][l]。第三,longtable 自带标题(见下文)。分页只发生在行与行之间或 \hline 处,绝不会在 p 列的单个单元格中间断开。

位置和上下左右的留白由若干长度决定,\begin{document} 之后随时可以修改。在 TeX Live 2024 上实测的默认值是:\LTleft\LTright 都是 0pt plus 1fill——两侧推力相同,这正是表格 居中 的原因。若要左对齐,保留 \setlength{\LTright}{0pt plus 1fill} 并设 \setlength{\LTleft}{0pt}。表格上下的空白是 \LTpre\LTpost,默认都是 12pt plus 4pt minus 4pt

每页重复表头与表脚 — endhead 与 endfoot

在表格开头排列 四个标记,各自声明「到此为止的行要在哪些页面重复」。标记放在对应行的末尾,取代 本来的 \\。四个都可省略;如果所有页面用同一个表头,只写 \endhead 就够了。

标记前面的行出现在哪里典型内容
\endfirsthead仅第一页的表头\caption{…} 和列标题
\endhead除第一页外每一页的表头“(continued)” 加列标题
\endfoot仍会继续的页面表脚(除最后一页)“Continued on next page” 行
\endlastfoot表格最末尾,仅最后一页收尾横线或注记
\kill不出现在任何地方——只用于测量宽度写有预计最宽内容的假行
document.tex
\documentclass{article}
\usepackage{longtable}
\begin{document}

\begin{longtable}{l l r}
  % head of the first page only
  \caption{Annual sales log}\\
  \hline
  Date & Item & Amount \\
  \hline
  \endfirsthead

  % head of every later page
  \multicolumn{3}{l}{\small (continued)}\\
  \hline
  Date & Item & Amount \\
  \hline
  \endhead

  % foot of every page that continues
  \hline
  \multicolumn{3}{r}{\small Continued on next page}\\
  \endfoot

  % foot of the last page
  \hline
  \endlastfoot

  % everything below the markers is real data
  2026-01-05 & Apple  & 380 \\
  2026-01-06 & Orange & 120 \\
  % ...rows continue; pages are broken automatically...
\end{longtable}

\end{document}

顺序很关键。四个标记必须 按这个顺序排在表格最上方,而 写在它们下面的行才是真正的数据,会被流入各页。反过来说,表格内容从 \endlastfoot 之后的第一行开始。破坏这个顺序,本想当表头的行就会变成数据的第一行。\kill 稍有不同:它生成一行 只测量、不打印 的内容。当表头很短、却在第 100 页才出现一个很长的值时,在开头写一行含最大宽度样本的 \kill,就能让列从一开始就足够宽。

Table widths have changed. Rerun LaTeX. — 为什么要编译两次

为了让每一页的列宽一致。 longtable 逐页排版表格,但某一页最宽的单元格要等这一页排完才知道。若各页独立排版,各页就会有各自的列宽。因此 longtable 把测得的宽度写进 .aux 文件,并在 下一次运行时读回,作为所有页面共同的宽度。这就是第一次编译会打印下面这条信息的原因。

log
Package longtable Warning: Column widths have changed
(longtable)                in table 1 on input line 24.


Package longtable Warning: Table widths have changed. Rerun LaTeX.

忽略这条警告的代价可以实测。从一张跨 3 页的表格 PDF 中读取右侧列标题“Amount”的位置:第一次运行时它位于 x = 320.41pt,而下方数据位于 x = 488.69pt——标题和数字站在不同位置。运行第二次后,标题移到 x = 471.53pt,且三页完全一致。也就是说,第一次的输出是一张列过窄、标题左移的表格。这正是「编译一次表格就不对」的真相。

这个 .aux 文件是可以打开看的。下面就是一张三列 longtable 留下的内容。\LT@i 表示「本文档中的第一张 longtable」,第二张则是 \LT@ii。每个条目的第二个引数是该列的实测宽度,下一次运行会直接使用这些数字。

document.aux
\gdef \LT@i {\LT@entry
    {2}{58.6668pt}\LT@entry
    {2}{274.36153pt}\LT@entry
    {1}{47.55566pt}}

测量并不是一次完成,而是 按块进行,块的大小由计数器 LTchunksize(默认 200 行)决定。如果行数极多的表格耗尽了 TeX 的内存,可以用 \setcounter{LTchunksize}{10} 调小;调大则减少 .aux 的往返次数,有时收敛更快。实务上,用 latexmk 就完全不必操心这些往返——它会一直重跑到警告消失为止。

标题与编号 — LTcapwidth 默认是 4 英寸

longtable 不是浮动体,却 自带 \caption{…}。它推进标准的 table 计数器,所以标题会自动编号为「表 N」,并出现在 \listoftables 中。由于标题是表格布局的一部分,规矩是把它 写在表头块中、\endfirsthead 之前,并用 \\ 结束该行,即 \caption{…}\\。宽度上有个意外:标题宽度 \LTcapwidth 默认是 4 英寸,即 289.08pt。而 article\textwidth 是 345pt,所以默认情况下 标题比正文窄 56pt。想与正文同宽,就写 \setlength{\LTcapwidth}{\textwidth}

其余是些细节约定。不想要编号 时用 \caption*{…}——没有编号,也不进表目录。后续页面上「(continued)」这类 附属标题,应写成 可选引数为空的 \caption[]{…},以免在表目录中重复列出。而交叉引用的 \label{…} 绝不能放进会重复的 \endhead:那样同一个标签会被定义与页数相同的次数。请放在 \endfirsthead 中,或表格主体的第一行。

booktabs 的线条在分页处会怎样

\toprule\midrule\bottomrule 可以直接在 longtable 中使用:booktabs 在 2000 年的版本加入了 longtable 兼容,booktabs.sty 中至今保留着 \ifx\longtable\undefined 分支。但行为上有一处不对称——longtable 为了分页会 自动把 \hline 变成双线(在断裂处上下各画一条),而 booktabs 的线条不会加倍。因此,如果某条线恰好落在分页位置,按 booktabs 手册自己的说法只能 手工处理,通常是删掉那条碍事的线。实务上的正解是把线条放进 \endfoot\endhead,从设计上让断裂处永远不出现线条。

还有一份额外好处。本来只属于 \cmidrule左右修剪引数,在 longtable 内部也能作用于 \toprule\midrule\bottomrule。写 \toprule(r) 会让右端向内收,\midrule(lr) 则两端都收。在 TeX Live 2024 上同时载入 longtablebooktabs 实测通过。反过来说,在普通 tabular 中写不了同样的东西;booktabs 的作者在手册里半开玩笑地说,如果非要修剪过的线条,那就干脆把所有表格都做成 longtable。

supertabular 与 xtab — 只需一次编译,代价是列会跳

能做跨页表格的宏包不只有 longtablesupertabular(Johannes Braams 与 Theo Jurriens)以及它的衍生品 xtab(Peter Wilson)采取了完全不同的策略。longtable 是「排出整张表,再交给 TeX 分页」,而这两者是 「测量页面剩余高度,再新建一个刚好放得下的 tabular——也就是逐页排列彼此独立的 tabular。因此它们不需要 .aux 往返,一次编译即可完成

但代价会直接显示在版面上。用同样的 119 行数据,只在第 90 行混入一个特别长的条目来对比。在 supertabular 下,列标题“Amount”在 第 1、2 页位于 x = 249.16pt,而在含长条目的第 3 页位于 x = 471.53pt——翻页的瞬间表格宽度就变了。同样的数据用 longtable,第二次编译后 三页都是 x = 471.53pt。编译两次,买到的正是这份一致性。写法也不同:supertabular环境之外\tablefirsthead\tablehead\tabletail\tablelasttail 声明表头与表脚,标题用 \topcaption / \bottomcaptionxtab 在此基础上增加了诸如指定最少行数以免断口难看之类的功能。

既能换行又能跨页的 X 列 — xltabular 与 ltxtable

longtable 的列指定与 tabular 相同,所以长文本只能放进固定宽度的 p{width} 列换行。若想要一列自动扩展占满剩余宽度,那就需要 tabularxX,但 tabularx 环境本身不能跨页。有两座桥梁能连通两者。xltabular(Rolf Niepraschk 与 Herbert Voß)是「带 X 列的 longtable」,写成 \begin{xltabular}{\linewidth}{l X r},接收 总宽度与列指定\endfirsthead 等标记也照常可用。它内部载入 ltablex,但不改变已有 tabularx 环境的行为。

document.tex
\documentclass{article}
\usepackage{xltabular}
\begin{document}

\begin{xltabular}{\linewidth}{l X r}
  \caption{Glossary}\\
  \hline
  Term & Description & Page \\
  \hline
  \endfirsthead
  \hline
  Term & Description & Page \\
  \hline
  \endhead

  longtable  & Breaks across pages; sits in the text flow, not in a float. & 12 \\
  xltabular  & A longtable whose X column takes the leftover width.       & 34 \\
\end{xltabular}

\end{document}

另一座桥是同样出自 David Carlisle 之手的 ltxtable。这里要把表格主体 写进单独的文件,再用 \LTXtable{\textwidth}{文件名} 读入。文件中是带 X 列的 longtableltxtable 先跑一遍 tabularx 的宽度计算,再把结果交给 longtable。拆分文件确实麻烦,但这样 tabularx 的反复展开就不会与 longtable.aux 往返互相干扰,长表更稳定。选择很简单:图省事用 xltabular,大而复杂的表出现怪现象时用 ltxtable。另有一个小差别:xltabular 只有在你确实写了 \caption 时才会消耗 table 编号。