IO流:物件流、序列化、隨機讀寫流與NIO
尚硅谷JavaSE筆記-27
標準輸出/入流
System.in
:標準輸入字節流,預設是從鍵盤輸入,類型是InputStream
System.out
:標準輸出流,預設輸出到控制台,類型是PrintStream
,繼承自FilterOutputStream
,繼承自OutputStream
重定向方法
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
範例-讀取輸入
不使用
scanner
public static void main(String[] args) {
// 把標準輸入字節流先轉換成字符流
BufferedReader bufferedReader = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
// 把字符流用bufferedReader包起來,為了調用readline方法
bufferedReader = new BufferedReader(isr);
for (; true; ) {
System.out.println("輸入要轉換成大寫的字串...");
String str = bufferedReader.readLine();
if ("exit".equalsIgnoreCase(str)) { // "exit"放前面防空指針
System.out.println("離開");
break;
}
System.out.println(str.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Print流
只有輸出,只會輸出
不是特別重要,認識一下有這2個PrintStream
、PrintWriter
範例-寫log
public static void main(String[] args) throws FileNotFoundException {
// 造一個輸出到檔案的流
FileOutputStream fileOutputStream = new FileOutputStream("abc.txt");
// 造一個Print輸出流
PrintStream printStream = new PrintStream(fileOutputStream, true);// true表示自動flush
// 將系統標準輸出重定向到Print輸出流
System.setOut(printStream);
System.out.println("原本想印在控制台,現在印到檔案abc.txt裡了!");
}
數據流
用於讀取或寫出
String
或基本數據類型,只有字節流
-
DataOutputStream
:將記憶體中的數據寫到文件檔案中,這個文件基本不能給人看,因為可能有奇奇怪怪比如string
、int
、boolean
-
DataInputStream
:將文件檔案中的數據讀取記憶體中,寫的時候類型怎寫,讀的時候也必須照順序讀,否則報異常 -
套在對應的
in/outputStream
字節流的子類使用,舉例:public static void main(String[] args) throws Exception { DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("data.txt")); dataOutputStream.writeUTF("張三"); dataOutputStream.flush(); // 刷新,確實寫到文件中 dataOutputStream.writeBoolean(true); dataOutputStream.flush(); dataOutputStream.writeFloat(654321.56465421f); dataOutputStream.flush(); DataInputStream dataInputStream = new DataInputStream(new FileInputStream("data.txt")); System.out.println(dataInputStream.readUTF()); System.out.println(dataInputStream.readBoolean()); System.out.println(dataInputStream.readFloat()); }
物件流
序列化
-
定義:將一個
Java物件
轉換為二進制檔案,用以通信交換或保存 -
條件:
- 物件的類滿足
Serializable
接口或Externalizable
接口 - 在類聲明版本標識號:
private static final
long
serialVersionUID
,用以作為反序列化時的版本依據,- 比如第一代
person
類號碼是001,物件"小明
“存成一個dat
檔案 - 後來
person
類更新了,多加了性別屬性,物件”阿花-女
“存成的dat
檔案與當初小明的顯然格式不同了,所以必須更改版本標示號 - 如果不顯式聲明標示號,每次編譯都會自動給一個,反序列化時可能報錯
- 用
private
是因為serialVersionUID
顯然不該也不能被繼承
- 比如第一代
- 類中所有的屬性也都必須是可序列化的(基本數據類型天生可以,要注意的是引用其他類的情況)
- 物件的類滿足
-
避免序列化:
static
:表示該屬性或方法是屬於類本身
,物件
被序列化當然不關我的事;有時候可能會迷惑某屬性用static
修飾了一樣能寫能讀,實際上那是JVM
從對應類中拿到的值而非反序列化而來transient
:暫時的,發音"tran-洗-ent”,顧名思義只存在記憶體中不實際寫出;注意這邊講的是Serializable
接口,此接口特性是會自動序列化- 如果是透過實現
Externalizable
接口者,需要在writeExternal
方法手動指定要序列化的變量,該方法會蓋過transient
的修飾
- 如果是透過實現
-
序列化是RMI,Remote Method Invocation(遠端方法呼叫),是允許執行在一個Java虛擬機器的物件呼叫執行在另一個Java虛擬機器上的物件的方法,駭客常用反序列化的過程做壞事,要注意安全漏洞
-
反序列化:相反的過程,舉例:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.dat")); oos.writeObject(new String("天線寶寶說你好")); oos.flush(); System.out.println("將物件序列化為檔案成功"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.dat")); Object obj = ois.readObject(); System.out.println("反序列化,還原成物件..."); String str = (String) obj; System.out.println(str);
RandomAccessFile類
隨機讀寫流,“隨機"其實"任意"的意思
特性
- 聲明在
java.io
包下,直接繼承於java.io.Object
類 - 實現了
DataInput
與DataOutput
接口,雙頭龍 - 支持隨機訪問,想讀寫哪邊就讀寫哪邊
- 其物件包含了一個紀錄指針,用以標示當前讀寫位置
- 可以自由移動指針位置,透過方法:
long getFilePointer()
:獲取當前指針位置void seek(long pos)
:將指針移到pos
實例化
構造器:
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
mode
:參數,決定訪問模式
r
:只讀,如果檔案不存在就報異常rw
:讀寫,如果檔案不存在就創建;用write()
方法如果檔案存在會對"內容"進行覆蓋,且預設從頭開始覆蓋rwd
:讀寫並同步更新內容rws
:讀寫並同步更新內容及元數據
範例:
// 實例化
RandomAccessFile rw = new RandomAccessFile(new File("hello.txt"), "rw");
// 將string轉為byte編碼寫入
rw.write("12345abcde".getBytes(StandardCharsets.UTF_8));
// 在末尾追加,相當於append
rw.seek(new File("hello.txt").length());
rw.write("zzz".getBytes(StandardCharsets.UTF_8));
範例-插入字串
public void test1() throws Exception {
RandomAccessFile rw = new RandomAccessFile(new File("hello.txt"), "rw");
rw.seek(5); // 將指針移到5
// 造一個StringBuilder用以保存5以後的資料
StringBuilder builder = new StringBuilder(Math.toIntExact(new File("hello.txt").length()));
byte[] buf = new byte[20];
int len;
while ((len = rw.read(buf)) != -1) { // 將讀取的"後面的"資料寫進buf
builder.append(new String(buf, 0, len)); // 將buf寫入builder
}
rw.seek(5); // 將指針移再移回5,因為read也會動指針
rw.write("***INSERT***".getBytes(StandardCharsets.UTF_8));
// 寫回後面的部分
rw.write(builder.toString().getBytes(StandardCharsets.UTF_8));
rw.close()
// 用中文可能會亂碼,因為中文字是3個Byte,read到buf寫入builder的時候被切壞了
改良-避免亂碼
使用ByteArrayOutputStream
rw.seek(6); // 將指針移
// 解決亂碼,用ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[32];
int len;
while ((len = rw.read(buf)) != -1) { // 將讀取的"後面的"資料寫進buf
baos.write(buf, 0, len);
}
rw.seek(6); // 將指針移再移回,因為read也會動指針
rw.write("中文中文".getBytes(StandardCharsets.UTF_8));
// 寫回後面的部分
rw.write(baos.toString().getBytes(StandardCharsets.UTF_8));
// 還要注意一開始seek的位置,一開始就切在要害(例如奇數位)也會導致小部分亂碼
NIO
JDK4引入了NIO,(Non-blocking I/O),非阻塞式,加入觀察與通知的機制
JDK7強力更新成NIO.2版本,之後說的NIO基本都是指這個
- IO主要是流本身,NIO是以緩衝區、管道為導向,讀寫效率更高
- 位於
java.nio.channels.Channel
,其下有兩套NIO:- 處理本地文件
FileChannel
- 處理網路:
SocketChannel
、ServerSocketChannel
、DatagramChannel
- 處理本地文件
Path接口
- 用以取代
File
類 - 實例化:
static Path get(String first, String … more)
:用於將多個字串串連成路徑static Path get(URI uri)
:返回指定uri對應的Path路徑
Paths類
Paths
是工具類,除了上面兩種get
靜態方法,還有許多常用方法:
String toString()
:返回調用Path 物件的字串表示形式boolean startsWith(String path)
:判斷是否以path路徑開始boolean endsWith(String path)
:判斷是否以path路徑結束boolean isAbsolute()
:判斷是否是絕對路徑Path getParent()
:返回Path物件包含整個路徑,不包含Path 物件指定的檔路徑Path getRoot()
:返回調用Path 物件的根路徑Path getFileName()
:返回與調用Path 物件關聯的檔案名int getNameCount()
:返回Path 根目錄後面元素的數量Path getName(int idx)
:返回指定索引位置idx 的路徑名稱Path toAbsolutePath()
:作為絕對路徑返回調用Path 物件Path resolve(Path p)
:合併兩個路徑,返回合併後的路徑對應的Path物件File toFile()
:將Path轉化為File類的物件
Files類
位於
java.nio.file.Files
,是操作文件或目錄的工具類,常用方法:
Path copy(Path src, Path dest, CopyOption … how)
:檔的複製Path createDirectory(Path path, FileAttribute<?> … attr)
:創建一個目錄Path createFile(Path path, FileAttribute<?> … arr)
:創建一個檔void delete(Path path)
:刪除一個檔/目錄,如果不存在,執行報錯void deleteIfExists(Path path)
:Path對應的檔/目錄如果存在,執行刪除Path move(Path src, Path dest, CopyOption…how)
:將src移動到dest位置long size(Path path)
:返回path
指定文件的大小
用於判斷
boolean exists(Path path, LinkOption … opts)
:判斷檔是否存在boolean isDirectory(Path path, LinkOption … opts)
:判斷是否是目錄boolean isRegularFile(Path path, LinkOption … opts)
:判斷是否是檔boolean isHidden(Path path)
:判斷是否是隱藏檔boolean isReadable(Path path)
:判斷檔是否可讀boolean isWritable(Path path)
:判斷檔是否可寫boolean notExists(Path path, LinkOption … opts)
:判斷檔是否不存在
用於操作內容
SeekableByteChannel newByteChannel(Path path, OpenOption…how)
:獲取與指定檔的連接,how 指定打開方式DirectoryStream<Path> newDirectoryStream(Path path)
:打開path 指定的目錄InputStream newInputStream(Path path, OpenOption…how)
:獲取InputStreamj
物件OutputStream newOutputStream(Path path, OpenOption…how)
:獲取OutputStreamj
物件
小結
- 序列化:實現
Serializable
接口、定義serialVersionUID
、確認類中的屬性也都是可以序列化的 RandomAccessFile
:雙向、可於任意位置存取,構造時用mode
參數決定類型,使用seek(index)
移動指針、getFilePointer()
找指針- NIO:用
Path
取代File
,緩衝區跟管道取代IO
上次修改於 2021-12-12