บทความเทคนิค

พื้นที่จัดเก็บเซลล์แบบ Row-Block และการบันทึก XLSX แบบสตรีมใน HotXLS

HotXLS เก็บเซลล์เวิร์กชีตในบล็อก 256 แถวกระชับ คลี่คลายการจัดรูปแบบแถว คอลัมน์ และสี่เหลี่ยมผ่าน interval overlay แบบ lazy แทนที่จะสร้างออบเจกต์เซลล์ และสตรีมแต่ละแถวตรงเข้าสู่ deflate stream ของแพ็กเกจขณะบันทึก สามการเปลี่ยนแปลงนี้ร่วมกันตัดสินโปรไฟล์หน่วยความจำของเวิร์กบุ๊กขนาดใหญ่ การใช้งานสูงสุดตามแถวเดียวที่ใหญ่ที่สุด ไม่ใช่ตามขนาดของ worksheet XML ทั้งหมด

เหตุผลที่เรื่องนี้สำคัญคือรูปร่างที่นักพัฒนาสเปรดชีตทุกคนพบในที่สุด ผู้ใช้จัดรูปแบบคอลัมน์ทั้งคอลัมน์ — คลิกเดียว หนึ่งล้านเซลล์ — และ object model ที่ไร้เดียงสาตอบโดยจัดสรรออบเจกต์เซลล์หนึ่งล้านเพื่อถือ index number-format เดียว ไฟล์บนดิสก์ยังคงเล็กเพราะรูปแบบ XLSX แทนมันเป็น entry <col> เดียว โพรเซสไม่เล็กเลย

ทำไมการจัดรูปแบบคอลัมน์จึงกินหน่วยความจำมากกว่าการเติม

เพราะการจัดรูปแบบไม่มีข้อมูลที่จะให้เหตุผลกับออบเจกต์ เซลล์ที่มีค่าต้องมีอยู่ที่ไหนสักแห่ง เซลล์ที่ว่างแต่มีสไตล์อยู่เพื่อถือ style index เท่านั้น และการสร้างออบเจกต์เซลล์เป็นล้าน ๆ เป็นวิธีคลาสสิกที่แอปพลิเคชันสเปรดชีต Delphi หมดพื้นที่อยู่บนไฟล์ที่ Excel เปิดทันที

interval style overlay เอาความจำเป็นนั้นออกไป คำสั่งจัดรูปแบบแถว คอลัมน์ หรือสี่เหลี่ยมถูกเก็บครั้งเดียวเป็นช่วงบวกส่วนสไตล์ที่มันสนับสนุน แล้วคลี่คลายแบบ lazy เมื่อเซลล์ในช่วงนั้นถูกเข้าถึงจริง overlay รอดจากการแก้ไขเชิงโครงสร้าง — การแทรกแถวในบล็อกที่จัดรูปแบบแล้วย้ายช่วงแทนที่จะสร้างใหม่ — และเดินทางไปกลับเป็น entry คอลัมน์ แถว และเซลล์เฉพาะสไตล์แบบกระชับ ซึ่งเป็นวิธีที่ Excel เขียนพอดี

var
  Sheet: TXLSXWorksheet;
  State: TXLSXCellStyleState;
begin
  Sheet := Workbook.Sheets[1];
  // Style indexes come from the workbook style pools, e.g. from a cell
  // you have already formatted the way you want the range to look
  State := BuildStateFromTemplateCell(Sheet.Cells.Item[1, 1]);
  // Format columns B..D without creating a single empty cell object
  Sheet.StyleOverlays.Add(1, 2, MaxRowIndex, 4,
    [xfpNumberFormat, xfpAlignment], State);
end;

TXLSXFormatParts เป็นเซ็ตที่ตัดสินว่า overlay สนับสนุนอะไรบ้าง xfpFont, xfpFill, xfpBorder, xfpNumberFormat, xfpAlignment และ xfpProtection การระบุเฉพาะส่วนที่คุณตั้งใจคือสิ่งที่ทำให้ overlay ซ้อนกันได้อย่างสมเหตุสมผล — column overlay ที่ส่ง number format ไม่ขัด row overlay ที่ส่ง fill เพราะไม่มีฝั่งใดอ้างส่วนของอีกฝั่ง

บล็อก 256 แถวให้อะไรคุณ

locality เซลล์ถูกถือในบล็อก 256 แถวพร้อมแฮนเดิลสาธารณะที่คงที่ ทำให้เป็นอนุกรมแบบ row-major ดังนั้นการเขียนเวิร์กชีตเดินผ่านหน่วยความจำตามลำดับที่มันจะปล่อยไบต์ แทนที่จะไล่ตามพอยน์เตอร์ข้าม heap แฮนเดิลที่คงที่สำคัญต่อพื้นผิว API แฮนเดิลที่ผู้เรียกถือยังคงใช้ได้ข้ามการรีออร์แกไนซ์ภายในที่เลย์เอาต์บล็อกกระทำ ซึ่งเป็นสิ่งที่ทำให้การแทนกระชับเป็นรายละเอียดการทำงานไม่ใช่การเปลี่ยนแปลงที่ทำลาย

การทำ style pool ให้กระชับรันควบคู่กับมัน ก่อนทุกการบันทึก font fill border number format alignment และ protection ที่ไม่มีเซลล์อ้างถึงจะถูกทิ้ง เวิร์กบุ๊กที่อายุยืนสะสมเรคคอร์ดสไตล์ที่ไม่ถูกอ้างถึงเหมือนที่เอกสารที่อายุยืนสะสมสไตล์ที่ไม่ใช้ เวิร์กบุ๊กที่ผู้ใช้แก้มาหนึ่งชั่วโมงสามารถพกมันเข้าไปในไฟล์เป็นร้อย ๆ ที่ไม่มีใครจะอ่านจาก

