Sprechen Sie über die Excel-Analyse: Wie geht man mit Millionen Zeilen von Excel-Dateien um? | Technisches Team von JD Cloud

I. Einleitung

Excel-Tabellen werden häufig in Hintergrundverwaltungssystemen verwendet und hauptsächlich für die Stapelkonfiguration und den Datenexport verwendet. In der täglichen Entwicklung kommen wir nicht um die Verarbeitung von Excel-Daten herum.

Wie geht man also mit Excel-Dateien mit großen Datenmengen richtig um, um Speicherüberlaufprobleme zu vermeiden? In diesem Artikel werden die gängigen Excel-Analysetechnologien der Branche verglichen und analysiert und Lösungen aufgezeigt.

Wenn dies Ihr erster Kontakt mit der Excel-Analyse ist, wird empfohlen, dass Sie die grundlegenden Konzepte dieses Artikels aus Kapitel 2 verstehen. Wenn Sie bereits über POI-Kenntnisse verfügen, fahren Sie bitte mit Kapitel 3 fort, um den Hauptinhalt dieses Artikels zu lesen.

2. Grundlegende Artikel - POI

Wenn es um das Lesen und Schreiben von Excel geht, kommt man in diesem Kreis nicht um den großen Bruder herum – POI.

Apache POI ist eine kostenlose und plattformübergreifende Open-Source-Java-API, die von der Apache Software Foundation in Java geschrieben wurde. Es unterstützt uns bei der Verwendung der Java-Sprache für die Interaktion mit allen Microsoft Office-Dokumenten, einschließlich Word, Excel, PowerPoint und Visio, sowie für die Durchführung von Lese-, Schreib- und Änderungsvorgängen für Daten.

(1) „Schlechte“ Tabellenkalkulationen

In POI verfügt jedes Dokument über ein entsprechendes Dokumentformat, beispielsweise die 97-2003-Version der Excel-Datei (.xls). Das Dokumentformat ist HSSF – Horrible SpreadSheet Format, was „schlechtes Tabellenformat“ bedeutet. Obwohl Apache seine API humorvoll und bescheiden als „schrecklich“ bezeichnet, handelt es sich tatsächlich um eine umfassende und leistungsstarke API.

Im Folgenden sind einige „schlechte“ POI-Dokumentformate aufgeführt, darunter Excel, Word usw.:

Office-Dokumente Entsprechendes POI-Format
Excel (.xls) HSSF (schreckliches Tabellenkalkulationsformat)
Word (.doc) HWPF (schreckliches Textverarbeitungsformat)
Visio (.vsd) HDGF (Horrible DiaGram Format)
PowerPoint(.ppt) HSLF (schreckliches Folienlayoutformat)

(2) Einführung in OOXML

Microsoft hat in der Office 2007-Version eine XML-basierte technische Spezifikation eingeführt: Office Open XML, kurz OOXML. Anders als bei der Binärspeicherung der alten Version werden unter der neuen Spezifikation alle Office-Dokumente im XML-Format geschrieben und im ZIP-Format komprimiert und gespeichert, was die Standardisierung erheblich verbessert, die Komprimierungsrate verbessert, die Dateigröße verringert und die Rückwärtsunterstützung unterstützt kompatibel. Vereinfacht ausgedrückt definiert OOXML, wie Office-Dokumente als eine Reihe von XML-Dateien dargestellt werden.

Die Essenz von XLSX-Dateien ist XML

Werfen wir einen Blick auf die Zusammensetzung einer Xlsx-Datei unter Verwendung des OOML-Standards. Wir klicken mit der rechten Maustaste auf eine XLSX-Datei und stellen fest, dass sie mit dem ZIP-Dekomprimierungstool dekomprimiert werden kann (oder die Erweiterung direkt in .zip ändern und dann dekomprimieren), was bedeutet: XLSX-Dateien werden im ZIP-Format komprimiert . Nach der Dekomprimierung sehen Sie das folgende Verzeichnisformat:

Öffnen Sie das Verzeichnis „/xl“, das die Hauptstrukturinformationen dieses Excel enthält:

Unter anderem speichert workbook.xml die Struktur der gesamten Excel-Arbeitsmappe, einschließlich mehrerer Blätter, und die Struktur jedes Blattes wird im Ordner /wooksheets gespeichert. „styles.xml“ speichert die Formatinformationen der Zelle und der Ordner „/theme“ speichert einige vordefinierte Schriftarten, Farben und andere Daten. Um das Komprimierungsvolumen zu reduzieren, werden alle Zeichendaten im Formular in sharedStrings.xml gespeichert. Nach der Analyse ist es nicht schwer festzustellen, dass die Hauptdaten der XLSX-Datei im XML-Format geschrieben sind.

XSSF-Format

Um die neuen Standard-Office-Dokumente zu unterstützen, hat POI außerdem eine Reihe von APIs eingeführt, die mit dem OOXML-Standard kompatibel sind, genannt poi-ooxml. Beispielsweise ist das POI-Dokumentformat, das der Excel 2007-Datei (.xlsx) entspricht, XSSF (XML SpreadSheet Format).

Folgendes ist Teil des OOXML-Dokumentformats:

Office-Dokumente Entsprechendes POI-Format
Excel (.xlsx) XSSF (XML-Tabellenformat)
Word (.docx) XWPF (XML-Textverarbeitungsformat)
Visio (.vsdx) XDGF (XML-Diagrammformat)
PowerPoint (.pptx) XSLF (XML-Folienlayoutformat)

