-
Notifications
You must be signed in to change notification settings - Fork 0
/
java-reader-und-writer.html
18 lines (17 loc) · 16.2 KB
/
java-reader-und-writer.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html><html lang="de-ch"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Java Reader und Writer - Finecloud</title><meta name="description" content="Lese und Schreiboperationen an Dateien sind in Java streambasiert. Diese Streams haben aber nichts mit der Stream-API zu tun. Streambasierte I/O bedeutet, dass nicht alle Dateien im Speicher behalten werden müssen, damit man mit ihnen Arbeiten kann. Man muss also nicht den gesamten Dateiinhalt lesen,…"><meta name="generator" content="Publii Open-Source CMS for Static Site"><link rel="stylesheet" href="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism-black.css"><link rel="canonical" href="https://www.finecloud.ch/java-reader-und-writer.html"><link rel="alternate" type="application/atom+xml" href="https://www.finecloud.ch/feed.xml"><link rel="alternate" type="application/json" href="https://www.finecloud.ch/feed.json"><meta property="og:title" content="Java Reader und Writer"><meta property="og:site_name" content="Finecloud"><meta property="og:description" content="Lese und Schreiboperationen an Dateien sind in Java streambasiert. Diese Streams haben aber nichts mit der Stream-API zu tun. Streambasierte I/O bedeutet, dass nicht alle Dateien im Speicher behalten werden müssen, damit man mit ihnen Arbeiten kann. Man muss also nicht den gesamten Dateiinhalt lesen,…"><meta property="og:url" content="https://www.finecloud.ch/java-reader-und-writer.html"><meta property="og:type" content="article"><link rel="shortcut icon" href="https://www.finecloud.ch/media/website/finecloud.png" type="image/png"><link rel="stylesheet" href="https://www.finecloud.ch/assets/css/style.css?v=39da73365516a098a9b73b721fc970e2"><script type="application/ld+json">{"@context":"http://schema.org","@type":"Article","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.finecloud.ch/java-reader-und-writer.html"},"headline":"Java Reader und Writer","datePublished":"2022-06-06T11:40","dateModified":"2022-06-20T07:09","description":"Lese und Schreiboperationen an Dateien sind in Java streambasiert. Diese Streams haben aber nichts mit der Stream-API zu tun. Streambasierte I/O bedeutet, dass nicht alle Dateien im Speicher behalten werden müssen, damit man mit ihnen Arbeiten kann. Man muss also nicht den gesamten Dateiinhalt lesen,…","author":{"@type":"Person","name":"Finecloud","url":"https://www.finecloud.ch/authors/finecloud/"},"publisher":{"@type":"Organization","name":"Finecloud"}}</script><meta name="google-site-verification" content="seFY9U12uiEq5U3_MyZiX6XWzk0AVFl9zITr2ZKsytY"></head><body><div class="site-container"><header class="top" id="js-header"><a class="logo" href="https://www.finecloud.ch/">Finecloud</a><nav class="navbar js-navbar"><button class="navbar__toggle js-toggle" aria-label="Menu" aria-haspopup="true" aria-expanded="false"><span class="navbar__toggle-box"><span class="navbar__toggle-inner">Menu</span></span></button><ul class="navbar__menu"><li><a href="https://www.finecloud.ch/" target="_self">Blog</a></li><li><a href="https://www.finecloud.ch/tags/" target="_self">Tags</a></li></ul></nav><div class="search"><div class="search__overlay js-search-overlay"><div class="search__overlay-inner"><form action="https://www.finecloud.ch/search.html" class="search__form"><input class="search__input js-search-input" type="search" name="q" placeholder="search..." aria-label="search..." autofocus="autofocus"></form><button class="search__close js-search-close" aria-label="Close">Close</button></div></div><button class="search__btn js-search-btn" aria-label="Search"><svg role="presentation" focusable="false"><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#search"/></svg></button></div></header><main><article class="post"><div class="hero"><figure class="hero__image hero__image--overlay"><img src="https://www.finecloud.ch/media/website/download.jpg" srcset="https://www.finecloud.ch/media/website/responsive/download-xs.jpg 300w, https://www.finecloud.ch/media/website/responsive/download-sm.jpg 480w, https://www.finecloud.ch/media/website/responsive/download-md.jpg 768w, https://www.finecloud.ch/media/website/responsive/download-lg.jpg 1024w, https://www.finecloud.ch/media/website/responsive/download-xl.jpg 1360w, https://www.finecloud.ch/media/website/responsive/download-2xl.jpg 1600w" sizes="100vw" loading="eager" alt=""></figure><header class="hero__content"><div class="wrapper"><div class="post__meta"><time datetime="2022-06-06T11:40">Juni 6, 2022</time></div><h1>Java Reader und Writer</h1></div></header></div><div class="wrapper post__entry"><div class="post__toc"><h3>Table of Contents</h3><ul><li><a href="#mcetoc_1g4tdq62j5mq">Lesen und Schreiben von Textdaten</a></li><li><a href="#mcetoc_1g4tdq62j5mr">Puffern und zeilenweise lesen</a></li><li><a href="#mcetoc_1g4tdq62j5ms">Schreiben mit Writer</a></li></ul></div><p>Lese und Schreiboperationen an Dateien sind in Java streambasiert. Diese Streams haben aber nichts mit der Stream-API zu tun. Streambasierte I/O bedeutet, dass nicht alle Dateien im Speicher behalten werden müssen, damit man mit ihnen Arbeiten kann. Man muss also nicht den gesamten Dateiinhalt lesen, bevor man ihn verarbeiten kann. Folglich muss man bei einer Netzwerkverbindung nicht darauf warte, dass alle Daten eingegangen sind, stattdessen kann man die Daten Stück für Stück aus einem <em>InputStream</em> lesen und verarbeiten. Im Idealfall kann das gerade eingelesene Datenstück bereits wieder aus dem Speicher entfernt werden, bevor das nächste Datenstück gelesen wird. Genau so können auch Datenstücke in einen OutputStream bereits geschrieben werden sobald diese zur Verfügung stehen und muss nicht zuerst gewartet werden, bis alle Daten bereit sind.</p><p>Java macht einen Unterschied ob, mit Textdaten oder mit Binärdaten gearbeitet wird. Textdaten werden mit einem <em>Reader</em> gelesen und mit einem <em>Writer</em> geschrieben, für Binärdaten gibt es dafür <em>InputStream</em> und <em>OutputStream</em>.</p><h3 id="mcetoc_1g4tdq62j5mq">Lesen und Schreiben von Textdaten</h3><p>Folgender Code liest Daten zeilenweise ein:</p><p><code>try (BufferedReader reader = new BufferedReader(new FileReader(dateiname))){</code><br><code> …</code><br><code>}</code></p><p>Das ist bereits eine spezialisierte Funktion, die nur BufferedReader bietet. Andere Reader, zum Beispiel der <em>FileReader</em>, wissen nichts von Zeilen, sie arbeiten nur mit Zeichen. Dazu bietet <em>Reader</em> eine parameterlose Methode read, die genau ein Zeichen liest. Das ist zwar die für den Entwickler einfachste Variante, sie ist aber auch äusserst ineffektiv. Der komplexere, aber bessere Weg, aus einem <em>Reader</em> zu lesen, ist ein char[] als Puffer zu benutzen:</p><p><code>File quelle = new File(…);</code><br><code>char[] buffer = new char[1024];</code><br><code>try (Reader reader = new FileReader(quelle)) {</code><br><code> int gelesen;</code><br><code> while ((gelesen = reader.read(buffer)) > -1) {</code><br><code> char[] geleseneDaten = (gelesen == buffer.length)</code><br><code> ? buffer</code><br><code> : Arrays.copyOf(buffer, gelesen);</code><br><code> vearbeitePuffer(geleseneDaten);</code><br><code> }</code><br><code>}</code></p><p>Damit wird wesentlich effizienter gelesen, als Zeichen für Zeichen. Mit jedem Aufruf von read wird der Puffer gefüllt. Der Rückgabewert ist die Anzahl Zeichen, die vom Stream gelesen wurden. Meist entspricht er der Grösse des Puffers, es können aber weniger Zeichen gelesen werden, wenn das Ende der Daten erreicht ist oder gerade in diesem Moment keine Daten mehr zur Verfügung stehen. Wenn das Ende des Datenstroms erreicht ist, gibt read -1 zurück: Daten werden in einer Schleife gelesen und verarbeitet, bis dieser Punkt erreicht ist.</p><p><strong>Wenn weniger Zeichen gelesen werden, als die Puffergrösse gross ist, wird der Rest des Puffers nicht verändert. Das bedeutet, dass am Ende des char-Arrays Daten aus dem Vorherigen Schleifen-Durchlauf stehen können. Deswegen werden die gelesenen Daten, falls es weniger als die Puffergrösse waren, in ein neues Array kopiert; so kann die Methode <em>verarbeitePuffer</em> immer mit einem vollständigen Array arbeiten und muss sich keine Sorgen um übrig gebliebene Daten am Ende des Arrays machen. </strong>Es ist zwar performanter, der verarbeitenden Methode das teilweise gefüllte Array und den Endindex zu übergeben, die gezeigt Variante ist aber weniger fehleranfällig, weil man in <em>verarbeitePuffer</em> nicht darauf achten muss, wann man mit lesen aufhört.</p><p><strong>Es ist sehr wichtig, dass man eine Datei nach dem Zugriff darauf wieder schliesst.</strong> Im Beispiel geschieht das implizit durch das Statement try-with-resources, das an seinen Ressourcen automatisch close aufruft. Sollte man aus irgendeinem Grund dieses Statement nicht verwenden können oder wollen, dann muss man selbst sicherstellen, dass der Reader (oder Writer, InputStream, OutputStream oder jedes Objekt, das auf eine Datei zugreift) ordnungsgemäss geschlossen wird.</p><p><code>public void liesAusDatei(File quelle) throws IOException{</code><br><code> Reader reader = null;</code><br><code> try {</code><br><code> reader = new BufferedReader(new FileReader(quelle));</code><br><code> //Daten lesen und verarbeiten</code><br><code> } finally {</code><br><code> if (reader != null){</code><br><code> reader.close();</code><br><code> }</code><br><code> }</code><br><code>}</code></p><p>Dieser Code ist etwas unhandlicher und hat zwei Unschönheiten. Die Reader-Variable muss ausserhalb des try-Blocks deklariert werden, da try und Finally keinen gemeinsamen Scope haben. Ausserdem muss im finally-Block geprüft werden, ob der Reader nicht null ist. Das kann passieren, wenn schon beim Erzeugen des Readers eine Exception auftritt, weil Beispielsweise die Datei nicht existiert. In diesem Fall gibt es keinen Reader, der geschlossen werden kann, und ohne die entsprechende Prüfung käme es zu einer weiteren NullPointerException.</p><p>Ein weiteres Problem ist, dass auch reader.close eine IOException werfen kann. Im Beispiel werden innerhalb der Methode liesAusDatei keine Fehler behandelt, alle Fehler werden an den Aufrufer weitergereicht. Im schlimmsten Fall kann es so passieren, dass sowohl im try- als auch im finally-Block Fehler geworfen werden. Der Aufrufer erhält dann nur die Exception aus dem finally-Block, obwohl sie wahrscheinlich nur eine Konsequenz der Exception aus dem try-Block ist. <strong>Das untere Code-Beispiel ist also länger, komplexer und fehleranfälliger. Es gibt somit keinen Grund, diese Variante zu bevorzugen, wenn die Java-Version try-with-resources unterstützt.</strong></p><h3 id="mcetoc_1g4tdq62j5mr">Puffern und zeilenweise lesen</h3><p>Das Puffern der Daten in einem char[] kann man sich theoretisch sparen, wenn man einen BufferedReader einsetzt. Dessen Hauptaufgabe ist es, zu verhindern, dass Daten Byte für Byte von der Festplatte gelesen werden. Dazu liest er immer einen Puffer voll Daten ein, genau wie im oberen Beispiel. Nachfolgende read-Aufrufe werden dann aus dem Puffer bedient, solange dieser noch genügend Daten enthält, erst danach wird wieder auf die Festplatte zugegriffen.</p><p>Als Nebeneffekt seines Puffers hat der BufferedReader aber eine weitere nützliche Fähigkeit: Er kann Textdateien zeilenweise lesen. BufferedReader bietet sowohl die Methode readLine, die die nächste Zeile der Datei liefert, als auf die Methode lines, die alle Zeilen der Datei in einem Stream liefert. Wenn der Inhalt der Datei zeilenorientiert ist, dann ist das viel praktischer, als Daten Zeichen für Zeichen oder Puffer für Puffer einzulesen und selbst nach den Umbrüchen zu suchen.</p><p>Eine BufferedReader lässt sich aus jedem anderen Reader erzeugen, indem man diesen als parameter an den Konstruktor von BufferedReader übergibt:</p><p><code>try (BufferedReader reader = new BufferedReader(new FileReader(quelle))) {</code><br><code> String zeile;</code><br><code> while ((zeile = reader.readLine()) != null){</code><br><code> verarbeiteZeile(zeile);</code><br><code> }</code><br><code>}</code></p><p>Der zugrunde liegende FileReader wird in einem BufferReader verpackt, um die Fähigkeit zu puffern und zeilenweise zu lesen hinzuzufügen. Das ist eine Anwendung des Decorator-Entwurfsmusters, das für Ein- und Ausgabe in Java extensiv zum Einsatz kommt. Man muss in diesem Fall nur den BufferedReader schliessen, dessen close-Methode ruft automatisch die close-Methode des dekorierten Readers auf.</p><h3 id="mcetoc_1g4tdq62j5ms">Schreiben mit Writer</h3><p>Das Schreiben in eine Date funktioniert fast genau so wie das Lesen aus einer Datei. Man erzeugt ein FileWriter-Objekt, dekoriert es noch mit einem BufferedWriter, schreibt Daten hinein und schliesst den Writer wieder:</p><p><code>try (BufferedWriter writer = new BufferedWriter(new FileWriter(ziel))) {</code><br><code> for (String zeile : zeilen){</code><br><code> writer.write(zeile);</code><br><code> writer.newLine();</code><br><code> }</code><br><code>}</code></p><p>So schreibt man eine Datei Zeile für Zeile. Writer sind in vielerlei Hinsicht das Spiegelbild von Readern. Sie besitzen eine Methode, die einzelne char-Werte schreibt und eine Methode, die ein ganzes char[] schreibt - man kann einen Schreibpuffer erzeugen, indem man seinen Writer mit einem BufferedWriter dekoriert und auch den Writer in diesem Fall schliesst, wenn man damit fertig ist. Writer selbst kennen das Konzept der Zeile ebenfalls nicht. Wenn man zeilenweise schreiben möchte, dann ist der beste Weg, einen BufferedWriter und seine Methode newLine zu verwenden, um an Ende jeder Zeile einen Umbruch zu erzeugen.</p><p> </p><p> </p></div><footer class="wrapper post__footer"><p class="post__last-updated">This article was updated on Juni 20, 2022</p><ul class="post__tag"><li><a href="https://www.finecloud.ch/tags/java/">java</a></li><li><a href="https://www.finecloud.ch/tags/softwareentwicklung/">software development</a></li></ul><div class="post__share"></div></footer></article><div class="post__related related"><div class="wrapper"><h2 class="h5 related__title">You should also read:</h2><article class="related__item"><div class="feed__meta"><time datetime="2022-05-26T14:07" class="feed__date">Mai 26, 2022</time></div><h3 class="h1"><a href="https://www.finecloud.ch/java-collection-iteratoren.html">Java Collection Iteratoren</a></h3></article><article class="related__item"><div class="feed__meta"><time datetime="2022-05-26T13:28" class="feed__date">Mai 26, 2022</time></div><h3 class="h1"><a href="https://www.finecloud.ch/java-collection-sets.html">Java Collection Sets</a></h3></article><article class="related__item"><div class="feed__meta"><time datetime="2022-05-26T13:19" class="feed__date">Mai 26, 2022</time></div><h3 class="h1"><a href="https://www.finecloud.ch/java-collection-listen.html">Java Collection Listen</a></h3></article></div></div></main><footer class="footer"><div class="footer__copyright"><p>Powered by Publii</p></div><button onclick="backToTopFunction()" id="backToTop" class="footer__bttop" aria-label="Back to top" title="Back to top"><svg><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#toparrow"/></svg></button></footer></div><script>window.publiiThemeMenuConfig = {
mobileMenuMode: 'sidebar',
animationSpeed: 300,
submenuWidth: 'auto',
doubleClickTime: 500,
mobileMenuExpandableSubmenus: true,
relatedContainerForOverlayMenuSelector: '.top',
};</script><script defer="defer" src="https://www.finecloud.ch/assets/js/scripts.min.js?v=6ca8b60e6534a3888de1205e82df8528"></script><script>var images = document.querySelectorAll('img[loading]');
for (var i = 0; i < images.length; i++) {
if (images[i].complete) {
images[i].classList.add('is-loaded');
} else {
images[i].addEventListener('load', function () {
this.classList.add('is-loaded');
}, false);
}
}</script><script defer="defer" src="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism.js"></script></body></html>