Yumを利用したMySQLのインストールと設定

前回はPostgreSQLでしたが今回はMySQLのインストールと設定メモです。とりあえずCentOS6の標準のYumリポジトリにあるMySQLを利用するのでバージョンは5.1です。

以下ルートユーザで実行
mysqlのインストール


$su -
#yum -y install mysql-server

my.cnfの設定
インストール直後、/etc配下に標準でmy.cnfが作成されテイルが/usr/share/mysql配下より用途に合ったmy-xxx.cnfを/etc配下にコピーするのもいいと思います。この方が楽なので自分の場合はよくその手を利用します。元のファイルはバックアップを取っておいてもいいかもしれません。

データベースの文字コードとストレージをそれぞれ設定


#cd /etc
#mv my.cnf my.cnf_backup ←バックアップを取る場合のみ実行
#cp /usr/share/mysql/my-xxx.cnf ./
#vi my.cnf

[mysqld]
...
character-set-server=utf8
default-storage-engine=InnoDB

InnoDBの設定項目のコメントを解除
innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /var/lib/mysql
innodb_buffer_pool_size = 16M
innodb_additional_mem_pool_size = 2M
innodb_log_file_size = 5M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50

MySQL5.1の標準のデータベースエンジンはMyISAMです。自分の場合は基本的にInnoDBを利用する為、標準のエンジンをInnoDBに設定します。
ちなみにMySQL5.5以降はInnoDBが標準のデータベースエンジンになっているようなので、5.5以上を利用する場合はdefault-storage-engine=InnoDBは不要かと思います。

サービスの起動


#service mysql start

OS起動時に自動起動するようにするには以下を実行


#chkconfig mysqld on

MySQLの初期設定を行うために以下のコマンドを実行


#mysql_secure_installation

...

Enter current password for root (enter for none): ←ENTER
OK, successfully used password, moving on...

...

Set root password? [Y/n] ←ENTER
New password: ←rootパスワード入力
Re-enter new password: ←rootパスワード再入力
Password updated successfully!
Reloading privilege tables..
... Success!

...

Remove anonymous users? [Y/n] ←匿名ユーザの削除(Y)
... Success!

...

Disallow root login remotely? [Y/n] ←rootによるリモートログインを許可しない(Y)
... Success!

...

Remove test database and access to it? [Y/n] ←テストデータベースの削除(Y)
- Dropping test database...
... Success!
- Removing privileges on test database...
... Success!

...

Reload privilege tables now? [Y/n] ←権限テーブルのリロード(Y)
... Success!

Cleaning up...

...

Thanks for using MySQL!

接続確認とユーザの作成


#mysql -u root -p
↓ローカル接続ユーザ
grant all privileges on [データベース名].[オブジェクト名(*を指定するとすべてのオブジェクトが対象)] to [ユーザ名]@localhost identified by 'パスワード';
↓リモート接続ユーザ(同一ネットワーク内からのアクセスのみ許可する場合)
grant all privileges on [データベース名].[オブジェクト名(*を指定するとすべてのオブジェクトが対象)] to [ユーザ名]@"xxx.yyy.zzz.%" identified by 'パスワード'; ←IPアドレスは各環境に合わせてください。
select user from mysql.user where user = 'ユーザ名';
flush privileges;
exit

リモート接続ユーザ作成時にたとえばhoge@"%"とするとあらゆるホストからの接続を受け付けるようになるためセキュリティ上LAN内のシステムでもお勧めできません。
"all privileges"はあらゆる権限を付与する為、セキュリティ上問題がある場合は適切に権限を設定して下さい。

新規作成したユーザでログインしデータベースを作成


$mysql -u ユーザ名 -p
create database データベース名;
show databases;

ファイアフォールの設定


#cd /etc/sysconfig
#vi iptables

以下の一文をCOMMITの前のいずれかの行に追加する
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT

設定を変更したら以下のコマンドでiptablesを再起動


#service iptables restart

ネットワークの確認


$netstat -tln | grep 3306