(3)Benutzermodell

In POI stellen wir zwei Modelle zum Parsen von Excel bereit: UserModel (Benutzermodell) und EventModel (Ereignismodell) . Beide Analysemodi können Excel-Dateien verarbeiten, die Analysemethoden, die Verarbeitungseffizienz und die Speichernutzung sind jedoch unterschiedlich. Das einfachste und praktischste ist UserModel.

UserModel- und DOM-Parsing

Das Benutzermodell definiert die folgende Schnittstelle:

  1. Arbeitsmappe-Arbeitsmappe, entsprechend einem Excel-Dokument. Je nach Version gibt es HSSFWorkbook, XSSFWorkbook und andere Klassen.

  2. Sheet-Form, mehrere Blätter in Excel, verfügt auch über HSSFSheet, XSSFSheet und andere Klassen.

  3. Zeile-Zeile, ein Formular besteht aus mehreren Zeilen, und es gibt auch HSSFRow, XSSFRow und andere Klassen.

  4. Zelle-Zelle, eine Zeile besteht aus mehreren Zellen, es gibt auch HSSFCell, XSSFCell und andere Klassen.

Darstellung des Benutzermodells

Es ist ersichtlich, dass das Benutzermodell sehr gut zu den Gewohnheiten von Excel-Benutzern passt und leicht zu verstehen ist, genau wie das Öffnen einer Excel-Tabelle. Gleichzeitig stellt das Benutzermodell eine umfangreiche API bereit, die uns dabei unterstützen kann, dieselben Vorgänge wie in Excel auszuführen, z. B. ein Formular zu erstellen, eine Zeile zu erstellen, die Anzahl der Zeilen in einer Tabelle abzurufen und die Anzahl der Spalten abzurufen hintereinander, Lesen und Schreiben von Zellwerten usw.

Warum unterstützt uns UserModel bei der Durchführung solch umfangreicher Operationen? Da im UserModel alle XML-Knoten in Excel in einen DOM-Baum analysiert werden und der gesamte DOM-Baum in den Speicher geladen wird, kann bequem auf jeden XML-Knoten direkt zugegriffen werden .

UserModel-Datenkonvertierung

Da wir das Benutzermodell kennen, können wir seine API direkt für verschiedene Excel-Vorgänge verwenden. Eine bequemere Möglichkeit besteht natürlich darin, das Benutzermodell zum Konvertieren einer Excel-Datei in die von uns gewünschte Java-Datenstruktur für eine bessere Datenverarbeitung zu verwenden.

Es fällt uns leicht, an relationale Datenbanken zu denken – denn das Wesen beider ist dasselbe. Im Vergleich zur Datentabelle der Datenbank lautet unsere Idee wie folgt:

  1. Stellen Sie sich ein Blatt als zwei Teile vor, den Kopf und die Daten, die jeweils die Struktur der Tabelle und die Daten der Tabelle enthalten.

  2. Überprüfen Sie für den Header (die erste Zeile), ob die Header-Informationen mit den definierten Attributen der Entitätsklasse übereinstimmen.

  3. Durchlaufen Sie für die Daten (verbleibende Zeilen) jede Zeile von oben nach unten, konvertieren Sie jede Zeile in ein Objekt und verwenden Sie jede Spalte als Attribut des Objekts, um eine Objektliste zu erhalten, die alle Daten in Excel enthält.

Als nächstes können wir unsere Daten nach unseren Bedürfnissen verarbeiten. Wenn wir die manipulierten Daten zurück nach Excel schreiben wollen, gilt die gleiche Logik.

Verwenden Sie UserModel

Sehen wir uns an, wie man Excel-Dateien mit UserModel liest. Verwenden Sie hier die POI 4.0.0-Version. Führen Sie zunächst die Abhängigkeiten von poi und poi-ooxml ein:

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.0.0</version>
    </dependency>

Wir möchten eine einfache SKU-Informationstabelle lesen. Der Inhalt lautet wie folgt:

Wie konvertiere ich UserModel-Informationen in eine Datenliste?

Wir können die Zuordnungsbeziehung vom Header zu den Daten definieren, indem wir Reflection + Annotation implementieren, was uns hilft, die Konvertierung vom UserModel in das Datenobjekt zu realisieren. Die Grundidee der Implementierung lautet: ① Passen Sie die Anmerkung an und definieren Sie die Spaltennummer in der Anmerkung, mit der jedes Attribut der Entitätsklasse markiert wird, das der Spalte in der Excel-Kopfzeile entspricht. ② Fügen Sie in der Entitätsklassendefinition Anmerkungen zu den Attributen jeder Entitätsklasse gemäß der Tabellenstruktur hinzu. ③ Erhalten Sie durch Reflexion die Spaltennummer, die jedem Attribut der Entitätsklasse in Excel entspricht, um den Wert des Attributs in der entsprechenden Spalte zu erhalten.

Das Folgende ist eine einfache Implementierung. Bereiten Sie zunächst die benutzerdefinierte Annotation ExcelCol vor, die die Spaltennummer und den Header enthält:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCol {

    /**
     * 当前列数
     */
    int index() default 0;

    /**
     * 当前列的表头名称
     */
    String header() default "";
}

