編碼問題
String s = "Java 教程";
byte[] byte1 = s.getBytes(); //使用默認編碼。
byte[] byte2 = s.getBytes("utf-8"); //顯示指定編碼格式爲 utf-8
//把字節(轉換成int)以16進制方式顯示
// GBK編碼 中文佔2個字節,英文佔1個字節
// UTF-8 編碼,中文佔用3個字節,英文佔用1個字節
// Java 是雙字節編碼 UTF-16be
for( byte b : byte1) {
System.out.print(Integer.toHexString(b & 0xff) + " ");
}
當你的字節序列是某種編碼時,在把字節序列變成字符串時,也需要使用該種編碼,否則會亂碼。
String str = new String(b,"utf-16be");
文本文件就是字節序列,可以是任意編碼的字節序列。
如果在中文機器上直接創建的文本文件,那麼文本文件只認識ansi編碼。
複製的還是按照原來的編碼格式
File 類常用API
Java中, java.io.FIle
類用於表示文件(目錄),只用於表示文件(目錄)的信息(名稱、大小),不能用於文件訪問。
File file = new File("E:\\java\\javaTest"); // 一個目錄
File file2 = new File("E://java//test1.java"); // 一個文件
File file3 = new File("E://java","test2.java") //一個文件
if(!file.exists()) {
file.mkdir(); //創建一個目錄
file.createNewFile(); //創建一個文件
} else {
file.delete();
}
if(file.isDirectory()) { }
if(file2.isFile()) { }
//常用的File對象API
file.getAbsolutePath();
file.getName();
file.getParent();
File常用操作:遍歷,過濾
//列出指定目錄下的目錄和文件
String[] filenames = dir.list(); //列出當前目錄下的子目錄和文件,返回字符串數組。不包含子目錄下的內容
// 需要遍歷目錄下的內容時,就要構造File對象做遞歸操作。
//File提供了直接返回對象的方法。
File[] files = dir.listFiles(); //返回直接子目錄(文件)的對象
if(files != null && files.length > 0) {
for (file : files) {
if(file.isDirectory()) {
//遞歸
}
else {
//直接返回
}
}
}
RandomAccessFile的使用
RandomAcceseeFile 是Java 提供的對文件內容的訪問類。
既可以讀取文件,也可以寫文件。並且可以隨機訪問文件,可以訪問文件的任意位置
java 文件模型
- 在硬盤上的文件是按 字節 存儲的,是數據的集合;
打開文件
- 兩種模式: “rw”“r”。
- RandomAccessFile raf = new RandomAccessFile(file, “rw”);
- 文件指針: 打開文件時指針在開頭 pointer = 0;
寫方法
- raf.write(int) ; //只寫一個字節(後8位),同時指針指向下一個字節的位置,準備再次寫入
讀方法
- int b = raf.read(); //只讀一個字節
讀寫文件後一定要關閉文件,否則可能出現不可預測的錯誤。
File file = new File("001.dat") {
if(!file.exist()) {
file.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(file,"rw");
int i = 0x7fffffff;
//用write方法沒戲只寫一個字節。int4個字節,需要寫4次
raf.write(i >>> 24); 高8位
raf.write(i >>> 16);
raf.write(i >>> 8);
//或是用 writeInt 直接寫一個int
raf.writeInt(i);
//write 可以直接寫一個字節數組
String s = "中";
byte[] gbk = s.getBytes("gbk");
raf.write(gbk); //寫入一個字節數組
//讀文件,必須把指針移到頭部
raf.seek(0);
//一次性讀取文件中的所有內容到字節數組中
byte[] buf = new Byte[(int)raf.length()];
raf.read(buf);
IO流(輸入流,輸出流)
字節流的使用
- InputStream、OutputStream
InputStream 抽象了應用程序讀取數據的方式
OutputStream 抽象了應用程序寫出數據的方式 - EOF == end of file 讀到-1表示讀到文件結尾
輸入流基本方法:(注意,鍵盤是輸入流。 從鍵盤讀入數據寫到文件中)
int b = in.read(); // 讀取一個字節無符號填充int低8位。 -1 是 EOF
- `int read(byte[] buf); //讀取數據填充到字節數組buf
int read(byte[] buf, int start, int size); //讀取數據到字節數組buf,從buf 的start位置開始存放size長度的數據
輸出流基本方法
out.write(int b); // 寫出一個byte到流中,b的低8位
out.write(byte[] buf); //將buf字節數組寫入到流
out.write(byte[] buf, int start, int size); //字節數組buf從start位置開始寫size長度的字節到流
FileInputStream 繼承InputStream,具體實現了文件上讀取數據。
FileInputStream in = new FileInputStream("file");
int b = in.read();
Byte[] buf = new byte[20*1024];
int bytes = in.read(buf,0,buf.length);//從in中批量讀取字節,放入到buf這個字節數組中,從第0個位置開始放,最多放buf.length個字節。返回的是讀到的字節的個數
FileOutputStream 繼承了OutputStream,具體實現了向文件中寫出byte數據的方法
//如果文件不存在,直接創建。如果存在,刪除後創建
FileOutputStream out = new FileOutputStream(file);
//true 表示 append,在文件後面追加.
//如果文件不存在,直接創建。如果文件存在,直接在文件末尾寫入
FileOutputStream out = new FileOutputStream(file,true);
DataInputStream 和 DateOutputStream 數據輸入輸出流
是對“流”功能的擴展,可以更加方便的讀取int,long,字符等類型數據writeInt()/ writeDouble()/ writeChar()
//有OutputStream嵌套生成
FileOutputStream fout = new FileOutputStream("E:\\001.txt");
DataOutputStream dos = new DataOutputStream(fout);
dos.writeInt(10);
dos.writeUTF("中國");// 採用utf-8編碼寫出,每個漢字3個字節
dos.writeChars("中國"); //採用utf-16be 編碼,每個漢字2個字節
BufferedInputStream & BufferedOutputStream 帶緩衝區的字節流操作。
一般打開文件進行寫入或讀取操作時,都會加上緩衝,這種流模式提高了IO的性能。
FileOutoutStream ---> write()方法:類似一滴一滴的把水“轉移”過去
DataOutputStream ---> writeXxx方法:較方便,類似一瓢一瓢的把水‘轉移’過去
BufferedOutputStream ---> write() 方法: 更方便,類似把水先一瓢一瓢放到桶中,在從桶中導入水缸中。
FileOutoutStream ---> write(buf,start,size)方法: 速度最快
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int c;
while((c = bis.read()) != -1) {
bos.write(c);
bos.flush(); //一定要刷新緩衝區,將數據寫入文件中
}
bis.close();
bos.close();
字符流的使用
認識文本和文本文件
- 文本: Java 文本(char)是16位無符號整數,是字符的unicode編碼(雙字節編碼)
- 文件: 是按照byte存儲的數據序列
- 文本文件: 是文本(char)序列按照某種編碼方案(utf-8,utf-16be, gbk) 序列化爲byte的存儲結構
字符流輸入輸出流(Reader, Writer) 操作文本文件
字符的處理: 一次處理一個字符
字符的底層仍然是基本的字節序列
字符流的基本實現
InputStreamReader
: 完成byte流解析爲char流,按照編碼解析OuputStreamWriter
: 提供char流發到byte流,按照編碼解析
FileInputStream in = new FileInputStram("001.txt");
InputStreamReader isr = new InputStreamReader(in,"gbk"); //默認按照gbk編碼
int c = isr.read();
char[] buffer = new char[8 * 1024];
c = isr.read(buffer, 0, buffer.length); //批量讀取字符,放入buffer字符數組,從0位置開始放置,最多放置length個,返回讀取到的字符個數
String s = new String(buffer,0,c); //把字符存儲到字符串中
另外一種實現: FileReader&FileWriter
注意: 沒有編碼參數,
需要設置編碼時,就用InputStreamReader&OutputStreamWriter
FileReader fr = new FileReader("001.txt");
FileWriter fw = new FileWriter("002.txt");
//FileWriter fw = new FileWriter("002.txt","true");//文件後面追加
while((int c = fr.read(buffer,0,buffer,length) != -1) {
fw.write(buffer,0,c);
fw.flush();
}
fr.close();
fw.close();
字符流的過濾器 BufferedReader
BufferedReader 除了基本的讀取外,可以一次讀取一行
BufferedWriter/ PrintWriter 可以一次寫一行
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("001.txt"[,"utf-8"])));
//或是用FileReader()
//BufferedReader br = new BufferedWriter(FileReader("001.txt"));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileInputStream("002.txt"[,"utf-8"]));
String line;
//一次讀取一行,不能識別換行,返回是字符串
while(line = br.readLine() != null) {
bw.write(line);
//單獨寫出換行操作
bw.newLine(); //換行操作
bw.flush();
}
br.close();
bw.close();
// 可以直接用PrintWriter 代替 BufferedWriter
// PrintWriter pw = new PrintWriter("003.txt");
// PrintWriter pw1 = new PrintWriter(outputStream, boolean autoFlush); //自動刷新
// pw.print(line); //輸出一行,沒有換行
// pw.println(line); // 輸出一行,有換行
對象的序列化和反序列化
對象的序列化&反序列化
- 對象的序列化,就是將Object 轉換成byte序列
對象的反序列化,就是將byte序列轉換成Object
序列化流:ObjectOutputStream, 是過濾流 -> writeObject 方法
- 反序列化流:ObjectInputStream, -> readObject方法
序列化接口 (Serializable)
對象必須實現序列化接口,才能進行序列化,否則會出現異常;
序列化接口沒有任何實現方法,只是一個標準。
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("001.txt"));
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("002.txt"));
Student stu = new Student("1001","張三"); // Student 類要實現Serializable 接口,否則出現異常
oos.writeObject(stu);
Student stu2 = (Student)ois.readObject();
oos.flush();
oos.close();
ois.close();
關鍵字: transient
transient 修飾的元素不會進行JVM默認的序列化
但是可以自己定義給元素的序列化。
例如在ArrayLis中,底層的數組是不一定會被填滿的。
所以ArrayList 自己定義數組元素的序列化和反序列化,這樣可以捨去空元素,提高性能。
序列化中子類和父類構造函數的調用
父類實現了序列化接口,其子類都能進行序列化。
定義的3個foo類
序列化時遞歸調用父類構造函數
反序列化操作1
反序列化操作1 結果: 只打印出了對象,沒有打印出構造函數調用結果
另外3個類
序列化時會打印3個構造函數。
反序列化時會打印父類的構造函數:
反序列化操作 bar 類
說明 對子類對象進行反序列化操作時,如果其父類沒有實現序列化接口,那麼其父類的構造函數會被調用。