Java的io通過java.io
包下的類和接口支持。主要有輸入、輸出流,又分為字節流和字符流。Java的io流使用了一種裝飾器設計模式,他將io流分為底層節點流和上層處理流,節點流用于和底層的物理存儲結點直接關聯。
Java7在java.nio
及其子包下提供了一系列全新的api,是對原有新io的升級。
一、File類
File類是java.io
包下代表與平臺無關的文件和目錄。File能新建、刪除、重命名文件和目錄,不能訪問文件本身內容。
訪問文件和目錄
訪問文件名的方法
- String getName()
- String getPath()
- File getAbsoluteFile()
- String getAbsolutePath()
- String getParent()
- boolean renameTo(FIle newName)
文件檢測相關的方法
- boolean exists()
- boolean canWrite()
- boolean canRead()
- boolean isFile()
- boolean isDirectory()
- boolean isAbsolute()
獲取常規文件信息
- long lastModified()
- long length()
文件操作相關的方法
- boolean createNewFile()
- boolean delete()
- static File createTempFile(String prefix, String suffix)
- static File CreateTempFIle(String prefix, String suffix, File directory)
- void deleteOnExit()
目錄操作相關的方法
- boolean mkdir()
- String[] list()
- File[] listFiles()
- static File[] listRoots()
文件過濾器
File類的list()
方法可以接收一個FilenameFilter
參數,通過該參數可以只列出符合條件的文件。
FilenameFilter
接口和javax.swing.filechooser
包下的FileFilter
抽象類的功能非常相似。
二、Java的IO流
流的分類
輸入流和輸出流
抽象基類:
InputStream和Reader
OutputStream和Writer
字節流和字符流
字節流:8位,InputStream和OutputStream
字符流:16位,Reader和Writer
節點流和處理流
向一個特定的io設備讀寫數據的流為節點流,Low Level Stream
處理流對于已有的節點流進行連接和封裝,通過封裝后的流實現數據讀/寫功能。High Level Stream
處理流是一種典型的裝飾器設計模式,也稱為包裝流
流的概念模型
io流涉及40多個類。
三、字節流和字符流
InputStream和Reader
InputStream接口的方法:
- int read()
- int read(byte[] b)
- int read(byte[] b, int off, int len)
Reader接口的方法:
- int read()
- int read(char[] cbuf)
- int read(char[] cbuf, int off, int len)
讀取文件:FileInputStream,FileReader
還有一些移動指針的方法:
- void mark(int readAheadLimit)
- boolean markSupported()
- void reset()
- long skip(long n)
OutputStream和Writer
提供的方法
- void write(int c)
- void write(byte[]/char[]/String buf)
- void write(byte[]/char[]/String buf, int off, int len)
Writer可以使用String
代替char[]
四、輸入/輸出流體系
處理流的用法
使用處理流包裝節點流,程序通過處理流執行輸入/輸出功能,讓節點流與底層的IO設備、文件交互。
所有節點流都是以物理io節點作為構造器參數的。其他的流為處理流。
處理流的優點
- 操作簡單
- 執行效率更高
PrintStream
的輸出功能很強大,System.out
的類型就是PrintStream
在使用處理流包裝了底層節點流之后,關閉輸入/輸出流資源時,只要關閉最上層的處理流即可。
輸入/輸出流體系