Definieren Sie als Nächstes das SKU-Objekt gemäß dem SKU-Feld und fügen Sie Anmerkungen hinzu. Die Spaltennummern sind 0, 1, 2 und geben Sie den Headernamen an:

import lombok.Data;
import org.shy.xlsx.annotation.ExcelCol;

@Data
public class Sku {

    @ExcelCol(index = 0, header = "sku")
    private Long id;

    @ExcelCol(index = 1, header = "名称")
    private String name;

    @ExcelCol(index = 2, header = "价格")
    private Double price;
}

Verwenden Sie dann Reflection, um jedes Feld des Tabellenkopfes abzurufen, und verwenden Sie die Spaltennummer als Index, um es in der Karte zu speichern. Beginnen Sie mit der zweiten Zeile von Excel (die erste Zeile ist die Tabellenüberschrift), durchlaufen Sie jede nachfolgende Zeile und ermitteln Sie für jedes Attribut jeder Zeile den Wert der entsprechenden Zelle entsprechend der Spaltennummer und weisen Sie den Daten einen Wert zu Objekt. Abhängig von der Art des Werts in der Zelle, z. B. Text/Zahl usw., wird eine unterschiedliche Verarbeitung durchgeführt. Um die Logik zu vereinfachen, werden im Folgenden nur die Typen verarbeitet, die im Tabellenkopf erscheinen. Die Verarbeitungslogik für andere Fälle ist ähnlich. Der gesamte Code lautet wie folgt:

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.shy.domain.pojo.Sku;
import org.shy.xlsx.annotation.ExcelCol;

import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyUserModel {

    public static void main(String[] args) throws Exception {
        List<Sku> skus = parseSkus("D:\sunhaoyu8\Documents\Files\skus.xlsx");
        System.out.println(JSON.toJSONString(skus));
    }

    public static List<Sku> parseSkus(String filePath) throws Exception {
        FileInputStream in = new FileInputStream(filePath);
        Workbook wk = new XSSFWorkbook(in);
        Sheet sheet = wk.getSheetAt(0);
        // 转换成的数据列表
        List<Sku> skus = new ArrayList<>();

        // 获取Sku的注解信息
        Map<Integer, Field> fieldMap = new HashMap<>(16);
        for (Field field : Sku.class.getDeclaredFields()) {
            ExcelCol col = field.getAnnotation(ExcelCol.class);
            if (col == null) {
                continue;
            }
            field.setAccessible(true);
            fieldMap.put(col.index(), field);
        }

        for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
            Row r = sheet.getRow(rowNum);
            Sku sku = new Sku();
            for (int cellNum = 0; cellNum < fieldMap.size(); cellNum++) {
                Cell c = r.getCell(cellNum);
                if (c != null) {
                    setFieldValue(fieldMap.get(cellNum), getCellValue(c), sku);
                }
            }
            skus.add(sku);
        }
        return skus;
    }

    public static void setFieldValue(Field field, String value, Sku sku) throws Exception {
        if (field == null) {
            return;
        }
        //得到此属性的类型
        String type = field.getType().toString();
        if (StringUtils.isBlank(value)) {
            field.set(sku, null);
        } else if (type.endsWith("String")) {
            field.set(sku, value);
        } else if (type.endsWith("long") || type.endsWith("Long")) {
            field.set(sku, Long.parseLong(value));
        } else if (type.endsWith("double") || type.endsWith("Double")) {
            field.set(sku, Double.parseDouble(value));
        } else {
            field.set(sku, value);
        }
    }

    public static String getCellValue(Cell cell) {
        DecimalFormat df = new DecimalFormat("#.##");
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case NUMERIC:
                return df.format(cell.getNumericCellValue());
            case STRING:
                    return cell.getStringCellValue().trim();
            case BLANK:
                return null;
        }
        return "";
    }

Drucken Sie abschließend die konvertierte Datenliste aus. Das Ergebnis der Operation ist wie folgt:

[{"id":345000,"name":"电脑A","price":5999.0},{"id":345001,"name":"手机C","price":4599.0}]

Tipps: Wenn „NoClassDefFoundError“ in Ihrem Programm auftritt, führen Sie bitte die Abhängigkeit von ooxml-schemas ein:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>ooxml-schemas</artifactId>
    <version>1.4</version>
</dependency>

Informationen zur Versionsauswahl finden Sie in der folgenden Tabelle. Beispielsweise entspricht POI 4.0.0 der ooxml-schemas-Version 1.4:

Einschränkungen von UserModel

Die obige Verarbeitungslogik ist auf die meisten Excel-Dateien anwendbar, der größte Nachteil ist jedoch der große Speicheraufwand, da alle Daten in den Speicher geladen werden. Gemäß der tatsächlichen Messung beträgt die OOM in der Excel-Datei mit den oben genannten drei Spalten etwa 70.000 Zeilen, während die maximale Anzahl von Zeilen in der XLS-Datei 65.535 Zeilen beträgt und die XLSX 1.048.576 Zeilen erreicht hat. Wenn Sie Zehntausende lesen oder sogar Millionen von Daten in den Speicher, ist die Gefahr eines Speicherüberlaufs extrem hoch.

Wie kann also das Problem gelöst werden, dass das herkömmliche UserModel große Excel-Stapel nicht verarbeiten kann? Entwickler haben viele wunderbare Lösungen gefunden, siehe nächstes Kapitel.

3. Fortgeschrittene Artikel – Erkundung der Speicheroptimierung