特定のIPアドレスにバインドさせたい場合はmy.cnfファイルの[mysqld]項目のbind-addressにIPアドレスを設定してください。


■その他メモ
MySQLサーバの権限のあるユーザで実施

ユーザの権限削除


revoke 権限(all privilegesの場合はすべての権限) on データベース名.オブジェクト名 from ユーザ名@ホスト名(localhost or www.xxx.yyy.zzzなど);
データベース名、オブジェクト名に'*'を設定した場合すべてのデータベース、オブジェクトが対象となります。

ユーザの削除


delete from mysql.user where user = 'ユーザ名' and host = 'ホスト名';

PostgreSQLのインストールと各種設定

PCの不慮の事故により、バックアップを取っていなかったために仮想マシンのサーバをすべて再構築する羽目になってしまってからOSをインストールしての基本的な設定と、SSH関連の設定を行って以来放置していたのですがとりあえずPostgreSQLのインストールと設定を行いました。
1度インストールするとなかなかやる機会がなく、今後同様の事態に陥っても苦労せずに済むように手順をメモしておきます。

OSはCentOS6で基本的には標準のyumレポジトリに登録されているPostgreSQL8.4をyumを利用してインストールします。

PostgreSQLのインストール


su -
yum -y install postgresql-server

yum経由でインストールするとpostgresユーザが自動的に作成されるので当該ユーザの確認とパスワードの設定をおこなう


id postgres
passwd postgres

サービス名の確認


chkconfig --list | grep postgres

データベースの初期化を行いデータベースディレクトリの作成を行う


service postgresql initdb

postgresユーザにスイッチして以下の作業を行う

■/var/lib/pgsql/data/postgresql.confの編集
listen_addressをコメントアウトlocalhostから任意のアドレスに変更しネットワーク接続を可能にする


#listen_addresses = 'localhost'

listen_addresses = '*'


■/var/lib/pgsql/data/pg_hba.confファイルを編集
pg_hba.confファイルを編集しアクセス許可の設定を行う。
同一ネットワーク内からのパスワード認証によるアクセスを許可する。(IPv6は利用しないのでとりあえずreject)に設定。


#local
local all all ident
#ipv4
host all all 127.0.0.1/32 password
host all all www.xxx.yyy.zzz/24 password #←IPアドレスは自身のネットワーク環境に合わせて変更すること
#ipv6
host all all ::1/128 reject

ファイアウォールの設定


su -
vi /etc/sysconfig/iptables
以下の一文をCOMMITの前のいずれかの行に追加する
-A INPUT -m state --state NEW -m tcp -p tcp --dport 5432 -j ACCEPT


PostgreSQLの起動


service postgresql start

postgresユーザでログインしデータベースの一覧の確認とパスワードを設定する。


su - postgres

データベースの確認


psql -l

パスワードの設定と確認


psql
alter user postgres with password '任意のパスワード';
select usename, passwd from pg_user;

パスワードによる接続が許可されるか確認


psql -U postgres -h www.xxx.yyy.zzz

pg_hba.confファイルを開きlocalからのアクセスもパスワード認証に変更する


local all all password

PostgreSQLの再起動


su -
service postgresql restart

ローカルからの接続確認


su - postgres
psql -U postgres

OS起動時に自動的に起動するようにするには以下のように設定する


su -
chkconfig postgresql on

テスト用のユーザ作成とデータベースの作成


su - postgres
createuser -P hoge #← -Pはパスワードの設定を行うパラメータ
新しいロールのパスワード:###新規ユーザのパスワード
もう一度入力してください:###新規ユーザのパスワード
新しいロールをスーパーユーザとしますか? (y/n)n
新しいロールにデータベース作成権限を与えますか? (y/n)y
新しいロールにロールを作成する権限を与えますか? (y/n)n
パスワード: ###postgresユーザのパスワード

createdb -O hoge hogedb #←データベースの作成(-Oで所有者を先ほど作成したhogeユーザに設定)

新ユーザでの接続確認


