Java:MacからWindowsへのファイル転送で作成される余分なファイルを削除する

【課題】
Mac環境のファイルをHDやUSBメモリなどを使ってWindows環境へコピーした場合,Windows側で見ると以下のようなファイルができていることがあります。
1)ディレクトリごとに「.DS_Store」ファイル
2)1つのファイルに対してファイル名の先頭に「._」がついた同名ファイル
このうち1)はMacのFinderに関する情報で,2)はFAT32のディスクにコピーした時に作られる,ファイル情報が格納されたファイルです。1)はコピー元のMac上に存在しますが,2)はありません。
これらのファイルはWindowsでは不要なファイルなので,今回はこれらを一括削除するプログラムをJavaで作成してみます。

【仕様】
プログラムを作成するにあたって以下の点に留意する必要があります。
●処理の内容
1)ファイル名の先頭に「._」がついたファイルが存在した場合,それとペアになる「._」がついていないファイルが存在するかを確認した上で削除します。
2)「.DS_Store」ファイルの削除について,「.DS_Store」に対して「._.DS_Store」が存在するので「.DS_Store」を先に削除してしまうと1)の処理で「._.DS_Store」の削除が行われなくなってしまいます。そのため,先に1)の処理を行ってから「.DS_Store」の削除を行う必要があります。
●実装について
ディレクトリ内のファイルを削除する際,for文の中でリストからFileを1件づつ取り出し,削除すべきファイルかどうか判定して,その場で削除する処理を行うと,1つのファイル削除でリストの順番が1つ繰り上がるので,for文で取り出す「次」のFileがリストの「2つ先」になってしまいます。
そのため削除すべきファイルをその場で削除せず,キューに保存して後で一括して削除するようにする必要があります。

【実装】
1)ファイル名の先頭が「._」のファイルを削除する「removeSpecific」メソッドの実装は以下になります。