Als nächstes werden wir den Hauptinhalt dieses Artikels vorstellen und gleichzeitig das in diesem Artikel angesprochene Problem lösen: Wie kann der Speicher der Excel-Analyse optimiert werden, um Millionen von Zeilen in Excel-Dateien zu verarbeiten?

(1)EventModel

Wie bereits erwähnt, bietet POI neben UserModel auch ein weiteres Modell zum Parsen von Excel: das EventModel-Ereignismodell. Im Gegensatz zur DOM-Analyse des Benutzermodells verwendet das Ereignismodell die SAX-Methode zum Parsen von Excel.

EventModel- und SAX-Analyse

Der vollständige Name von SAX ist Simple API for XML, eine ereignisgesteuerte XML-Analysemethode. Im Gegensatz zu DOM, das XML gleichzeitig liest, verarbeitet SAX XML beim Lesen. Vereinfacht ausgedrückt scannt der SAX-Parser das XML-Dokument Zeile für Zeile. Wenn er auf ein Tag stößt, löst er den Parsing-Prozessor und damit den entsprechenden Ereignishandler aus. Was wir tun müssen, ist, die DefaultHandler-Klasse zu erben und eine Reihe von Ereignisbehandlungsmethoden neu zu schreiben, um Excel-Dateien entsprechend zu verarbeiten.

Hier ist ein Beispiel für eine einfache SAX-Analyse. Hier ist die zu analysierende XML-Datei: eine SKU-Tabelle, die zwei SKU-Knoten enthält. Jeder Knoten verfügt über ein ID-Attribut und drei untergeordnete Knoten.

<?xml version="1.0" encoding="UTF-8"?>
<skus>
    <sku id="345000">
        <name>电脑A</name>
        <price>5999.0</price>
   </sku>
    <sku id="345001">
        <name>手机C</name>
        <price>4599.0</price>
   </sku>
</skus>

Erstellen Sie eine Java-Entitätsklasse anhand der XML-Struktur:

import lombok.Data;

@Data
public class Sku {
    private Long id;
    private String name;
    private Double price;
}

Benutzerdefinierte Ereignisverarbeitungsklasse SkuHandler:

import com.alibaba.fastjson.JSON;
import org.shy.domain.pojo.Sku;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SkuHandler extends DefaultHandler {
    /**
     * 当前正在处理的sku
     */
    private Sku sku;
    /**
     * 当前正在处理的节点名称
     */
    private String tagName;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if ("sku".equals(qName)) {
            sku = new Sku();
            sku.setId(Long.valueOf((attributes.getValue("id"))));
        }
        tagName = qName;
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if ("sku".equals(qName)) {
            System.out.println(JSON.toJSONString(sku));
            // 处理业务逻辑
            // ...
        }
        tagName = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if ("name".equals(tagName)) {
            sku.setName(new String(ch, start, length));
        }
        if ("price".equals(tagName)) {
            sku.setPrice(Double.valueOf(new String(ch, start, length)));
        }
    }
}

Darunter schreibt SkuHandler drei Ereignisantwortmethoden neu:

startElement() – Rufen Sie diese Methode immer dann auf, wenn ein neues XML-Element gescannt wird, und übergeben Sie dabei den XML-Tagnamen qName und die XML-Attributlistenattribute.

Zeichen() – Rufen Sie diese Methode immer dann auf, wenn eine Zeichenfolge gescannt wird, die nicht im XML-Tag enthalten ist, und übergeben Sie das Zeichenarray, den Anfangsindex und die Länge.

endElement() – Immer wenn das End-Tag eines XML-Elements gescannt wird, wird diese Methode aufgerufen und übergibt den XML-Tag-Namen qName.

Wir verwenden eine Variable tagName, um die aktuell gescannten Knoteninformationen zu speichern und aktualisieren den tagName jedes Mal, wenn der gescannte Knoten eine Änderung sendet;

Verwenden Sie eine SKU-Instanz, um die aktuell in den Speicher eingelesenen SKU-Informationen beizubehalten. Immer wenn die SKU gelesen wird, drucken wir die SKU-Informationen aus und führen die entsprechende Geschäftslogik aus. Auf diese Weise kann jeweils eine SKU-Information gelesen und bei der Analyse verarbeitet werden. Da jede SKU-Zeile die gleiche Struktur hat, muss nur eine SKU-Information im Speicher gehalten werden, wodurch vermieden wird, dass alle Informationen gleichzeitig in den Speicher eingelesen werden.

Wenn Sie den SAX-Parser aufrufen, verwenden Sie SAXParserFactory, um eine Parser-Instanz zu erstellen und den Eingabestream zu analysieren. Die Main-Methode lautet wie folgt:

import org.shy.xlsx.sax.handler.SkuHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.InputStream;

public class MySax {
    public static void main(String[] args) throws Exception {
        parseSku();
    }

    public static void parseSku() throws Exception {
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        SAXParser saxParser = saxParserFactory.newSAXParser();
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("skus.xml");
        saxParser.parse(inputStream, new SkuHandler());
    }
}

Die Ausgabe ist wie folgt:

{"id":345000,"name":"电脑A","price":5999.0}
{"id":345001,"name":"手机C","price":4599.0}

Das Obige zeigt die Grundprinzipien der SAX-Analyse. Die API von EventModel ist komplexer und die SAX-Analyse wird auch durch Umschreiben des Event-Handlers implementiert. Interessierte Leser lesen bitte den Beispielcode der offiziellen POI-Website: https://poi.apache.org/components/spreadsheet/how-to.html