psql -U hoge hogedb

接続確認で接続ができればOKです。

かなり雑ですがメモなので。。。

よく利用するyumコマンドメモ

よくCentOSを利用するのですが、yum関連のコマンドはどうも忘れやすいので個人的によく利用するコマンドをメモします。

yum -y update
→インストールされているパッケージをすべて更新

yum update <パッケージ名>
→指定したパッケージの更新

yum [-y] install <パッケージ名>
→対象パッケージをインストール

yum remove <パッケージ>
→対象パッケージをアンインストール

yum list | grep <検索対象>
→リスト内から検索対象文字列を含むパッケージを取得

yum list installed
→インストール済みパッケージ一覧の表示

yum list available
リポジトリに存在する利用可能なパッケージ一覧の表示

yum list update
→更新可能パッケージ一覧の表示

yum deplist <パッケージ>
→対象パッケージに依存するパッケージを表示

yum info <パッケージ>
→対象パッケージの情報を出力する

Java7のPhaserクラスを利用した同期処理

Java7ではPhaserという同期処理用のクラスが追加になっています。PhaserはCyclicBarriarやCountDownLatch同様にスレッドの同期処理を行うために利用しますが、より柔軟性に富んでいるようです。

Phaserクラスの詳細はJavaDocを参照すればわかると思うので、今回も自分用のメモとして簡単な利用方法を示したサンプルプログラムを残します。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Phaser;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

public class PhaserTest {

    public static void main(String[] args) {
        List<Runnable> tasks = new ArrayList<>();
        tasks.add(new Task());
        tasks.add(new Task());
        tasks.add(new Task());
        tasks.add(new Task());
        runTask(tasks);
    }
    
    private static void runTask(List<Runnable> tasks) {
        // タスク数+1(メインスレッド)となるように初期値1でPhaserを生成
        final Phaser phaser = new Phaser(1);
        for (final Runnable t : tasks) {
            final String taskName = t.toString();
            // Phaserに登録
            phaser.register();
            new Thread() {
                public void run() {
                    System.out.println(taskName + " is waiting for the remaining tasks.");
                    // すべてのタスクの実行準備が整うまで待機
                    phaser.arriveAndAwaitAdvance();
                    System.out.println(taskName + " is ready to start.");
                    t.run();
                }
            }.start();
        }
        // 登録解除
        phaser.arriveAndDeregister();
        System.out.println("Phaser was deregistered.");
    }
   
    private static final class Task implements Runnable {

        private static final AtomicInteger idGenerator = new AtomicInteger();
        private final long id = idGenerator.incrementAndGet();
        
        @Override
        public void run() {
            System.out.println(toString() + " started.");
            try {
                Thread.sleep(getSleepTime());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(toString() + " stopped.");
        }
        
        @Override
        public String toString() {
            return "Task [id = " + id + "]";
        }
        
        private static long getSleepTime() {
            return ThreadLocalRandom.current().nextLong(100, 1000);
        }
    }

}

基本的なことはコメントとPhaserのJavaDocを読めばわかると思うのでポイントだけ簡単にまとめると、まずnew Phaser(1)でPhaserオブジェクトを生成し、for-eachループでタスク数分ループしながらPhaser#registerで未到達のパーティを登録します。

ループを抜けた時点でPhaserに登録されているパーティ数は5になっています。Phaser#getRegisteredPartiesメソッドで確認可能です。

その後、メインスレッド側で登録を解除します。(phaser.arriveAndDeregister())

この時点でパーティ数は4となります。ループ内で実行したスレッドはPhaser#arriveAndAwaitAdvanceというメソッドで登録したPhaser全てが当該ポイントまで到達するまで待機します。

メインスレッド側で登録解除を行った時点で、Phaser#arriveAndAwaitAdvanceに到達しているスレッドがいくつあっても関係はありません。

もしすべてのスレッドが当該地点まで到達して最後にメインスレッドが登録の解除を行ったならば待機中のスレッドが動作し始めます。

まだ未到達のスレッドがあった場合は、とりあえずメインスレッドは終了し、登録パーティ数が4となった状態で4スレッドが当該ポイントに到達した時点で待機しているスレッド含めすべてのスレッドが処理を開始します。

Phaserクラスにはこのほかにも様々なメソッドが用意されているので時間があるときにいろいろ実験してみようと思います。

Fork/Joinフレームワークを用いたマージソート

Java7ではFork/Joinフレームワークが新たに追加になっているおかげでマージソートなどでデータをソートするアルゴリズムを効率的に処理するプログラミングが比較的簡単に作成することが可能です。

今回は自分用のメモとしてFork/Joinフレームワークを利用したマージソート処理を記載します。

まず最初にRecursiveTaskを継承したマージソートを行うMergeSortTaskクラスを作成します。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;

public abstract class MergeSortTask<T> extends RecursiveTask<List<T>> {

