7.3 空間配置與碎片整理#
概念說明#
檔案系統的空間配置策略決定碎片的命運,三大經典策略:
- 連續配置(contiguous):檔案占一整段連續區塊。讀取快(順序 I/O)、隨機也快,但外部碎片嚴重,檔案擴充困難(可能要整檔搬移)。
- 鏈結配置(linked):如 FAT,無外部碎片但隨機存取 O(k),且鏈跳來跳去。
- 索引配置(indexed):如 inode,隨機存取好,但小型檔案也要負擔指標開銷。
現代檔案系統(ext4、XFS)用折衷:延遲配置(delayed allocation)——先讓資料留在 page cache,累積到一定量後一次決定「往哪裡放」,爭取把相鄰寫入配到連續區塊,從源頭減少碎片。
碎片整理(defragmentation)是把分散的區塊搬動集中,使檔案盡量連續。HDD 上意義重大(每次跳磁軌 10ms),SSD 上則幾乎無意義甚至有害:SSD 沒有尋找時間,碎片不影響讀取,而搬移反而增加寫入、消耗壽命。F2FS 等為快閃設計的檔案系統根本不做傳統碎片整理。
碎片整理演算法(HDD 版):
- 掃描位圖,找出碎片最嚴重的檔案(區塊間跳轉率最高)。
- 為該檔案在位圖中找一段等長的連續空間。
- 逐區塊搬移(讀舊 → 寫新 → 更新映射 → 釋放舊區塊)。
- 重複直到碎片率低於門檻。
Python 範例:碎片測量與整理#
class BitmapFS:
def __init__(self, total=64):
self.total = total
self.blocks = [None] * total # None=空閒, 否則 (檔名, 邏輯序)
self.io = 0
def alloc(self, name, n, scattered=False):
"""scattered=True 模擬碎片化的配置"""
import random
free = [i for i, b in enumerate(self.blocks) if b is None]
if len(free) < n: raise RuntimeError("空間不足")
random.seed(7 if scattered else None)
picks = random.sample(free, n) if scattered else free[:n]
for i, blk in enumerate(picks):
self.blocks[blk] = (name, i)
def frag_ratio(self, name):
locs = [i for i, b in enumerate(self.blocks) if b and b[0] == name]
order = sorted(locs, key=lambda i: self.blocks[i][1])
jumps = sum(1 for a, c in zip(order, order[1:]) if c != a + 1)
return jumps / max(len(order) - 1, 1), order
def defrag(self, name):
order = [i for i, b in enumerate(self.blocks)
if b and b[0] == name]
order.sort(key=lambda i: self.blocks[i][1])
n = len(order)
# 找連續空段
run, start = 0, None
for i in range(self.total):
run = run + 1 if self.blocks[i] is None or self.blocks[i][0] == name else 0
if run == n:
start = i - n + 1; break
if start is None:
return False # 沒有連續空間,需先搬其他檔案
for j, old in enumerate(order):
tgt = start + j
if tgt == old: continue
self.io += 2 # 讀舊 + 寫新
self.blocks[tgt] = (name, j)
if self.blocks[old] and self.blocks[old][0] == name:
self.blocks[old] = None
return True
fs = BitmapFS(total=64)
fs.alloc("A", 6); fs.alloc("B", 8); fs.alloc("C", 6)
# 製造碎片:刪掉 B 的一部分概念用散置配置模擬
fs2 = BitmapFS(total=64)
fs2.alloc("A", 6, scattered=True); fs2.alloc("C", 6, scattered=True)
r1, order1 = fs2.frag_ratio("A")
print(f"整理前碎片率: {r1:.0%},區塊位置 {order1}")
fs2.defrag("A")
r2, order2 = fs2.frag_ratio("A")
print(f"整理後碎片率: {r2:.0%},區塊位置 {order2},搬移 I/O: {fs2.io}")
複雜度與 I/O 成本分析#
- 連續配置:讀取任意範圍 1 次順序 I/O;配置需找長度 ≥ n 的空段(first-fit O(表長))。
- 鏈結/索引配置:隨機讀分別為 O(k) 與 O(1~3) 次 I/O。
- 碎片整理:搬一個檔案 2n 次 I/O;全碟整理最壞 O(N) 次 I/O,且需先挪出連續空間(可能引發連鎖搬移)。
- HDD 上碎片率 30% 的大檔案讀取可慢 2~3 倍;SSD 上幾乎無差——「碎片」是機械時代的演算法約束。
練習#
- 在 64 區塊的模擬中製造 40% 碎片率,統計讀整個檔案相比連續配置多付出多少 I/O。
- 實作 first-fit 與 best-fit 兩種連續配置策略,比較外部碎片的累積速度。
- 說明為何 SSD 上「定期碎片整理」反而縮短壽命,連結 1.3 節的寫入放大概念。