Einschränkungen von EventModel

Obwohl die von POI offiziell bereitgestellte EventModel-API die SAX-Methode verwendet, um das Problem der DOM-Analyse zu lösen, gibt es einige Einschränkungen:

① Gehört zur Low-Level-API, hat einen niedrigen Abstraktionsgrad, ist relativ komplex und erfordert hohe Lern- und Verwendungskosten.

② Die Verarbeitungsmethoden für HSSF- und XSSF-Typen sind unterschiedlich und der Code muss je nach Typ kompatibel sein.

③ Das ​​Problem des Speicherüberlaufs kann nicht perfekt gelöst werden, und es gibt noch Raum für eine Optimierung des Speicheroverheads.

④ Es wird nur für die Excel-Analyse verwendet und unterstützt nicht das Schreiben in Excel.

Daher empfiehlt der Autor nicht, das native EventModel von POI zu verwenden . Weitere empfohlene Tools finden Sie unten.

(2)SXSSF

Einführung in SXSSF

SXSSF, der vollständige Name von Streaming XML SpreadSheet Format, ist eine Streaming-Excel-API mit geringem Speicher, die nach POI 3.8-beta3 eingeführt wurde und darauf abzielt, das Speicherproblem beim Schreiben von Excel zu lösen. Es handelt sich um eine Erweiterung von XSSF. Wenn Sie große Datenmengen in Excel schreiben müssen, müssen Sie nur XSSF durch SXSSF ersetzen. Das Prinzip von SXSSF ist ein Schiebefenster, bei dem eine bestimmte Anzahl von Zeilen im Speicher gespeichert und der Rest auf der Festplatte gespeichert wird. Der Vorteil davon ist die Speicheroptimierung, der Preis ist jedoch der Verlust des Direktzugriffs. SXSSF ist mit den meisten APIs von XSSF kompatibel und eignet sich sehr gut für Entwickler, die UserModel verstehen.

Die Speicheroptimierung bringt zwangsläufig bestimmte Einschränkungen mit sich:

① Zu einem bestimmten Zeitpunkt kann nur auf eine begrenzte Anzahl von Zeilen zugegriffen werden, da die restlichen Zeilen nicht in den Speicher geladen werden.

② Unterstützt keine XSSF-APIs, die wahlfreien Zugriff erfordern, z. B. Löschen/Verschieben von Zeilen, Klonen von Blättern, Formelberechnungen usw.

③ Der Excel-Lesevorgang wird nicht unterstützt.

④ Da es sich um eine Erweiterung von XSSF handelt, wird das Schreiben von XLS-Dateien nicht unterstützt.

Vergleich von UserModel, EventModel und SXSSF

Bisher wurden alle POI-Excel-APIs eingeführt. Die folgende Tabelle ist ein Vergleich der Funktionen aller dieser APIs von der offiziellen POI-Website:

Es ist ersichtlich, dass UserModel auf DOM-Analyse basiert, über die umfassendsten Funktionen verfügt und Direktzugriff unterstützt. Der einzige Nachteil besteht darin, dass die CPU- und Speichereffizienz instabil ist.

EventModel ist eine von POI bereitgestellte Streaming-Leselösung, die auf SAX-Analyse basiert und nur den Vorwärtszugriff unterstützt, andere APIs jedoch nicht.

SXSSF ist eine von POI bereitgestellte Streaming-Schreiblösung. Sie kann auch nur vorwärts zugreifen und unterstützt einige XSSF-APIs.

(3)EasyExcel

Einführung in EasyExcel

Um das Problem der nativen SAX-Analyse von POI zu lösen, hat Ali EasyExcel basierend auf POI neu entwickelt. Das Folgende ist eine Einleitung, die von der offiziellen Website von EasyExcel zitiert wird:

Zu den bekannten Frameworks für das Java-Parsen und Generieren von Excel gehören Apache Poi und JXL. Aber sie alle haben ein ernstes Problem, das viel Speicher verbraucht. POI verfügt über eine SAX-Modus-API, die einige Speicherüberlaufprobleme bis zu einem gewissen Grad lösen kann, aber POI weist immer noch einige Mängel auf. Dies geschieht im Speicher und der Speicherverbrauch ist hoch immer noch sehr groß. easyexcel hat die Poi-Analyse von Excel Version 07 neu geschrieben. Ein 3M-Excel benötigt immer noch etwa 100 MB Speicher für die POI-Sax-Analyse. Mit easyexcel kann dieser auf ein paar M reduziert werden, und egal wie groß das Excel ist, es wird keinen Speicherüberlauf geben; Version 03 Hängt vom Sax-Modus von POI ab und kapselt die Modellkonvertierung auf der oberen Ebene, was sie für Benutzer einfacher und bequemer macht.

Wie in der Einleitung erwähnt, verwendet EasyExcel auch SAX-Parsing, aber da das SAX-Parsing von xlsx neu geschrieben wird, wird der Speicheraufwand optimiert; die xls-Datei wird weiter in der oberen Schicht gekapselt, um die Nutzungskosten zu senken. Auf der API werden Annotationen verwendet, um die Excel-Entitätsklasse zu definieren, die einfach zu verwenden ist. Excel wird über den Ereignis-Listener gelesen. Im Vergleich zum nativen EventModel ist die API stark vereinfacht. Beim Schreiben von Daten verwendet EasyExcel wiederholt mehrere Write-at-Befehle Der -a-time-Modus reduziert den Speicheraufwand.

