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

พื้นที่จัดเก็บเซลล์แบบ 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 เขียนพอดี

แผนภาพการซ้อนสไตล์แบบช่วงสำหรับ HotXLS แสดงช่วงจัดรูปแบบคอลัมน์ที่เก็บเป็น record เดียวพร้อมส่วนรูปแบบที่อ้างสิทธิ์ไว้ แทนออบเจ็กต์เซลล์ว่างล้านตัวใน Delphi
รูปแบบคอลัมน์ แถว หรือสี่เหลี่ยมมีอยู่เพียงครั้งเดียวในรูประเบียน overlay แบบช่วงที่อ้างสิทธิ์เฉพาะส่วนที่ตนเป็นเจ้าของ สไตล์ที่มีผลจะถูกประกอบแบบ lazy ในครั้งแรกที่เซลล์ภายในช่วงถูกแตะจริง
var
  Sheet: TXLSXWorksheet;
  State: TXLSXCellStyleState;
begin
  Sheet := Workbook.Sheets[1];
  // ดัชนีสไตล์มาจาก pool สไตล์ของเวิร์กบุ๊ก เช่น จากเซลล์
  // ที่คุณจัดรูปแบบแล้วในแบบที่คุณต้องการให้ช่วงดู
  State := BuildStateFromTemplateCell(Sheet.Cells.Item[1, 1]);
  // จัดรูปแบบคอลัมน์ B..D โดยไม่ต้องสร้างออบเจกต์เซลล์ว่างแม้แต่ตัวเดียว
  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 แฮนเดิลที่ผู้เรียกถือยังคงใช้ได้ข้ามการรีออร์แกไนซ์ภายในที่เลย์เอาต์บล็อกกระทำ ซึ่งเป็นสิ่งที่ทำให้การแทนกระชับเป็นรายละเอียดการทำงานไม่ใช่การเปลี่ยนแปลงที่ทำลาย

แผนภาพการจัดเก็บแบบบล็อกแถวแสดงเซลล์เวิร์กชีตของ HotXLS เก็บในบล็อกคงที่ 256 แถว พร้อม handle Delphi ที่เสถียร และการ serialize ตามลำดับแถวก่อนลง stream deflate ของ XLSX
เซลล์อยู่ในบล็อก 256 แถวคงที่ที่จองเฉพาะแถวที่มีข้อมูล และทำอนุกรมตามลำดับเดียวกับที่ไบต์ออกจากโปรเซส handle สาธารณะอยู่รอดผ่านการจัดระเบียบภายใน โครงสร้างบล็อกจึงมองไม่เห็นจากฝั่งผู้เรียก

การทำ 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;
  try
    Workbook.Open('ledger-2026.xlsx');
    // StreamingWrite ค่าเริ่มต้นเป็น True; ปิดมันเมื่อขั้นตอนปลายทาง
    // ต้องการ XML ของเวิร์กชีตทั้งหมดให้มีอยู่ก่อนบีบอัด
    Workbook.StreamingWrite := True;
    Workbook.SaveAs('ledger-2026-out.xlsx');
  finally
    Workbook.Free;
  end;
end;

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

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

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

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

คลี่คลายสไตล์เมื่อคุณต้องการรูปแบบที่มีผลของเซลล์หนึ่ง อย่าคลี่คลายสไตล์สำหรับเซลล์ล้านเซลล์เพื่อค้นหาว่าคอลัมน์มี 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 ที่รองรับ