    private static final long serialVersionUID = 1L;

    private final List<T> values;
    
    protected MergeSortTask(List<T> values) {
        this.values = values;
    }

    @Override
    protected List<T> compute() {
        if (values.size() > 1) {
            // ソート対象リストサイズの中間値を算出
            int mid = values.size() / 2;
            final MergeSortTask<T> task1 = createMergeSortTask(values.subList(0, mid));
            final MergeSortTask<T> task2 = createMergeSortTask(values.subList(mid, values.size()));
            // 分割した最初のタスクをfork
            task1.fork();
            // 次タスクの処理を実行
            List<T> sortedValues2 = task2.compute();
            // forkした最初の処理を待ち合わせる
            List<T> sortedValues1 = task1.join();
            return merge(sortedValues1, sortedValues2);
        } else {
            return values;
        }
    }
    
    protected abstract MergeSortTask<T> createMergeSortTask(List<T> values);
    
    /*
     * マージメソッド
     */
    protected List<T> merge(List<T> arg1, List<T> arg2) {
        final List<T> mergedValues = new ArrayList<>(arg1.size() + arg2.size());
        int i = 0, j =0,  k = 0;
        while (i < arg1.size() || j < arg2.size()) {
            // 値の入れ替え
            if (j >= arg2.size() || ((i < arg1.size()) && compare(arg1.get(i), arg2.get(j)))) {
                mergedValues.add(k, arg1.get(i++));
            } else {
                mergedValues.add(k, arg2.get(j++));
            }
            k++;
        }
        return mergedValues;
    }
    
    /*
     * 大小比較メソッド
     */
    protected abstract boolean compare(T arg1, T arg2);

}

ポイントはcomputeメソッド内でソート対象となるリストを分割して、最初のタスクをforkし、次のタスクを処理後にforkしたタスクの待ち合わせを行いmergeメソッドでマージしているという点です。

抽象メソッドとなっている部分は昇順と降順用のソートクラスを作成するためにあえてテンプレートメソッドパターンを利用しています。

それぞれ昇順用と降順用のサブクラスは以下のようになります。

import java.util.List;

/**
 * 昇順マージソートタスク
 */
public class ComparableAscMergeSortTask<T extends Comparable<? super T>> extends MergeSortTask<T> {
    
    private static final long serialVersionUID = 1L;

    public ComparableAscMergeSortTask(List<T> values) {
        super(values);
    }

    @Override
    protected MergeSortTask<T> createMergeSortTask(List<T> values) {
        return new ComparableAscMergeSortTask<>(values);
    }

    @Override
    protected boolean compare(T arg1, T arg2) {
        return arg1.compareTo(arg2) < 0 ? true : false;
    }
}
import java.util.List;

/**
 * 降順マージソートタスク
 */
public class ComparableDescMergeSortTask<T extends Comparable<? super T>> extends MergeSortTask<T> {

    private static final long serialVersionUID = 1L;

    public ComparableDescMergeSortTask(List<T> values) {
        super(values);
    }
    
    @Override
    protected MergeSortTask<T> createMergeSortTask(List<T> values) {
        return new ComparableDescMergeSortTask<>(values);
    }