Der größte Vorteil von EasyExcel besteht darin, dass es einfach zu bedienen ist und innerhalb von zehn Minuten genutzt werden kann. Aufgrund der High-Level-Paketierung der POI-API eignet sie sich für Entwickler, die die grundlegende API von POI nicht verstehen möchten. Alles in allem ist EasyExcel eine API, die es wert ist, ausprobiert zu werden.

Verwenden Sie EasyExcel

Einführung der EasyExcel-Abhängigkeit:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.3</version>
</dependency>

Definieren Sie zunächst die Excel-Entitätsklasse mit Anmerkungen:

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class Sku {
    @ExcelProperty(index = 0)
    private Long id;

    @ExcelProperty(index = 1)
    private String name;

    @ExcelProperty(index = 2)
    private Double price;
}

Schreiben Sie als Nächstes die Methoden invoke und doAfterAllAnalysed in AnalysisEventListener neu. Diese beiden Methoden werden aufgerufen, wenn das Ereignis des Abschlusses einer einzeiligen Analyse bzw. das Ereignis des Abschlusses aller Analysen überwacht wird. Jedes Mal, wenn eine einzeilige Analyse abgeschlossen ist, drucken wir das Analyseergebnis aus. Der Code lautet wie folgt:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import org.shy.domain.pojo.easyexcel.Sku;

public class MyEasyExcel {
    public static void main(String[] args) {
        parseSku();
    }

    public static void parseSku() {
        //读取文件路径
        String fileName = "D:\sunhaoyu8\Documents\Files\excel.xlsx";
        //读取excel
        EasyExcel.read(fileName, Sku.class, new AnalysisEventListener<Sku>() {
            @Override
            public void invoke(Sku sku, AnalysisContext analysisContext) {
                System.out.println("第" + analysisContext.getCurrentRowNum() + "行:" + JSON.toJSONString(sku));
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                System.out.println("全部解析完成");
            }
        }).sheet().doRead();
    }
}

Machen Sie einen Test und analysieren Sie damit eine Excel-Datei mit 100.000 Zeilen. Die Datei wird beim Lesen durch UserModel wie folgt OOM sein:

Operationsergebnis:

(4)Xlsx-Streamer

Einführung in den Xlsx-Streamer

Xlsx-streamer ist ein Tool zum Streamen von Excel, das ebenfalls auf der POI-Sekundärentwicklung basiert. Obwohl EasyExcel das Problem des Excel-Lesens sehr gut lösen kann, ist die Analysemethode SAX, die durch die Implementierung eines Listeners ereignisgesteuert analysiert werden muss. Gibt es eine andere Möglichkeit, es zu analysieren? Xlsx-Streamer gibt die Antwort.

Beschreibung aus offizieller Dokumentation übersetzt:

Wenn Sie in der Vergangenheit Apache POI zum Lesen von Excel-Dateien verwendet haben, ist Ihnen möglicherweise aufgefallen, dass es nicht sehr speichereffizient ist. Das Lesen der gesamten Arbeitsmappe kann zu erheblichen Speicherauslastungsspitzen führen, die verheerende Auswirkungen auf den Server haben können. Es gibt viele Gründe, warum Apache die gesamte Arbeitsmappe lesen muss, aber die meisten davon haben mit der Bibliothek zu tun, die es Ihnen ermöglicht, zufällige Adressen zum Lesen und Schreiben zu verwenden. Wenn (und nur wenn) Sie einfach nur den Inhalt einer Excel-Datei schnell und speichereffizient lesen möchten, benötigen Sie diese Funktion wahrscheinlich nicht. Leider erfordert die einzige Möglichkeit in der POI-Bibliothek zum Lesen von Streaming-Arbeitsmappen, dass Ihr Code einen SAX-ähnlichen Parser verwendet. Alle benutzerfreundlichen Klassen wie Row und Cell fehlen in der API. Diese Bibliothek fungiert als Wrapper für die Streaming-API und behält gleichzeitig die Syntax der Standard-POI-API bei. Lesen Sie weiter, um herauszufinden, ob es das Richtige für Sie ist. HINWEIS: Diese Bibliothek unterstützt nur das Lesen von XLSX-Dateien.

Wie in der Einleitung erwähnt, besteht der größte Vorteil von Xlsx-streamer darin, dass er mit der Gewohnheit des Benutzers, POI UserModel zu verwenden, kompatibel ist und eine eigene Streaming-Implementierung für alle UserModel-Schnittstellen wie StreamingSheet, StreamingRow usw. bereitstellt Mit der Entwicklung von UserModel vertraut Für Benutzer gibt es fast keine Lernschwelle und sie können UserModel direkt verwenden, um auf Excel zuzugreifen.

Das Implementierungsprinzip von Xlsx-streamer ist das gleiche wie das von SXSSF. Bei beiden handelt es sich um Schiebefenster: Begrenzen Sie die Größe der in den Speicher gelesenen Daten, lesen Sie die zu analysierenden Daten in den Speicherpuffer und bilden Sie eine temporäre Datei, um dies zu verhindern eine große Speichermenge wird nicht verwendet. Der Inhalt des Puffers ändert sich während des Parsing-Vorgangs weiter. Wenn der Stream geschlossen wird, wird auch die temporäre Datei gelöscht. Aufgrund des Speicherpuffers wird der gesamte Stream nicht vollständig in den Speicher eingelesen, wodurch ein Speicherüberlauf verhindert wird.