規則:
輸入輸出是文本,則考慮使用字符流;是二進制,則使用字節流
轉換流
InputStreamReader,OutputStreamWriter
將字節流轉換為字符流。使處理更方便。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test{
public static void main(String[] args) {
try(
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(reader);
){
String line = null;
while((line = br.readLine() != null){
if (line.equals("exit")){
System.exit(1);
}
System.out.println(line);
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
BufferedReader有一個readLine()方法,很方便,所以經常把讀取文本內容的流包裝成BufferedReader
推回輸入流
PushbackInputStream和PushbackReader。
提供的方法:
- void unread(byte[]/char[] buf)
- void unread(byte[]/char[] b, int off, int len)
- void unread(int b)
五、重定向標準輸入/輸出
Java的標準輸入/輸出分別通過Systemin
和System.out
代表。在默認情況下代表鍵盤和顯示器。
在System
類里包含三個重定向標準輸入/輸出的方法:
- static void setErr(PrintStream err)
- static void setIn(InputStream in)
- static void setOut(OutputStream out)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class Test{
public static void main(String[] args) {
try(
PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));
){
System.setOut(ps);
System.out.println();
}
catch(IOException e){
e.printStackTrace();
}
}
}
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;
public class Test{
public static void main(String[] args) {
try(
FileInputStream fis = new FileInputStream("test.txt");
){
System.setIn(fis);
Scanner s = new Scanner(System.in);
s.useDelimiter('\n');
while(s.hasNext()){
System.out.println(s.next());
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
六、RandomAccessFile
可以讀寫文件。支持"隨機訪問"。
只訪問文件的部分內容,可以選擇使用該流。
追加內容也應該選擇。
只能讀寫文件。
- long getFilePointer()
返回文件記錄指針的當前位置
- void seek(long pos)
將文件記錄指針定位到pos位置
包含了InputStream的read方法和OutputStream的write方法。
創建RandomAccessFile除了需要一個String
或File
還需要一個mode
參數。
- "r":只讀。
- "rw":讀寫
- "rws":讀寫,每個更新同步寫入到底層存儲設備
- "rwd":讀寫,同步寫入到底層設備。
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test{
public static void main(String[] args) {
try(
RandomAccessFile ra = new RandomAccessFile("Test.java", "r");
){
ra.seek(300);
byte[] buf = new byte[1024];
int hasRead = 0;
while((hasRead = ra.read(buf))>0){
System.out.println(new String(buf, 0, hasRead));
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
追加內容:
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test{
public static void main(String[] args) {
try(
RandomAccessFile ra = new RandomAccessFile("Test.java", "r");
){
ra.seek(ra.length());
ra.write("\r\nHello world".getBytes());
}
catch(IOException e){
e.printStackTrace();
}
}
}
向指定文件、指定位置插入內容:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test{
public static void insert(String filename, long pos, String content) throws IOException{
File temp = File.createTempFile("temp", null);
temp.deleteOnExit();
try(
RandomAccessFile ra = new RandomAccessFile(filename, "rw");
FileOutputStream fos = new FileOutputStream(temp);
FileInputStream fis = new FileInputStream(temp);
){
raf.seek(pos);
byte[] buf = new byte[64];
int hasRead = 0;
while((hasRead=ra.read(buf))>0){
fos.write(buf, 0, hasRead);
}
raf.seek(pos);
raf.write(content.getBytes());
while((hasRead=fis.read(buf))>0){
raf.write(buf, 0, hasRead);
}
}
}
public static void main(String[] args) {
try{
Test.insert("test.java", 45, "Hello World\r\n");
}
catch(IOException e){
e.printStackTrace();
}
}
}
七、NIO
Java7對原來的io進行了重大改進:
- 提供了全面的文件IO和文件系統訪問支持
- 基于異步Channel的IO
第一個體現為java.nio.file
包及其子包
第二個體現為java.nio.channels
包下面增加了多個以Asynchronous
開頭的Channel
接口和類。
Path、Paths和Files核心api
早期只有File訪問文件系統,但File類的功能比較有限,不能利用特定文件系統的特性;FIle提供的方法的性能不高,其方法在出錯時返回失敗,但不提供異常信息。
NIO為了彌補這種不足,引入了一個Path
接口,代表一個平臺無關的平臺路徑。Files
和Paths
兩個工具類。
Files包含大量靜態工具方法來操作文件,Paths包含兩個返回Path的靜態工廠方法。
import java.nio.file.Path;
import java.nio.file.Paths;
public class Test{
public static void main(String[] args) {
Path path = Paths.get(".");
System.out.println(path.getNameCount());
Path abPath = path.toAbsolutePath();
System.out.println(abPath.getRoot());
//c:/test
Path path2 = Paths.get("c", "test");
}
}
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Test{
public static void main(String[] args) {
Files.copy(Paths.get("Test.java"), new FileOutputStream("a.txt"));
System.out.println(Files.isHidden(Pahts.get("Test.java")));
List<String> lines = Files.readAllLines(Paths.get("Test.java"), Charset.forName("gbk"));
System.out.println(Files.size(Paths.get("test.java")));
Files.write(Paths.get("test.txt"), lines, Charset.forName("gbk"));
Files.list(Paths.get("."));
Files.lines(Paths.get("Test.java"), Charset.forName("gbk"));
FileStore cStore = Files.getFileStore(Paths.get("c:"));
cStore.getTotalSpace();
cStore.getUsableSpace();
}
}
使用FileVisitor遍歷文件和目錄
- walkFileTree(Path start, FileVisitor<? super Path> visitor)
- walkFileTree(Path start, Set<FileVisitOpeion> options, int maxDepth, FileVisitor<? super Path> visitor)
FileVisitor定義了如下4個方法:
- FileVisitResult postVisitDirectory(T dir, IOException exc)
- FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
- FileVisitResult visitFIle(T file, BasicFileAttributes attrs)
- FileVisitResult visitFileFailed(T file, BasicFileAttributes attrs)
FileVisitResult
是一個枚舉類,代表了訪問之后的后續行為。
- CONTINUE
- SKIP_SIBLINGS
- SKIP_SUBTREE
- TERMINATE
實際編程可以通過繼承SimpleFileVisitor
(FileVisitor
的實現類)實現自己的文件訪問器可以根據需要選擇性重寫指定方法。
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class Test{
public static void main(String[] args) throws IOException{
//c:/test
Files.walkFileTree(Paths.get("c:", "test"), new SimpleFileVisitor<Path>(){
//訪問文件時觸發的方法
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{
return FileVisitResult.CONTINUE;
}
//開始訪問目錄時觸發該方法
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException{
return FileVisitResult.CONTINUE;
}
});
}
}
使用WatchService
監控文件變化
早期需要后臺啟用一個線程每隔一段時間去遍歷一次指定目錄的文件。繁瑣,性能不好
Path類提供了如下一個方法監控文件系統的變化:
- register(WatchService watcher, WatchEvent.Kind<?>... events)
WatchService
代表一個文件系統監聽服務,它負責監聽path
代表目錄下的文件變化。一旦使用register
方法完成注冊,接下來可以調用WatchService的如下三個方法獲取監聽目錄的文件變化:
- WatchKey poll()
獲取一個WatchKey,如果沒有WatchKey發生,則返回null
- WatchKey poll(long timeout, TimeUnit unit)
嘗試等待timeout時間去獲取下一個WatchKey
- WatchKey take()
獲取下一個WatchKey,如果沒有則一直等待
如果程序需要一直監控,應該選用take
方法,否則可以選用poll
方法
import java.nio.file.FileSystems;
import java.nio.file.WatchService;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
public class Test{
public static void main(String[] args) {
WatchService watchService = FileSystems.getDefault().newWatchService();
Paths.get("c:/").register(watcherService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
while(true){
WatchKey key = watchService.take();
for(WatchEvent<?> event:key.pollEvents()){
System.out.println(event.context()+" 文件發生了 "+event.kind()+" 事件!");
}
//重設watchkey
boolean valid = key.reset();
if(!valid){
break;
}
}
}
}
訪問文件屬性
早期的File
類可以訪問一些簡單的文件屬性。獲取或者修改更多會很難。
Java7下面的java.nio.file.attribute
包下面提供了大量的工具類,通過這些工具類,可以簡單方便的讀取、修改文件屬性。這些工具類主要分為一下兩類:
- XxxAttributeView
代表某種文件屬性的"視圖"
- XxxAttributes
代表某種文件屬性的"集合",程序一般通過XxxAttributeView獲取XxxAttributes
FileAttributeView
是其他view的父接口。
AclFileAttributeView
可以為特定文件設置ACL(access control list)及文件所有屬性。getAcl(),setAcl(List)
BasicFileAttributeView
獲取和修改文件的基本屬性,包括文件的最后修改時間、最后訪問時間、創建時間、大小、是否為目錄、是否為符號鏈接等。readAttributes()放回BasicFileAttributes對象
DosFileAttributeView
主要用于獲取或修改文件dos相關屬性。是否只讀、是否隱藏、是否為系統文件、是否是存檔文件等。
FileOwnerAttributeView
獲取或修改文件的所有者
PosixFileAttributeView
主要用于修改POSIX(portable operating system interface of unix)屬性。
UserDefinedFileAttributeView
讓開發者為文件設置一些自定義屬性