在應用系統中,不管多大的系統或者是項目,總結起來都可以看成是:輸入--->處理--->輸出,所以說輸入輸出是非常重要的,除此之外我們還經常會遇到文件的上傳下載,備份等等常用的功能,這些都需要使用文件和IO操作,而Java.io圍繞File InputStream OutputStream Write Reader 等五個重要的對象和Serializable這個接口提供了強大的輸入輸出處理的功能。
1,首先看File類,File類可以直接用來操作文件,包括了對文件的新建和刪除等常用的操作。
<span style="white-space:pre"> </span>// 常用的構造方法
File file1=new File("weijun"); //直接使用路徑創建文件
File file2=new File(file1,"file2"); // 指定創建文件的父文件 file2爲File1 的子文件
File file3=new File("weijun","file3"); //用路徑指定父文件 file3 是當前路徑中"weijun"目錄下的子文件、、
// File中的常量,文件分割符和路徑分割符
System.out.println("fileSeparator:"+File.separator); //fileSeparator:\
System.out.println("FilePathSeparator:"+File.pathSeparator); // FilePathSeparator:;
// File 類中的主要方法
file1.delete(); // 刪除文件
file1.exists(); //判斷文件是夠存在
file1.isDirectory(); // 判斷當前文件是否是目錄
file1.isFile(); //判斷是否是普通文件
file1.mkdir(); //如果當前目錄不存在則創建一個目錄
file1.getAbsolutePath(); //得到文件的絕對路勁
file1.getPath(); //得到文件袋餓相對路勁
file1.getParentFile(); //得到文件的父文件
file1.getParent(); //得到父文件名
file1.listFiles(); //得到文件中所有文件列表,返回所有文件
file1.list(); //返回所有子文件名
file1.listFiles(new FileFilter() { //根據文件來篩選返回文件,需要一個FileFilter接口,實現該接口的accept函數
public boolean accept(File pathname) { //當調用listFiles方法時,會逐一的取當前文件的子文件,來匹配accept方法,如果爲true則加入返回結果中
return false;
}
}); //根據文件名來篩選返回文件
file1.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return false;
}
});
}
使用File類可以很方便的對文件進行操作,但是如果要對文件的內容進行操作,則可以使用隨機讀取類RandomAccessFile,可以隨機的讀取一個文件中指定位置的數據,用一個例子來說明:
import java.io.File;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main (String[] args) {
try{
File f=new File("test.txt"); // 要操作的文件
RandomAccessFile rdf=new RandomAccessFile(f,"rw"); // 對文件test 可讀可寫
// 之所以隨機讀取類能夠對指定的文件內容進行操作,相當於其內部設置了文件的指針指向文件的當前位置,所以在我們實現隨機讀取時最好固定讀取內容的長度一定
String name="xiaozhang "; // 10字節
rdf.writeBytes(name); // 寫入第一個人的姓名
name="xiaoli "; //10字節
rdf.writeBytes(name); // 寫入第二個人的姓名
name="xiaowang "; //10字節
rdf.writeBytes(name); // 寫入第三個人的姓名
// 現在要安順尋查找第二個人 第一個人 第三個人
byte[] b=new byte[10];
rdf.seek(0); //從文件起始處開始讀寫
rdf.skipBytes(10); //跳過是10字節,從第二個人開始
for(int i=0;i<b.length;i++)
b[i]=rdf.readByte(); //逐個字節的讀取
name=new String(b); // 將字節數組轉化成字符串
System.out.println("第二個人是:"+name);
rdf.seek(0); //跳到文件起始位置,準備讀取第一個人的姓名
b=new byte[10]; // 重新初始化字節緩衝區
for(int i=0;i<b.length;i++)
b[i]=rdf.readByte(); //逐個字節的讀取
name=new String(b); // 將字節數組轉化成字符串
System.out.println("第一個人是:"+name);
rdf.skipBytes(10); //當第一個人的信息取完後指針應該指向第二個人的信息的開始,所以跳過後就到了第三個人信息的開始處
b=new byte[10]; // 重新初始化字節緩衝區
for(int i=0;i<b.length;i++)
b[i]=rdf.readByte(); //逐個字節的讀取
name=new String(b); // 將字節數組轉化成字符串
System.out.println("第三個人是:"+name);
rdf.close(); //關閉隨機讀寫文件
}
catch(Exception e){
e.printStackTrace();
System.out.println(e.getMessage());
}
/*得到的結果:
* 第二個人是:xiaoli
第一個人是:xiaozhang
第三個人是:xiaowang
*/
}
}
2,字節輸入輸出流: InputStream和OutputStream 這兩個抽象類是所有字節輸入輸出流類的父類,字節流是最基本的流,文件的操作,網絡數據的傳輸都依賴於字節流。
InputStream 常用方法:
int read() 讀出下一個字節,當不在有內容時返回-1;
int read(byte[] b);將讀出的內容放入字節數組中,放回實際讀取的字節數,如果沒有讀取內容放回-1
int read(byte[] b,int off,int len );將讀出的內容放入字節數組中,從數組的off開始 放入len個字節,返回實際讀取的長度,如果沒有讀取內容返回-1
void close();關閉文件流
OutoutStream常用的方法:
void write(int b);講一個字節數據寫入數據流
void write(byte[] b);x寫入一個字節數組
void write(bute[] b,int off,int len) 將字節數組從off 開始的len個字節寫入到數據流
void close() 關閉流
void flush()刷新緩衝區
2.1首先最常用的就是文件的字節輸入輸出流FileInputStream 和 FileOutputStream,使用文件的字節流能夠很方便的對文件內容(內容非純中文)進行讀寫。
文件讀寫通常包含四個操作: 使用File類找到一個文件;用文件創建一個流;執行讀寫的操作;關閉文件流
文件流操作實例,複製一個文件:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyTest {
// 將源路勁的文件複製到目的路勁的文件
public boolean copyFile(File src ,File dist){ // 第一步找到目標文件
boolean isCopy =true;
FileInputStream in=null;
FileOutputStream out=null;
try {
in=new FileInputStream(src); // 第二步 創建讀寫流
out=new FileOutputStream(dist);
byte[] buff=new byte[1024];
int length=0; // 每次複製的字節數 // 第三步開始文件讀寫
while((length=in.read(buff, 0, 1024))>0){
out.write(buff, 0, length);
}
} catch (Exception e) {
isCopy=false; // 文件複製失敗
e.printStackTrace();
}
finally{
try {
in.close(); //第四步關閉流
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return isCopy;
}
public static void main(String[] args) {
File src=new File("test.txt"); // 必須是存在的文件
File dist=new File("testCopy.txt"); // 如果不存在會自動創建
if(new FileCopyTest().copyFile(src, dist))
System.out.println("文件複製成功");
}
}
2.2使用對象輸入輸出流ObjectOutputStream和ObjectInputStream可以實現對象的輸入輸出,但是前提是對象必需實現序列化接口,所謂序列化就是把一個對象變爲二進制流的一種方法,通過對象序列化可以方便的實現對象的傳輸和存儲。
如果一個對象想被序列化,則對象需要實現接口java.io.Serializable接口。這個接口的定義:
public interface Serializable{} 此接口中並沒有定義任何方法,所以這個接口只是一個標示接口,標示一個實現了該接口的類具備了被序列化的能力。
使用對象流操作對象的讀寫:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ObjectStreamTest {
private File test; // 用來保持對象的文件
public ObjectStreamTest(File test) {
super();
this.test = test;
}
public ObjectStreamTest() {
super();
}
public static void main(String[] args)throws Exception {
Student stu=new Student("xiaoxiao",24,new SimpleDateFormat("yyyy-MM-dd").parse("1990-06-16"));
ObjectStreamTest obTest=new ObjectStreamTest(new File("objectStreamTest.txt"));
obTest.writeObject(stu);
Student redObj=(Student)obTest.readObject();
System.out.println("取得學生的信息:");
System.out.println("姓名:"+redObj.getStuName());
System.out.println("年齡:"+redObj.getStuAge());
System.out.println("出身日期:"+new SimpleDateFormat("yyyy-MM-dd").format(redObj.getStuBirth()));
}
public void writeObject(Object obj) throws Exception{ // 向文件中寫入對象
ObjectOutputStream objOut=new ObjectOutputStream(new FileOutputStream(test));
objOut.writeObject(obj); // 寫入對象
objOut.close();
}
public Object readObject() throws Exception{ // 從文件中讀取對象
Object result=null;
ObjectInputStream objIn=new ObjectInputStream(new FileInputStream(test));
result=objIn.readObject(); // 從流中讀取對象
objIn.close();
return result;
}
}
class Student implements java.io.Serializable{ // 學生類實現序列化接口
private String stuName;
private int stuAge;
private Date stuBirth;
public String getStuName() {
return stuName;
}
public int getStuAge() {
return stuAge;
}
public Date getStuBirth() {
return stuBirth;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public void setStuAge(int stuAge) {
this.stuAge = stuAge;
}
public void setStuBirth(Date stuBirth) {
this.stuBirth = stuBirth;
}
public Student(String stuName, int stuAge, Date stuBirth) {
super();
this.stuName = stuName;
this.stuAge = stuAge;
this.stuBirth = stuBirth;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
}
2.3 文件流和對象流輸入輸出的位置都是針對文件,java流也提供了直接在內存中操作的流:ByteArrayInputStream,ByteArrayOutputStream。內存流用的並不多,一般只在生成一些臨時文件的時候纔會用到,而如果這些臨時信息放在文件中的話,則程序運行完之後還必需要刪除文件,所以這種情況直接使用內存流比較合適。 看下面的例子:使用內存流完成字符串的大小寫轉換(使用這兩個流的習慣跟平時要反過來,這裏要特別說明,因爲我們運行的程序也是放在內存中):
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStreamTest {
public static void main (String[] args) throws Exception{
String msg="hello word";
// 定義一個內存流,並將內存中的字節數組讀入到內存流中
ByteArrayInputStream bis=new ByteArrayInputStream(msg.getBytes());
// 定義內存輸出流,用來讀取數據,這裏可以看出內存流的使用上跟平時文件操作是相反的
ByteArrayOutputStream bos=new ByteArrayOutputStream();
int len; // 表示每次讀出的字節數
while((len=(bis.read()))!=-1){ // 每次從流中讀取一個字節,放回字節的內容(0--255)
char ch=(char)len; // 轉成字符
bos.write(Character.toUpperCase(ch)); // 將字符從小寫轉到大寫,並從流中寫會到內存中
}
msg=bos.toString(); // 將內存中的內容轉化成字符串賦值給msg
System.out.println("字符串轉變大寫後爲:"+msg);
}
}
2.4 java中還提供了管道流來實現兩個線程之間的通信(在多線程的操作中會有總結),要注意的是:如果要使用管道輸出,則必需要把輸出連接到輸入上如圖:
PipeOutputStream類上使用 pubic void connect(PipeInputStream snk)Throws IOException 連接管道流
2.5 java 還提供了與平臺無關的數據操作流DataOutputStream和DataInputStream,可以很方便的對一定格式的數據進行處理。
使用DataOutputStream 和 DataInputStream 對一定格式的數據進行處理:
package edu.hue.jk.io;
import java.io.DataInputStream;
class Student implements java.io.Serializable{ // 學生類實現序列化接口
private String stuName;
private int stuAge;
private Date stuBirth;
public String getStuName() {
return stuName;
}
public int getStuAge() {
return stuAge;
}
public Date getStuBirth() {
return stuBirth;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public void setStuAge(int stuAge) {
this.stuAge = stuAge;
}
public void setStuBirth(Date stuBirth) {
this.stuBirth = stuBirth;
}
public Student(String stuName, int stuAge, Date stuBirth) {
super();
this.stuName = stuName;
this.stuAge = stuAge;
this.stuBirth = stuBirth;
}
public Student() {
super();
}
}
public class DataStreamTest {
public static void main(String[] args) throws Exception{
// 定義數據輸出流,輸出到指定的文件中
DataOutputStream dos=new DataOutputStream(new FileOutputStream(new File("DateTest.txt")));
// 構造學生數組
Student stu1=new Student("student1", 22, new SimpleDateFormat("yyyy-MM-dd").parse("1992-06-16"));
Student stu2=new Student("student2", 22, new SimpleDateFormat("yyyy-MM-dd").parse("1992-06-16"));
List<Student> students=new LinkedList<Student>();
students.add(stu1);
students.add(stu2);
// 向文件中寫入學生的數據
for(Student s:students){
dos.writeChars(s.getStuName()); // 寫入字符串
dos.writeChar('\t');//加入分割符
dos.writeInt(s.getStuAge()); // 寫入整數
dos.writeChar('\t');
dos.writeChars(new SimpleDateFormat("yyyy-MM-dd").format(s.getStuBirth()));
dos.writeChar('\n'); //插入換行符
}
// 定義數據輸入流,從文件中得到學生的信息
DataInputStream dis=new DataInputStream(new FileInputStream(new File("DateTest.txt")));
for(int i=0;i<2;i++){
char c;
int len=0;
char[] byteName=new char[100]; // 用來將字節數組轉換成字符串使用
while((c=dis.readChar())!='\t'){ // 讀出姓名
byteName[len++]=c;
}
String name=new String(byteName,0,len);
//dis.readChar(); // 讀出分割字符
int age=dis.readInt(); // 讀出年紀
dis.readChar(); // 讀出分割字符
len=0;
byteName=new char[100]; // 用來將字節數組轉換成字符串使用
while((c=dis.readChar())!='\n'){ // 讀出出生年月
byteName[len++]=c;
}
String birth=new String(byteName,0,len);
//dis.readChar(); // 讀出回車字符
System.out.println("學生姓名:"+name+"年齡:"+age+"出生年月:"+birth);
}
dos.close();
dis.close();
}
}
數據輸出:
學生姓名:student1年齡:22出生年月:1992-06-16
學生姓名:student2年齡:22出生年月:1992-06-16
3,字符流Reader和Writer java在使用字符流的操作和字節流基本上一樣只是代碼不同而已,關於字符流與字節流的區別和如何使用,我覺得百度上的這個回答總結的很清楚(下面關於字符流和字節流的區別引用自百度上別人的回答):
字符流處理的單元爲2個字節的Unicode字符,分別操作字符、字符數組或字符串,而字節流處理單元爲1個字節, 操作字節和字節數組。所以字符流是由Java虛擬機將字節轉化爲2個字節的Unicode字符爲單位的字符而成的,所以它對多國語言支持性比較好!如果是 音頻文件、圖片、歌曲,就用字節流好點,如果是關係到中文(文本)的,用字符流好點. 所有文件的儲存是都是字節(byte)的儲存,在磁盤上保留的並不是文件的字符而是先把字符編碼成字節,再儲存這些字節到磁盤。在讀取文件(特別是文本文件)時,也是一個字節一個字節地讀取以形成字節序列. 字節流可用於任何類型的對象,包括二進制對象,而字符流只能處理字符或者字符串; 2. 字節流提供了處理任何類型的IO操作的功能,但它不能直接處理Unicode字符,而字符流就可以。
除此之外java在字節流和字符流對文件的操作上也是有些不同的,字節流直接針對文件進行處理,而字符流在操作時會使用到緩衝區,再通過緩衝區去操作文件,至於什麼事緩衝區,我的理解其實緩衝區就是一段內存,因爲對文件的讀寫比直接操作內存中的數據的速度要慢的多,所以在這中間就加入的緩衝區(相當於cache的作用)解決文件讀寫和內存直接讀寫的速度不匹配的問題。
在使用字符流操作時,千萬不要忘了輸出緩衝的內容 writer.flush()
使用字符流的操作(也是複製文件內容):
import java.io.File;
public class CopyFile_Reader {
public static void main(String[] args) {
//源文件對象
File file1 = new File("DateTest.txt");
//目標文件
File file2 = new File("copyTest.txt");
FileReader reader = null ;
FileWriter writer = null ;
try {
reader = new FileReader(file1); // 處理步奏跟字節流差不多
writer = new FileWriter(file2);
int length = 0;
char[] c = new char[1024];
while((length=reader.read(c, 0, 1024))!=-1){
writer.write(c, 0, length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.1 很多時候由於需要,我們要將字節流轉化成字符流(例如從鍵盤得到中文信息),java提供了兩個轉換流InputStreamReader和OutputStreamReader用來將字節流轉換成對應的字符流。實例:從鍵盤輸入一句話(中文)保存到文件
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class StreamReaderAndWriterTest {
// 從鍵盤中得到中文信息,寫入到文件中
public static void main(String[] args) throws Exception{
InputStreamReader isr=new InputStreamReader(System.in); //將從鍵盤上的得到的字節流轉化成字符流
BufferedReader br=new BufferedReader(isr); // 使用緩存來讀取
String msg=br.readLine(); // 讀取一行信息
FileOutputStream fos=new FileOutputStream("streamReaderAndWriterTest.txt");
OutputStreamWriter osw=new OutputStreamWriter(fos); // 轉化成字符輸出流
BufferedWriter bw=new BufferedWriter(osw);
bw.write(msg); // 寫入得到的信息
bw.close(); // 關閉的時候會輸出緩衝區的信息
isr.close();
}
}
上面總結了很多關於流的操作和文件的操作,下面總結了一個綜合的例子,<span style="color:#990000;">用來處理文件或者文件夾的備份,採用深度優先搜索的方式來備份文件夾</span>:
<pre class="java" name="code">import java.io.File;
// 要不要加入多線程來專門處理每個文件的複製
// 用來備份一個文件或則是文件夾的類
public class FileCopy {
private File srcFile; // 源文件
private File distFile; //目標文件
public FileCopy() {
super();
}
public FileCopy(File srcFile, File distFile) {
super();
this.srcFile = srcFile;
this.distFile = distFile;
}
public boolean copy(){ // 使用深度優先搜索來處理文件備份,誰說算法在應用中用不上???
boolean result=true;
distFile=new File(distFile,srcFile.getName());
if(srcFile.isFile()){ //如果源文件是普通文件 直接複製源文件
try {
InputStream in=new FileInputStream(srcFile);
OutputStream out=new FileOutputStream(distFile);
byte[] buff=new byte[1024];
int length=0;
while((length=in.read(buff, 0, 1024))>0){
out.write(buff, 0, length);
}
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
result=false;
}
distFile=distFile.getParentFile(); // 如果當前複製的是文件,則目標文件要上移
}
else{ // 如果是文件夾
if(distFile.mkdir()) // 創建文件夾
System.out.println("文件夾"+distFile.getName()+"創建成功");
else
System.out.println(distFile.getPath()+"創建失敗");
File[] fileList=srcFile.listFiles();
for(File f:fileList){ // 開始複製文件夾中的子文件
srcFile=f;
copy();
}
distFile=distFile.getParentFile(); // 如果一個文件複製完成之後,目標文件的位置也要上移
}
return result;
}
}