    @Override
    protected boolean compare(T arg1, T arg2) {
        return arg1.compareTo(arg2) > 0 ? true : false;
    }

}

それぞれの違いはcompareメソッド内の比較演算子の向きぐらいです。

メインクラスは以下の通り。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;

public class MergeSortMain {

    public static void main(String[] args) {
        List<Integer> values = new ArrayList<>();
        for (int i= 0; i < 10; i++) {
            long seed = System.nanoTime();
            Random random = new Random(seed);
            values.add(Math.abs(random.nextInt()));
        }

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        System.out.println("========= ASC =========");
        List<Integer> results = forkJoinPool.invoke(new ComparableAscMergeSortTask<Integer>(values));
        for (Integer r : results) {
            System.out.println(r);
        }

        System.out.println("========= DESC =========");
        results = forkJoinPool.invoke(new ComparableDescMergeSortTask<Integer>(values));
        for (Integer r : results) {
            System.out.println(r);
        }
    }

}

ForkJoinPoolクラスを生成し、invokeメソッドにそれぞれのタスクを引数で渡し、ソート結果をリストで受け取ります。

実行結果は以下のような感じです。


========= ASC =========
220809008
313991963
599017177
882423875
946292192
1064163273
1156976803
1363513242
2090228414
2138086498
========= DESC =========
2138086498
2090228414
1363513242
1156976803
1064163273
946292192
882423875
599017177
313991963
220809008

メモリにやさしくない感はありますがサンプルなので。

AsynchronousFileChannelを用いたファイルの読書き処理について

AsynchronousFileChannelを用いたファイルの非同期書き込みと、読み込み処理について記載します。

まずは書き込み処理を行う処理。

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class AsyncFileWriteMain {
    
    private static final int LINE_SIZE = 10;

    public static void main(String[] args) throws IOException {
        final Path path = Paths
                .get("path_to_file");
        
        if (Files.exists(path)) {
            Files.delete(path);
        }
        
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            System.out.println("Start to write");
            final CompletionHandler<Integer, String> handler = new CompletionHandlerImpl();
            for (int i = 0, position = 0; i < LINE_SIZE; i++) {
                final String message = "Hello World-" + Integer.toString(i) + "\n";
                // Buffer、書き込み位置、アタッチメント、ハンドラを与えて書き込み処理実行
                channel.write(ByteBuffer.wrap(message.getBytes()), position, getAttachment(i), handler);
                // 次にメッセージを書き込む位置を設定
                position += message.length();
            }
            
        }
    }
    
    /*
     * Attachmentはサンプルなので適当なメッセージを返す。
     */
    private static String getAttachment(int i) {
        return (i + 1) == 1 ? Integer.toString(i + 1) + " time" : Integer.toString(i + 1) + " times";
    }
    
    private static final class CompletionHandlerImpl implements CompletionHandler<Integer, String> {

        @Override
        public void completed(Integer result, String attachment) {
            System.out.println("[Completed to write] Current thread : " + Thread.currentThread().getId() + " Attachment : " + attachment + " Written bytes : " + result);
        }

        @Override
        public void failed(Throwable e, String attachment) {
            System.out.println("[Failed to write] Current thread : " + Thread.currentThread().getId() + " Attachment : " + attachment + " Exception : " + e);
            
        }
        
    }

}

try-with-resourcesブロック内で、AsynchronousFileChannelクラスのopenメソッドを用いてチャネルをオープンします。

あとはコメントにある通りAsynchronousFileChannel#writeメソッドで書き込み処理を行います。CompletionHandlerは書き込み完了時に実行されるハンドラクラスで、書き込みが正常に完了すると、completedメソッドが実行され、失敗するとfailedメソッドが実行されます。

上記プログラムを実行するとコンソールに以下のようなメッセージが出力されると思います。