Da wie bei SXSSF nur einige Zeilen in den Speicher geladen werden, wird die Fähigkeit des Direktzugriffs geopfert, und auf die gesamte Tabelle kann nur über die Durchlaufreihenfolge zugegriffen werden, was eine unvermeidliche Einschränkung darstellt. Mit anderen Worten: Wenn die Methode StreamingSheet.getRow(int rownum) aufgerufen wird, ruft die Methode die angegebene Zeile des Blatts ab und es wird die Ausnahme „Der Vorgang wird nicht unterstützt“ ausgelöst.

Der größte Vorteil von Xlsx-streamer besteht darin, dass es mit UserModel kompatibel ist, insbesondere für Entwickler, die mit UserModel vertraut sind, aber kein umständliches EventModel verwenden möchten. Wie SXSSF bietet es eine Lösung für das Speicherproblem, indem es die UserModel-Schnittstelle implementiert und die Lücke füllt, die SXSSF nicht unterstützt. Man kann sagen, dass es sich um die „Leseversion“ von SXSSF handelt.

Verwenden Sie den Xlsx-Streamer

Pom-Abhängigkeiten einführen:

    <dependency>
        <groupId>com.monitorjbl</groupId>
        <artifactId>xlsx-streamer</artifactId>
        <version>2.1.0</version>
    </dependency>

Hier ist eine Demo mit xlsx-streamer:

import com.monitorjbl.xlsx.StreamingReader;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.FileInputStream;

public class MyXlsxStreamer {
    public static void main(String[] args) throws Exception {
        parseSku();
    }

    public static void parseSku() throws Exception {
        FileInputStream in = new FileInputStream("D:\sunhaoyu8\Documents\Files\excel.xlsx");
        Workbook wk = StreamingReader.builder()
                //缓存到内存中的行数,默认是10
                .rowCacheSize(100)
                //读取资源时,缓存到内存的字节大小,默认是1024
                .bufferSize(4096)
                //打开资源,必须,可以是InputStream或者是File
                .open(in);
        Sheet sheet = wk.getSheetAt(0);

        for (Row r : sheet) {
            System.out.print("第" + r.getRowNum() + "行:");
            for (Cell c : r) {
                if (c != null) {
                    System.out.print(c.getStringCellValue() + " ");
                }
            }
            System.out.println();
        }
    }
}

Wie im Code gezeigt, lautet die Verwendungsmethode von Xlsx-streamer: Verwenden Sie StreamingReader für die Parameterkonfiguration und das Streaming-Lesen. Wir können die feste Schiebefenstergröße manuell konfigurieren. Es gibt zwei Indikatoren, nämlich die maximale Anzahl der im Speicher zwischengespeicherten Zeilen und die Die maximale Anzahl der im Speicher zwischengespeicherten Bytes begrenzen diese beiden Indikatoren gleichzeitig die Obergrenze des Schiebefensters. Als nächstes können wir die API von UserModel verwenden, um die Lesetabelle zu durchlaufen und darauf zuzugreifen.

Verwenden Sie zum Testen eine Excel-Datei mit 100.000 Zeilen. Das laufende Ergebnis ist:

StAX-Analyse

Die unten im Xlsx-Streamer verwendete Analysemethode heißt StAX-Analyse. StAX wurde im März 2004 in der JSR 173-Spezifikation eingeführt und ist eine neue Funktion, die mit JDK 6.0 eingeführt wurde. Der vollständige Name lautet Streaming API for XML, Streaming-XML-Analyse. Genauer gesagt heißt es „Streaming Pull Analytics“. Der Grund, warum sie Pull-Analyse genannt wird, liegt darin, dass sie das Gegenteil der „Streaming-Push-Analyse“ – der SAX-Analyse – ist.

Wir haben bereits erwähnt, dass SAX-Parsing ein ereignisgesteuertes Parsing-Modell ist. Immer wenn ein Tag analysiert wird, wird der entsprechende Ereignishandler ausgelöst, um das Ereignis an den Responder zu „pushen“. In einem solchen Push-Modell ist der Parser aktiv und der Responder passiv. Wir können nicht auswählen, auf welche Ereignisse wir reagieren möchten, daher ist eine solche Analyse relativ unflexibel.

Um das Problem der SAX-Analyse zu lösen, verwendet die StAX-Analyse eine „Pull“-Methode: Wenn der Parser den Stream durchläuft, wird der ursprüngliche Responder zum Treiber, durchläuft aktiv den Ereignisparser (Iterator) und zieht einen nach dem anderen daraus. Ereignis erkennen und damit umgehen. Beim Parsen unterstützt StAX die Verwendung der peek()-Methode, um einen Blick auf das nächste Ereignis zu werfen und zu entscheiden, ob eine Analyse des nächsten Ereignisses erforderlich ist, ohne das Ereignis aus dem Stream lesen zu müssen. Dadurch können Flexibilität und Effizienz effektiv verbessert werden.

Lassen Sie uns dasselbe XML noch einmal mit StAX analysieren:

<?xml version="1.0" encoding="UTF-8"?>
<skus>
    <sku id="345000">
        <name>电脑A</name>
        <price>5999.0</price>
   </sku>
    <sku id="345001">
        <name>手机C</name>
        <price>4599.0</price>
   </sku>