import java.io.File;
import java.io.IOException;
import java.util.Deque;
import java.util.LinkedList;
 
  //--------------------------------------------------------------------------
  /**
   * ファイル名の先頭が「._」のファイルの削除.
   * @param dir ファイルが格納されているディレクトリ.
   * @throws IOException
   */
  private static void removeSpecific (
    File dir)
    throws IOException
  {
    Deque<File> que = new LinkedList<File>();
    String name;
    File[] paths = dir.listFiles();
    for (File path : paths) {
      if (path.isDirectory()) {
        removeSpecific(path);
      }
      else if ((name = path.getName()).startsWith("._")) {
        File peerfile = new File(path.getParentFile(), name.substring(2));
        if (peerfile.exists()) {
          que.push(path);
        }
      }
    }
    while (!que.isEmpty()) {
      File path = que.pop();
      System.out.println(path.getPath());
      path.delete();
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

2)「.DS_Store」ファイルを削除するメソッド「removeDSstore」は以下になります。

import java.io.File;
import java.io.IOException;
import java.util.Deque;
import java.util.LinkedList;
 
  //--------------------------------------------------------------------------
  /**
   * 「.DS_Store」ファイルの削除.
   * @param dir ファイルが格納されているディレクトリ.
   * @throws IOException
   */
  private static void removeDSstore (
    File dir)
    throws IOException
  {
    Deque<File> que = new LinkedList<File>();
    File[] paths = dir.listFiles();
    for (File path : paths) {
      if (path.isDirectory()) {
        removeDSstore(path);
      }
      else if (".DS_Store".equals(path.getName())) {
        que.push(path);
      }
    }
    while (!que.isEmpty()) {
      File path = que.pop();
      System.out.println(path.getPath());
      path.delete();
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

3)各処理を呼び出すmain関数は以下になります。

  //--------------------------------------------------------------------------
  /**
   * main関数:エントリーポイント.
   * @param args
   */
  public static void main(String[] args)
  {
    try {
      String pathStr = (args != null && 0 < args.length)? args[0]: null;
      if (pathStr == null || pathStr.isEmpty()) { pathStr = "."; }
 
      File dir = new File(pathStr);
      if (dir.isDirectory()) {
        removeSpecific(dir);
        removeDSstore(dir);
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。


【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
増補改訂版 Java言語で学ぶデザインパターン入門(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ソニーストアドミノ・ピザ【PC向けサイト】

| | トラックバック (0)

Java:Reader/Writerにおけるclose()メソッド呼び出しの流儀

【課題】
今回の記事では,次の2点について確認します。

●確認1
テキストファイルを読み込むためのクラスインスタンスを以下の手順で作成してファイル読み込みを行ったとします

  File infile = new File("入力ファイルのパス");
 
  FileInputStream is = new FileInputStream(infile);
  InputStreamReader sr = new InputStreamReader(is,"utf-8");
  BufferedReader r = new BufferedReader(sr);
上記で作成したBufferedReaderでテキストを読み込み終わったあと,作成した3つのクラスインスタンス
・BufferedReader
・InputStreamReader
・FileInputStream
これ全部に対してcloseを実行する必要があるか,それとも最後に作成したBufferedReaderだけをcloseすれば大丈夫か。

ちなみにBufferedReaderのAPIリファレンスに使用例として以下のコードが掲載されています。

BufferedReader in = new BufferedReader(new FileReader("foo.in"));
この例の通りに記述すると,FileReaderへの参照がないためFileReaderをcloseできません。
それで問題ないのか確認します。
https://docs.oracle.com/javase/jp/6/api/java/io/BufferedReader.html

●確認2
Writerの派生クラスのインスタンスからデータを書き出した後,すべてのデータを書き出すためにflushが実行される必要がありますが,そのためにflushメソッドを明示的に呼び出すべきかどうか確認します。

●検証用テストコード
確認1と2の両方とも以下のコードで確認します。

public class FileIOTest
{
  public static void main(String[] args)
  {
    try {
      File infile = new File("入力ファイルのパス");
      File outfile = new File("出力ファイルのパス");
 
      FileInputStream is = new FileInputStream(infile);
      InputStreamReader sr = new InputStreamReader(is);
      BufferedReader r = new BufferedReader(sr);
 
      FileOutputStream os = new FileOutputStream(outfile);
      OutputStreamWriter sw = new OutputStreamWriter(os);
      BufferedWriter w = new BufferedWriter(sw);
 
      try {
        String line;
        while ((line = r.readLine()) != null) {
          w.write(line);
          w.newLine();
        }
      }
      finally {
        //try { r.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { r.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { sr.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { sr.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { is.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { is.close(); } catch(IOException ex) { ex.printStackTrace(); }
 
        //try { w.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { w.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { sw.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { sw.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { os.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { os.close(); } catch(IOException ex) { ex.printStackTrace(); }
      }
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
テストコードのうち,finallyブロックの前段6行がReaderクラスメソッドの検証用,後段6行がWriterクラスメソッドの検証用コードになります。
finallyブロックの「read」メソッドは,これを呼んでIOExceptionが起きるかどうかで各インスタンスがまだ読み込み可能か確認するために呼んでいます。

●確認1:検証
テストコードのfinallyブロックのうち,前段6行のコメントをはずして実行してみます。
コメントをはずしたfinallyブロック前段:

  try { r.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { r.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sr.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sr.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { is.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { is.close(); } catch(IOException ex) { ex.printStackTrace(); }

上記のように修正して実行すると,以下の2つの例外が起こります。
java.io.IOException: Stream closed
  at sun.nio.cs.StreamDecoder.ensureOpen(StreamDecoder.java:27)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:128)
  at sun.nio.cs.StreamDecoder.read0(StreamDecoder.java:107)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:93)
  at java.io.InputStreamReader.read(InputStreamReader.java:151)
  at ymnk.FileIOTest.main(FileIOTest.java:38)
java.io.IOException: Bad file descriptor
  at java.io.FileInputStream.read(Native Method)
  at ymnk.FileIOTest.main(FileIOTest.java:40)

1つ目の例外は,finallyブロック3行目の「sr.read();」を実行して「すでにcloseされている」という例外が起きています。
「r.close();」を実行することでsrもcloseされることがわかります。
2つ目の例外は,finallyブロック5行目の「is.read();」を実行して「間違ったファイル記述子」という例外が起きています。
エラーの意味がイマイチよくわかりませんが,「r.close();」「sr.close();」をコメントにすると例外が起きなくなるので,原因は先に実行された「r.close();」メソッドの実行かと思われます。
「sr.read();」と「sr.close();」をコメントにしても例外が起きるので,「r.close();」を実行すると,以降のreadメソッド呼び出しはすべて例外になるようです。
ちなみにcloseメソッドでは例外は起きません。これについて各APIリファレンスによると「InputStreamReader」と「BufferedReader」のcloseメソッドの説明では「すでに閉じられているストリームを閉じても、何の影響もありません」となっています。
※「FileInputStream」のcloseメソッドの説明には多重closeについての記述はありません。
●確認1:結論
ファイル入力について,
BufferedReader r = new BufferedReader(
            new InputStreamReader(
            new FileInputStream(new File("入力ファイルパス"))));
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
このコードでBufferedReaderインスタンスを生成して読み込みを行い「r.close()」で閉じた場合,BufferedReaderだけでなくInputStreamReaderもFileInputStreamも閉じられます。BufferedReaderだけをcloseすれば大丈夫です。

●確認2:検証
テストコードのfinallyブロックのうち,前段6行を元に戻して後段6行のコメントをはずして実行してみます。
コメントをはずしたfinallyブロック後段:

  try { w.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { w.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sw.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sw.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { os.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { os.close(); } catch(IOException ex) { ex.printStackTrace(); }

これで実行すると,以下の例外が起こります。
java.io.IOException: Stream closed
  at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:26)
  at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:121)
  at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
  at ymnk.FileIOTest.main(FileIOTest.java:45)

finallyブロック後段3行目の「sw.flush();」を実行して「すでにcloseされている」という例外が起きています。closeは2行目の「w.close();」で行われており,closeしたストリームをflushすると例外になることがわかります。
これについてOutputStreamWriterのAPIリファレンスを調べてみると,closeメソッドを実行した後は,write及びflushメソッドの呼び出しでIOException例外が発生する旨の記述があります。
同時に,closeが呼ばれたら,まずflushが行われること,closeしたストリームをcloseしても問題ない旨の記述があります。
https://docs.oracle.com/javase/jp/6/api/java/io/Writer.html
●確認2:結論
Writerの派生クラスは出力が完了したあとで明示的にflushを呼ばなくてもcloseでflushが行われます。
また,closeを実行してからflushを呼ぶと例外が起きてしまいます。
flushを呼んで例外が起きた場合,その例外を確実にcatchしないと以降のclose処理が行われないため,closeを呼ぶべき場所でflushも呼ぶのは逆に危険です。

【まとめ】
どちらの確認でも結論は同じでした。
「入出力の終了時はcloseを1回呼べばOK」

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
スッキリわかるJava入門 第2版(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


| | トラックバック (0)

Java:例外が起こったときに実行されるコードとされないコード

【課題】
try〜catch〜finallyのうち,finallyブロックの中で例外が起こったときの挙動を改めて確認します。
finallyブロックについて,tryブロックの中で例外が発生してcatchされなかった場合でもfinallyブロックは必ず実行されることになっています。
この挙動について,いくつか疑問が生じます。今回はその疑問について,実際に検証コードを書いてどのような動作をするのか確認してみます。
(※本記事は2015年6月6日に全面改訂しました)

●問1) finallyブロックの中で例外が発生した場合どうなるのか。
「必ず実行される」finallyブロックですが,finallyブロックの中で例外が発生した場合,例外が発生した場所以降のコードは実行されるのでしょうか?
試してみましょう。テストコードは以下になります。

public class ThrowTest
{
  //------------------------------------------------------------------
  public static void main (String[] args)
  {
    try {
      ThrowTest.thrower();
    }
    catch (Throwable ex) {
      System.out.println("main:例外をキャッチしました");
      ex.printStackTrace();
    }
  }
  //------------------------------------------------------------------
  // 例外が発生するメソッド.
  private static void thrower ()
    throws Exception
  {
    try {
      String str = null;
      str.toString(); // tryの中で例外発生.
    }
    finally {
      String str = null;
      str.toString(); // catchの中で例外発生.
      System.out.println("finally:例外発生の後");
    }
  }
}
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
実行結果
main:例外をキャッチしました
java.lang.NullPointerException
  at test.ThrowTest.thrower(ThrowTest.java:30)
  at test.ThrowTest.main(ThrowTest.java:12)
結論
「finally:例外発生の後」が出力されなかったので,finallyの中で例外が発生した場合,それ以降のコードは実行されませんでした。
考察
finallyブロックの中ですべての処理を確実に行わせるには,メソッドの呼び出しで発生する可能性があるすべての例外をfinallyブロックの中で確実にキャッチする必要があります。
一例として,ファイルの入出力でfinallyブロック内でcloseメソッドを呼ぶ場合,すべての行をtry〜catchで囲む実装が考えられます。
  Reader r = null;
  Writer w = null;
  try {
    … // ReaderからWriterへのデータ転送処理
  }
  finally {
    try { if (r != null) { r.close(); }} catch(IOException ex){ ex.printStackTrace(); }
    try { if (w != null) { w.close(); }} catch(IOException ex){ ex.printStackTrace(); }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

●問2) tryブロックとfinallyブロックの両方で例外が発生したら,catchブロックはどちらの例外を受け取るか。
試してみましょう。問1のコードのうち,例外が発生するメソッドを以下のように書き換えます。

  //------------------------------------------------------------------
  // 例外が発生するメソッド.
  private static void thrower ()
    throws Exception
  {
    try {
      throw new Exception("tryブロックで例外発生");
    }
    finally {
      throw new Exception("finallyブロックで例外発生");
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
実行結果
main:例外をキャッチしました
java.lang.Exception: finallyブロックで例外発生
  at test.ThrowTest.thrower(ThrowTest.java:33)
  at test.ThrowTest.main(ThrowTest.java:12)
結論
finallyブロックで例外が発生した場合,呼び出し元はfinallyの例外をcatchしました。
そしてtryブロックの例外はcatchできませんでした。
後から発生したfinallyブロックの例外が,先に発生したtryブロックの例外を上書きした形になりました。
考察
tryブロックの例外がfinallyブロックの例外に上書きされないようにするには,以下の2つの対応が考えられます。
 1) try〜finallyではなくtry〜catch〜finallyとし,tryブロックで発生した例外はcatchで受け取るようにする。
 2) 問1の考察で述べたのと同じく,finallyブロックの例外はすべてfinallyブロックの中でcatchする。
どちらを採用すべきかは,例外が発生したときに,それ以降の処理をどこまでスキップさせるかで変わってくるかと思います。
tryブロック内の処理だけをスキップするのであれば,1)を採用することになるかと思います。
tryブロック以降の処理もスキップするのであれば,2)を採用することになるかと思います。

●問3) try〜catch〜finallyの入れ子は可能なのか?
試してみましょう。問1のコードのうち,例外が発生するメソッドを以下のように書き換えます。

  //------------------------------------------------------------------
  // 例外が発生するメソッド.
  private static void thrower ()
    throws Exception
  {
    try {
      try {
        System.out.println("Start");
        throw new Exception("例外発生");
      }
      finally {
        System.out.println("内側のfinally:で実行");
      }
    }
    finally {
      System.out.println("外側のfinally:で実行");
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
実行結果
Start
内側のfinally:で実行
外側のfinally:で実行
main:例外をキャッチしました
java.lang.Exception: 例外発生
  at test.ThrowTest.thrower(ThrowTest.java:24)
  at test.ThrowTest.main(ThrowTest.java:9)
結論
try〜catch〜finallyの入れ子は可能でした。
考察
この書き方は,以下のケースで使えるかと思います。
 1) try〜catch〜finallyではJavaの文法としてfinallyを先に書くことはできないが,先に書いたほうがコードが理解しやすくなるケース。
 2) 問2の考察で述べた,例外のcatchを先送りにするケース。
 3) 複数のtry〜finallyを最後の1箇所でまとめてcatchするケース。

●問4) tryブロックの中でreturnしたら,finallyブロックは実行されるのか?
試してみましょう。問1のコードのうち,例外が発生するメソッドを以下のように書き換えます。今回はtry〜finallyの入れ子の中でreturnしてみました。

  //------------------------------------------------------------------
  // 今回は例外は発生しません.
  private static void thrower ()
    throws Exception
  {
    try {
      try {
        System.out.println("Start");
        return; // ここでreturn.
      }
      finally {
        System.out.println("内側のfinally:で実行");
      }
    }
    finally {
      System.out.println("外側のfinally:で実行");
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
実行結果
Start
内側のfinally:で実行
外側のfinally:で実行
結論
returnで終了した場合もfinallyは実行されました。
考察
処理の途中でreturnするケースとしてよくあるのが,入力値のチェックをして不合格だったら以降の処理を行わずにreturnで抜ける,というものがあるかと思います。
このケースではfinallyの処理は必要ない場合があります。
長大なメソッドを書いてしまった場合,メソッドの最後にfinallyがあるのを忘れる可能性が高いので注意が必要です。

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
スッキリわかるJava入門 第2版(kindle版)

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ドミノ・ピザ【PC向けサイト】

| | トラックバック (0)

シェルスクリプト:「.svn」ディレクトリを一括削除する

【概要】
Subversionでリビジョン管理した場合,管理下の各ディレクトリに「.svn」ディレクトリが作られます。
今回は,この「.svn」ディレクトリを一括削除するシェルスクリプトを書いてみます。
このスクリプトの用途としては,ソースコードがSubversionの管理下にあるサーブレットを本番環境にデプロイしたときに,一緒に本番環境にデプロイされてしまう「.svn」ディレクトリを後から削除するのに使えるかと思います。

【仕様】
このスクリプトの使用方法を以下のように定めます。
1)スクリプトの引数に指定したディレクトリ以下の「.svn」ディレクトリを探して,これを削除する。
2)スクリプトを引数なしで実行した場合,ディレクトリ指定はカレントディレクトリが指定されたものとする。
3)引数がディレクトリでなければ,エラーを出力して終了する。

【実装】
このスクリプトの実装は下記になります。下記スクリプトの後に,このスクリプトの詳細について解説しています。

#!/bin/bash

ARGUMENT=$1

if [ -z $ARGUMENT ] ; then
  ARGUMENT=$PWD
fi
echo "rmsvn Path: $ARGUMENT"

if [ ! -d $ARGUMENT ] ; then
  echo "rmsvn ERROR: not a directory."
  exit 1
fi

FILES=(`find $ARGUMENT -name .svn -print`)
COUNT=${#FILES[*]}

if [ $COUNT -gt 0 ]; then
  for i in `seq $COUNT`
  do
    SVNPATH=${FILES[$i-1]}

    sudo rm -rfv $SVNPATH
  done
fi

※上記コードでは,整形のため空白部分は全角スペースを使用しています。

【解説】
上記スクリプトの各部分について解説します。

ARGUMENT=$1
if [ -z $ARGUMENT ] ; then
  ARGUMENT=$PWD
fi
$1がスクリプトの引数で,これを変数ARGUMENTに代入して,以降はARGUMENTを引数として扱っています。
次のif文では,ARGUMENTが空かどうかを判定して,空であれば環境変数$PWDをARGUMENTとしています。
環境変数$PWDはカレントディレクトリのパスが設定されています。

if [ ! -d $ARGUMENT ] ; then
  echo "rmsvn ERROR: not a directory."
  exit 1
fi
このif文ではARGUMENTがディレクトリか判定して,そうでなければスクリプトを終了しています。
注)ここで条件を「-f」とすると,存在しない名前が指定されたときにエラーになりません。

FILES=(`find $ARGUMENT -name .svn -print`)
COUNT=${#FILES[*]}
コマンドをバッククオート「`」で囲むと,囲んだターミナルコマンドが実行されて,コマンドが出力した標準出力が結果になります。
これをカッコで括ることで,コマンドの結果を配列にすることができます。
ここではfindコマンドを実行して,結果を配列にして変数FILESにセットしています。
また,配列FILESの要素数をCOUNTにセットしています。

if [ $COUNT -gt 0 ]; then
 …
fi
このif文は,要素数が0以上か(削除する要素が存在するかどうか)チェックしています。

for i in `seq $COUNT`
do
  SVNPATH=${FILES[$i-1]}
 …
done
FILESから要素を1つづつ取り出して変数SVNPATHへ代入しています。
ここで,「seq」は1から引数の数値までカウントアップするコマンドですが,BSD系UNIX(Mac OS Xなど)にはこのコマンドがないため,代わりに「jot」コマンドを使います。
for i in `jot $COUNT`
do
  SVNPATH=${FILES[$i-1]}
 …
done
BSD(Mac OS X)では上記のようなコードになります。

sudo rm -rfv $SVNPATH
取り出した要素を削除します。

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[シェルスクリプト]
プロフェショナル・シェルプログラミング (アスキー書籍) Kindle版

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ボーズ・オンラインストア

| | トラックバック (0)

Java:前回作成したコードの処理速度を比較する

【課題】
前回の記事にて,自作メソッドのほかにStringクラスのformatメソッドを使って同等機能を実現する例を挙げましたが,String.formatは処理速度が遅いメソッドであるという話を聞いたので,String.formatによる実装と,前回作成したdecimalToStringメソッドとを速度比較してみました。
※両メソッドを10,000回繰り返し処理した所要時間で比較します。

比較した環境:
OS: Mac OS X 10.10.2
CPU: 2.3GHz Intel Core i5
Java: 1.6.0_65

●String.formatメソッドの処理速度

速度計測用コード:

Deque<String> que = new LinkedList<String>();
long start = System.currentTimeMillis();
for (long value = 0; value < 10000; value++) {
  que.add(String.format("%1$,3d",value));
}
System.out.println(String.valueOf(System.currentTimeMillis()-start)+" msec");
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

測定結果:
1回目:471 msec
2回目:491 msec
3回目:487 msec
4回目:497 msec
5回目:481 msec

●前回作成したdecimalToStringメソッドの処理速度
※ decimalToStringメソッドの中身は前回記事参照。

速度計測用コード:

Deque<String> que = new LinkedList<String>();
long start = System.currentTimeMillis();
for (long value = 0; value < 10000; value++) {
  BigDecimal dec = new BigDecimal(value);
  que.add(decimalToString(dec));
}
System.out.println(String.valueOf(System.currentTimeMillis()-start)+" msec");
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

測定結果:
1回目:168 msec
2回目:190 msec
3回目:189 msec
4回目:189 msec
5回目:197 msec

結果として,String.formatはdecimalToStringの倍以上の処理時間がかかる結果になりました。

【結論】
文字列を大量に処理する場所では,String.formatの使用はできれば避けたほうが良さそうです。

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
増補改訂版 Java言語で学ぶデザインパターン入門(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ノートンシリーズ新登場バナーソニーストアブックオフオンライン【PC・携帯共通】

| | トラックバック (0)

Java:数値を3桁ごとのカンマ区切りの文字列にする

【課題】
前回は数値文字列を数値に変換してBigDecimalを生成しましたが,今回はその逆を行います。
BigDecimalクラスの数値を3桁ごとにカンマで区切った数値文字列に変換します。
また,小数点を含む値についても対応します。
ただ,整数値の場合はStringクラスのformatメソッドを使って以下のように書くことで実現できてしまいます。
仕事でプログラミングを行っている人で,これで要求を満たすならこちらを使いましょう。

BigDecimal dec = new BigDecimal("12345678");
long value = dec.longValue();

String valueStr = String.format("%1$,3d", value);

System.out.println(valueStr);

次回記事にて上記コードと下記コードの速度比較を行っています。

【概要】
カンマの挿入は,少数点以上は下の桁から数えて3桁ごとに挿入する必要があります。
逆に小数点以下は,上の桁から3桁ごとにカンマを挿入します。
これを行うロジックはいくつか考えられますが,今回は以下の手順で行います。

(1)BigDecimalの値を文字列化して,数値文字列を小数点で区切って2つに分ける。
(2)少数点以上の数値文字列を文字配列に変換する。
(3)文字配列の最後(下の桁)から数字を1文字づつ取り出しStringBuilderに追加する。同時に3文字ごとにカンマを入れる。
※このときのStringBuilderの内容は,下の桁→上の桁の順に数字が並んでいます。
(4)負の値の場合は最後にマイナスを追加する。
(5)StringBuilderの内容をreverse()メソッドで反転させる。
※以降は小数点以下の桁があった場合の処理です。
(6)StringBuilderに小数点を挿入する。
(7)((1)で作成した)小数点以下の数値文字列を文字配列に変換する。
(8)文字配列の最初(上の桁)から数字を1文字づつ取り出しStringBuilderに追加する。同時に3文字ごとにカンマを入れる。
(9)StringBuilderの内容を文字列に変換する。

【実装】
上記手順をコードにすると以下のようになります。

import java.math.BigDecimal;

  //------------------------------------------------------------------
  /**
   * 動作確認用mainメソッド.
   */
  public static void main(String[] args)
  {
    BigDecimal dec = new BigDecimal("-12345678.9012345");
    String str = decimalToString(dec);
    System.out.println(str);
  }
  //------------------------------------------------------------------
  /**
   * BigDecimalを数値文字列に変換.
   * @param dec 変換するBigDecimal値.
   * @return 変換した文字列.
   */
  private static String decimalToString (
    BigDecimal dec)
  {
    String valueStr = String.valueOf(dec.toString());
    String[] valueStrs = valueStr.split("[.]");

    StringBuilder buff = new StringBuilder(valueStr.length());

    if (0 < valueStrs.length && !valueStrs[0].isEmpty()) {
      char[] chars = valueStrs[0].toCharArray();
      boolean isnegative = (0 < dec.scale());
      int lastindex = (isnegative)? 1: 0;
      int count = 1;
      for (int index = chars.length-1; lastindex <= index; index--) {
        buff.append(chars[index]);
        if (count++ % 3 == 0) {
          buff.append(',');
        }
      }
      if (',' == buff.charAt(buff.length()-1)) {
        buff.setLength(buff.length()-1);
      }
      if (isnegative) {
        buff.append("-");
      }
      buff.reverse();
    }

    if (1 < valueStrs.length && !valueStrs[1].isEmpty()) {
      buff.append(".");
      char[] chars = valueStrs[1].toCharArray();
      int count = 1;
      for (char c : chars) {
        buff.append(c);
        if (count++ % 3 == 0) {
          buff.append(',');
        }
      }
      if (',' == buff.charAt(buff.length()-1)) {
        buff.setLength(buff.length()-1);
      }
    }
    return buff.toString();
  }

※上記コードでは,整形のため空白部分は全角スペースを使用しています。
【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
増補改訂版 Java言語で学ぶデザインパターン入門(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ソニーストア

| | トラックバック (0)

Java:全角の数値文字列を数値として受け付ける

【課題】
ユーザが入力を行う場合,数値は文字列として入力フィールドに入力され,この文字列は内部で処理するためにどこかで数値に変換する処理が必要になるかと思います。
今回は,数値文字列をBigDecimalに変換する処理をJavaで作成してみます。
この変換には以下の2つの処理も含めることにします。
1)全角の数値文字列もBigDecimalに変換できるようにしてみます。
2)ユーザが数値入力するとき,慣習的に3桁ごとにカンマを入れて入力する場合がありますが,数値に変換する前にこれを取り除くことでカンマの入力を許容します。


【概要】
●入力文字列に対する事前処理
BigDecimalのコンストラクタは,文字列を受け取ると,これを数値として解釈しようとします。
コンストラクタに渡される数値文字列について,数字以外は以下のように解釈します。そのためユーザプログラムは以下の事前処理を行う必要はありません。
・文字列の先頭が「-」の場合は負の値として読み込む。「+」の場合は「+」を読み飛ばす。
・文字列の先頭(先頭が「+」「-」のときは次の文字)がピリオドの場合は前にゼロを補完する。
・文字列の最後がピリオド(小数点)で終わっていたら,小数点を無視する。

以上より,BigDecimalのコンストラクタに渡す入力文字列には,上記で行われない以下の事前処理が必要になります。
・半角及び全角の数字を半角に揃える。
・半角及び全角のマイナス,プラス,ピリオドを半角に揃える。
・半角及び全角のカンマは読み飛ばす。
・それ以外の文字が出現したら,NumberFormatException例外にする。

●半角及び全角の数字の扱い
文字が数字かどうかの判定は,CharacterクラスのisDigit()メソッドを使用します。
このメソッドは半角及び全角の数字を与えるとtrueを返します。
数字の数値への変換は,Characterクラスのdigit()メソッドを使用します。
このメソッドは半角及び全角の数字を与えると,これを数値に変換します。
この2つのメソッドにより,全角半角の区別なく数字を数値に置き換えることができます。

【実装】
概要から,具体的なコードは以下のようになります。

●事前処理

  //-----------------------------------------------------------------------
  private static final char CHAR_ZEN_MINUS = 0xFF0D;
  private static final char CHAR_ZEN_PLUS = 0xFF0B;
  private static final char CHAR_ZEN_PERIOD = 0xFF0E;
  private static final char CHAR_ZEN_COMMA = 0xFF0C;
  //-----------------------------------------------------------------------
  /**
   * 事前処理:数値を表現する全角文字を半角に置き換える。カンマは読み飛ばす.
   * @param numStr 入力文字列.
   * @return 置き換えを行った入力文字列.
   * @throws NumberFormatException 数値を表現する文字以外の文字が含まれていた.
   */
  public static String buildNumString (
    String numStr)
    throws NumberFormatException
  {
    String result = "0";
    if (numStr != null && !numStr.isEmpty()) {
      StringBuilder buff = new StringBuilder();
      char[] chars = numStr.toCharArray();
      for (char numChar : chars) {
        if (Character.isDigit(numChar)) {
          buff.append(Character.digit(numChar, 10));
        }
        else if (numChar == '-' || numChar == CHAR_ZEN_MINUS) {
          buff.append('-');
        }
        else if (numChar == '+' || numChar == CHAR_ZEN_PLUS) {
          buff.append('+');
        }
        else if (numChar == '.' || numChar == CHAR_ZEN_PERIOD) {
          buff.append('.');
        }
        else if (numChar != ',' && numChar != CHAR_ZEN_COMMA) {
          throw new NumberFormatException("数値表現に誤りがあります:"+numStr);
        }
      }
      result = buff.toString();
    }
    return result;
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

●BigDecimalへの変換
事前処理の結果をコンストラクタに渡してBigDecimalのインスタンスを作ります。

import java.math.BigDecimal;
  //-----------------------------------------------------------------------
  /**
   * 入力文字列を事前処理して,その結果からBigDecimalを作成.
   * @param args 入力文字列.
   */
  public static void main(String[] args)
  {
    try {
      String numStr = args[0];
      String str = buildNumString(numStr);
      BigDecimal dec = new BigDecimal(str);
      System.out.println(dec.toString());
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
増補改訂版 Java言語で学ぶデザインパターン入門(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ioPLAZA【DVDミレル】

| | トラックバック (0)

Java:JPEGファイルから画像サイズのみ読み込む

【課題】
今回は,JPEGファイルから画像サイズ(縦×横ピクセル値)の情報だけを取得してみます。

【概要】
Javaのクラスを使って画像サイズを取得する方法として,ImageBufferクラスインスタンスにJPEGデータを読み込むと,サイズ取得メソッドで値を取り出すことができます。
この方法は,画像データを全部読み込んでのサイズ取得になるので,サイズ取得のみが目的の場合はメモリと処理時間を無駄に費やしてしまいます。
今回はローレベルクラスを使ってJPEGファイルから必要な情報のみ読み取ります。

●JPEGファイルの画像サイズの格納場所
まずはJPEGファイルの中身について。JPEGファイルはJFIFというフォーマットに従って作られています。JPEGのデータは複数のセグメントに分かれており,1つのセグメントは基本的に次のような構成になっています。

 サイズ
(1)マーカー2byte
(2)セグメントサイズ値2byte
(3)データ(2)の値-2byte
マーカーはそのセグメントを識別する値で,各マーカーの値には対応するマーカー名が存在します。
セグメントサイズに格納される値は,自分自身とデータサイズの合計です。マーカー値のサイズは含みません。
そのため(2)セグメントサイズの値は(3)データの実サイズより+2byte大きい値になります。
この構成の例外として,JPEGデータの最初のセグメント「SOI(0xFFD8)」はマーカーのみです。マーカーのみなので,SOIセグメントのサイズは2バイトです。
画像サイズが入っているセグメントのマーカー名はSOFです。SOFはSOF0(0xFFC0)からSOF15(0xFFCF)まであるようですが,サイズ値の格納場所は変わらないようです。
SOFで画像サイズが格納されている場所は以下になります。下記の「縦ピクセル数」と「横ピクセル数」が目的の値です。

 サイズ
(1)マーカー:SOF (SOF0〜SOF15のいずれか)2byte
(2)セグメントサイズ値2byte
(3)データ(1)(詳細不明)1byte
(2)縦ピクセル数2byte
(3)横ピクセル数2byte
(以下略)

●処理の概要
以上から,サイズ取得の処理は以下のようになります。
(1)SOIをスキップ
(2)マーカがSOF0〜SOF15のいずれかのセグメントを探す。
(2-1)マーカを参照してSOFではないセグメントだったら,そのセグメントをスキップして次のセグメントに移る。
(2-2)セグメントをスキップするときのサイズは,そのセグメントのセグメントサイズ値から決定する。
(3)マーカSOF0〜SOF15が見つかったら,セグメントサイズの2バイトとデータ部の先頭1バイトをスキップして,2バイトづつデータを取得し整数化する。

●使用するクラス
JPEGファイルを読み込むために今回使用するJavaのクラスは「FileImageInputStream」です。
ファイルのアクセスは,このクラスの以下のメソッドを使用します。
(1)readUnsignedShort() …… 2バイト読み込んで値をintで返す。ストリーム位置は2進む。
(2)skipBytes() ………… データを読み込まずにストリーム位置のみ移動する。
(3)readShort() ………… 2バイト読み込んで,値をshortで返す。ストリーム位置は2進む。

●実装
以上より,実際のコードは以下のようになります。
(2015/06/28改訂)

import java.awt.Dimension;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
 
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
 
public class JPEGImagesize
{
  //----------------------------------------------------------------------------------
  private static final int SOI  = 0xFFD8;
  private static final int SOF0 = 0xFFC0;
  private static final int SOF15 = 0xFFCF;
  //----------------------------------------------------------------------------------
  /**
   * JPEG画像データの縦横サイズ取得.
   * @param file JPEGファイル.
   * @return ファイルサイズ. 幅,高さの順のintの配列.
   * @throws IOException
   */
  private static Dimension getSize (
    File file)
    throws IOException
  {
    int width = 0, height = 0;
    ImageInputStream stream = null;
    try {
      stream = new FileImageInputStream(file);
 
      if (stream.readUnsignedShort() == SOI) {
        int size, kind;
        while ((kind = stream.readUnsignedShort()) < SOF0 || SOF15 < kind) {
          size = stream.readUnsignedShort() - 2;
          if (stream.skipBytes(size) < size) { break; }
        }
        if (SOF0 <= kind && kind <= SOF15) {
          size = stream.readUnsignedShort();
          stream.skipBytes(1);
          height = stream.readShort();
          width = stream.readShort();
        }
      }
    }
    catch (EOFException ex) {
      width = 0; height = 0;
    }
    finally {
      try {
        if (stream != null) { stream.close(); }
      } catch (IOException ex) { ex.printStackTrace(); }
    }
    return new Dimension(width, height);
  }
  //----------------------------------------------------------------------------------
  /**
   * (検証用)JPEGの縦横サイズを取得して表示する.
   * @param args args[0]に画像ファイルのパスが指定されている想定.
   */
  public static void main(String[] args)
  {
    try {
      if (0 < args.length) {
        File file = new File(args[0]);
        if (file.exists()) {
          Dimension size = JPEGImagesize.getSize(file);
          System.out.println("width :"+String.valueOf(size.width));
          System.out.println("height:"+String.valueOf(size.height));
        }
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
現場で使えるJavaライブラリ(Kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ノートンシリーズ新登場バナー

| | トラックバック (0)

Java:文字コード変換ツールを作る(3)コマンドラインツール その2

今回は,前回の記事で作成したコマンドラインツールについて,文字コード変換の機能はそのままで入出力の仕様が異なるツールを作成してみます。
文字コード変換機能は前々回の記事のものをそのまま使います。
今回作成するツールの仕様は以下になります。

【仕様】
1. コマンドライン引数にファイルまたはディレクトリのパスを指定することができる。
1.1. 引数にファイルが指定されたときは,そのファイルを変換処理する。
1.2. 引数にディレクトリが指定されたときは,そのディレクトリに含まれるファイルを一括変換する。
1.3. 引数が指定されていない場合は,カレントディレクトリに含まれるファイルを一括変換する。
2. 出力ファイルを保存する場所として,入力ファイルと同じディレクトリに「utf8」というディレクトリを新規作成する。
2.1 すでに「utf8」というファイルまたはディレクトリが存在した場合はなにもせず終了。
3. 出力ファイルのファイル名は入力ファイル名と同じ。

※前回のツールとは2.以降が異なります。

【実装】
以上の仕様を実現するために,必要な機能を実装していきます。

■1入力ファイルに対して1出力ファイルを生成する。 入力が1ファイルの場合とディレクトリの場合に対応するために,入力ファイル1つに対する変換処理をまとめたメソッドを作ります。このメソッドは,前回作成した「convertOneFile」から出力ファイル名を生成するコードを省いたもので,前回同様入力ファイル1つに対して出力ファイル1つを生成します。

※下記コードの「ConvtoUTF8.convertFile(infile, outfile);」は前々回作成したコード変換プログラムです。

import java.io.File;
import java.io.IOException;

 

public class ConvtoUTF8
{
  //----------------------------------------------------------------------
  /**
   * 1入力ファイルのコード変換処理を行う。
   * @param outdir 出力ファイルを保存するディレクトリ。
   * @param infile 入力ファイル。
   */
  private static void convertOneFile (
    File  outdir,
    File  infile)
    throws IOException
  {
    if (infile.canRead()) {
      File outfile = new File(outdir, infile.getName());
      if (!outfile.exists()) {
        ConvtoUTF8.convertFile(infile, outfile);
        System.out.println(infile.getName());
      }
    }
  }
}

※上記コードでは,整形のため空白部分は全角スペースを使用しています。

■上記コードを起動するmain関数。

main関数の引数(args)からコマンドライン引数を取得するルールは,前回と同じくargs[0]を処理対象のファイルまたはディレクトリパスとして取得します。
また,引数が指定されておらずargsが空の場合はカレントディレクトリが処理対象です。
ここでは処理する対象を特定してconvertOneFileメソッドを実行するのは前回と同じですが,更に,事前に出力先ディレクトリを作成して出力先をconvertOneFileに渡します。
例外のcatchは前回同様main関数でのみ行います。これにより処理中に例外が発生した場合は,それ以降の処理をすべてキャンセルしてmain関数のcatchに飛んできます。

  //----------------------------------------------------------------------
  private static final String EXTENSION_NAME = "utf8";
  //----------------------------------------------------------------------
  /**
   * @param args コマンドライン引数。ファイルまたはディレクトリパス指定として解釈する。
   */
  public static void main(String[] args)
  {
    try {
      String pathStr = (0 < args.length)? args[0]: ".";
      File path = new File(pathStr);
      if (path.isDirectory()) {
        File outdir = new File(path, EXTENSION_NAME);
        if (!outdir.exists() && outdir.mkdir()) {
          File[] infiles = path.listFiles();
          for (File file : infiles) {
            if (!file.canRead()) { continue; }
            ConvtoUTF8.convertOneFile(outdir, file);
          }
        }
      }
      else if (path.canRead()) {
        File parent = path.getParentFile();
        File outdir = new File(parent, EXTENSION_NAME);
        if (!outdir.exists() && outdir.mkdir()) {
          ConvtoUTF8.convertOneFile(outdir, path);
        }
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

※上記コードでは,整形のため空白部分は全角スペースを使用しています。

以上の処理と前々回のコード変換を1つのクラス(「ConvtoUTF8」クラス)にまとめると,変換ツールが完成します。

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
Java言語プログラミングレッスン 第3版(上) Java言語を始めよう(kindle版)


にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


高速・夜行バス予約サイト WILLER TRAVEL 高速・夜行バス予約サイト WILLER TRAVEL

| | トラックバック (0)

Java:文字コード変換ツールを作る(2)コマンドラインツール その1

今回は,前回の記事で作成した文字コード変換プログラムに機能追加してコマンドラインツールに仕立てます。今回作成するツールの仕様は以下になります。

【仕様】
1. コマンドライン引数にファイルまたはディレクトリのパスを指定することができる。
1.1. 引数にファイルが指定されたときは,そのファイルを変換処理する。
1.2. 引数にディレクトリが指定されたときは,そのディレクトリに含まれるファイルを一括変換する。
1.3. 引数が指定されていない場合は,カレントディレクトリに含まれるファイルを一括変換する。
2. 出力ファイルは入力ファイルと同じディレクトリに作成する。
3. 出力ファイルのファイル名は,入力ファイル名の拡張子の前に拡張子「utf8」を追加する。
  例:「XXX.txt」→「XXX.utf8.txt」
3.1. ファイル名に拡張子がない場合は,拡張子を「utf8」にする。

【実装】
以上の仕様を実現するために,必要な機能を実装していきます。

■出力ファイルのファイル名を生成する。

入力ファイルのファイル名から出力ファイルのファイル名を生成します。
処理内容は,入力ファイルのファイル名をピリオドで分割してからピリオドで連結し直します。このとき最後の連結の前に「utf8」を挿入します。

import java.io.File;
import java.util.regex.Pattern;

public class ConvtoUTF8
{
  //----------------------------------------------------------------------
  private static final String EXTENSION_NAME = "utf8";
  private static final Pattern PAT_SEPA_PERIOD = Pattern.compile("[.]");
  //----------------------------------------------------------------------
  /**
   * 拡張子を「.XX」から「.utf8.XX」に変更したファイル名を作る.
   * 拡張子がない場合は拡張子を「.utf8」にする.
   * @param file 入力ファイル.
   * @return 生成した出力ファイル名.
   */
   private static String buildOutputFileName (
     File  file)
   {
     StringBuilder buff = new StringBuilder();
     String fileName = file.getName();
     String[] sepas = PAT_SEPA_PERIOD.split(fileName);
     if (1 < sepas.length) {
       int maxlen = sepas.length - 1;
       int index = 0;
       for (; index < maxlen; index++) {
         buff.append(sepas[index]).append(".");
       }
       buff.append(EXTENSION_NAME).append(".").append(sepas[index]);
     }
     else {
       buff.append(fileName).append(".").append(EXTENSION_NAME);
     }
     return buff.toString();
   }
}

※上記コードでは,整形のため空白部分は全角スペースを使用しています。

■1入力ファイルに対して1出力ファイルを生成する。

処理対象が,1ファイルの場合とディレクトリの場合があるので,まず入力ファイル1つに対する変換処理をまとめたメソッドを作ります。二種類の処理はこのメソッドの呼び出し方の違いで実現します。

ここでは最初に出力ファイル名を生成しますが,生成した出力ファイル名と同名の入力ファイルが存在していた場合の対応としては,単に当該ファイルの変換処理をスキップしているだけです。

※下記コードの「ConvtoUTF8.convertFile(infile, outfile);」は前回作成したコード変換プログラムです。

import java.io.IOException;

public class ConvtoUTF8
{
  //----------------------------------------------------------------------
  /**
   * 1入力ファイルのコード変換処理を行う。
   * @param parent 入力ファイルが保存されているディレクトリ。
   * @param infile 入力ファイル。
   */
   private static void convertOneFile (
     File  parent,
     File  infile)
     throws IOException
   {
     if (infile.canRead()) {
       String outFileName = ConvtoUTF8.buildOutputFileName(infile);
       File outfile = new File(parent, outFileName);
       if (!outfile.exists()) {
         ConvtoUTF8.convertFile(infile, outfile);
         System.out.println(infile.getName());
       }
     }
   }
}

※上記コードでは,整形のため空白部分は全角スペースを使用しています。

■上記コードを起動するmain関数。

main関数の引数(args)にはコマンドライン引数が文字列の配列として格納されています。argsの最初の要素を処理対象のファイルまたはディレクトリパスとして取得します。
引数に何も指定されていない場合はargsは空の配列です。この場合はカレントディレクトリを処理対象にします。カレントディレクトリは「File dir = new File(".");」で取得することができます。

処理対象のファイルまたはディレクトリが確定したら,ファイルの場合はそのファイルに対して,ディレクトリの場合はディレクトリに含まれる各ファイルに対して,さきに作成したconvertOneFile()メソッドを実行して変換処理を行います。

例外のcatchはツール全体のうちmain関数でのみ行います。これにより処理中に例外が発生した場合は,それ以降の処理をすべてキャンセルしてmain関数のcatchを実行して処理を終了させます。

 //----------------------------------------------------------------------
 /**
  * @param args コマンドライン引数。ファイルまたはディレクトリパス指定として解釈する。
  */
 public static void main(String[] args)
 {
   try {
     String pathStr = (0 < args.length)? args[0]: ".";
     File path = new File(pathStr);
     if (path.isDirectory()) {
       File[] files = path.listFiles();
       for (File file : files) {
         if (file.canRead()) {
           ConvtoUTF8.convertOneFile(path, file);
         }
       }
     }
     else if (path.canRead()) {
       ConvtoUTF8.convertOneFile(path.getParentFile(), path);
     }
   }
   catch (Exception ex) {
     ex.printStackTrace();
   }
 }
※上記コードでは,整形のため空白部分は全角スペースを使用しています。

以上の処理と前回のコード変換を1つのクラス(「ConvtoUTF8」クラス)にまとめると,変換ツールが完成します。


【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
やさしいJava 第5版 (「やさしい」シリーズ)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ノートンスタンダードバナー上新電機 パソコン買取サービス

| | トラックバック (0)

«Java:文字コード変換ツールを作る(1)文字コード変換