目次 | 索引 |
---|---|
コンピュータのデータの単位は ビット ( bit )です。 ただし、これは理論上の話です。 現実のコンピュータ・システムは、 バイト ( byte )を単位としています。 1バイトは8ビットであることを思い出してください。
1バイトで表現できるデータには次のようなものがあります(2 8 = 256)。
また、2バイトで表現できるデータは次の通りです(2 16 = 65,536)。
4バイトで表現できるデータは、
などです。
Javaは、文字(
char
型)を1バイトではなく2バイトで表現します。
つまり、漢字なども文字として取り扱えるように設計されています。
このため、ちょうど1バイトで表現されるデータ型として、バイト(
byte
)が用意されています。
なお、特に断らない限り、整数は
int
型になります。
int
型は4バイトで表現されます。
文字は、内部では整数として処理されています。 この、文字から整数への対応を 文字符号 ( character code )と呼びます。 Javaの採用している文字符号は、 Unicode と呼ばれる体系です。 Unicodeは ASCII という体系の拡張になっています。 主な文字のASCII符号は次の通りです。
制御文字 | 水平タブ | 改行 | 復帰 | スペース |
---|---|---|---|---|
表示 | '\t' | '\n' | '\r' | ' ' |
符号 | 9 | 10 | 13 | 32 |
文字 | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
文字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
文字 | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
文字 | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
文字 | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |
文字 | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
符号 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
文字をバイトにしたり、整数を文字にするには、型の変換を指定する必要があります。 型の変換は キャスト ( cast )と呼ばれます。 式 exp の値を型 type に変換するには、
(type)exp
と書きます。
Javaを用いますと、ファイルの内容を読み込んだり、ファイルにデータを書き込んだりできます。 Javaは、ファイルをバイトの列として取り扱います。 この列は、具体的には ストリーム ( stream )とよばれるデータ構造です。 データの列という点で、ストリームは配列に似ています。 ストリームと配列の違いは次の通りです。
ストリームがどうしてもイメージできない人は、工場のベルトコンベアを思い浮かべてください。 ベルトに載ったデータが次々と自分に近づいてくるのが入力ストリーム、ベルトにデータを載せてだんだん自分から離れていくのが出力ストリームです。
はじめに、ファイルの内容を読み込む方法を説明します。 ファイルの内容の読み込みは、次の手順で行ないます。
Javaでは、
FileInputStream
クラスのインスタンスを生成しますと、入力ストリームの生成とストリームを開くことが同時に行なえます。
ファイル
file
に対して
FileInputStream stream = new FileInputStream(file);
としますと、 stream に入力ストリームが格納され、このストリームが開きます。
データの読み込みには、インスタンスメソッド
read
を用います。
式
stream.read()
の値は、0以上255以下の整数か、-1になります。
前者は、読み込んだ1バイトのデータを整数と見なしたものです。
後者は、そのストリームのデータが終わったことを意味します。
通常、読み込んだデータをいったん
int
型の変数に格納し、それが-1でなければ必要に応じてキャストするといったことを行ないます。
stream.close();
としますと、ストリームが閉じます。
配列の要素をすべて取り出すには次のようにしました。
int[] a = {...}; int i; ... for (i = 0; i < a.length; i++) { ... a[i] ... }
これに対して、入力ストリームからすべてのデータを読み込むには次のようにします。
FileInputStream fis = new FileInputStream("data.txt"); int in; ... for (in = fis.read(); in != -1; in = fis.read()) { ... in ... } ... fis.close();
以下のプログラムは、ファイルの内容を読み込んで、それを画面に出力するものです。
Berlin Moscow Paris
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileInputTest { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileInputStream fis = new FileInputStream(args[0]); /* 6*/ int in; /* 7*/ char c; /* 8*/ for (in = fis.read(); in != -1; in = fis.read()) { /* 9*/ c = (char) in; /*10*/ System.out.print(c); /*11*/ } /*12*/ System.out.println(); /*13*/ fis.close(); /*14*/ } /*15*/ }
b00a001@Ampere:~% cat cities1.txt Berlin Moscow Paris b00a001@Ampere:~% java FileInputTest cities1.txt Berlin Moscow Paris b00a001@Ampere:~%
1行目は、入出力関係の組み込みクラスを利用するということです。
4行目の
throws IOException
は、入出力関係のエラー(正確には例外)が起こるかもしれないが、その際に特別なことはしないという意味です。
これらを省略しますと、プログラムはコンパイルできません。
5行目で、コマンドライン引数をファイル名と見なして入力ストリームを生成します。
9行目で、読み込んだデータを文字に変換します。
次に、ファイルへデータを書き込む方法を説明します。 ファイルへのデータの書き込みは、次の手順で行ないます。
入力ストリームと同様、
FileOutputStream
クラスのインスタンスを生成しますと、出力ストリームの生成とストリームを開くことが同時に行なえます。
ファイル
file
に対して
FileOutputStream stream = new FileOutputStream(file);
としますと、 stream に出力ストリームが格納され、このストリームが開きます。
データの書き込みには、インスタンスメソッド
write
を用います。
stream.write(byte);
としますと、ストリームにバイト
byte
が書き込まれます。
通常、途中の計算は
int
型で行ない、最後に
byte
型に変換して
write
メソッドを呼び出すといったことを行ないます。
stream.close();
としますと、ストリームが閉じます。
以下のプログラムは、コマンドライン引数の内容を順にファイルに書き込むものです。 第1引数がファイル名、第2引数以降が書き込む内容になります。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileOutputTest { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileOutputStream fos = new FileOutputStream(args[0]); /* 6*/ int i, j; /* 7*/ byte b; /* 8*/ String s; /* 9*/ for (i = 1; i < args.length; i++) { /*10*/ s = args[i]; /*11*/ for (j = 0; j < s.length(); j++) { /*12*/ b = (byte) s.charAt(j); /*13*/ fos.write(b); /*14*/ } /*15*/ b = (byte) '\n'; /*16*/ fos.write(b); /*17*/ } /*18*/ fos.close(); /*19*/ } /*20*/ }
b00a001@Ampere:~% java FileOutputTest cities2.txt London "New York" Tokyo b00a001@Ampere:~% cat cities2.txt London New York Tokyo b00a001@Ampere:~%
10行目で第
i
+ 1 引数の文字列が
s
に格納されます。
12行目でその
j
番目の文字が取り出され、これをバイトに変換します。
15行目では、制御文字の改行をバイトに変換しています。
なお、コマンドライン引数はスペースで区切られますが、ダブルクオート(
"
)で囲みますと、スペースも文字列の一部と見なされます。
以下のプログラムは、ファイルをコピーするものです。 入力ストリームと出力ストリームの両方を用いています。
London New York Tokyo
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileCopyTest { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileInputStream fis = new FileInputStream(args[0]); /* 6*/ FileOutputStream fos = new FileOutputStream(args[1]); /* 7*/ int in; /* 8*/ byte b; /* 9*/ for (in = fis.read(); in != -1; in = fis.read()) { /*10*/ b = (byte) in; /*11*/ fos.write(b); /*12*/ } /*13*/ fis.close(); /*14*/ fos.close(); /*15*/ } /*16*/ }
b00a001@Ampere:~% cat cities2.txt London New York Tokyo b00a001@Ampere:~% java FileCopyTest cities2.txt cities3.txt b00a001@Ampere:~% cat cities3.txt London New York Tokyo b00a001@Ampere:~%
Javaにとってファイルはバイトの列ですので、バイト単位でファイルの読み書きができれば、ファイルの処理は十分なはずです。
しかし、テキストファイルの内容を一行ずつ読み込みたいときに、改行かどうかを調べながら1バイトずつ読み込まなくてないけないのでは不便です。
また、数100をファイルに書き込みたいときに、
1
,
0
,
0
と分解しなくてはいけないも面倒です。
組み込みのクラスである
DataInputStream
クラスを用いますと、1行ずつ読み込むことが簡単にできます。
同様に、
PrintStream
クラスを用いますと、数や文字列などのデータを自動的に分解して書き込むことができます。
この2つのクラスは、フィルタとしての機能を提供します。 ベルトコンベアのイメージですと、フィルタとは、ベルトにのっているデータを取り出して、加工して、またベルトに戻す人のことです。 フィルタによって、ファイルから読み込んだバイトの列が文字列にまとめられたり、データが分解されてファイルに書き込むバイトの列になったりするのです。
DataInputStream
クラスのインスタンスメソッド
readLine
によって、入力ストリームから1行分の文字列が読み込めます。
ここで1行とは、
\n
,
\r\n
,
\r
または入力ストリームの終わりまでの文字列です。
入力ストリームが終わってから読み込もうとしますと、特別な値
null
が返ってきます。
printStream
クラスのインスタンスメソッド
print
を用いますと、画面に出力する要領でデータを分解して出力ストリームに書き込めます。
println
ですと、データの後に改行が追加されます。
次のプログラムは、ファイルの内容を読み込んで画面に出力するものです。 上記の FileInputTest.java と比較してください。
Berlin Moscow Paris
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileInputTest2 { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileInputStream fis = new FileInputStream(args[0]); /* 6*/ DataInputStream dis = new DataInputStream(fis); /* 7*/ String s; /* 8*/ for (s = dis.readLine(); s != null; s = dis.readLine()) { /* 9*/ System.out.println(s); /*10*/ } /*11*/ fis.close(); /*12*/ dis.close(); /*13*/ } /*14*/ }
b00a001@Ampere:~/java% cat cities1.txt Berlin Moscow Paris b00a001@Ampere:~/java% java FileInputTest2 cities1.txt Berlin Moscow Paris b00a001@Ampere:~/java%
6行目で、ファイルの入力ストリームからフィルタ付き入力ストリームを生成します。
8行目で、
null
、すなわち入力ストリームが終わるまで、入力ストリームから一行ずつ読み込みます。
12行目で、入力ストリームを閉じます。
次のプログラムは、コマンドライン引数の内容をファイルに書き込むものです。 同様に、FileOutputTest.java と比べてください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileOutputTest2 { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileOutputStream fos = new FileOutputStream(args[0]); /* 6*/ PrintStream prs = new PrintStream(fos); /* 7*/ int i; /* 8*/ for (i = 1; i < args.length; i++) { /* 9*/ prs.println(args[i]); /*10*/ } /*11*/ fos.close(); /*12*/ prs.close(); /*13*/ } /*14*/ }
b00a001@Ampere:~/java% java FileOutputTest2 cities2.txt London "New York" Tokyo b00a001@Ampere:~/java% cat cities2.txt London New York Tokyo b00a001@Ampere:~/java%
6行目で、ファイルの出力ストリームからフィルタ付き出力ストリームを生成します。 9行目で、コマンドライン引数の文字列を分解することなく出力ストリームに書き込みます。 12行目で、出力ストリームを閉じます。
次のプログラムは、ファイルをコピーするものです。 FileCopyTest.java と見比べてください。
London New York Tokyo
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class FileCopyTest2 { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ FileInputStream fis = new FileInputStream(args[0]); /* 6*/ DataInputStream dis = new DataInputStream(fis); /* 7*/ FileOutputStream fos = new FileOutputStream(args[1]); /* 8*/ PrintStream prs = new PrintStream(fos); /* 9*/ String s; /*10*/ for (s = dis.readLine(); s != null; s = dis.readLine()) { /*11*/ prs.println(s); /*12*/ } /*13*/ fis.close(); /*14*/ dis.close(); /*15*/ fos.close(); /*16*/ prs.close(); /*17*/ } /*18*/ }
b00a001@Ampere:~/java% cat cities2.txt London New York Tokyo b00a001@Ampere:~/java% java FileCopyTest2 cities2.txt cities3.txt b00a001@Ampere:~/java% cat cities3.txt London New York Tokyo b00a001@Ampere:~/java%
シーザー暗号に基づいて暗号化を行なうJavaアプリケーションを作成してください。 ここで、 シーザー暗号 ( Caesar cipher )とはシーザー(カエサル)が用いたとされる暗号化の方法です。 また、 暗号化 ( encryption )とは、秘密にしたいメッセージ( 平文 、 plaintext )を特定の方法で第三者には読めない形式( 暗号文 、 ciphertext )にすることです。
暗号を用いた通信は、一般的には次の通りです。 まず、メッセージの送信者は、暗号化の方法の他に、 鍵 ( key )とよばれるデータを決定します。 そして、メッセージと鍵から暗号文を構成し、受信者に送信します。 受信者は、暗号化の方法とその鍵を知っています。 暗号文を受けとりますと、鍵を使って暗号文を元のメッセージに戻します( 復号 、 decryption )。 第三者は、暗号化の方法は知っていても鍵を知らないので、元のメッセージに戻すことが困難になります。 なお、鍵を使わずに元のメッセージに戻すことを 解読 ( cryptanalysis )と言います。
ここでは簡単のため、平文はアルファベット大文字の並びとします。 また、スペースや各種記号は解読のヒントになりますので取り除いておきます。
シーザー暗号では、鍵 K は整数です。 平文のアルファベットを一文字ずつ K だけずらしたアルファベットに置き換えて、暗号化を行ないます。 例えば K = 3 でしたら、平文
MYNAMEISKYOKOAZUMA(私の名前は東京子です。)
は暗号文
PBQDPHLVNBRNRDCXPD
に暗号化されます。
アルファベットの置き換えは以下の表の通りです。
Y
が
B
に置き換えられることに注意してください。
平文 | A | B | C | D | E | F | G | H | I | J | K | L | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
暗号文 | D | E | F | G | H | I | J | K | L | M | N | O | P |
平文 | N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
暗号文 | Q | R | S | T | U | V | W | X | Y | Z | A | B | C |
この表を逆にたどりますと復号ができます。 K = 26 - 3 = 23 としてもう一度暗号化することでも復号可能です。
なお、シーザー暗号はすぐに解読されます。 なぜならば、 K = 1 の場合、 K = 2 の場合、…、 K = 25 の場合をすべて確かめ、その中から意味の通るものを選べばよいからです。 ここでシーザー暗号を取り上げたのは、暗号の仕組みに慣れるためです。
プログラムの方針は特に指定しません。
方針が思い付かない人は、以下のプログラムを穴埋めしてください。
このプログラムは、第1引数が鍵、第2引数が平文のファイル名、第3引数が暗号文のファイル名となります。
穴埋めでは、アルファベット大文字のみを置き換え、他の改行などはそのままにすることと、ずらして文字
Z
をすぎてしまったら26だけ戻すことがポイントです。
MYNAMEISKYOKOAZUMAIAMNINETEENYEA RSOLDIAMASTUDENTATTOKYOWOMANSCHR ISTIANUNIVERSITYILIVEINKOKUBUNJI
import java.io.*; class CaesarCipher { public static void main (String[] args) throws IOException { int key = Integer.parseInt(args[0]); FileInputStream fis = new FileInputStream(args[1]); FileOutputStream fos = new FileOutputStream(args[2]); int in, out; byte b; char c; ??? // 入力ストリーム fis から 1 バイト読み込み、こ ??? // れを入力データ in に格納する。この格納と以下 ??? // の手続きを in が -1 でない限り繰り返す。 ??? // in を文字 c に変換する。c が大文字ならば出力 ??? // データ out を in + key とする。ただし、out ??? // が文字 'Z' をすぎたら、out から 26 を引く。 ??? // もともと大文字でなければ out を in とする。 ??? // どちらにしても、out をバイト b に変換し、出 ??? // 力ストリーム fos に b を書き込む。 fis.close(); fos.close(); } }
b00a001@Ampere:~% cat plain.txt MYNAMEISKYOKOAZUMAIAMNINETEENYEA RSOLDIAMASTUDENTATTOKYOWOMANSCHR ISTIANUNIVERSITYILIVEINKOKUBUNJI b00a001@Ampere:~% java CaesarCipher 3 plain.txt cipher.txt b00a001@Ampere:~% cat cipher.txt PBQDPHLVNBRNRDCXPDLDPQLQHWHHQBHD UVROGLDPDVWXGHQWDWWRNBRZRPDQVFKU LVWLDQXQLYHUVLWBLOLYHLQNRNXEXQML b00a001@Ampere:~% java CaesarCipher 23 cipher.txt decrypt.txt b00a001@Ampere:~% cat decrypt.txt MYNAMEISKYOKOAZUMAIAMNINETEENYEA RSOLDIAMASTUDENTATTOKYOWOMANSCHR ISTIANUNIVERSITYILIVEINKOKUBUNJI b00a001@Ampere:~%
今日の演習12に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(12/6)を明記してください。