一、對象序列化
ObjectOutputStram和ObjectInputStream
1、概述:
將堆內(nèi)存中的對象存入硬盤,保留對象中的數(shù)據(jù),稱之為對象的持久化(或序列化)
2、特有方法:
1、write(int val) ---> 寫入一個字節(jié)(最低八位)
2、writeInt(int vale) ---> 吸入一個32為int值
3、使用步驟:
說明:serialVersion
a、給類一個可被編譯器識別的的序列號,在編譯類時,會分配一個long型UID,通過序列號,將類存入硬盤中,并序列化,即持久化。序列號根據(jù)成員算出的。靜態(tài)不能被序列化。如果非靜態(tài)成員也無需序列化,可以用transien修飾。
b、接口Serializable中沒有方法,稱之為標(biāo)記接口
1、寫入流對象:
1)創(chuàng)建對象寫入流,與文件關(guān)聯(lián),即傳入目的
2)通過寫入writeObject()方法,將對象作為參數(shù)傳入,即可寫入文件
2、讀取流對象
1)創(chuàng)建對象讀取流,與文件關(guān)聯(lián),即傳入源
2)通過writeObject()方法,讀取文件中的對象,并返回這個對象
示例:
import java.io.*;
//創(chuàng)建Person類,實現(xiàn)序列化
class Person implements Serializable{
//定義自身的序列化方式
public static final long serialVersionUID = 42L;
//定義私有屬性
private String name;
private int age;
transient String id;
static String country = "cn";
//構(gòu)造Person類
Person(String name,int age,String id,String country){
this.name = name;
this.age = age;
this.id = id;
this.country = country;
}
//覆寫toString方法
public String toString(){
return name+ ":" + age + ":" + id + ":" + country;
}
}
//對象序列化測試
class ObjectStreamDemo{
public static void main(String[] args){
//對象寫入流
writeObj();
//對象讀取流
readObj();
}
//定義對象讀取流
public static void readObj(){
ObjectInputStream ois = null;
try{
//創(chuàng)建對象讀取流
ois = new ObjectInputStream(new FileInputStream("obj.txt"));
//通過讀取文件數(shù)據(jù),返回對象
Person p = (Person)ois.readObject();
System.out.println(p);
}catch (Exception e){
throw new RuntimeException("寫入文件失敗");
}
//最終關(guān)閉流對象
finally{
try{
if(ois!=null)
ois.close();
}catch (IOException e){
throw new RuntimeException("寫入流關(guān)閉失敗");
}
}
}
//定義對象寫入流
public static void writeObj(){
ObjectOutputStream oos = null;
try{
//創(chuàng)建對象寫入流
oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
//寫入對象數(shù)據(jù)
oos.writeObject(new Person("lisi",25,"01","cn"));
}catch (Exception e){
throw new RuntimeException("寫入文件失敗");
}
//關(guān)閉流資源
finally{
try{
if(oos!=null)
oos.close();
}catch (IOException e){
throw new RuntimeException("寫入流關(guān)閉失敗");
}
}
}
}
二、管道流
1、概述:
1、管道流:PipedInputStream和PipedOutputStream
2、管道流涉及到多線程的問題
2、使用步驟:
1、要先創(chuàng)建一個讀和寫的兩個類,實現(xiàn)Runnable接口,因為是兩個不同的線程,覆蓋run方法,注意,需要在內(nèi)部拋異常
2、創(chuàng)建兩個管道流,并用connect()方法將兩個流連接
3、創(chuàng)建讀寫對象,并傳入兩個線程內(nèi),并start執(zhí)行
示例:
import java.io.*;
//創(chuàng)建Read類,實現(xiàn)run方法
class Read implements Runnable{
private PipedInputStream in;
Read(PipedInputStream in){
this.in = in;
}
//實現(xiàn)run方法
public void run(){
try{
//讀取寫入的數(shù)據(jù)
//System.out.println("開始寫入數(shù)據(jù),等待時間");//測試用
Thread.sleep(3000);
byte[] b = new byte[1024];
int len = in.read(b);
//System.out.println("讀取完畢");//測試用
String s = new String(b,0,len);
System.out.println(s);
in.close();
}catch (Exception e){
throw new RuntimeException("管道流讀取失敗");
}
}
}
//創(chuàng)建Write類
class Write implements Runnable{
private PipedOutputStream out;
//Write構(gòu)造函數(shù)
Write(PipedOutputStream out){
this.out = out;
}
//實現(xiàn)run方法
public void run(){
try{
//寫入數(shù)據(jù)
//System.out.println("開始寫入數(shù)據(jù)");//測試用
out.write("管道流來嘍~~~~".getBytes());
out.close();
}catch (IOException e){
throw new RuntimeException("管道流寫入失敗");
}
}
}
class PipedStreamDemo{
public static void main(String[] args) throws IOException
{
//創(chuàng)建管道流對象
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
//將讀取流(輸入流)和寫入流(輸出流)關(guān)聯(lián)起來
in.connect(out);
//創(chuàng)建讀寫對象,并創(chuàng)建線程對象
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
三、RandomAccessFile 類
1、概述:
1、RandomAccessFile稱之為隨機訪問文件的類,自身具備讀寫方法。
2、該類不算是IO體系中的子類,而是直接繼承Object,但是它是IO包成員,因為它具備讀寫功能,內(nèi)部封裝了一個數(shù)組,且通過指針對數(shù)組的元素進(jìn)行操作,同時可通過seek改變指針的位置。
3、可以完成讀寫的原理:內(nèi)部封裝了字節(jié)輸入流
4、構(gòu)造函數(shù):RandomAccessFile(File file,String mode),可已從它的構(gòu)造函數(shù)中看出,該類只能操作文件(也有字符串),而且操作文件還有模式。
模式傳入值:”r“:以只讀方式打開;”rw“:打開以便讀寫
如果模式為只讀,則不會創(chuàng)建文件,會去讀一個已存在的文件,若文件不存在,則會出現(xiàn)異常,如果模式為rw,且該對象的構(gòu)造函數(shù)要操作的文件不存在,會自動創(chuàng)建,如果存在,則不會覆蓋,也可通過seek方法修改。
2、特有方法:
1、seek(int n):設(shè)置指針,可以將指針設(shè)置到前面或后面
2、skipBytes(int n):跳過指定字節(jié)數(shù),不可往前跳
3、使用步驟:
1、創(chuàng)建RandomAccessFile對象
2、將數(shù)據(jù)寫入到指定文件中
3、讀取數(shù)據(jù),讀入到指定文件中
注意:堯都區(qū)后面的數(shù)據(jù),需要調(diào)用數(shù)組指針,通過改變角標(biāo)位置,取出相應(yīng)的數(shù)據(jù)
a.調(diào)整對象的指針:seek()
b.跳過指定字節(jié)數(shù)
示例:
import java.io.*;
//注:這幾個函數(shù)內(nèi)部都需要try,為測試,在函數(shù)上拋異常
class RanAccFileDemo{
public static void main(String[] args) throws IOException{
//readRaf();
readRaf2();
//writeRaf();
}
//寫入數(shù)據(jù)
public static void writeRaf()throws IOException{
//創(chuàng)建對象,寫入數(shù)據(jù)
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.write("王五".getBytes());
raf.writeInt(99);
raf.write("李四".getBytes());
raf.writeInt(97);
raf.close();
}
//讀取數(shù)據(jù)
public static void readRaf()throws IOException{
//創(chuàng)建對象,讀取數(shù)據(jù)
RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
byte[] b = new byte[4];
raf.read(b);
String name = new String(b);
int age = raf.readInt();
System.out.println("name="+ name);
System.out.println("age=" + age);
raf.close();
}
//讀取數(shù)據(jù)
public static void readRaf2()throws IOException {
//創(chuàng)建對象,讀取數(shù)據(jù)
RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
//調(diào)整對象中的指針
//raf.seek(8);
//跳過指定字節(jié)數(shù)
raf.skipBytes(8);
byte[] b = new byte[4];
raf.read(b);
String name = new String(b);
int age = raf.readInt();
System.out.println("name="+ name);
System.out.println("age=" + age);
raf.close();
}
}
四、操作基本數(shù)據(jù)類型的流對象
1、概述:
1、操作基本數(shù)據(jù)類型的流對象:DataInputStream和DataOutputStream
2、這兩個讀寫對象,可用于操作基本數(shù)據(jù)類型的流對象,包含讀寫各種基本數(shù)據(jù)類型的方法
2、特有方法:
| 讀 | 寫 | |
|---|---|---|
| int型 | writeInt(int n) | int readInt() |
| boolean型 | writeBoolean(boolean b) | boolean readBoolean() |
| double型 | writeDouble(double d) | double readDouble() |
五、操作數(shù)組和字符串
1、操作字節(jié)數(shù)組的對象:ByteArrayInputStream和ByteArrayOutputStream
1、這個對象并沒有調(diào)用底層資源,所以不用關(guān)閉流資源
2、存入的是緩沖區(qū),并未用到鍵盤和硬盤燈,所以不需要拋任何IO異常
3、對象中封裝了數(shù)組
4、構(gòu)造函數(shù):
1)ByteArrayInputStream:在構(gòu)造函數(shù)的時候,需要接受數(shù)據(jù)源,而且數(shù)據(jù)源是一個字節(jié)數(shù)據(jù)。
2)ByteArrayOutputStream:在構(gòu)造函數(shù)的時候,不用定義數(shù)據(jù)目的,因為該對象中已經(jīng)在內(nèi)部封裝了可變長度的字節(jié)數(shù)組,這就是數(shù)據(jù)的目的地
4、因為兩個流對象都是操作的是數(shù)據(jù),并沒有使用系統(tǒng)資源,所以不用進(jìn)行close關(guān)閉。
6、其實就是用流的思想操作數(shù)組
7、特有方法:writeTo(OutputStream out) 這個方法用到了字節(jié)輸出流,有異常存在,需要拋IO異常
2、對應(yīng)的字符數(shù)組和字符串:
字符數(shù)組流對象:CharArrayReader和CharArrayWriter
字符串流對象: StringReader和StringWriter
示例:
import java.io.*;
class ArrayStreamDemo
{
public static void main(String[] args)
{
//數(shù)據(jù)源
ByteArrayInputStream bais = new ByteArrayInputStream("ABCDEFF".getBytes());
//數(shù)據(jù)目的
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int by = 0;
//讀取和寫入數(shù)據(jù)
while((by=bais.read())!=-1)
{
baos.write(by);
}
System.out.println(baos.size());
System.out.println(baos.toString());
try
{
//方法,此處拋異常,所以上面需要拋出去
baos.writeTo(new FileOutputStream("a.txt"));
}
catch (IOException e)
{
throw new RuntimeException("寫入文件失敗");
}
}
}
六、字符編碼
1、概述:
1、字符流的出現(xiàn)為了方便操作字符,更重要的是加入了編碼的轉(zhuǎn)換,即轉(zhuǎn)換流。
2、通過子類進(jìn)行轉(zhuǎn)換
3、在兩個對象進(jìn)行構(gòu)造時,可加入編碼表
4、可傳入編碼表的有:
1)轉(zhuǎn)換流:InuputStreamReader和OutputStreamWriter
2)打印流:PrintStream和PrintWriter,只有輸出流
5、常見的編碼表:
1)ASCII:美國標(biāo)準(zhǔn)信息交換碼表。用一個字節(jié)的7位表示
2)IOS8859-1:拉丁碼表;歐洲碼表。用一個字節(jié)的8位表示
3)GB2312:中國的中文編碼表
4)GBK:中國的中文編碼表升級,融合了更多的中文文字字符。打頭的是兩個高位為1的兩個字節(jié)編碼。為負(fù)數(shù)
5)Unicode:國際標(biāo)準(zhǔn)碼,融合了多種文字
6)UTF-8:最多用三個字節(jié)表示一個字符的編碼表,包括:一位、兩位、三位表示的字符
UTF-8有自己的字節(jié)碼:
一個字節(jié):0開頭
兩個字節(jié):
》字節(jié)一 ---> 110 位數(shù):10 ~ 6
》字節(jié)二 ---> 10 位數(shù):5 ~ 0
三個字節(jié):
》字節(jié)一 ---> 110 位數(shù):15 ~ 12
》字節(jié)二 ---> 10 位數(shù):11 ~ 6
》字節(jié)三 ---> 10 位數(shù):5 ~ 0
2、編碼和解碼:
1、編解碼:
編碼:字符串變成字節(jié)數(shù)組
解碼:字節(jié)數(shù)組變成字符串
2、轉(zhuǎn)換:
1)默認(rèn)字符集:
String ---> byte[] :srt.getBytes()
byte[] ---> String :new String(byte[])
2)指定字符集:
String ---> byte[] :srt.getBytes(charsetName)
byte[] ---> String :new String(byte[],charsetName)
3、對于編碼和解碼的字符集轉(zhuǎn)換
1、如果編碼失敗,解碼就沒意義了。
2、如果編碼成功,解碼出來的是亂碼,,則需對亂碼通過再次編碼(用解錯碼的編碼表),然后再通過正確的編碼表解碼。針對于IOS8859-1是通用的。
3、如果用的是GBK編碼,UTF-8解碼,那么再通過2的方式,就不能成功了,因為UTF-8也支持中文,在UTF-8解的時候,會將對應(yīng)的字節(jié)數(shù)改變,所以不會成功。
4、特別注意:
對于中文的”聯(lián)通“,這兩個字比較特別,它的二進(jìn)制位正好是和在UTF-8中兩個字節(jié)打頭的相同,可以找到對應(yīng)的符號,但不再是”聯(lián)通“了。
示例:
import java.util.*;
class EncodeDemo
{
public static void main(String[] args) throws Exception{
CodeDemo();
//編譯成功,解碼失敗后的解決方式
CodeBack();
}
public static void CodeDemo()throws Exception{
String s = "你好";
byte[] b1 = s.getBytes();
String s1 = new String(b1);
System.out.println(Arrays.toString(b1));
byte[] b2 = s.getBytes("GBK");//默認(rèn)編碼
String s2 = new String(b2);
System.out.println("s1=" + s1 + ",s2=" + s2);
System.out.println(Arrays.toString(b2));
byte[] b3 = s.getBytes("UTF-8");//國際編碼
String s3 = new String(b3);
System.out.println("s3=" + s3);
System.out.println(Arrays.toString(b3));
byte[] b4 = s.getBytes("ISO8859-1");//歐洲編碼
String s4 = new String(b4);
System.out.println("s4=" + s4);
System.out.println(Arrays.toString(b4));
}
//編碼與解碼
public static void CodeBack()throws Exception{
String s = "你好";
System.out.println("原數(shù)據(jù):" + s);
byte[] b1 = s.getBytes("GBK");//默認(rèn)編碼
System.out.println(Arrays.toString(b1));
String s1 = new String(b1,"ISO8859-1");
System.out.println("s1=" + s1);
System.out.println("----對s1進(jìn)行ISO8859-1編碼-----");
//對s1進(jìn)行ISO8859-1編碼
byte[] b2 = s1.getBytes("ISO8859-1");//歐洲編碼
System.out.println(Arrays.toString(b2));
String s2 = new String(b2,"GBK");
System.out.println("s2=" + s2);
}
}
七、練習(xí)
五個學(xué)生,每個學(xué)生有3門課程的成績,從鍵盤輸入以上數(shù)據(jù)(姓名,三門課成績),
輸入格式:如:zahngsan,30,40,60計算出總成績,并把學(xué)生的信息和計算出的總分?jǐn)?shù)高低按順序存放在磁盤文件stud.txt中
步驟:
1、描述學(xué)生對象
2、定義一個可操作學(xué)生對象的工具類
思路:
1、通過獲取鍵盤錄入一行的數(shù)據(jù),并將該行數(shù)據(jù)的信息取出,封裝成學(xué)生對象
2、因為學(xué)生對象很多,則需要存儲,使用集合,因為要對學(xué)生總分排序
所以可以使用TreeSet
3、將集合中的信息寫入到一個文件中
import java.io.*;
import java.util.*;
//定義學(xué)生類
class Student implements Comparable<Student>
{
//定義私有屬性
private String name;
private int ma,cn,en;
private int sum;
//構(gòu)造Student函數(shù),初始化
Student(String name,int ma,int cn,int en)
{
this.name = name;
this.ma = ma;
this.cn = cn;
this.en = en;
sum = ma+cn+en;
}
//覆寫compareTo方法,按學(xué)生總成績排序
public int compareTo(Student s)
{
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0)
return this.name.compareTo(s.name);
return num;
}
//獲取學(xué)生信息
public String getName()
{
return name;
}
public int getSum()
{
return sum;
}
//覆寫hasdCode()和equals()方法,排除相同的兩個學(xué)生
public int hashCode()
{
return name.hashCode() + sum*39;
}
public boolean equals(Object obj)
{
if(obj instanceof Student)
throw new ClassCastException("類型不匹配");
Student s = (Student)obj;
return this.name.equals(s.name) && this.sum==s.sum;
}
//定義學(xué)生信息顯示格式
public String toString()
{
return "student[" + name + ", " + ma + ", " + cn + ", " + en + "]";
}
}
//工具類,將鍵盤錄入的輸入存入集合,并將集合的元素寫入文件中
class StudentInfoTool
{
//無比較器的學(xué)生集合
public static Set<Student> getStudents()
{
return getStudents(null);
}
//具備比較器的學(xué)生集合
public static Set<Student> getStudents(Comparator<Student> cmp)
{
BufferedReader bufr = null;
Set<Student> stus = null;
try
{
//創(chuàng)建讀取流對象緩沖區(qū),鍵盤錄入
bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
//選擇集合是否有比較器
if(cmp==null)
stus = new TreeSet<Student>();
else
stus = new TreeSet<Student>(cmp);
//循環(huán)讀取鍵盤錄入的數(shù)據(jù)
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
//對讀取的數(shù)據(jù)進(jìn)行分割并存入集合
String[] info = line.split(",");
Student stu = new Student(info[0],Integer.parseInt(info[1]),
Integer.parseInt(info[2]),
Integer.parseInt(info[3]));
stus.add(stu);
}
}
catch (IOException e)
{
throw new RuntimeException("學(xué)生信息讀取失敗");
}
//關(guān)閉流資源
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("讀取流關(guān)閉失敗");
}
return stus;
}
}
//將數(shù)據(jù)寫入指定文件
public static void write2File(Set<Student> stus,String fileName)
{
BufferedWriter bufw = null;
try
{
//創(chuàng)建寫入流對象
bufw = new BufferedWriter(new FileWriter(fileName));
//循環(huán)寫入數(shù)據(jù)
for(Student stu : stus)
{
bufw.write(stu.toString() + "\t");
bufw.write(stu.getSum() + "");
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("讀取流關(guān)閉失敗");
}
//關(guān)閉流資源
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("寫入流關(guān)閉失敗");
}
}
}
}
class Demo
{
public static void main(String[] args)
{
//反轉(zhuǎn)比較器,將成績從大到小排
Comparator<Student> cmp = Collections.reverseOrder();
//將錄入的學(xué)生信息存入集合
Set<Student> stus = StudentInfoTool.getStudents(cmp);
//將信息寫入指定文件中
StudentInfoTool.write2File(stus,"sudentinfo.txt");
}
}