七、java的IO操作
1、概述
Java的IO流主要包括输入、输出两种IO流(流向分类),流又分为字节流和字符流,字节流以字节为单位来处理输入和输出,字符流以字符为单位来处理输入输出操作。按功能不同又可分为节点流和处理流
流的输出输出以程序本身为主体,数据进入程序,为输入流,数据自程序输出,则为输出流。
Java流类的层次结构如下图:
Java流类的基本用法:
1、创建流对象:FileInputStream in = new FileInputStream(“a.txt”);
2、通过流对象实现数据传输:int num;byte[] date = new byte[20];
num = in.read(dat,0,20);
3、关闭流:in.close();
UseOfStream.java(java流的基本用法,从文件中读取内容,控制台打印输出)
package blog7;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 流的基本用法,从文件中读取内容,控制台打印输出
* 本方法采用字节流读取中文却不乱码的方法可以留意一下,不过需要在最后一次读取做额外处理
*/
public class UseOfStream {
@SuppressWarnings("unused")
public static void main(String[] args) {
int num;
byte [] dat = new byte[20];
try {
FileInputStream in = new FileInputStream(
"D:\\Java\\workspace\\blogSource\\src\\blog7\\青春.txt");
while( (num = in.read(dat, 0, 20))!= -1){
String s = new String(dat,0,20);
System.out.print(s);
//按理说字节流读取中文会出现乱码,这里却没有,因为dat正好是20字节,10个中文字符
//而且此处最后一次读取打印出现了问题,因为最后一次dat没有全部重新read,即最后一次的时候
//num!=20,导致最后一次输出,dat前面一小部分是最后一次读取的内容,而后面的是上一次读取的内容
}
in.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
}
2、File类
File类代表与平台无关的文件和目录,通过其方法可以新建、删除、重命名文件和目录,但是不能访问文件内容本身,只能通过流对象访问内容。
File类常用方法:
1、文件名操作
获取文件名String getName();
返回文件名字符串String toString();
获取文件对象父路径String getParent();
获取相对路径String getPath();
获取绝对路径String getAbsolutePath();
返回规范的路径String getCanonicalPath();
返回文件规范形式File getCanonicalFile();返回此抽象路径名的绝对路径名形式,等同于 new File(this.getAbsolutePath()) File getAbsoluteFile();重命名文件boolean renameTo(File dest);
当指定文件不存在时,创建一个空文件boolean createNewFile();
2、文件属性测试
是否能读取 boolean canRead();
是否能修改 boolean canWrite();
是否存在 boolean exists();
是否是文件 boolean isFile();
是否是目录 boolean isDirectory();
是否是隐藏的 boolean isHidden();
3、文件常用信息和工具
返回文件最后被修改的时间 long lastModified();
返回文件字节长度 long length();
删除指定文件 boolean delete();
当虚拟机执行结束时删除指定文件或目录 void deleteOnExit();
4、目录操作
创建指定文件夹 boolean mkdir();
创建多级文件夹 boolean mkdirs();
返回指定目录下的文件 String[] list();
返回指定目录下的指定类型文件 String[] list(FilenameFilter filter);
返回指定目录下的文件 File[] listFiles()、listFiles(FileFilter filter);
返回系统根目录结构 static File[] listRoots();
TestFile.java(测试File类的方法、mkdir与mkdirs的区别,getPath、getCanonicalPath和getAbsolutePath的区别,文件夹的复制、删除、移动(包含子文件夹的情况下))
package blog7;
import java.io.File;
import java.io.IOException;
/**
* File类方法的测试 mkdir和mkdirs的区别 getPath、getAbsolutePath和getCanonicalPath的区别
* 递归实现文件夹的复制、移动、删除(包含子文件夹)
*/
public class TestFile {
public static void main(String[] args) throws Exception {
//TestFile.testMehthods();
//TestFile.testMkdir();
//TestFile.testPath();
//TestFile.showDir("d:\\java");//遍历d盘下java文件夹
}
//测试File的常用方法
public static void testMethods() throws IOException {
File f = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\青春.txt");
System.out.println(f.createNewFile());
System.out.println(f.getName());
System.out.println(f.getParent());
System.out.println(f.length());
}
//测试mkdir方法和mkdirs的区别
public static void testMkdir() throws IOException{
File f = new File("d:\\a\\b\\c");
System.out.println(f.mkdir()); //输出false,创建失败,因为a、b文件夹都不存在,不能建立c
System.out.println(f.mkdirs());//输出true,且建立所有未存在的目录
File f2 = new File("e:\\aa\\a.txt"); //这一句不会生成文件
f2.createNewFile(); //aa文件夹必须存在才会创建文件
}
//测试getPath、getAbsolutePath和getCanonicalPath方法的区别
public static void testPath() throws IOException{
//测试getPath和getAbsolutePath的区别
//此处“.\\test.txt”的‘.’表示当前目录,绑定到当前的工程目录下
File file1 = new File(".\\test1.txt");//当前工程目录下
File file2 = new File("D:\\workspace\\test\\test1.txt");
System.out.println("-----默认相对路径:取得路径不同------");
System.out.println(file1.getPath()); //.\test1.txt
System.out.println(file1.getAbsolutePath());//D:\Java\workspace\blogSource\.\test1.txt
System.out.println("-----默认绝对路径:取得路径相同------");
System.out.println(file2.getPath());//D:\workspace\test\test1.txt
System.out.println(file2.getAbsolutePath());//D:\workspace\test\test1.txt
/**
* getPath()得到的是构造file的时候的路径。
* getAbsolutePath()得到的是全路径
* 如果构造的时候就是全路径那直接返回全路径
* 如果构造的时候试相对路径,返回当前目录的路径+构造file时候的路径
* */
//测试getAbsolutePath和getCanonicalPath
File file = new File("..\\src\\test1.txt");
System.out.println(file.getAbsolutePath());//D:\Java\workspace\blogSource\.\src\test1.txt
System.out.println(file.getCanonicalPath());//D:\Java\workspace\blogSource\src\test1.txt
// 可以看到CanonicalPath不但是全路径,而且把..或者.这样的符号解析出来。
// 注意windows和Linux的区别,前者大小写不敏感,后者敏感,在使用getCanonicalPath的区别。
}
//递归方法实现文件夹的删除、复制、移动,重点是对文件夹的遍历,采用递归思想,这里只作一个文件夹的列出
public static void showDir(String filePath) throws IOException{
File f = new File(filePath);
if(f.isFile()){
System.out.println("输入有误,请输入文件夹地址!!!");
return ;
}
File[] fileArray = f.listFiles();
for(File file:fileArray){
if(file.isDirectory()){
showDir(file.getCanonicalPath());
}else if(file.isFile()){
showFile(file);
}
}
}
public static void showFile(File file) throws IOException{
System.out.println(file.getCanonicalPath());
//可在此通过IO操作进行文件的复制、移动、删除等等
}
}
3、文件过滤器FilenameFitler与FileFilter
TestFilter.java(两种文件过滤器的用法实例,前者效率高于后者)
package blog7;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
/**
* 两种文件过滤器的用法实例,前者效率高于后者
* filenameFilter效率高于FileFilter
*/
public class TestFilter {
//过滤列出文件夹中java文件输出
//内部类的形式定义一个过滤器,筛选出java文件,通过重写accept方法
private static FileFilter filter = new FileFilter() {
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
if( pathname.isDirectory()){
return true;
}else if(name.endsWith(".java") || name.endsWith(".JAVA")){
return true;
}else{
return false;
}
//该方法中,返回true的是被筛选选出的类型,本例为文件夹和java文件。
}
};
public static void showDir(String path) throws IOException{
File f = new File(path);
System.out.println("当前目录:" + f.getCanonicalPath());
File[] fileArray = f.listFiles(filter); //在获取文件列表时传入过滤器参数
for(File file:fileArray){
if(file.isDirectory()){
System.out.println("当前目录:" + f.getCanonicalPath());
showDir(file.getCanonicalPath());
}else if(file.isFile()){
System.out.println("java文件:" + file.getCanonicalPath());
}
}
}
public static void main(String[] args) throws IOException {
TestFilter.showDir("d:\\java");
}
}
// FilenameFilter类的使用
class OtherMethod {
public static void main(String[] args)throws Exception {
File f = new File("d:\\java\\workspace");
MyFilter filter = new MyFilter(".java");
String[] files = f.list(filter);
for(String a:files){
System.out.println(a);
}
}
static class MyFilter implements FilenameFilter{
private String type;
public MyFilter(String type){
this.type = type;
}
public boolean accept(File dir,String name){
return name.endsWith(type);
}
}
}
4、字节流、字符流和转换流
字节流和字符流仅仅是操作的单位不同,字节流以byte字节为单位,提供两个抽象类作为基类:InputStream、OutputStream ,字符流以Unicode编码的字符为单位,即双字节处理,主要用来操作char类型数据,也提供两个抽象类作为基类:Writer、Reader。
两者的区别:字节流操作不会使用到缓冲区(类本身,而非操作时使用者人工添加的资源处理缓冲区)。而字符流操作会使用到内存中的一块缓冲区。也因此,字节流的输出可以不用close(但建议还是关闭资源),而字符流必须关闭资源,因为调用close的同时会清空缓冲区,也可人工调用方法flush清空缓冲区。字节流处理过程:程序->文件;字符流处理过程:程序->缓冲区->文件。
总结:字节流使用广泛方便,各种文件都可以处理,但是处理文字时,字节流会将一个2字节的汉字分作两部分处理,出现乱码,所以文字操作时最好使用字符流。
TestStream.java(几种流的使用实例(字符输入输出、字节输出输出)、图片和文本的复制)
package blog7;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 几种流的使用实例(字符输入输出、字节输出输出)、图片和文本的复制
*/
public class TestStream {
public static void main(String[] args) throws IOException {
//TestStream.testByteInput();
//TestStream.testByteOutput();
//TestStream.charInput();
//TestStream.charOutput();
//TestStream.textCopy();
TestStream.picCopy();
}
//字节输入
@SuppressWarnings("resource")
public static void testByteInput() throws IOException{
//1、建立输入源
File file = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\yestday.txt"); //中英文对照文本
//2、建立输入流
FileInputStream in = new FileInputStream(file);
//3、对输入流进行操作,让数据输入到程序中(读取 ):3种方法,利用三种重载的read方法
/* 方法1:逐字读取:这里是逐个int读取的,所以中文乱码
int rec = in.read();
while(rec!=-1){
System.out.println((char)rec);
rec = in.read();
}*/
/* 方法2:依旧是中文问题,常用这种方法,数组读取,注意最后一次读取的时候可能出现问题。
byte [] buffer = new byte[100];
int len = 0;
while( (len = in.read(buffer)) != -1 ){
// for(int i=0;i<len;i++){
// System.out.print((char)buffer[i]);
// 这里是逐byte读取的
// }
//为解决中文乱码问题,可以这样
String s = new String(buffer,0,len);
System.out.print(s);
}*/
//方法3:建立了很大的缓冲数组,一次读取完毕
byte [] buffer = new byte[1024];
int len = in.read(buffer,0,in.available());
String s = new String(buffer,0,len);
System.out.println(s);
//总结:不论用什么方法读取,记住中文乱码的解决办法,用byte数组取数据,将byte转成String
}
//字节输出
public static void testByteOutput() throws IOException{
//1、建立输入源
File file2 = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\testOutput.txt");
//2、建立输出流
FileOutputStream out = new FileOutputStream(file2);
//3、IO操作写入到输出源中,常用两种方法。
String dest = "Why she had to go 为何她远去"
+ "\nI don't know she wouldn't say.没留下一句话"
+ "\nI said something wrong,这都是我的错"
+ "\nNow I long for yesterday.";
/* 法一:一次性全写入
byte [] str = dest.getBytes();
out.write(str);
*/
//法二:分批写入,适用于量大的数据,需要计算写入的次数
int len = 10;//一次写10个byte
int time = dest.length()/len; //此处不一定整除,注意余数的处理
byte [] str2 = dest.getBytes();
for(int i=0;i<time;i++){
out.write(str2, i*len, len);
}
//操作最后一次的余数,及不够10个byte的最后的数据
out.write(str2,time*len,str2.length-time*len);
//奇怪:这里我使用str2.length%len或者dest.length()%len都不对,非要str2.length-time*len才对
//4 、 关闭资源
out.flush(); //可以刷新一下输出流
out.close();
}
// 字符输入
public static void charInput() throws IOException{
// 1、建立输入源
File f = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\yestday.txt");
// 2、建立字符输入流
FileReader reader = new FileReader(f);
// 3、从本地文件输入到程序中,控制台打印出来,同样有3种方法,利用三个重载的read方法
/* 逐字读取
int len;
while( (len = reader.read()) != -1){
System.out.print((char)len);
}*/
char [] buffer = new char[10];//一次读取10个char
int len;
while( (len = reader.read(buffer))!= -1 ){
String s = new String(buffer,0,len);
System.out.print(s);
}
//关闭资源
reader.close();
}
// 字符输出
public static void charOutput() throws IOException{
File f = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\charOutput.txt");
FileWriter writer = new FileWriter(f);
String dest = "Why she had to go 为何她远去"
+ "\nI don't know she wouldn't say.没留下一句话"
+ "\nI said something wrong,这都是我的错"
+ "\nNow I long for yesterday.";
char [] buffer = dest.toCharArray();//一次读取10个,也可逐字处理
int len = 10;
int time = buffer.length/len;
for(int i=0;i<time;i++){
writer.write(buffer, i*len, len);
}
//补写最后一次不满10个的数据
writer.write(buffer, time*len, buffer.length-time*len);
//其实此处也可以一次性写进所有数据:write(string)、write(char[])、writer(char )(逐个写入)
writer.close();
}
//字符流实现的文本复制
public static void textCopy() throws IOException{
// 1、建立源文件,及对应的输入流
File f = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\yestday.txt");
FileReader reader = new FileReader(f);
// 2、建立目标文件,及对应的输出流
File file = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\yestdayCopy.txt");
FileWriter writer = new FileWriter(file);
// 3、操作数据流,实现边读取边写入,此处不采用逐字处理,采用分批,一批10个字符
char [] buffer = new char[10];
int len;
while( (len = reader.read(buffer)) != -1 ){
writer.write(buffer, 0, len);
}
// 4、完成任务,关闭资源
writer.flush();
writer.close();
reader.close();
}
//字节流实现的图片复制
public static void picCopy() throws IOException{
File f = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\MyPic.jpg");
FileInputStream in = new FileInputStream(f);
File file = new File("D:\\Java\\workspace\\blogSource\\src\\blog7\\MyPicCopy.jpg");
FileOutputStream out = new FileOutputStream(file);
byte [] buffer = new byte[20];
int len;
while( (len=in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
out.flush();
out.close();
in.close();
}
}
字符流本质上最终还是通过字节流来操作数据的,只不过使用了一个缓冲区,所以字符流不需转成字节流,但是字节流可以转换成字符流,通过两个类:OutputStreamWriter和InputStreamReader。注意:FileOutputStream和InputStream都是抽象基类(OutputStream、InputStream)的直接子类,但是FileWriter和FileReader不是抽象基类的直接子类,而是转换流的子类。
例如:
InputStreamReader inR = new InputStreamReader(new FileInputStream(new File(“aaa.txt”)));
5、Java7提供了简化关闭资源的try语句:try-with-resources自动关闭资源的try语句。
格式:
Try(此处多了圆括号,内写打开资源的代码,这里创建的对象必须实现AutoCloseable接口)
{ //IO操作}catch(){ //异常处理}
TryWithAutoClose.java(自动关闭资源try语句实例)
package blog7;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 自动关闭资源try语句
*/
public class TryWithAutoClose {
public static void copy(File src,File dest){
try(InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);){
byte [] buffer = new byte[20];
int len;
while( (len = in.read(buffer)) != -1 ){
out.write(buffer, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
//可以不用关闭资源,此形式的try语句会自动关闭资源
}
public static void main(String[] args) {
File src = new File(".\\src\\blog7\\青春.txt");
File dest = new File(".\\src\\blog7\\青春copy.txt");
TryWithAutoClose.copy(src, dest);
System.out.println("well done!!");
}
}
6、Java的IO操作·其他类
1、内存操作流
文件的操作以文件的输入输出为主,但是当输出位置变成了内存,那么就称为了内存操作流,此时需要使用内存操作流完成输入输出操作。使用内存流并未直接操作磁盘文件,所以能提升性能;在操作内存流的时候,注意一定要将真正的数据用toByteArray或toCharArray将数据读出来。
将内容写到内存中 :ByteArrayInputStream或CharArrayReader
将内存中的数据写出:ByteArrayOutputStream或CharArrayWriter
通常操作:现将数据写到内存中,再从内存中读出,相当于在内存中虚拟文件的操作,但避免真正的磁盘文件操作。
ByteArrayIO.java(分别用字节流和字符流对数据进行写入内存再读出)
package blog7;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
/**
* 内存IO操作,将数据写入内存,再从内存中读取
*/
public class ByteArrayIO {
public static void main(String[] args) throws IOException {
//ByteArrayIO.byteMethod();
ByteArrayIO.charMethod();
}
//字节型读写
public static void byteMethod() throws IOException{
String str = "我爱你,my friends!!!";
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(str.getBytes()); //写入内存中
byte [] data = out.toByteArray(); //获取内存输出流中的数据
ByteArrayInputStream in = new ByteArrayInputStream(data);//输入流,需要源,为内存中的数据data
byte [] buffer = new byte[1024];
int len;
while( (len=in.read(buffer)) != -1 ){
String s = new String(buffer,0,len);
System.out.println(s);
}
}
//字符型内存读写
public static void charMethod() throws IOException{
String str = "I love you,我的朋友们!!!";
CharArrayWriter out = new CharArrayWriter();
out.write(str); //写入内存中
char [] data = out.toCharArray(); //获取内存输出流中的数据
CharArrayReader in = new CharArrayReader(data);//输入流,需要源,为内存中的数据data
char [] buffer = new char[1024];
int len;
while( (len=in.read(buffer)) != -1 ){
String s = new String(buffer,0,len);
System.out.print(s);
}
}
}
2、打印流(格式化输出)
PrintWriter(字符)和PrintStream(字节)。
因为字节流只能操作字节数据,面对boolean或是其他类型数据的时候就会难以下手,此时打印流可以输出各种类型的数据。
注:print()参数不能为空,println()可以为空,PrintStream调用println方法有自动flush功能。
TestPrint.java(两种打印流的使用,分别输出到文件和控制台,格式化输出实例)
package blog7;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 打印流的使用实例
* 本例:字节流将数据打印到文件中,字符流将数据打印到控制台输出
*/
public class TestPrint {
public static void main(String[] args) throws IOException {
// 1 print to file
PrintStream pOut = new PrintStream(".\\src\\blog7\\printText.txt");
pOut.print(true);
pOut.println('Y');//若是println的话会在输出参数后输出一个\r\n
pOut.print(12.953);
String name = "何钰枫";
int age = 20;
pOut.printf("我是%s,几年%d岁", name,age); //格式化输出
pOut.close();
// 2 print to console
PrintWriter wr = new PrintWriter(System.out);
wr.print(false);
wr.print("我爱北京天安门");
wr.println(654.158);
wr.println('我');
wr.print(".....");
wr.close();
}
}
3、过滤流DataInputStream和DataOutputStream
DateInput/OutputStream分别继承自过滤流FilterInput/OutputStream,流提供了同步机制,一个时刻只有一个线程能访问数据流,防止发生意想不到的错误。
过滤流使用时,用户需要在创建过滤流的时候将过滤流连接到另一个输入或输出流上。
如:DataInputStream dis = new DataInputStream(new FileInputStream(“bb.txt”));
TestDataStream.java(使用方法和打印流一样,不过只针对字节流,且需要以一个已存在的输入或输入流为基础建立,而且既可以输出也可以输入,注意内存模型,写入的时候,不同的数据写入的字节不同)
4、标准流
标准输入流:System.in
标准输出流:System.out
标准输入流默认表示从键盘输入,标准输出流默认表示从屏幕输出,但是也可以用户采用流的重定向方式,重新分配“标准”输入输出流的标准!!!使用setIn和setOut方法(注意这两个方法是System的方法)。
SystemIO.java(标准流的实例)
package blog7;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 标准流的使用:System.in键盘录入,System.out屏幕输出
*/
public class SystemIO {
/**
* 标准输入输出流:默认的是键盘录入和屏幕输出,但是可以人工的改变输入输出流
* 使用setOut(PrintStream out)和SetIn(InputStream in)
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//输出
System.out.println("现在是在控制台输出:");
System.out.print("这就是内容!!!");
System.out.println("接下来通过SetOut改变输出对象...");
System.setOut(new PrintStream(".\\test.txt")); //改变默认的输出源,此时输出到文件test.txt
System.out.println("现在就输入到文本中啦...");
//输入
int i;
i = System.in.read();
System.out.println("输入的是:" + (char)i ); /*键盘输入*/
System.out.println();
System.setIn(new FileInputStream(".\\src\\blog7\\青春.txt"));/*修改输入源,为本地文件*/
byte [] buffer = new byte[20];
int len;
while( (len=System.in.read(buffer)) != -1 ){
String str = new String(buffer,0,len);
System.out.print(str); //此句是输出到文本中的,所以想当与将青春.txt复制到了test.txt中
}
}
}
5、Scanner类
TestScanner.java(Scanner的使用实例)
package blog7;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;
/**
* Scanner的使用,Scanner是一个扫描器,扫描源的数据,源可以使标准输入源System.in,也可以是文件
*/
public class TestScanner {
public static void main(String[] args) throws IOException {
/*
//1、从键盘录入,即标准输入流扫描,输出到控制台
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine()); //next不能检测到空格,nextLine可以输出带空格的String
System.out.println(scanner.nextLine());
System.out.println(scanner.nextLine());
scanner.close();
//可以连续不停扫描
*/
//将扫描源变成文件,标准输出SetOut到文件,则变成了复制文件
Scanner scanner = new Scanner(new File(".\\src\\blog7\\青春.txt"));
System.setOut(new PrintStream("Scanner青春.txt"));
int line = 1;
while( scanner.hasNextLine()){ //是否有下一行,即按行读取,本身不会读取换行符,所以需要手动添加,使用println
System.out.println("第" + line++ + "行:" + scanner.nextLine());
}
scanner.close();
}
}
6、缓冲流
缓冲流要套在节点流上,对读写添加了缓冲,提高效率。常用四种缓冲流:BufferedWriter、BufferedReader、BufferedInputStream和BufferedOutputStream,同时四种流还分别提供了一种指定缓冲大小的构造方法。
对于输出的缓冲流,写出的数据会被先写入到内存缓冲中,使用flush方法会立刻将内存中的数据写出。
读取提供了readLine方法整行读取,写入提供了newLine方法写入一个行分隔符。readLine读取返回值为String,当这个String为null时读取完毕。
TestBufferedIO.java(缓冲流的使用实例)
package blog7;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 缓冲流使用实例:效率较高
*/
public class TestBufferedIO {
public static void main(String[] args) throws IOException {
TestBufferedIO.bufferedCopy();
}
//字符缓冲流的文件复制
public static void bufferedCopy() throws IOException{
//1.创建输入流,锁定到输入文件,bufferedReader对象通过FileReader对象建立,事实上市对后者做一个封装
BufferedReader in = new BufferedReader(new FileReader(new File(".\\src\\blog7\\青春.txt")));
//2.创建输出流,锁定到输出文件
BufferedWriter out = new BufferedWriter(new FileWriter(new File("青春copy.txt")));
//3.进行数据读写(传输)
//主要用 String readLine() 这个方法,进行行读取
//用void write(String s, int off, int len) 方法进行写入
//注意行读取写入会有回车符省略问题,人工写入
String buffer = null;
int i = 0;
while( (buffer = in.readLine()) != null ){
if(i != 0){
out.write('\n'); //小技巧,防止多输出一个空行,可这样写:out.newLine();
}
out.write(buffer);
i++;
}
//4.关闭资源,注意,只需关注被封装后的对象即可,内部会依次去关闭原始的对象
out.flush();
in.close();
out.close();
}
}
7、合并流SequenceInputStream
将两个文件的内容合并成一个文件,根据两个字节输入流来创建合并流对象。注意先后顺序。
TestSequenceInputStream.java(合并流使用实例)
package blog7;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
/**
* 合并输入流
*/
public class TestQuenceInputStream {
public static void main(String[] args) throws IOException {
@SuppressWarnings("resource") //应匿名对象,si、s2两个匿名的输入流也关闭,此处忽略了
SequenceInputStream sin = new SequenceInputStream(new FileInputStream(".\\src\\blog7\\青春.txt"),
new FileInputStream(".\\src\\blog7\\yestday.txt") );
FileOutputStream out = new FileOutputStream(new File("合并输出文本.txt"));
byte [] buffer = new byte[20];
int len;
while( (len=sin.read(buffer)) != -1 ){
out.write(buffer, 0, len);
}
out.flush();
out.close();
sin.close();
}
}
8、随机文件访问RandomAccessFile类
RandomAccessFile的原理是将文件看做字符数组,并用文件指针指示当前位置,所以可以根据指针位置进行非顺序的读写。
TestRandomAccessFile.java(随机文件访问的实例)
package blog7;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* 随机文件访问:中文出现了乱码,未解决
*/
public class TestRandomAccessFile {
public static void main(String[] args) throws IOException {
TestRandomAccessFile.TestRAF();
}
public static void TestRAF() throws IOException{
// 测试得,randomAccessFile是一个字节流的IO资源
//1.创建对象
RandomAccessFile rio = new RandomAccessFile(".\\src\\blog7\\yestday.txt", "rw");
//2.读写文件
String str = null;
while((str = rio.readLine()) != null){
System.out.println(str);
}
//此时文件指针在文本末尾,所以i love you会追加在文本末尾
rio.writeBytes("i love you \n");
//注:RandomAccessFile.writeBytes的写入会覆盖该指针所在位置的原有字符!故,插入字符实现较复杂
//通常是将剩下的内容读到buffer中,然后在该位置插入,最后将buffer中内容取出在插入在后部,即可。
//3.关闭资源
rio.close();
}
}
9、ObjectIO及对象的序列化
对象的寿命通常随着生成该对象的程序的终止而终止,有时候,可能需要将对象的状态保存下来,在需要的时候再将对象恢复,将对象的这种能记录自己的状态以便将来再生的能力叫做对象的持久性(Persistence),对象通过写出描述自己状态的数值来记录自己,这个过程叫对象的序列化(Serialization)。
对象序列化机制是将内存中的Java对象转换成与平台无关的字节流,并持久保存在存储设备上,通过网络传输,其他程序一旦获得这种字节流,就可以恢复原来的Java对象。
Java对象序列化只需要实现Java.io.Serializable接口即可。这个接口没有实际内容,只是一个标识。
序列化分为序列化和反序列化。序列化:将数据分解成字节流,以便存储成文件进行网络传输。反序列化:打开字节流并重构对象。
ObjectInputStream和ObjectOutputStream将数据流扩展至可读写对象。这两个流建立在节点流基础之上,通过readObject和writeObject方法实现对象的读写。
TestObjectIO.java(自定义一个类,实现序列化,将对象保存到磁盘并读取)
package blog7;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.junit.Test;
/**
* 对象序列化、对象的读写
*/
public class TestObjectIO {
@Test //采用测试单元,写入对象到文本中
public void writeObject() throws IOException{
Student stu = new Student(1001,"小明","男",18);
System.out.println(stu);
ObjectOutputStream oin = new ObjectOutputStream(new FileOutputStream("object.txt"));
oin.writeObject(stu);
oin.close();
}
@Test //读取类对象,此处没有创建对象
public void readObject() throws IOException, ClassNotFoundException{
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("object.txt"));
Student stu = (Student)oin.readObject();
System.out.println(stu);
oin.close();
}
}
class Student implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
private int id;
public Student(int id,String name,String sex,int age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "[ " + this.id + " " + this.name + " " + this.sex + " " + this.age + " ]";
}
}
注:串行化只能保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量,而且只能保存变量的值,对于变量的任何修饰符都不能保存,对于某些瞬时状态的对象,无法保存期状态,如Thread对象、FileInputStream对象,对于这些字段,必须用transient关键字标明。若需要打破缺省的序列化默认的读写字段、信息的顺序(按名称升序写入数据),可重写readObject方法和writeObject方法进行定制序列化。
如果某个类的字段不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化