Javaで配列をコピーする。シャーロンコピーとディープコピーの違いとは?

Java

Javaで配列をコピーする際、気を付けなければいけないポイントがあります。
シャーロンコピーとディープコピーの違いです。


この2つの挙動の違いを把握しておかないと、思わぬバグを埋め込んでしまうことなります。


今回は、シャーロンコピーとディープコピーの違いを、サンプルプラグラムをもとに説明していきます。


環境情報


  • OS:Windows10
  • Java:1.8.0_60

シャーロンコピーとは?ディープコピーとは?


シャーロンコピーとディープコピーとは何でしょうか?

まずは、この2つのコピーの違いについて説明します。


シャーロンコピー


C言語を経験した人の場合は、アドレスコピーときくとピンとくるかと思います。
要素を指定するアドレスをポインタを使用してコピーしており、要素がもつ値はコピーしていないです。


ディープコピー


値コピー、と言い換えることができます。
シャーロンコピーとは異なり、アドレスではなく、要素そのものをコピーします。


シャーロンコピーの概念が理解し辛い人が多い気がします。
C言語を使った人はわかるかと思いますが、「ポインタ」のイメージですね。


シャーロンコピーとディープコピーのサンプルプログラム


それでは、さっそくですがサンプルプログラムです。

例として、以下のサンプルプログラムになります。

  • コピー対象は、独自エントリクラス(Entry)の配列を「ArrayList」で保持する。
  • シャーロンコピー→ディープコピーの順番でコピーを実施。
  • コピー元配列の内容を表示。

サンプルプログラム


作成するクラスは2つになります。
「CopyTest.java」がコピーするメインクラス、「Entry.java」が配列要素となるエントリクラスです。


CopyTest.java

import java.util.ArrayList;

public class CopyTest {

    // メイン処理
    public static void main(String[] args){
        System.out.print("start: CopyTest\r\n");

        /**
        ①:エントリ内容の作成と確認
        */
        ArrayList<Entry> srcList = new ArrayList<Entry>();
        Entry newEnt1 = new Entry();
        newEnt1.setSei("Yamada");
        newEnt1.setNa("Taro");
        srcList.add(newEnt1);
        Entry newEnt2 = new Entry();
        newEnt2.setSei("Suzuki");
        newEnt2.setNa("jiro");
        srcList.add(newEnt2);
        System.out.print("エントリ内容の作成確認\r\n");
        dispList(srcList);

        /**
        ②:シャーロンコピー
        */
        ArrayList<Entry> shtList = srcList;
        Entry entry = shtList.get(0);
        entry.setSei("Satou");
        entry.setNa("Saburo");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

        /**
        ③:ディープコピー
        */
        ArrayList<Entry> deList = 
            new ArrayList<Entry>(srcList.size());
        for (Entry ent : srcList) {
            deList.add(ent.clone());
        }
        entry = deList.get(0);
        entry.setSei("Tanaka");
        entry.setNa("Shiro");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

        System.out.print("end: CopyTest\r\n");
    }

    private static void dispList(ArrayList<Entry> dispList){
        for (Entry dispEnt : dispList) {
            System.out.print("sei = " + dispEnt.getSei() + "\r\n");
            System.out.print("na = " + dispEnt.getNa() + "\r\n");
        }
    }
}

Entry.java

public class Entry implements Cloneable {
    private String sei;
    private String na;

    // 姓の設定
    public void setSei(String sei) {
        this.sei = sei;
    }

    // 名の設定
    public void setNa(String na) {
        this.na = na;
    }

    // 姓の取得
    public String getSei() {
        return this.sei;
    }

    // 名の取得
    public String getNa() {
        return this.na;
    }

