Javaでファイル読み込みする超実践的な3つの方法【初心者でもできる】
Javaが誕生したのは1995年、2019年現在では24年ほどの歴史があります。その長い歴史の中では、ファイルの読み込み方も少しずつ変わっています。
Javaでのファイルの読み込み方には、Java 11の時点では大きく分けて以下の三つの方法があります。それぞれ特徴がありますので、この記事でぜひそれぞれの使い方を身に着けてください。
- java.nio.file.Filesで読み込む
- java.io.FileReader/FileInputStreamで読み込む
- java.nio.channels.FileChannelで読み込む
※この記事のサンプルは、Java 11の環境で動作確認しています
目次
1.【お手軽】Filesで読み込む
Java 11が出てしばらく経った今、一番お手軽なのはjava.nio.file.Filesを使うことです。ものすごく簡単で、今まで勉強してきたノウハウはなんだったのかと、愕然とするくらいです。
1-1.ファイルの内容をStringにする【Java 11以降】
テキストファイルをすべて一度に読み込んで一つのStringにするには、Files.readStringを使います(Java 11以降)。
// Java 11以降 Path file = Paths.get("/A/B/C.txt"); String text = Files.readString(file);
ちなみに、引数がPathだけのFiles.readStringは、ファイルのエンコーディング(いわゆる文字コードのことです)がUTF-8であることが前提です。エンコーディングがUTF-8以外のファイルを読ませると、文字化けしてしまいます。
Shift-JISなど、UTF-8以外のエンコーディングのファイルを読みたいなら、引数にjava.nio.charset.Charsetを指定できるメソッドがありますので、それらを使いましょう。
// Java 11以降 // ファイルのエンコーディングを指定する場合(Charset) Path file = Paths.get("/A/B/C.txt"); String text = Files.readString(file, Charset.forName("MS932"));
Filesには他にも便利なメソッドがたくさんありますが、エンコーディングの指定の仕方は全て同じです。
1-2.ファイルの内容をStringのListにする【Java 7以降】
Files.readAllLinesを使えば、ファイルの内容をStringのListにすることも超簡単です(Java 7以降)。
// Java 7以降 Path file = Paths.get("/A/B/C.txt"); List<String> text = Files.readAllLines(file); // UTF-8 List<String> text2 = Files.readAllLines(file, Charset.forName("MS932")); // MS932
1-3.ファイルの内容をbyte配列にする【Java 7以降】
同じノリで、Files.readAllBytesを使って、ファイルをバイナリデータとしてbyte配列に読み込んでみましょう(Java 7以降)。これもメソッド一つで簡単にできます。
// Java 7以降 Path file = Paths.get("/A/B/C.txt"); byte[] bytes = Files.readAllBytes(file);
1-4.ファイルの内容をStreamで読み込む【Java 8以降】
Files.linesを使えば、ファイルの各行を持つStreamを作れます(Java 8以降)。ファイルの内容を入力元としたStreamを簡単に作れるので、色々と手間が省けます。
// Java 8以降 Path file = Paths.get("/A/B/C.txt"); Files.lines(file).forEach(System.out::println); // UTF-8 Files.lines(file, Charset.forName("MS932")).forEach(System.out::println); // MS932
1-5.Filesを使う時はメモリ不足に気を付けよう
Filesのメソッドを使う時は、何Gバイトもあるような巨大なファイルには使わないようにしましょう。OutOfMemoryErrorが簡単に起きてしまいます。
Filesのメソッドは使い方がとても簡単です。プログラムもとても短く書けますので、普段使いとしては便利なものです。でも、逆にプログラム上でのファイルの読み込み方を隠してしまってもいます。
ですので、もしJavaのファイル読み込みの勉強をしたいなら、次章で説明するReader/InputStreamの使い方を学んでおいた方が良いでしょう。メモリを効率的に使えますし、クラスごとの機能を組み合わせて作る、Decoratorパターンの見本でもあるからです。
2.【基本】Reader/InputStreamで読み込む
Java 7でFilesが追加されるまでは、java.io.FileReaderやFileInputStreamなどを使ってファイルを読み込むのが、Javaでの普通でした。もちろん、これらは今でも使えます。
このやり方なら、現在のJavaの環境であれば、まず100%どこでも使えます。互換性を重視した大安定な方法と言えますし、このやり方ならわかるという人も非常に多いでしょう。
2-1.テキストファイルを一行ずつ読み込む
テキストファイルを読み込む場合は、FileReaderの他に、BufferedReader/FileInputStream/InputStreamReaderなどを組み合わせます。
FileReaderだけでもテキストファイルの読み込みはできますが、intでの文字単位やcharの配列でしか読み込めないので、使い勝手はよくありません。
BufferedReaderと組み合わせれば、BufferedReader.readLineを使って行単位で読み込めます。しかも、BufferedReaderでのバッファリングにより読み込みの性能も向上しますので、このやり方がJavaでのスタンダードです。
2-1-1.FileReaderで読み込む
FileReaderとBufferedReaderを使うのは、ファイルのエンコーディングがJavaを実行している環境のデフォルトでいい場合です。
この例ではファイルを先頭行から順番に読み込んでいます。ファイルを読み込み終わったらcloseを忘れないように!!
File file = new File("/A/B/C.txt"); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String text; while ((text = br.readLine()) != null) { System.out.println(text); } br.close();
プログラムを短くするのと、closeを忘れないためにも、Java 7以降で使えるtry-with-resourcesと組み合わせるのが鉄板です。Java 6以前なら、try-finallyと組み合わせましょう。
// Java 7以降 File file = new File("/A/B/C.txt"); try (BufferedReader br = new BufferedReader(new FileReader(file))) { String text; while ((text = br.readLine()) != null) { System.out.println(text); } }
// Java 6以前 File file = new File("/A/B/C.txt"); BufferedReader br = null; try { br = new BufferedReader(new FileReader(file)); String text; while ((text = br.readLine()) != null) { System.out.println(text); } } finally { if (br != null) { br.close(); } }
2-1-2.FileInputStreamでエンコーディングを指定して読み込む
ファイルのエンコーディングを指定する場合は、java.io.InputStreamReaderとFileInputStremも組み合わせましょう。このように、やり方が少し違うのが覚えづらいところです。
File file = new File("/A/B/C.txt"); try (BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream(file), Charset.forName("MS932")))) { String text; while ((text = br.readLine()) != null) { System.out.println(text); } }
2-1-3.【Java 8以降】FilesからBufferedReaderを取得して読み込む
ちなみに、ファイルを読み込むためのBufferedReaderは、Files.newBufferedReaderでも取得できます(Java 8以降)。BufferedReaderを取得すれば、同様の使い方が出来ます。
// Java 8以降 Path file = Paths.get("/A/B/C.txt"); try (BufferedReader br = Files.newBufferedReader(file)) { String text; while ((text = br.readLine()) != null) { System.out.println(text); } }
// Java 8以降 // ファイルのエンコーディングを指定する場合 Path file = Paths.get("/A/B/C.txt"); try (BufferedReader br = Files.newBufferedReader(file, Charset.forName("MS932"))) { String text; while ((text = br.readLine()) != null) { System.out.println(text); } }
2-2.バイナリファイルを読み込む
バイナリファイルを読み込む場合は、java.io.BufferedInputStreamとFileInputStreamを組み合わせます。BufferedInputStreamは、BufferedReaderと同じような、バッファリングをする役割を持つInputStreamです。
以下の例のように、ある程度の大きさのbyte配列を作って、配列の中に少しずつ読み込んでいくのが普通です。readの戻り値は読み込んだデータのバイト数ですので、それを上限として配列からデータを読みます。
File file = new File("/A/B/C.txt"); try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { byte[] bytes = new byte[1024]; int read = 0; while ((read = bis.read(bytes, 0, bytes.length)) != -1) { // 配列bytesに読み込まれたバイナリデータで何かをする // 配列bytesには、インデックスreadまでデータが読みこまれている } }
ファイル全体を一気に読み込むこともできます。ファイルのサイズを調べた上で、その大きさのbyte配列を作り、一気にreadします。ある程度小さいことが分かっているファイルなら、こちらの方がループがいらないので簡単です。
File file = new File("/A/B/C.txt"); byte[] bytes = new byte[(int) file.length()]; try (FileInputStream fis = new FileInputStream(file)) { fis.read(bytes, 0, bytes.length); }
3.【速度重視】FileChannelで読み込む
3-1.Channelとは速度重視のフレームワーク
Java 1.4で導入されたNIO(New I/O)で、Channelというフレームワークが導入されました。これはBufferというクラスと組み合わせて使うもので、何かへの入出力の速さを重視する場合に使います。
その「何か」には、当然ファイルもあります。ファイルの入力にjava.nio.channels.FileChannelを使うと、Readerよりもずっと速く読み込めるのです。
3-2.FileChannelとReaderは使い方が全く違う
ただし、FileChannelはReader/InputStreamとは使い方がまったく違います。Channel/Bufferの概念や、使い方の知識を学ぶ必要があります。
速度が求められないファイル読み込みにFileChannelを使うと、ChannelやBufferのことを知らないプログラマがついてこれない可能性が高いので、あまりお勧めはできません。分かっている人向けです。
ここではFileChannelとByteBufferについて、簡単に触れるだけに留めます。もし、プログラム上で速度を重視するファイル読み込みが必要になった場合は、この内容を起点にして調べてみてください。
なお、FileChannelではファイルのロック(排他制御)が出来るなど、ReaderやFilesにはない、便利な機能もありますよ。
3-3.FileChannelとByteBufferを使ってみる
FileChannelは、FileChannel.openで直接取得するか(Java 7以降)、FileInputStreamやRandomAccessFileのようなファイル系Streamから取得します。ここでは、FileChannelから取得してみます。
そして、FileChannelからのデータの受け口として、1024バイト分の領域を持ったByteBufferも用意します。これは、Readerでバイナリファイルを読み込む時に用意したbyte配列の役割に近いものです。
FileChannel.readをByteBufferを引数に実行すると、Channel自身が把握しているファイル内の現在位置から、ByteBufferへファイルの読み込みが行われます。これをファイルのすべてを読み込むまで繰り返します。
// Java 7以降 Path file = Paths.get("/A/B/C.txt"); try (FileChannel fc = FileChannel.open(file)) { // FileChannelを生成 ByteBuffer buf = ByteBuffer.allocate(1024); // 1024バイト分のバッファを確保 while (true) { buf.clear(); // バッファのposition/limitをリセット if (fc.read(buf) == -1) { // バッファにファイルからのデータを読み込む break; } buf.flip(); // positionとlimitを入れ替え // バッファ内の内容を全て、標準出力へ出力してみる while (buf.hasRemaining()) { System.out.println((char) buf.get()); } } }
この例では単純にファイルの先頭から1024バイトずつ読み込んでいますが、FileChannelではファイル内のランダムアクセスが出来ますので、用途によっては便利なものです。
3-4.FileChannelを使ったファイルコピー
Java 7になるまで、Javaには標準のファイルコピー用メソッドがありませんでした。ですから、FileInputStreamとFileOutputStreamを組み合わせて自作するか、外部のライブラリを使うのが普通でした。
でも、速度重視の場合はFileChannelを使って、以下のようにもできます。さすがにFiles.copyよりは行数が多いですが、InputStream/OutputStreamを使うよりは短くできていますね。
Path src = Paths.get("/A/B/C.txt"); Path dest = Paths.get("/A/B/D.txt"); try (FileChannel srcChannel = FileChannel.open(src); FileChannel destChannel = FileChannel.open(dest, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { srcChannel.transferTo(0, srcChannel.size(), destChannel); }
4.【参考】Javaでよく使うファイルの読み込み方
4-1.プロパティファイルを読み込む【標準API】
Javaではプロパティファイル(.properties)を良く使います。それらを読み込むにはいくつかのやり方があるのですが、ここではjava.util.Propertiesとjava.util.ResourceBundleを使ったやり方を紹介します。
なお、ここではPC上のディレクトリ・フォルダにあるプロパティファイルを読み込む方法です。PC上に「/A/B/C.properties」というプロパティファイルがあるとします。
クラスパス上にあるプロパティファイルを読み込むには、ClassLoaderとクラスパス上にあるモノを指定するパスの知識が必要になります。そちらは別途調べてみましょう。
# C.properties hello=hello, world.
File propFile = new File("/A/B/C.properties"); Properties prop = new Properties(); prop.load(new FileReader(propFile)); System.out.println(prop.getProperty("hello")); // → hello, world.
File propDir = new File("/A/B"); ClassLoader classLoader = new URLClassLoader(new URL[] { propDir.toURI().toURL() }); ResourceBundle bundle = ResourceBundle.getBundle("C", Locale.getDefault(), classLoader); System.out.println(bundle.getString("hello")); // → hello, world.
4-2.XMLを読み込む【標準API】
XMLもとても良く使うファイルです。Javaでは標準APIにXMLを読み込むためのクラスがいくつかあります。それぞれが、XMLを操作するためにJavaの外で決められている標準APIに対応するものです。
それぞれやり方が異なりますので、以下では簡単に紹介します。
4-2-1.DOM(Document Object Model)
DOM(Document Object Model)は、JavaScriptでも用いられる、XML操作APIの標準です。
JavaではDOMを操作するためのクラスDocumentを、DocumentBuilderというクラスを使って作ります。Documentが持つメソッドは、インターネット標準のDOMと同じものです。
C.xml
<?xml version="1.0" encoding="UTF-8" ?> <hello>hello, world.</hello>
File xml = new File("/A/B/C.xml"); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); Document document = documentBuilder.parse(xml); Element root = document.getDocumentElement(); System.out.println(root.getTextContent()); // → hello, world.
4-2-2.SAX
JavaでXMLを読み込む際のもう一つのやり方はSAX(Simple API for XML)です。こちらはDOMとは少々プログラムの仕方が違い、イベント駆動型となります。
SAXがXMLを頭から順番に読み込み、XMLの要素や属性が出てくるたびに、指定したXML解析クラスのメソッドが呼ばれます。ですので、複雑な構造のXMLでも、特定の要素にフォーカスすればいい場合には便利なものです。
File xml = new File("/A/B/C.xml"); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse(xml, new SampleSAXParser());
// SampleXMLParser.java import org.xml.sax.helpers.DefaultHandler; class SampleSAXParser extends DefaultHandler { // XML中で文字が出現した時のコールバックメソッド public void characters(char[] ch, int offset, int length) { System.out.println(new String(ch, offset, length)); // → hello, world. } }
4-3.JSONを読み込む【外部ライブラリ】
最近はJSON(JavaScript Object Notation)を扱う機会が大変増えてきましたよね。
JavaではJSONを扱うやり方はいくつかあります。ここではその一つ、Jacksonを使ってのJSON形式のファイルからの読み込み方を簡単に紹介します。
Jacksonについては、以下の記事も参考にしてください。
関連記事C.json
{ "hello": "hello, world." }
File json = new File("/A/B/C.json"); ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(json); System.out.println(root.get("hello").textValue()); // → hello, world.
4-4.Excelを読み込む【外部ライブラリ】
プログラムではExcel形式のファイル(ファイル拡張子xls、xlsxなど)を読み込まなければならない時もあります。JavaでExcel形式のファイルを扱うなら、Apache POI(https://poi.apache.org/)を使うと便利ですよ。
以下の例では、1つ目のシートのA1セルにある値を、テキストとして読み取っています。Workbook、Sheet、Row、Cellなどのクラスにより、かなり直感的に扱えていることが分かるかと思います。
File xls = new File("/A/B/C.xlsx"); Workbook workbook = WorkbookFactory.create(xls); Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(0); Cell cell = row.getCell(0); System.out.println(cell.getStringCellValue()); // → hello, world.
4-5.CSVを読み込む【外部ライブラリ】
データをやり取りするのにお手軽なのは、今も昔もCSV(Comma-Separated Values)です。
JavaでCSVを扱う時は、テキストファイルとして読み込んで各行を“,”でsplitしてもいいのですが、実はCSVは結構複雑な仕様があって、きちんとしたプログラムを作るには大変なものだったりします。
これも外部ライブラリを使うと大変お手軽に読み込めます。ここでは、Apache Commons CSV(http://commons.apache.org/proper/commons-csv/)を簡単に紹介します。
C.csv
"hello, world.",こんにちは、世界
File csv = new File("/A/B/C.csv"); FileReader fr = new FileReader(csv); CSVParser parser = CSVFormat.DEFAULT.parse(fr); List<CSVRecord> records = parser.getRecords(); System.out.println(records.get(0).get(0)); // → hello, world.
5.まとめ
この記事では、Javaでのファイルの読み込み方をお伝えしました。
今では、一番簡単なのはFilesを使うことです。どの環境でも動かしたいなら、FileReader/FileInputStreamとBufferedReader/BufferedInputStreamを組み合わせます。読み込みの速度を優先するなら、FileChannelとByteBufferを使います。
テキストファイル・バイナリファイル以外にも、Javaで日常的に読み込むファイルの形式は多いです。よく使われるプロパティファイル、XML、JSON、Excel、CSVなども、Javaではしっかり読み込めます。
この記事でお伝えしたのは、大体はJavaの標準APIで提供されている機能を使ったやり方です。Javaには標準API以外にもApache Commons IOなどの豊富なライブラリがあります。それらを調べて、活用してみると便利ですよ。
コメント