目次
1.1. 一般的に使用されるのは、InputStream と OutputStream
1.2.1. ファイルが読み取られたことを示す方法は? (データ入力ストリーム)
1.2.2. 文字読み取り・テキストデータ読み取り(スキャナー)
1.2.3. ファイルのランダムな読み書き(RandomAccessFile)
一、入力ストリームと出力ストリーム
1.1. 一般的に使用されるのは、InputStream と OutputStream
InputStream には次のメソッドがあります。
- int read(): データを 1 バイト読み取ります。-1 を返すと、完全に読み取られたことを意味します。
- int read(byte[] b): b に最大 b.length バイトのデータを読み取り、実際に読み取った数を返します。-1 は読み取りが終了したことを意味します (これは、たらいを食堂に運ぶようなものです。 「叔母が食事の準備をしてくれると、叔母は食事の量に応じて間違いなくそれをあなたのために詰めてくれるでしょう。もし彼女があなたのためにそれを詰めることができるなら、彼女はあなたのためにそれを詰めるために最善を尽くします)。これもより一般的に使用されます」実際の方法。
- int read(byte[] b, int off, int len): len - off バイトまでのデータを b に読み取り、off から開始して、実際に読み取った数を返します。-1 は読み取りが完了したことを意味します。
- void close(): バイトストリームを閉じます(通常、InputStreamはtry()に書き込まれるため、手動で解放する必要はありません~)。
inputStream は単なる抽象クラスです。これを使用するには、やはり特定の実装クラスが必要です。たとえば、クライアントとサーバーが受け入れた後、ストリーム オブジェクトの特定の実装クラスが取得されます...しかし、最も一般的に使用されるのは file です読み取り、つまり FileInputStream です。
OutputStream には次のメソッドがあります。
- void write(int b): 指定されたバイトをこの出力ストリームに書き込みます。
- void write(byte[] b): 文字配列 b のすべてのデータを os に書き込みます。
- int write(byte[] b, int off, int len): 文字配列 b の off から始まるデータを os に書き込み、合計 len を書き込みます
- void close(): バイトストリームを閉じる
- void flash(): I/O の速度が非常に遅いことがわかっているため、デバイス操作の数を減らすために、ほとんどの OutputStreams は、メモリの指定された領域に一時的にデータを書き込みます。データの書き込みとは、領域がフルになった場合や指定された条件下で実際にデバイスにデータを書き込むことをいい、この領域を一般にバッファと呼びます。しかし、結果の 1 つは、書き込んだデータの一部がバッファーに残る可能性が高いということです。データをデバイスにフラッシュするには、フラッシュ操作を最後または適切な場所で呼び出す必要があります。
OutputStream も単なる抽象クラスであり、それを使用するには特定の実装クラスが必要です。ここではファイルへの書き込みのみを考慮しているため、FileOutputStream を使用します。
追伸: FileOutputStream には new FileOutputStream(String path, boolean append) というコンストラクタがあります。最初のパラメータはファイル パス、2 番目のパラメータは末尾にデータを追加するかどうかです。ファイルの末尾にデータを追加したい場合は、 、trueを入力するだけです〜
1.2. 特別な用途
1.2.1. ファイルが読み取られたことを示す方法は? (データ入力ストリーム)
int 値を返すには read() メソッドを使用します。この値が -1 の場合は、ファイルが完全に読み取られたことを意味します~
ただし、実際のプロジェクトでは、ファイルの読み込みが完了したことを示すために、ある種のフォローアップメソッドがよく使用されます〜データの形式に同意すれば、それは int (ペイロードの長さを示す) + ペイロード、その後に同じ形式のデータが続く場合、この時点で、 DataInputStreamのreadInt メソッドを通じてこの int を読み取る必要があります(このストリーム オブジェクトは、数値とバイト ストリームを読み取るために特別に使用され、 DataOutputStream で使用する必要があります) 。これは、ファイルの最後まで読み取った後、読み取りを続けるという点で特別です。EOFException 例外がスローされます(以前は、ファイルの最後を読み取ると、-1 または null が返されました)。 catch を通じてこの例外をキャッチし、読み取りが完了したことを示します~
Ps: DataInputStream / DataOutputStream によって数値の読み取りと書き込み (readInt、writeInt) が容易になることは注目に値しますが、ネイティブの InputStream / OutputStream はデジタル読み取りおよび書き込みメソッドを提供していないため、独自に変換する必要があります。
public LinkedList<Message> loadAllMessageFromQueue(MSGQueue queue) throws IOException {
//1.检查文件是否存在
if(!checkQueueFileExists(queue.getName())) {
throw new IOException("[MessageFileManager] 获取文件中所有有效消息时,发现队列文件不存在!queueName=" + queue.getName());
}
//2.获取队列中所有有效的消息
synchronized (queue) {
LinkedList<Message> messages = new LinkedList<>();
try (InputStream inputStream = new FileInputStream(getQueueDataFilePath(queue.getName()))) {
try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {
int index = 0;
while(true) {
int messageSize = dataInputStream.readInt();
byte[] payload = new byte[messageSize];
int n = dataInputStream.read(payload);
if(n != messageSize) {
throw new IOException("[MessageFileManager] 读取消息格式出错!expectedSize=" + messageSize +
", actualSize=" + n);
}
//记录 offset
Message message = (Message) BinaryTool.fromBytes(payload);
if(message.getIsValid() == 0x0) {
index += (4 + messageSize);
continue;
}
message.setOffsetBeg(index + 4);
message.setOffsetEnd(index + 4 + messageSize);
messages.add(message);
index += (4 + messageSize);
}
}
} catch (EOFException e) {
System.out.println("[MessageFileManager] 队列文件中有消息获取完成!queueName=" + queue.getName());
}
return messages;
}
}
1.2.2. 文字読み取り・テキストデータ読み取り(スキャナー)
InputStream を直接使って文字型を読み取るのは非常に面倒で難しいため、以前から使い慣れたクラスである Scanner クラスを使用して作業を完了します。
スキャナは通常、テキスト形式のデータを読み書きするために PrintWrite とともに使用されます。これにより、InputStream/OutputStream が UTF-8 を使用してバイト データやテキスト データをデコードおよび変換する必要が大幅になくなります。
たとえば、次のようなものがあります。
// 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "你好中国" 的内容
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.print(s);
}
}
}
}
}
例 2:
public void writeStat(String queueName, Stat stat) {
try (OutputStream outputStream = new FileOutputStream(getQueueStatFilePath(queueName))) {
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.write(stat.totalCount + "\t" + stat.validCount);
printWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Stat readStat(String queueName) {
Stat stat = new Stat();
try (InputStream inputStream = new FileInputStream(getQueueStatFilePath(queueName))) {
Scanner scanner = new Scanner(inputStream);
stat.totalCount = scanner.nextInt();
stat.validCount = scanner.nextInt();
return stat;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
1.2.3. ファイルのランダムな読み書き(RandomAccessFile)
以前は、DataInputStream/DataOutputStream は、ファイルの順次読み取りと書き込み (最初から最後まで読み取るか、最後に書き込みを追加するなど) のために FileInputStream/FileOutputStream を受け取るために使用されていました。指定された場所の読み取り/書き込み操作。
これにはカーソルの概念が関係しており、実際にファイルを書き込むと、どこに書いてもその場所でカーソルが点滅します~
RandomAccessFile では、seek() メソッドを使用してカーソルの位置 (単位はバイト) を指定できます。たとえば、ファイル内のメモリの特定のセクションを論理的に削除する場合 (実際の削除ではなく、最初にそれを読み取るだけです)無効としてマークし、ファイルを書き戻すと、ごみ箱にもほぼ同じロジックが適用されます)。
public void deleteMessage(MSGQueue queue, Message message) throws IOException {
//1.检查队列相关文件是否存在
if(!checkQueueFileExists(queue.getName())) {
throw new IOException("[FileDataCenter] 删除消息时,发现队列相关文件不存在!queueName=" + queue.getName());
}
synchronized (message) {
//2.将要删除的消息文件读出来
try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataFilePath(queue.getName()), "rw")) {
randomAccessFile.seek(message.getOffsetBeg() - 4);
int payloadSize = randomAccessFile.readInt();
byte[] payload = new byte[payloadSize];
int n = randomAccessFile.read(payload);
if(n != payloadSize) {
throw new IOException("[FileDataCenter] 读取文件格式出错!path=" + getQueueDataFilePath(queue.getName()));
}
//3.将待删除的消息标记为无效(isValid = 0x0)
Message toDeleteMessage = (Message) BinaryTool.fromBytes(payload);
toDeleteMessage.setIsValid((byte) 0x0);
//4.将消息写入文件
randomAccessFile.seek(message.getOffsetBeg());
randomAccessFile.write(BinaryTool.toBytes(toDeleteMessage));
}
//5.更新统计文件
Stat stat = readStat(queue.getName());
stat.validCount -= 1;
writeStat(queue.getName(), stat);
}
}
追伸:
RandomAccessFile には 2 つのコンストラクター (実際には 1 つ) があります。RandomAccessFile(String name, String mode) は RandomAccessFile(new File(name), String mode) と同等です。
mode このパラメータはアクセスモードを示します~
➢ "r": 指定されたファイルを読み取り専用モードで開きます。RandomAccessFile に対して書き込みメソッドを実行しようとすると、IOException がスローされます。
➢ "rw": 指定されたファイルを読み取りおよび書き込みモードで開きます。ファイルがまだ存在しない場合は、ファイルの作成を試みます。
➢ "rws": 指定されたファイルを読み取りおよび書き込みモードで開きます。「rw」モードと比較して、ファイルのコンテンツまたはメタデータに対するすべての更新が基礎となるストレージ デバイスに同期的に書き込まれることも必要です。
➢ "rwd": 指定されたファイルを読み取りおよび書き込みモードで開きます。「rw」モードと比較して、ファイル コンテンツへのすべての更新が基礎となるストレージ デバイスに同期的に書き込まれることも必要です。