    // クーロン
    public Entry clone() {
        try {
            return (Entry) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

シャーロンコピーの実行結果


サンプルプログラムの②の部分がシャーロンコピーのサンプルプログラムです。
シャーロンコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro

実際にシャーロンコピーしている部分は以下です。

ArrayList<Entry> shtList = srcList;

シャーロンコピーしたコピー先リスト(shtList)の内容を変更しています。
エントリの1つ目について、以下のように変更しています。

・Sei=Satou 
・Na=Saburo

シャーロンコピーしたコピー先リスト(shtList)を変更したにも関わらず、コピー元のリスト(srcList)が変更されています。
これはなぜかというと、コピーしたのはあくまでアドレスであるシャーロンコピーをおこなっているからになります。
変更先リストを変更しているにも関わらず変更元リストが変更されている、という事です。


ディープコピーの実行結果


サンプルプログラムの③の部分がディープコピーのサンプルプログラムです。
ディープコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro
end: CopyTest

実際のディープコピーは、「Entry」クラスのcloneメソッドになります。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList.size());
for (Entry ent : srcList) {
    deList.add(ent.clone());
}

要素を完全にコピーしています。
完全にコピーした要素に対して変更しているので、変更元の配列要素は変更されていないです。


これがディープコピーになります。
通常は、上記のようにcloneメソッドを準備して、要素を完全コピーする実装を準備しておきます。


よく、様々なウェブサイトで以下のように実装すればディープコピーされると記載されているのですが、それは間違いです。
気を付けてください。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList);

まとめ


ディープコピーとシャーロンコピーの違いを正しく理解してプログラミングしましょう!


よくあるのが、ディープコピーしていると思っていたがシャーロンコピーで、コピー元の要素が変更されてしまった!というパターンです。
思わぬバグを作りこんでしまう事になります。



FTPサーバとの通信をJavaでおこなうサンプルプログラム

Java

システムで外部システムと連携する方法はいろいろありますが、FTPサーバを使ってファイルのやり取りで連携するパターンも多々あります。


FTPサーバを使ってファイルのやり取りをおこなう場合、プログラムからFTPサーバにログインして、ファイルのアップロードやダウンロードをおこなう必要が出てきます。


今回は、Javaを使ってFTPサーバとのファイル送受信をおこなうサンプルプログラムを紹介します。


環境情報


  • OS:Windows10
  • Java:Java1.8.0_60
  • FTPサーバ:IIS(インターネットインフォメーションサービス)

JavaでFTPを使うために、「commons-net-3.6.jar」を使用しています。


FTPサーバについては、以前の記事で作成したIIS(インターネットインフォメーションサービス)を使います。



FTPサンプルプログラム


サンプルプログラムを紹介します。
サンプルプログラムのクラスは2つです。


FTPに対する操作をおこなうクラスとして「FtpMng.java」を準備し、「FtpMng.java」のインスタンスを使って処理をおこなうクラスが「FtpSample.java」になります。


以下のクラスがFTPに対する操作をおこなうクラスである「FtpMng.java」です。
FTPに対する操作一つ一つに対してメソッドを準備しています。


FTPサーバに対しておこなっているのは、ファイルのアップロード(FTPサイトにファイルを置く)と、ファイルのダウンロード(FTPサイトからファイルをもらう)の2つになります。

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;

public class FtpMng {

    private FTPClient   cli;

    // コンストラクタ
    public FtpMng() {
    }

    // 接続
    public boolean connect() {
        int rep;

        System.out.print("start: connect\r\n");
        try {
            cli = new FTPClient();
            cli.setDataTimeout(60000);
            cli.connect("localhost");
            rep = cli.getReplyCode();
            if (!FTPReply.isPositiveCompletion(rep)) {
                return false;
            } 
            System.out.print("end: connect\r\n");
            return true;
        } catch (SocketException e) {
            return false;
        } catch (IOException ie) {
            return false;
        }
    }

