Java 學習記錄88 — Object I/O and RandomAccessFile class — 7/7

張小雄
13 min readOct 20, 2021

--

承 87 — Object Input Output including Serialization

Locations.java 寫出的部份

public static void main(String[] args) throws IOException {        try (RandomAccessFile raOut = new RandomAccessFile("src/introduceIo/randomAccessFile/locations_raf.dat", "rwd")) {            // The whole contents were consisted of  A part, B part, C part, data of locations.
// A part 0 ~ 3 byte
// B part 4 ~ 7 byte
// C part 8 ~ 1699 bytes
// data of locations 1700 ~ end
// A part - total number of locations which is 141
raOut.writeInt(locations.size());
// B part - start place of location's data, which is after A + B + C part
int indexSize = locations.size() * 3 * Integer.BYTES; // length of C part
int locationStart = (int) (indexSize + raOut.getFilePointer() + Integer.BYTES); // length of A + B + C part
System.out.println("locationStart: " + locationStart + ", which is the place of location's data start.");
raOut.writeInt(locationStart);
// record place of C part start
long indexStart = raOut.getFilePointer();
System.out.println("indexStart " + indexStart + ", which is the place of C part start.");
// measure every location's length and write all location's data
int startPointer = locationStart;
raOut.seek(startPointer); // jump to place of location's data start
for (Location location : locations.values()) {
raOut.writeInt(location.getLocationID());
raOut.writeUTF(location.getDescription());
// retrieve Map of exits data (Map<String, Integer> exits)
StringBuilder stringBuilder = new StringBuilder();
for (String direction : location.getExits().keySet()) {
if (!direction.equalsIgnoreCase("Q")) {
stringBuilder.append(direction); // get exits key
stringBuilder.append(",");
stringBuilder.append(location.getExits().get(direction)); // get exits value
stringBuilder.append(",");
// would like W,2,E,3,S,1,....
}
}
raOut.writeUTF(stringBuilder.toString());
// calculate each of location length
int length = (int) (raOut.getFilePointer() - startPointer); // end of location - start of location
IndexRecord record = new IndexRecord(startPointer, length);
index.put(location.getLocationID(), record); // put result to index map, so later C part could use it
startPointer = (int) raOut.getFilePointer(); // when current location was recorded, move pointer to the end of current location
}
// C part - index of location
raOut.seek(indexStart); // back to C part start
for (Integer locationID : index.keySet()) {
raOut.writeInt(locationID);
raOut.writeInt(index.get(locationID).getStartByte());
raOut.writeInt(index.get(locationID).getLength());
}
}
}

IndexRecord.java