Start to write
[Completed to write] Current thread : 16 Attachment : 2 times Written bytes : 14
[Completed to write] Current thread : 17 Attachment : 1 time Written bytes : 14
[Completed to write] Current thread : 16 Attachment : 7 times Written bytes : 14
[Completed to write] Current thread : 15 Attachment : 3 times Written bytes : 14
[Completed to write] Current thread : 17 Attachment : 8 times Written bytes : 14
[Completed to write] Current thread : 12 Attachment : 6 times Written bytes : 14
[Completed to write] Current thread : 14 Attachment : 4 times Written bytes : 14
[Completed to write] Current thread : 13 Attachment : 5 times Written bytes : 14
[Completed to write] Current thread : 17 Attachment : 10 times Written bytes : 14
[Completed to write] Current thread : 16 Attachment : 9 times Written bytes : 14

上記メッセージから当該書き込み処理は6つのスレッドにより処理されたことがわかると思います。
ファイルには次のようなメッセージが出力されていると思います。


Hello World-0
Hello World-1
Hello World-2
Hello World-3
Hello World-4
Hello World-5
Hello World-6
Hello World-7
Hello World-8
Hello World-9

次に、上で書き込みを行ったファイルから非同期で読み込む処理を紹介します。

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class AsyncFileReadMain {

    private static final int LINE_SIZE = 10;

    private static final int READ_BUFFER_SIZE = 14;

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IOException,
            InterruptedException, ExecutionException {
        final Path path = Paths.get("path_to_file");
        ExecutorService executorService = Executors.newFixedThreadPool(LINE_SIZE);
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, EnumSet.of(StandardOpenOption.READ), executorService)) {
            System.out.println("Start to read");
            ByteBuffer[] buffers = new ByteBuffer[LINE_SIZE];
            Future<Integer>[] futures = new Future[LINE_SIZE];
            for (int i = 0, position = 0; i < LINE_SIZE; i++, position += READ_BUFFER_SIZE) {
                buffers[i] = ByteBuffer.allocate(READ_BUFFER_SIZE);
                futures[i] = channel.read(buffers[i], position);
            }

            for(int i = 0; i < LINE_SIZE; i++) {
                Integer readByteSize = futures[i].get();
                System.out.println("Read bytes : " + readByteSize + " Read message : " + new String(buffers[i].array()));
                
            }
        }
        executorService.shutdown();
        executorService.shutdownNow();
    }
}

書き込み処理同様にtry-with-resourcesブロック内で、AsynchronousFileChannelクラスのopenメソッドを用いてチャネルをオープンしますが、ExecutorServiceを第3引数に渡しています。

このスレッドプール内で生成されたスレッドにより非同期読み込みが行われます。

AsynchronousFileChannelのreadメソッドをにBufferと読み込み開始位置を示す値を渡します。writeメソッド同様にCompletionHandlerを渡すことができるメソッドもありますがここでは省略します。

当該メソッドはFutureオブジェクトを返すので、読み込みループ後に、Future#getメソッドより読み込みが完了した際のバイト数を取得し、bufferに読み込まれたメッセージを出力します。

実行結果は以下のようになると思います。


Start to read
Read bytes : 14 Read message : Hello World-0

Read bytes : 14 Read message : Hello World-1

Read bytes : 14 Read message : Hello World-2

Read bytes : 14 Read message : Hello World-3

Read bytes : 14 Read message : Hello World-4

Read bytes : 14 Read message : Hello World-5

Read bytes : 14 Read message : Hello World-6

Read bytes : 14 Read message : Hello World-7

Read bytes : 14 Read message : Hello World-8

Read bytes : 14 Read message : Hello World-9

これだと非同期で読み込まれていることがよくわからないと思うのですが、CompletionHandlerを引数に渡すreadメソッドを実行し書き込み時同様にスレッドのIDを出力すると異なるスレッドにより処理されていることがわかると思います。

このようにJava7では非同期処理が非常に簡単にプログラミング可能です。

SeekableByteChannelを利用したファイルのランダムアクセス処理