    // ログイン
    public boolean login() {
        System.out.print("start: login\r\n");
        try {
            if (!cli.login("ftp", "password")) {
                return false;
            }
            System.out.print("end: login\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }

    // ダウンロード
    public void downLoad() {
        FileOutputStream    outputstream;
        boolean             isRetrieve;

        System.out.print("start: downLoad\r\n");
        try {
            outputstream =
                new FileOutputStream("download.txt");
            isRetrieve =
               cli.retrieveFile("download.txt", outputstream);
            outputstream.close();
            if (!isRetrieve) {
                System.out.print("error: downLoad\r\n");
            }
            System.out.print("end: downLoad\r\n");
            return;
        } catch (IOException ie) { 
            return;
        }
    }

    // アップロード
    public void upLoad() {
        FileInputStream     inputstream;
        boolean             isStore;

        System.out.print("start: upLoad\r\n");
        try {
            inputstream =
                new FileInputStream("upload.txt");
            isStore =
                cli.storeFile("upload.txt",
                inputstream);
            inputstream.close();
            if (!isStore) {
                System.out.print("error: upLoad\r\n");
            } 
            System.out.print("end: upLoad\r\n");
            return;
        } catch (IOException ie) {
            return;
        }
    }

    // 切断
    public boolean disConnect() {

        System.out.print("start: disConnect\r\n");
        try {
            if (cli != null && cli.isConnected()) {
                cli.disconnect(); 
            }
            System.out.print("end: disConnect\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }
}

以下のクラスが実際にFTP処理をおこなう「FtpSample.java」です。
「FtpMng.java」に準備しているメソッドを順番に呼び出しているだけです。

public class FtpSample {

    public static void main(String[] args){
        System.out.print("start: FtpSample\r\n");
        FtpMng ftpClient = new FtpMng();

        // 1:接続
        ftpClient.connect();

        // 2:ログイン
        ftpClient.login();

        // 3:ダウンロード
        ftpClient.downLoad();

        // 4:アップロード
        ftpClient.upLoad();

        // 5:切断
        ftpClient.disConnect();

        System.out.print("end: FtpSample\r\n");
    }
}

FTPサンプルプログラムの解説


それでは以下に解説していきます。
そんなに難しいプログラムではないですが。


接続・ログイン・切断


FTPサーバに対してファイルをアップロード・ダウンロードする前に、FTPサーバにログインしなければいけません。
手順としては、接続→ログイン、の順番になります。


まずは接続です。

cli = new FTPClient();
cli.setDataTimeout(60000);
cli.connect("localhost");
rep = cli.getReplyCode();
if (!FTPReply.isPositiveCompletion(rep)) {
    return false;
} 

正常に接続できたかどうか?については、getReplyCode()で返り値を取得し、FTPReplyクラスで返り値を検証することで判断することができます。


次にログインです。

if (!cli.login("ftp", "password")) {
    return false;
}

IDとパスワードを指定してログインを実行です。
これでFTPサーバにログインできます。


ID ftp
パスワード password

一連の処理をおこなった後に切断して処理終了です。

if (cli != null && cli.isConnected()) {
    cli.disconnect(); 
}

サンプルプログラムでは、一応、FTPClientインスタンスが存在しており、かつ、接続されている場合に切断しています。


ダウンロード


FTPサーバからファイルを取得します。

outputstream =
    new FileOutputStream("download.txt");
isRetrieve =
    cli.retrieveFile("download.txt", outputstream);
outputstream.close();

Javaのアウトプットストリームクラスを使用してFTPサーバからファイルをダウンロードしています。
ストリームのクローズは忘れずに、ですね。


アップロード


FTPサーバにファイルを格納します。


inputstream =
    new FileInputStream("upload.txt");
isStore =
    cli.storeFile("upload.txt",
        inputstream);
inputstream.close();

こちらはダウンロードとは逆の方式です。
Javaのインプットストリームクラスを使用してFTPサーバにファイルをアップロードしています。
こちらも、ストリームのクローズは忘れずに、です。


まとめ


FTPサーバに対するファイル操作は、Javaで実装することが可能です。
サンプルプログラムでは必要最低限のソースコードしか紹介していませんが、FTPコマンドで可能な操作はJavaが全て可能です。


まとめ
  • FTPサーバに対する操作は、Javaで全て実装可能。
  • サンプル以外のFTP操作についても可能



現場で使えるtailコマンド。ログ監視で必要なオプションなど。

Linux

運用・保守の業務をおこなっている人にとっては、とても馴染みがあるコマンドのtailコマンドについてまとめてみます。


筆者が一番使うタイミングは、やっぱりログ監視です。

tailは「尻尾」「末尾」の略ですが、tailコマンドを使えばファイルの終端を常に表示するので、稼働中システムのログを監視する際にとても便利です。


基本動作


tail ※ファイル名 で、ファイルの末尾を表示することができます。


$ tail file1.txt
テスト8
テスト9
テスト10

追記を監視(ラージFとスモールf)


基本動作の tail ※ファイル名 だけでは、ファイルが追記されいったとしてもコマンド上に追記内容が表示されません。


しかしこれだと、例えばアプリケーションのログ監視をおこなう場合に不便です。
アプリケーションがログに追記するたびに表示内容を更新する(常に末尾表示)ためには、-f オプションを使用します。


$ tail -f file1.txt
テスト8
テスト9
テスト10
$ tail -f file1.txt
テスト9
テスト10
テスト11 ← 追記されている。

上記は-f(スモールf)を使っていますが、ログ監視のことを考えると不便な事があります。
それはログのローテーションです。


指定しているファイルがリネームされて新しいファイル名となり、指定しているファイルが新規ファイルとなった場合、-f(スモールf)では末尾監視がおこなわれません。


tailコマンドで監視できない

ログローテーションのように、監視対象のファイルが新しく作成された場合も監視した場合は、-f(スモールf)ではなく-F(ラージF)を使います。
-F(ラージF)を使う事で、ファイル名が変わって監視対象ファイルが新規作成されたとしても、対象ファイルは監視が継続されます。


$ tail -F file1.txt
tail: `file1.txt' has become inaccessible: No such file or directory
tail: `file1.txt' has appeared;  following end of new file
tail: file1.txt: file truncated
ファイル1
ファイル2
ファイル3

行数を指定する(-n)


末尾の表示する行数を指定示することも可能です。


$ tail -n3 file1.txt
ファイル9
ファイル10
ファイル11

-fオプションを指定して、末尾N行を監視する、といったことも可能です。


$ tail -3f file1.txt
ファイル9
ファイル10
ファイル11

パイプで繋げてgrepで抽出


特にtailコマンドのオプションではないのですが、大量にログを出力するアプリケーションであれば、-fオプションを指定していてログを監視していてもログが流れるのが早くて目で追えないです。

その場合は、監視しておきたいキーワードを事前に決めておき、パイプ(|)で繋いでgrepコマンドで抽出するようにしておきます。


$ tail -f file1.txt | grep ※キーワード
ファイル12

上記のようにパイプで繋いでgrepを指定することで、grepで指定したキーワードが出現した時だけ表示してくれます。

とても効率的なログ監視をおこなうことができます。


まとめ


現場のエンジニアにとって、tailコマンドは必須のコマンドかと思います。
上手に使いこなして、日々の作業に役立ててください。




Windows10でFTPサーバを構築して、DOSコマンドで動作確認までおこなう。

FTP

FTPサーバを使った開発をおこなう場合、各開発メンバそれぞれにFTPサーバを準備するのは骨が折れる作業です。
そういった場合は、Windows10で標準的に使えるFTPサーバを使用する方法が一般的と言えます。


今回は、Windows10上でFTPサーバを構築する方法と、動作確認方法まで紹介していきます。


環境情報


  • Windows10

IISを使ってFTPサーバを構築する


IISはInternet Infomation Service(インターネット インフォメーション サービス)の略で、Windowsに標準装備されているサーバサービス群になります。
今回は、このIISを使ってFTPサーバを構築します。


IISを有効にする


IISはWindows10に標準搭載されていますが、デフォルトでは無効になっています。
まずはこれを有効にして、IISを使用できるようにします。


「すべてのコントロールパネル項目」→「プログラムと機能」から、『Windowsの機能の有効化または無効化』をクリックして、Windowsの機能ウィンドウをひらきます。


Windowsの機能の有効化または無効化

Windowsの機能ウィンドウにおいて、インターネットインフォメーションサービスの「FTP Service」と「IIS 管理コンソール」の2つをチェックし、OKボタンを押下して終了します。


FTP Service」と「IIS 管理コンソール」

変更の適用が始まりますので、しばらく待ちます。


変更の適用

「変更が完了しました」のメッセージが表示されれば、IISの有効化は完了です。



IISが有効化されたかを確認してみましょう。
Windowsのプログラムバーに「インターネットインフォメーションサービス(IIS)マネージャー」が表示されていれば、IISの有効化は成功です。


インターネットインフォメーションサービス(IIS)の有効化完了

FTPユーザを作成する


FTPのユーザは、Windowsのユーザとして準備する必要があります。
管理ツール → コンピュータの管理からユーザを選択し、新しいユーザを作成します。


今回は、以下の情報を入力しています。


ユーザ名 ftp
フルネーム FTPユーザ
説明 IIS接続用ユーザ
パスワード password

Windowsユーザの作成

作成ボタンを押下したら、ユーザが作成されていることを一覧で確認します。



FTPサイトの作成と設定


FTPサイトを作成し、ファイルの格納場所を確保します。


Windowsのプログラム検索でIISと入力するとIISが表示されるので、アプリケーションを選択してインターネットインフォメーションサービス(IIS)マネージャーを起動します。



接続ペーンで「サイト」を選択し、操作ペーンで「FTPサイトの追加」を選択します。



FTPサイトの追加ウィンドウが表示されます。


FTPサイト名は任意で構いません。
解りやすい名前をつける形でよいでしょう。
コンテンツディレクトリは、接続先のパスとなります。


今回は、以下の情報を入力しています。


FTPサイト情報の作成

FTPサイト名 FTP接続試験
物理パス D:\temp

入力が完了したら次へボタンを押下します。


バインドとSSLの設定ウィンドウに移ります。
バインドするIPアドレスはすべて「全て未割当」を選択します。


FTPサイトを自動的に接続するはチェックでよいです。


SSLは無しにします。


入力が完了したら次へボタンを押下します。


FTPサイトの追加

バインドとは、FTPでの接続を指定したIPでのみ許可する設定となります。


バインドに関しては、複数のNICでIPアドレスを複数所有していなければ、「全て未割当」でよいです。


認証情報の入力に進みます。


認証方法は匿名とし、匿名ユーザに対して読み取り・書き込みを許可するようにします。


入力が完了したら終了ボタンを押下します。



サイトの一覧に追加したサイトが表示されていれば、サイトの追加に成功です。



DOSコマンドを使って動作確認


FTPサーバで想定通りに構築できたかを確認します。
Windowsに標準搭載されているFTPコマンドを使って確認するのが一番早いです。


以下のFTPコマンドは、DOSプロンプトで実行します。


ftpモードにする(ftp)


ftpコマンドで、ftpモードに移行します。


D:\work>ftp
ftp>

サイトに接続する(open)


openコマンドでサイトに接続をおこないます。
「open ※ホスト名」の書式になります。


今回はローカルPC上でFTPサーバを構築しているので、ホスト名はlocalhostになります。


ftp> open localhost
DESKTOP-U7JE3FQ に接続しました。
220 Microsoft FTP Service
200 OPTS UTF8 command successful - UTF8 encoding now ON.

ユーザ名とパスワードを入力すればFTPサーバにログインできます。
最初の手順でWindowsユーザとして作成したユーザ名とパスワードを指定します。


ユーザー (DESKTOP-U7JE3FQ:(none)): ftp
331 Anonymous access allowed, send identity (e-mail name) as password.
パスワード:
230 User logged in.
ftp>

ローカルディレクトリの指定(lcd)


転送するファイルが格納されているディレクトリ(転送元)に移動します。


ftp> lcd d:\work
ローカル ディレクトリは現在 D:\work です。

リモートディレクトリの指定(cd)


転送先ディレクトリに移動します。


ftp> cd ./dest
250 CWD command successful.

現在のディレクトリ位置を確認(pwd)


cdで移動しましたが、意図したディレクトリに移動されているかを確認します。


ftp> pwd
257 "/dest" is current directory.

転送モードを指定(ascii bin)


転送モードはASCIIモード(ascii)とバイナリモード(bin)の二種類があります。
今回はファイルを転送しよう思いますので、asciiを指定します。


ftp> ascii
200 Type set to A.

ローカルディレクトリからFTPサーバへ転送する(put)


ファイルをFTPサイトにアップロードします。
アップロードはputコマンドを使用します。


ftp> put test.txt
200 EPRT command successful.
150 Opening data connection
226 Transfer complete.
ftp: 16 バイトが送信されました 0.01秒 1.60KB/秒。

「Transfer complete.」が表示されれば転送成功です。
実際に転送されているかは、FTPサーバをエクスプローラーで確認可能です。


FTPサイトをエクスプローラーで確認

FTPサーバからローカルディレクトリに転送する(get)


FTPサーバからファイルをダウンロードします。
ダウンロードはgetコマンドを使用します。


ftp> get test.txt
200 EPRT command successful.
125 Data connection already open; Transfer starting.
226 Transfer complete.
ftp: 16 バイトが受信されました 0.00秒 16000.00KB/秒。

「Transfer complete.」が表示されれば転送成功です。
ローカルディレクトリ(lcdで確認)にgetしたファイルが表示されていれば転送成功です。


FTPのコマンドを確認する(help)


この記事で紹介したコマンド以外にも、ftpコマンドは存在します。
全てのコマンドはhelpコマンドで確認できます。


ftp> help
コマンドは省略することができます。コマンド:

!               delete          literal         prompt          send
?               debug           ls              put             status
append          dir             mdelete         pwd             trace
ascii           disconnect      mdir            quit            type
bell            get             mget            quote           user
binary          glob            mkdir           recv            verbose
bye             hash            mls             remotehelp
cd              help            mput            rename
close           lcd             open            rmdir

まとめ


IISでFTPサーバを構築してから動作確認まで解説しました。


まとめ
  • IISはWindowsに標準搭載されているサーバサービス群
  • 構築後の動作確認はFTPコマンドで実施するのが早い

FTPコマンドを使用すると、実際のFTPの挙動を直感的に把握することもできるのでお勧めです。