การบันทึกแบบ row-streamed และเมื่อใดที่มันไม่นำไปใช้

เมื่อเปิดใช้ StreamingWrite — ค่าเริ่มต้น — แต่ละแถวเวิร์กชีตจะถูกเขียนตรงเข้า deflate stream ของแพ็กเกจ ทางเลือกอื่น ซึ่งคือสิ่งที่สถานะปิด คือสร้าง worksheet XML ทั้งหมดก่อนแล้วบีบอัดภายหลัง ทำให้หน่วยความจำสูงสุดขึ้นกับชีตทั้งหมด การสตรีมทำให้ขึ้นกับหนึ่งแถว

shared string และ part สนับสนุนทำตามวินัยเดียวกันผ่านตัวทำให้เป็นอนุกรม UTF-8 หนึ่งตัวที่ใช้ซ้ำได้ที่ปล่อย entry ทีละตัว จำกัดหน่วยความจำสูงสุดด้วย entry เดียวที่ใหญ่ที่สุดแทนทั้ง part นั่นครอบคลุม shared string table และเรคคอร์ด pivot ซึ่งบนเวิร์กบุ๊กวิเคราะห์กว้างมักใหญ่กว่าเวิร์กชีตเฉพาะใด ๆ

var
  Workbook: TXLSXWorkbook;
begin
  Workbook := TXLSXWorkbook.Create(nil);
  try
    Workbook.Open('ledger-2026.xlsx');
    // StreamingWrite defaults to True; turn it off only when a downstream
    // step requires the whole worksheet XML to exist before compression
    Workbook.StreamingWrite := True;
    Workbook.SaveAs('ledger-2026-out.xlsx');
  finally
    Workbook.Free;
  end;
end;

ปล่อยมันเปิดไว้ เว้นแต่คุณมีเหตุผลที่แน่ชัดที่จะไม่ทำ เส้นทางที่ไม่ใช่สตรีมมีไว้สำหรับกรณีที่สิ่งอื่นในไปป์ไลน์ต้องการ XML ที่ประกอบแล้ว และการจ่ายให้มันโดยค่าเริ่มต้นคือการจ่ายสำหรับกรณีที่แอปพลิเคชันส่วนใหญ่ไม่เคยพบ

จะบอกได้อย่างไรว่า overlay ถูกใช้จริง

ดูจำนวนเซลล์ ไม่ใช่กราฟหน่วยความจำ หากเวิร์กชีตรายงานจำนวนเซลล์ทางกายภาพที่เป็นไปได้หลังคุณใช้การจัดรูปแบบกว้าง ๆ overlay ก็กำลังทำงาน หากจำนวนกระโดดเท่ากับขนาดของช่วงที่จัดรูปแบบ สิ่งใดสิ่งหนึ่งในเส้นทางโค้ดสร้างเซลล์เป็นรูปธรรม — มักเป็นลูปที่อ่านทุกเซลล์ในช่วงเพื่อตรวจสไตล์ ซึ่งบังคับให้คลี่คลายทีละเซลล์และทำลายการจัดเรียงทั้งหมด

คลี่คลายสไตล์เมื่อคุณต้องการรูปแบบที่มีผลของเซลล์หนึ่ง อย่าคลี่คลายสไตล์สำหรับเซลล์ล้านเซลล์เพื่อค้นหาว่าคอลัมน์มี number format ถาม overlay กฎเดียวกันนำไปใช้กับการเขียน กำหนดค่าให้เซลล์ที่มีค่า แล้วปล่อยการจัดรูปแบบให้เป็นช่วงต่อไป

หน่วยความจำที่เหลือไปอยู่ที่ใด

เมื่อเซลล์และสไตล์กระชับแล้ว ผู้บริโภคที่ใหญ่ที่สุดถัดไปบนเวิร์กบุ๊กขนาดใหญ่คือ shared string table และ part ดาวเทียมใด ๆ ที่ไฟล์พก — pivot cache งานวาด XML ที่รักษาไว้จาก part ที่ object model ไม่ได้ model สิ่งเหล่านั้นมีกลยุทธ์ของตัวเอง และคำตอบที่ซื่อคือไม่มีการตั้งค่าเดียวที่แก้ทั้งหมดพร้อมกัน

หากคอขวดของคุณคือการเปิดไม่ใช่การบันทึก การโหลดแบบเลือกคือ lever คู่มือของ การโหลดเฉพาะเมตาดาต้าและเลือกเฉพาะเวิร์กชีต ครอบคลุมการอ่านเวิร์กบุ๊กโดยไม่จ่ายสำหรับชีตที่คุณจะไม่แตะ สำหรับ throughput เส้นทางอ่านบนไฟล์ขนาดใหญ่มาก ดูบันทึกของ การ parse XLSX แบบขนานและตัวจัดสรรหน่วยความจำ และสำหรับเวิร์กโหลดเฉพาะเอาต์พุตที่ไม่เคยต้องการ object model เลย การเขียนแบบสตรีมสำหรับชุดงานเซิร์ฟเวอร์ มักเหมาะสมกว่าการปรับแต่งใด ๆ ที่นี่

HotXLS อ่านและเขียน XLS และ XLSX จากโค้ด Delphi และ C++Builder พื้นเมืองโดยไม่ติดตั้ง Excel และไม่มี OLE automation ซึ่งเป็นสิ่งที่ทำให้คุณลักษณะหน่วยความจำเหล่านี้สังเกตและควบคุมได้ตั้งแต่แรก — หน้าคอมโพเนนต์สเปรดชีต HotXLS จัดประเภทรูปแบบและเวอร์ชัน RAD Studio ที่รองรับ