Java7ではSeekableByteChannelこれはファイルのランダムアクセス処理を行うためのノンブロッキングIOクラスが存在します。
ByteBufferクラスと合わせて利用するのですが、今回はこのクラスを用いた簡単なサンプルをメモとして残すことにします。

当該クラスを用いたランダムアクセス処理に利用するために以下のようなファイルを用意します。


It was a beautiful Sunday morning.
The rain was gone when I woke up. So I opened the window to look out.
I saw a dew on the class and heard the birds sing.

意味が分からない文章がありますが、これは後々その部分を当該クラスを用いて修正するためにあえて意味の通らない単語を利用しています。

SeekableByteChannelはFilesクラスのnewByteChannelメソッドにPathオブジェクトを渡して取得します。
読み込みを行う際はSeekableByteChannelのposition(long)メソッドでファイルのカーソルを指定位置まで移動し、readメソッドでByteBufferに指定バイト数読み込むことが可能です。
書き込み時は、SeekableByteChannel取得時にnewByteChannelの第2引数にStandardOptionOption.APPENDを指定した場合はカーソルがファイルの最終位置に移動し、特定の文字を追記することが可能です。
StandardOpenOption.WRITEを指定した場合はpositionメソッドで指定したバイト位置から、指定文字列で上書きすることが可能です。

では、最初に用意した文章からbeautifulという単語と「when I woke up」というセンテンスを読み込み表示する処理と、最終行のon the classのclassをgrassに変更し、文章の最後に「Clouds were moving fast in the sky.」という1文を追加する処理を以下に示します。

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class SeekableMain {
    
    private static final int BUFFER_SIZE = 64;

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("path_to_file");
        System.out.println("--------------------------------");
        readAndDisplay(path);
        System.out.println("--------------------------------");
        writeAndDisplayAll(path);
    }
    
    /*
     * SeekableByteChannelとByteBufferを用いて任意の位置から指定バイトの文字を読み込み出力する。
     */
    private static void readAndDisplay(final Path path) throws IOException {
        try (SeekableByteChannel channel = getSeekableByteChannel(path)) {
            ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
            // beautifulを出力
            display(channel, buffer, 9, 9);
            buffer.clear();
            // 「when I woke up」を出力
            display(channel, buffer, 53, 14);
        }
    }
    
    private static void display(SeekableByteChannel channel, ByteBuffer buffer, int start, int length) throws IOException {
        channel.position(start);
        channel.read(buffer);
        for (int i = 0; i < length; i++) {
            System.out.print((char)buffer.get(i));
        }
        System.out.println();
    }
    
    /*
     * 指定位置に特定の文字列を書き込む。
     */
    private static void writeAndDisplayAll(final Path path) throws IOException {
        // 書き込みモードでチャネルオープン
        try (SeekableByteChannel channel = getSeekableByteChannel(path, StandardOpenOption.WRITE)) {
            // classをgrassに修正
            String modifier = "grass";
            channel.position(124);
            channel.write(ByteBuffer.wrap(modifier.getBytes()));
        }
        // 追記モードでチャネルオープン
        try (SeekableByteChannel channel = getSeekableByteChannel(path, StandardOpenOption.APPEND)) {
            // 最終行に1文追加
            String appended = " Clouds were moving fast in the sky.";
            channel.write(ByteBuffer.wrap(appended.getBytes()));
        }
        // BufferedReaderで全ファイル内容を出力する。
        try (BufferedReader reader = Files.newBufferedReader(path, Charset.defaultCharset())) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
    
    private static SeekableByteChannel getSeekableByteChannel(Path path, StandardOpenOption... opts) throws IOException {
        return Files.newByteChannel(path, opts);
    }

}

処理結果は以下のようになるかと思います。

                                                              • -

beautiful
when I woke up

                                                              • -

It was a beautiful Sunday morning.
The rain was gone when I woke up. So I opened the window to look out.
I saw a dew on the grass and heard the birds sing. Clouds were moving fast in the sky.

簡単ではありますが、以上です。それではよいランダムアクセス生活を。