</skus>

Dieses Mal benötigen wir keinen Listener und integrieren die gesamte Verarbeitungslogik in einer Methode:

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.shy.domain.pojo.Sku;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.InputStream;
import java.util.Iterator;


public class MyStax {

    /**
     * 当前正在处理的sku
     */
    private static Sku sku;
    /**
     * 当前正在处理的节点名称
     */
    private static String tagName;

    public static void main(String[] args) throws Exception {
        parseSku();
    }
    
    public static void parseSku() throws Exception {
        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("skus.xml");
        XMLEventReader xmlEventReader = inputFactory.createXMLEventReader(inputStream);
        while (xmlEventReader.hasNext()) {
            XMLEvent event = xmlEventReader.nextEvent();
            // 开始节点
            if (event.isStartElement()) {
                StartElement startElement = event.asStartElement();
                String name = startElement.getName().toString();
                if ("sku".equals(name)) {
                    sku = new Sku();
                    Iterator iterator = startElement.getAttributes();
                    while (iterator.hasNext()) {
                        Attribute attribute = (Attribute) iterator.next();
                        if ("id".equals(attribute.getName().toString())) {
                            sku.setId(Long.valueOf(attribute.getValue()));
                        }
                    }
                }
                tagName = name;
            }
            // 字符
            if (event.isCharacters()) {
                String data = event.asCharacters().getData().trim();
                if (StringUtils.isNotEmpty(data)) {
                    if ("name".equals(tagName)) {
                        sku.setName(data);
                    }
                    if ("price".equals(tagName)) {
                        sku.setPrice(Double.valueOf(data));
                    }
                }
            }
            // 结束节点
            if (event.isEndElement()) {
                String name = event.asEndElement().getName().toString();
                if ("sku".equals(name)) {
                    System.out.println(JSON.toJSONString(sku));
                    // 处理业务逻辑
                    // ...
                }
            }
        }
    }
}

Der obige Code entspricht der Logik der SAX-Analyse. Verwenden Sie XMLEventReader als Iterator, um Ereignisse aus dem Stream zu lesen, den Ereignisiterator zu durchlaufen und dann entsprechend dem Ereignistyp zu klassifizieren. Interessierte Partner können es selbst ausprobieren und weitere Details der StAX-Analyse erkunden.

4. Fazit

EventModel, SXSSF, EasyExcel und Xlsx-streamer bieten jeweils ihre eigenen Lösungen für das Speichernutzungsproblem von UserModel. Im Folgenden finden Sie einen Vergleich aller in diesem Artikel erwähnten Excel-APIs:

  Benutzermodell EventModel SXSSF EasyExcel Xlsx-Streamer
Speichernutzung hoch untere Niedrig Niedrig Niedrig
Vollständiger Direktzugriff auf die Tabelle Ja NEIN NEIN NEIN NEIN
Excel lesen Ja Ja NEIN Ja Ja
Lesemethode DOM SAXOPHON -- SAXOPHON StAX
Excel schreiben Ja Ja Ja Ja NEIN

Es wird empfohlen, die entsprechende API entsprechend Ihrem Nutzungsszenario auszuwählen:

  1. Um den Anforderungen großer Mengen an Excel-Dateien gerecht zu werden, wird empfohlen, POI UserModel und EasyExcel zu wählen.

  2. Zum Lesen großer Mengen an Excel-Dateien werden EasyExcel und Xlsx-streamer empfohlen;

  3. Zum Schreiben großer Mengen an Excel-Dateien werden SXSSF und EasyExcel empfohlen.

Die Verwendung der oben genannten API wird definitiv den Anforderungen der Excel-Entwicklung gerecht. Natürlich ist die Excel-API nicht auf diese beschränkt, und es gibt viele APIs desselben Typs. Sie sind herzlich eingeladen, mehr zu erkunden und zu innovieren.

Seitenlink:

Offizielle POI-Website: https://poi.apache.org/

Offizielle EasyExcel-Website: https://easyexcel.opensource.alibaba.com

Xlsx-Streamer Github: https://github.com/monitorjbl/excel-streaming-reader

Autor: JD Insurance Sun Haoyu

Quelle: JD Cloud Developer Community

Absolventen der Nationalen Volksuniversität haben die Informationen aller Schüler der Schule gestohlen, um eine Website zur Schönheitsbewertung zu erstellen, und wurden strafrechtlich festgenommen. Die neue Windows-Version von QQ basierend auf der NT-Architektur wird offiziell veröffentlicht. Die Vereinigten Staaten werden die Verwendung durch China einschränken von Amazon, Microsoft und anderen Cloud-Diensten, die Trainings-KI-Modelle bereitstellen. Open-Source-Projekte haben angekündigt, die Funktionsentwicklung zu stoppen . LeaferJS , die bestbezahlte technische Position im Jahr 2023, wurde veröffentlicht: Visual Studio Code 1.80, eine Open-Source- und leistungsstarke 2D-Grafikbibliothek , unterstützt Terminal-Image-Funktionen . Die Anzahl der Threads-Registrierungen hat 30 Millionen überschritten. „Änderung“ Deepin übernimmt Asahi Linux, um sich im Juli an das Apple M1-Datenbankranking anzupassen: Oracle steigt und öffnet die Punktzahl erneut
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10086061
Recomendado
Clasificación