JavaSE(六)IO程式設計
IO程式設計
IO 基礎
簡介
IO:Input/Output
IO流是一種流式資料輸入、輸出模型:
1)二進位制資料以byte為最小單位在InputStream/OutputStream中單向流動 2)字元資料以char為最小單位在Reader/Writer中單向流動 3)JDK的java.io包提供了同步IO功能 4)JDK的java.nio包提供了非同步IO功能
Java的IO流的介面
位元組流介面:InputStream、OutputStream
字元流介面:Reader、Writer
File物件
java.io.File 表示檔案系統的一個檔案或者目錄
isFile():判斷是否是檔案
isDirectory():判斷是否是目錄
建立File物件本身不涉及IO操作
getPath():獲取路徑
getAbsolutePath():獲取絕對路徑
getCanonicalPath():獲取規範路徑
檔案操作
canRead():是否允許讀取該檔案 canWrite():是否允許寫入該檔案 canExecute():是否允許執行該檔案 length():獲取檔案大小 createNewFile():建立一個新檔案 static createTempFile():建立一個臨時檔案 delete():刪除該檔案 deleteOnExit():在JVM退出時刪除檔案
目錄操作
String[] list():列出目錄下的檔案和子目錄名
File[] listFiles():列出目錄下的檔案和子目錄名
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
mkdir():建立該目錄
mkdirs():建立該目錄,並在必要時將不存在的父目錄也創建出來
delete():刪除該目錄
練習
編寫程式,1)列出當前目錄下的所有子目錄和檔案,並按層次列印;2)如果不指定引數,則使用當前目錄,如果指定引數,則使用指定目錄
public static void printFileName(File file){
if(file.isDirectory() && 0 != file.listFiles().length){
File[] files = file.listFiles();
files = sortFile(files);
for(File f : files){
StringBuilder sb = new StringBuilder();
if(f.isFile()){
sb.append(getTab(number));
sb.append(f.getName());
}else{
sb.append(getTab(number));
sb.append(f.getName());
sb.append("\\");
}
System.out.println(sb);
if(f.isDirectory()){
number++;
printFileName(f);
number--;
}
}
}
}
//得到子目錄前需要加多少tab
private static String getTab(int number){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < number; i++){
sb.append("\t");
}
return sb.toString();
}
//對子目錄進行排序,資料夾在前,檔案在後
private static File[] sortFile(File[] files) {
ArrayList<File> list = new ArrayList<File>();
for(File f : files){
if(f.isDirectory()){
list.add(f);
}
}
for(File f : files){
if(f.isFile()){
list.add(f);
}
}
return list.toArray(new File[files.length]);
}
Input 和 Output
InputStream
InputStream是所有輸入流的超類
1)int read():讀取一個位元組,並返回位元組(0~255),已讀到末尾返回-1
2)int read(byte[]):讀取若干位元組並填充到byte[]陣列
3)int read(byte[] b, int off, int len)指定byte[]陣列的偏移量和最大填充數
4)void close():關閉輸入流
read()方法是阻塞的
使用try可以保證InputStream正確關閉
public static void readFile(){
InputStream input = null;
try {
input = new FileInputStream("src/1.txt");
int n;
while((n = input.read()) != -1){
System.out.println(n);
}
} catch(IOException e){
e.printStackTrace();
}finally {
if(input != null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// JDK7 新特性
public static void readFile() throws FileNotFoundException, IOException{
try(InputStream input = new FileInputStream("src/1.txt")){
int n;
while((n = input.read()) != -1){
System.out.println(n);
}
} // 在此自動關閉InputStream(推薦寫法)
}
// 利用快取區一次讀取多個位元組
public static void readFile() throws FileNotFoundException, IOException{
try(InputStream input = new FileInputStream("src/1.txt")){
byte[] buffer = new byte[1024];
int n;
while((n = input.read(buffer)) != -1){
System.out.println("read " + n + " bytes.");
}
}
}
常用的InputStream:
1)FileInputStream:可以從檔案獲取輸入流
2)ByteArrayInputStream:可以在記憶體中模擬一個InputStream
byte[] data = {72, 101, 108, 111, -28, -67, -96, -27, -91, -67};
try(InputStream input = new ByteArrayInputStream(data)){// 可以用在測試中
int n;
while((n = input.read()) != -1){
System.out.println(n)
}
}
OutputStream
outputStream是所有輸出流的超類
1)void write(int b):寫入一個位元組
2)void write(byte[]):寫入byte[]陣列的所有位元組
3)void write(vyte[] b, int off, int len):寫入byte[]陣列指定範圍的位元組
4)void close():關閉輸入流
5)void flush():將快取區內容輸出
write()方法也是阻塞的
使用try可以保證OutputStream正確關閉
常用OutputStream
1)FileOutputStream:可以輸出到檔案
2)ByteArrayOutputStream:可以在記憶體中模擬一個OutputStream
try(ByteArrayOutputStream output = new ByteArrayOutputStream()){
output.write("Hello ".getBytes("UTF-8"));
output.write("world!".getBytes("UTF-8"));
byte[] data = output.toByteArray();
}
練習
編寫程式,接收兩個命令列引數,分別表示原始檔和目標檔案,用InputStream、OutputStream把原始檔複製到目標檔案。複製後,請檢查原始檔和目標檔案是否相同(檔案長度相同,內容相同),分別用文字檔案、圖片檔案和zip檔案測試
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException {
if (args.length < 2) {
System.out.println("usage: java CopyFile sourcefile targetfile");
System.exit(0);
}
byte[] bytes = new byte[1024];
try(InputStream input = new FileInputStream(args[0]);
OutputStream output = new FileOutputStream(args[1])){
int n;
while((n = input.read(bytes)) != -1){
output.write(bytes, 0, n);
}
}
}
}
Filter模式
Filter模式是為了解決子類數量爆炸的問題
直接提供資料的InputStream:
1)FileInputStream
2)ByteArrayInputStream
3)ServletInputStream
提供附加功能的InputStream從FilterInputStream派生
1)BufferedInputStream:快取功能
2)DigestInputStream:計算簽名功能
3)CipherInputStream:加密/解密功能
4)GZIPInputStream:壓縮功能
InputStream input = new GZIPInputStram(
new BufferedInputStream(
new FileInputStream("test.gz");
)
)
Filter模式又稱Decorator模式,通過少量的類是實現了各種功能的組合
FilterInputStream 和 FilterOutputStream類似
// 實現一個位元組數計數的InputStream
public class CounInputStream extends FilterInputStream{
int count = 0;
public CounInputStream(InputStream in) {
super(in);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = super.read(b, off, len);
count++;
return n;
}
}
操作Zip
ZipInputStream可以讀取Zip流
JarInputStream提供了額外讀取jar包內容的功能
ZipOutputStream可以寫入Zip流
配合FileInputStream和FileOutputStream就可以讀寫Zip檔案
// ZipInputStream的基本使用
try(ZipInputStream zip = new ZipInputStream("test.zip")){
ZipEntry entry = null;
while((entry = zip.getNextEntry()) != null){
String name = entry.getName();
if(!entry.isDirectory()){
int n;
while((n = zip.read()) != -1){
// ...
}
}
}
}
// ZipOutputStream的基本使用
try(ZipOutputStream zip = new ZipOutputStream("test.zip")){
File[] files = ...;
for(File file : files){
zip.putNextEntry(new ZipEntry(file.getName));
zip.write(getFileDataAsBytes(file));
zip.closeEntry();
}
}
classpath資源
classpath中也可以包含任意型別的檔案
從classpath讀取檔案可以避免不同環境下檔案路徑不一致的問題
讀取classpath資源:
try(InputStream input = getClass().getResourceAsStream("/default.properties")){
if(input != null){
// Read from classpath
}
}
序列化
序列化是指把一個Java物件變成二進位制內容(byte[]),序列化後可以把byte[]儲存到檔案中,也可以通過網路傳輸
Java物件實現序列化必須實現Serializable介面
Serializable介面沒有定義任何方法,空介面被稱為“標記介面(Marker Interface)”
反序列化是指把一個二進位制內容(bytep[])變成Java物件
使用ObjectOutputStream和ObjectInputStream實現序列化和反序列化
try(ObjectOutputStream output = new ObjectOutputStream(...)){
output.writeObject(new Person("aa"));
output.writeObject(new Person("bb"));
}
try(ObjectInputStream input = new ObjectInputStream(...)){
Object obj = input.readObject();
Person per = (Person)input.readObject();
}
readObject()可能丟擲異常:
1)ClassNotFoundException:沒有找到對應的Class
2)InvalidClassException:Class不匹配
反序列化的重要特點:反序列化由JVM直接構造出Java物件,不呼叫構造方法
可設定serialVersionUID作為版本號(非必須)
Reader 和 Writer
Reader
Reader以字元為最小單位實現了字元輸入
1)int read():讀取下一個字元,並返回字元(0~65535),讀到末尾返回-1
2)int read(char[]):讀取若干字元並填充到char[]陣列
3)int read(char[] c, int off, int len):指定char[]陣列的偏移量和最大填充數
4)void close():關閉Reader
常用的Reader類
1)FileReader:從檔案讀取
2)CharArrayReader:從char[]陣列讀取,可以再記憶體中模擬一個Reader
Reader是基於InputStream構造的,任何InputStream都可指定編碼並通過InputStreamReader轉換為Reader:
InputStream input = new FileInputStream(filename)
Reader reader = new InputStreamReader(input, "UTF-8")
Writer
Writer以位元組為最小單位實現了字元流輸出
1)write(int c):寫入下一個字元
2)write(cahr[]):寫入char[]陣列的所有字元
常用Write類:
1)FileWriter:寫入檔案
2)CharArrayWriter:寫入cahr[]陣列
Writer是基於OutputStream構造的,任何Output都可指定編碼並通過OutputStreamWriter轉換為Writer:
Writer writer = new OutputStreamWriter(output, "UTF-8")
練習
編寫一個程式,接收兩個命令列引數,分別表示原始檔和目標檔案,然後用Reader、Writer把GBK編碼的原始檔轉換為UTF-8編碼的目標檔案
public class Main {
public static void main(String[] args) throws FileNotFoundException,
IOException {
if (args.length < 2) {
System.out.println("usage: java CopyFile sourcefile targetfile");
System.exit(0);
}
char[] chars = new char[1024];
try (Reader reader = new InputStreamReader(
new FileInputStream(args[0]), "GBK");
Writer writer = new OutputStreamWriter(new FileOutputStream(
args[1]), "UTF-8")) {
int ch;
while ((ch = reader.read(chars)) != -1) {
writer.write(chars, 0, ch);
}
}
}
}