package introduceIo.randomAccessFile;public class IndexRecord {
private int startByte;
private int length;
public IndexRecord(int startByte, int length) {
this.startByte = startByte;
this.length = length;
}
public int getStartByte() {
return startByte;
}
public void setStartByte(int startByte) {
this.startByte = startByte;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
}

RandomAccessFile 意思就是當檔案很大時,不用把所有資料從文檔讀到記憶體去,如果像現在幾百筆資料其實沒什麼,但如果大到幾千萬、幾億筆資料呢?而隨機訪問就是按需要,有需要再去讀取特定位置的資料即可,這樣記憶體的開銷就會大幅降低。

簡單的講就是做一個 index(目錄的概念),當我要訪問第 X 筆資料時,就去查看目錄這筆資料記載第幾頁,然後翻到該頁查看即可。

搭配下方同學熱心補充的圖片即可了解,而我也在代碼裡面放很多註解了

簡單的說,一共有四個部份

A part — 資料總數

B part — 實際資料開始的位置

C part — 實際資料的 index

D part — 實際資料

邏輯跟順序:

A、B part 都只是簡單計算很簡單

C part 這是此案例較難的地方,因為每筆資料的長度都不同,如果所有資料長度都一樣,寫個長度公式就好,例如一筆資料長度 10 byte,取第十筆時,只要乘上 10 就好。

所以是先處理 D part,for 循環實際資料,同時計算長度跟寫入,然後把取得的各筆資料長度存到 Map 裡,最後才用 Map 取得的資料來寫到 c part。

最難的地方就是指標,我還以為 Java 不用學這個呢,以為只有 C 要學,沒想到在這邊遇上了,這邊的指標意思就是告訴系統,我現在要去哪個位置。

也可以當作書籤來理解,我可以在 A 處紀錄一個書籤,B 處紀錄一個書籤,此刻我想先到B 書籤查看跟書寫資料,處理好之後想回到 A 書籤去查看跟書寫,只要用 seek 告訴系統回到哪個書籤就行。

也是計算資料長度的好幫手,寫完一筆資料就移動指標到此資料結尾處,只要把此筆結尾減掉上一筆資料結尾,就會是現在這筆資料的長度。

心得:我在這個小節卡了超久,一度懷疑人生,前期看不懂是非常正常。我也是聽 Tim 老師講解聽得迷迷糊糊,心想到底在公三小朋友,讀得非常痛苦。能說的就是讀累了隔天再看讓腦袋放鬆,這樣重複個幾天讓腦袋重開機幾次後,就會慢慢越來越理解。

小技巧,當我實在聽不懂老師在說三小的時候,就會只看代碼反推老師的思路,同時搭配 debug 功能去看各個參數跑出什麼資料。

同學補充:

J

資料來源 udemy 課程

Locations.java 讀取的部份

static {
try {
raIn = new RandomAccessFile("src/introduceIo/randomAccessFile/locations_raf.dat", "rwd");
int numLocations = raIn.readInt();
long locationStart = raIn.readInt();
while (raIn.getFilePointer() < locationStart) {
int locationID = raIn.readInt();
int locationsStart = raIn.readInt();
int locationsLength = raIn.readInt();
IndexRecord record = new IndexRecord(locationsStart, locationsLength);
index.put(locationID, record);
}
} catch (IOException e) {
System.out.println("IOException in static initializer " + e.getMessage());
}
}

怎麼寫出的,就怎麼讀入,相對簡單很多

Locations.java 新增功能

public Location getLocation(int locationID) throws IOException {
IndexRecord record = index.get(locationID);
raIn.seek(record.getStartByte());
int id = raIn.readInt();
String description = raIn.readUTF();
String exits = raIn.readUTF();
String[] exitPart = exits.split(",");
Location location = new Location(id, description, null); if (locationID != 0) {
for (int i = 0; i < exitPart.length; i++) {
String direction = exitPart[i];
int destination = Integer.parseInt(exitPart[++i]);
location.addExit(direction, destination);
}
}
return location;
}
public void close() throws IOException {
raIn.close();
}

因為不像之前全部讀到記憶體去了,現在是按需求讀取特定資料

所以新增查看特定資料的功能還有關閉 RandomAccessFile 的功能

Main.java

package introduceIo.randomAccessFile;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
private static final Locations locations = new Locations();
private static final int startRoom = 1;
public static void main(String[] args) throws IOException {
// Change the program to allow players to type full words, or phrases, then move to the
// correct location based upon their input.
// The player should be able to type commands such as "Go West", "run South", or just "East"
// and the program will move to the appropriate location if there is one. As at present, an
// attempt to move in an invalid direction should print a message and remain in the same place.
//
// Single letter commands (N, W, S, E, Q) should still be available.
Scanner scanner = new Scanner(System.in); Map<String, String> vocabulary = new HashMap<String, String>();
vocabulary.put("QUIT", "Q");
vocabulary.put("NORTH", "N");
vocabulary.put("SOUTH", "S");
vocabulary.put("WEST", "W");
vocabulary.put("EAST", "E");
Location currentLocation = locations.getLocation(startRoom);
while (true) {
System.out.println(currentLocation.getDescription());
if (currentLocation.getLocationID() == 0) {
break;
}
Map<String, Integer> exits = currentLocation.getExits();
System.out.print("Available exits are ");
for (String exit : exits.keySet()) {
System.out.print(exit + ", ");
}
System.out.println();
String direction = scanner.nextLine().toUpperCase();
if (direction.length() > 1) {
String[] words = direction.split(" ");
for (String word : words) {
if (vocabulary.containsKey(word)) {
direction = vocabulary.get(word);
break;
}
}
}
if (exits.containsKey(direction)) {
currentLocation = locations.getLocation(currentLocation.getExits().get(direction));
} else {
System.out.println("You cannot go in that direction");
}
}
locations.close();
}
}

修改對應讀取 Location 的方法

本篇代碼

--

--

張小雄
張小雄

Written by 張小雄

記錄成為軟體工程師的過程

No responses yet