write:2022-3-17
文章目录
1. 输入流和输出流
流是程序和外界进行数据交换的通道
分为输入流(InputStream)和输出流(OutputStream)。程序通过输入流从数据源读取数据,通过输出流向目的地写数据。
1.1 输入流InputStream
InputStream类是所有输入流的父类,它是一个抽象类,不能被实例化。它提供了一系列和读取数据有关的方法。
int read(), (不带参:一次只能读取一个字节的数据)
int read(byte[] b) (带参:一次读取设置的参数个字节的数据)
从数据源读取数据
void close()
当完成读操作后,应该关闭输入流。
1.1.1 输入流的层次
基本的输入流类:
1.1.2 文件输入流FileputStream
eg:
public class Main1 {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("D://test.txt");
int data;
while ((data=in.read())!=-1){
System.out.println(data+" ");
}
in.close();
}
}
假定在test.txt文件中包含的字符内容为“abc1好”,并且假定文件所在的操作系统的默认字符编码为GBK,那么在文件中实际存放的是这5个字符的GBK编码,字符“a”、“b”、“c”和“1”的GBK编码各占1个字节,分别是97、98、99和49。“好”的GBK编码占2个字节,为186和195。文件输入流的read()方法每次读取一个字节,因此以上程序的打印结果为:
97 98 99 49 186 195
1.1.3 缓冲区
eg:
public class UseBuffer {
public static void main(String[] args) throws IOException {
final int SIZE = 1024; //缓冲区大小
FileInputStream in = new FileInputStream("D://test.txt");
FileOutputStream out = new FileOutputStream("D://out.txt");
byte[] buff = new byte[SIZE]; //创建字节数组缓冲区
int len = in.read(buff); //把test.txt文件中的数据读入到buff中
while (len!=-1){
out.write(buff,0,len);
len = in.read(buff);
}
in.close();
out.close();
}
}
1.1.4 过滤输入流FilterInputStream
FilterInputStream是一种用于扩展输入流功能的装饰器,它有好几个子类,分别用来扩展输入流的某一种功能
子类之一: DataInputStream类
DataInputStream实现了DataInput接口,用于读取基本类型数据,如int、float、long、double和boolean等。此外,DataInputStream的readUTF()方法还能读取采用UTF-8编码的字符串。DataInputStream类的所有读方法都都以“read”开头,比如:
readByte():从输入流中读取1个字节,把它转换为byte类型的数据。
readLong():从输入流中读取8个字节,把它转换为long类型的数据。
readFloat():从输入流中读取4个字节,把它转换为float类型的数据。
readUTF():从输入流中读取1到3个字节,把它转换为采用UTF-8编码的字符串。
eg:
public class FormatDataIO{
public static void main(String[] args)throws IOException {
FileOutputStream out1=new FileOutputStream("D:\\test.txt");
BufferedOutputStream out2=new BufferedOutputStream(out1); //装饰文件输出流
DataOutputStream out=new DataOutputStream(out2); //装饰带缓冲输出流
out.writeByte(-12);
out.writeLong(12);
out.writeChar('1');
out.writeUTF("好");
out.close();
InputStream in1=new FileInputStream("D:\\test.txt");
BufferedInputStream in2=new BufferedInputStream(in1); //装饰文件输入流
DataInputStream in=new DataInputStream(in2); //装饰缓冲输入流
System.out.print(in.readByte()+" ");
System.out.print(in.readLong()+" ");
System.out.print(in.readChar()+" ");
System.out.print(in.readUTF()+" ");
in.close();
}
}
程序的打印结果为:
-12 12 1好
1.2 输出流OutputStream
OutputStream类是所有输出流的父类,它是一个抽象类,不能被实例化。它提供了一系列和写数据有关的方法。
void write(int),(一次写一个字节)
void write(byte[] b)
向输出流写数据。
void close()
当完成写操作后,应该关闭输出流。
void flush()
OutputStream类本身的flush()方法不执行任何操作,它的一些带有缓冲区的子类(比如BufferedOutputStream和PrintStream类)覆盖了flush()方法。通过带缓冲区的输出流写数据时,数据先保存在缓冲区中,积累到一定程度才会真正写到输出流中。缓冲区通常用字节数组实现,实际上是指一块内存空间。flush()方法强制把缓冲区内的数据写到输出流中。
1.2.1 输出流的层次
1.2.2 文件输出流FileOutputStream
FileOutputStream向文件写数据,它有以下构造方法:
FileOutputStream(File file)
FileOutputStream(String name)
FileOutputStream(String name, boolean append)
在创建FileOutputStream实例时,如果相应的文件并不存在,会自动创建一个空的文件。如果参数file或name表示的文件路径尽管存在,但是代表一个文件目录,那么会抛出FileNotFoundException异常。
默认情况下,FileOutputStream向文件写数据时,将覆盖文件中原有的内容。
以上第三个构造方法提供了一个布尔类型的参数append,如果append参数为true,将在文件末尾添加数据。
FileOutputStream的用法
eg:
import java.io.*;
public class FileWriterSample{
public static void main(String args[]) {
try {
FileOutputStream fileOut = new FileOutputStream("C:\\out.txt");
fileOut.write("你好".getBytes()); //按照本地系统默认的字符编码方式来返回字节数组
fileOut.close();
} catch(FileNotFoundException e) { }
catch(IOException e) { }
}
}
2. Reader/Writer
InputStream和OutputStream类处理的是字节流,也就是说,数据流中的最小单元为一个字节,它包括8个二进制位。 (补:一个字节一个字节的流动)
在许多应用场合,Java程序需要读写文本文件。在文件文件中存放了采用特定字符编码的字符。为了便于读写采用各种字符编码的字符,java.io包中提供了Reader/Writer类,它们分别表示字符输入流和字符输出流。 (补:一个字符一个字符的流动)
Reader和Writer能够实现本地平台的字符编码和Java程序可以处理的Unicode编码的自动转换。
2.1 字符编码方式转换
默认情况下,如果构造了一个连接到流的Reader和Writer,那么转换规则会对本地平台默认的字符编码和Unicode进行转换。
在构造InputStreamReader类和OutputStreamWriter类的实例时,可以指定输出流或输入流的字符编码。
2.2 关于字符编码
中文操作系统一般采用的字符编码是GBK。
String类的构造方法String(byte[] bytes, String enc)可以按指定的字符编码构造字符串 (补:设置enc参数即为字符编码方式)
String类的方法byte[] getBytes(String enc) 获得按指定的字符编码的字节数组
eg:
package IO;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class EncodeTester {
public static void main(String[] args) throws IOException {
System.out.println("在内存中采用Unicode字符编码:");
char c = '好';
int lowBit = c & 0xFF; //获得二进制的低8位
int highBit = (c & 0xFF) >> 8; //获得二进制的高8位
System.out.println(highBit+" "+lowBit);
String s = "好";
System.out.println("在本地操作系统中采用Unicode字符编码:");
readBuff(s.getBytes());
System.out.println("采用GBK字符编码:");
readBuff(s.getBytes("GBK"));
System.out.println("采用UTF-8字符编码:");
readBuff(s.getBytes("UTF-8"));
}
private static void readBuff(byte[] buff) throws IOException{
ByteArrayInputStream in = new ByteArrayInputStream(buff);
int data;
while ((data=in.read())!=-1){
System.out.print(data+" ");
System.out.println();
in.close();
}
}
}
2.3 Reader/Writer层次结构
2.3.1 InputStreamReader
InputStreamReader类把InputStream类型转换为Reader类型。
InputStreamReader有以下构造方法,这些构造方法的第一个参数都指定一个输入流:
- InputStreamReader(InputStream in)
按照本地平台的字符编码读取输入流中的字符。 - InputStreamReader(InputStream in, String charsetName)
按照参数charsetName指定的字符编码读取输入流中的字符。
InputStreamReader类的用法:
假设test.txt文件采用了UTF-8字符编码,为了正确地从文件中读取字符,可以按以下方式构造InputStreamReader的实例:
InputStreamReader reader=new InputStreamReader(new FileInputStream("D:\\test.txt"), "UTF-8" );
char c=(char)reader.read(); //c还是按照本地平台编码方式
假定InputStreamReader的read()方法从输入流中读取的字符为“好”, read()方法实际上执行了以下步骤。
(1)从输入流中读取三个字节:229、165和189,这三个字节代表字符“好”的UTF-8编码。
(2)计算出字符“好”的Unicode编码为89和125。
(3)为字符“好”分配两个字节的内存空间,这两个字节的取值分别为89和125。
2.3.2 BufferedReader
Reader类的read()方法每次都从数据源读入一个字符,为了提高效率,可以采用BufferedReader来装饰其他Reader。
BufferedReader带有缓冲区,它可以先把一批数据读到缓冲区内,接下来的读操作都是从缓冲区内获取数据,避免每次都从数据源读取数据并进行字符编码转换,从而提高读操作的效率。
BufferedReader的readLine()方法可以一次读入一行字符,以字符串形式返回。BufferedReader类有两个构造方法:
BufferedReader(Reader in) :参数in指定被装饰的Reader类。
BufferedReader(Reader in, int sz) :参数in指定被装饰的Reader类,参数sz指定缓冲区的大小,以字符为单位。
2.3.3 OutputStreamWriter
OutputStreamWriter类把OutputStream类型转换为Writer类型
OutputStreamWriter有以下构造方法,这些构造方法的第一个参数都指定一个输出流:
OutputStreamWriter (OutputStream out)
按照本地平台的字符编码向输出流写入字符。
OutputStreamWriter (OutputStream out, String charsetName)
按照参数charsetName指定的字符编码向输出流写入字符。
假设test.txt文件采用了UTF-8字符编码,为了正确地向文件中写字符,可以按以下方式构造OutputStreamWriter的实例:
OutputStreamWriter writer=new OutputStreamWriter (new FileOutputStream("D:\\test.txt"), "UTF-8" );
char c='好';
writer.write(c);
对于每一个写到test.txt文件中的字符,OutputStreamWriter都会把它转换为UTF-8编码。在执行writer.write©方法时,OutputStreamWriter的write()方法执行以下步骤。
(1)变量c在内存中占两个字节,取值分别为89和125,它们代表字符“好”的Unicode编码。
(2)字符“好”的UTF-8编码为:229、165和189。
(3)向输出流写入三个字节:229、165和189。
2.3.4 BufferedWriter
BufferedWriter带有缓冲区,它可以先把一批数据写到缓冲区内,当缓冲区满的时候,再把缓冲区的数据写到字符输出流中。这可以避免每次都执行物理写操作从而提高I/O操作的效率。
BufferedWriter类有两个构造方法:
BufferedWriter(Writer out) :参数out指定被装饰的Writer类
BufferedWriter(Writer out, intsz) :参数out指定被装饰的Writer类,参数sz指定缓冲区的大小,以字符为单位。
2.3.5 PrintWriter
PrintWriter能输出格式化的数据,PrintWriter的写数据的方法都以print开头,比如:
print(int i):向输出流写入一个int类型的数据。
print(long l): 向输出流写入一个long类型的数据。
print(float f): 向输出流写入一个float类型的数据。
print(String s): 向输出流写入一个String类型的数据。
println(int i): 向输出流写入一个int类型的数据和换行符。
println(long l): 向输出流写入一个long类型的数据和换行符。
println(float f): 向输出流写入一个float类型的数据和换行符。
println(String s): 向输出流写入一个String类型的数据和换行符
println(“hello”);
等价于:
print(“hello”)
print(“\n”)
2.3.6 用PrintWriter来装饰BufferedWriter
BufferedReader有一个readLine()方法,而BufferedWriter没有相应的writerLine()方法。
如果要输出一行字符串,应该用PrintWriter来装饰BufferedWriter,PrintWriter的println(String s)方法可以输出一行字符串。
OutputStream out=new FileOutputStream(to);
OutputStreamWriter writer=new OutputStreamWriter(out,charsetTo);
BufferedWriter bw=new BufferedWriter(writer);
PrintWriter pw=new PrintWriter(bw,true);
对文件按字符进行读写的范例
import java.io.*;
public class FileUtil {
/** 从一个文件中逐行读取字符串,采用本地平台的字符编码 */
public void readFile(String fileName)throws IOException{
readFile(fileName,null);
}
/** 从一个文件中逐行读取字符串,参数charsetName指定文件的字符编码 */
public void readFile(String fileName, String charsetName)throws IOException{
InputStream in=new FileInputStream(fileName);
InputStreamReader reader;
if(charsetName==null)
reader=new InputStreamReader(in);
else
reader=new InputStreamReader(in,charsetName);
BufferedReader br=new BufferedReader(reader);
String data;
while((data=br.readLine())!=null) //逐行读取数据
System.out.println(data);
br.close();
}
/** 把一个文件中的字符内容拷贝到另一个文件中,并且进行了相关的字符编码转换 */
public void copyFile(String from, String charsetFrom,String to,String charsetTo)
throws IOException{
InputStream in=new FileInputStream(from);
InputStreamReader reader;
if(charsetFrom==null)
reader=new InputStreamReader(in);
else
reader=new InputStreamReader(in,charsetFrom);
BufferedReader br=new BufferedReader(reader);
OutputStream out=new FileOutputStream(to);
OutputStreamWriter writer=new OutputStreamWriter(out,charsetTo);
BufferedWriter bw=new BufferedWriter(writer);
PrintWriter pw=new PrintWriter(bw,true);
String data;
while((data=br.readLine())!=null)
pw.println(data); //向目标文件逐行写数据
br.close();
pw.close();
}
public static void main(String args[])throws IOException{
FileUtil util=new FileUtil ();
//按照本地平台的字符编码读取字符
util.readFile("D:\\test.txt");
//把test.txt文件中的字符内容拷贝到out.txt中,out.txt采用UTF-8编码
util.copyFile("D:\\test.txt",null,"D:\\out.txt","UTF-8");
//按照本地平台的字符编码读取字符,读到错误的数据
util.readFile("D:\\out.txt");
//按照UTF-8字符编码读取字符
util.readFile("D:\\out.txt","UTF-8");
}
}
3. RandomAccessFile类的用法
1)前面介绍都是读取文件按照顺序读写,即从头开始一个一个的读写,而RandomAccessFile类用来随机读取和写入文件(即可以读取/写入文件中任何一个地方)。
2)RandomAccessFile实现了DataInput和DataOutput 接口。
3)RandomAccessFile类提供了定位文件的方法
4)RandomAccessFile类不属于流(因为流是按照顺序的),它具有随机读写文件的功能,能够从文件的任意位置开始执行读写操作。RandomAccessFile类提供了用于定位文件位置的方法:
getFilePointer():返回当前读写指针所处的位置。
seek(long pos):设定读写指针的位置,与文件开头相隔pos个字节数。
skipBytes(int n):使读写指针从当前位置开始,跳过n个字节。
length():返回文件包含的字节数。
5)RandomAccessFile类实现了DataInput和DataOutput接口,因此能够读写格式化的数据。RandomAccessFile类具有以下构造方法:
RandomAccessFile(File file, String mode) :参数file指定被访问的文件。
RandomAccessFile(String name, String mode):参数name指定被访问的文件的路径。
以上构造方法的mode参数指定访问模式,可选值包括“r”和“rw”。“r”表示随机读模式。“rw”表示随机读写模式。如果程序仅仅读文件,那么选择“r”,如果程序需要同时读和写文件,那么选择“rw”。
eg:
import java.io.*;
public class RandomTester {
public static void main(String args[])throws IOException{
RandomAccessFile rf=new RandomAccessFile("D:\\test.dat","rw");
for(int i=0;i<10;i++)
rf.writeLong(i*1000);
rf.seek(5*8); //从文件开头开始,跳过第5个long数据,接下来写第6个long数据
rf.writeLong(1234);
rf.seek(0); //把读写指针定位到文件开头
for(int i=0;i<10;i++)
System.out.println("Value"+i+":"+rf.readLong());
rf.close();
}
4. File类的用法
File类提供了若干处理文件、目录和获取它们基本信息的方法。
File 类的构造方法有三个:
File(String pathname)
File(String parent, String child) //parent:根路径 child:子路径
File(File parent, String child)
File类的实用用法:
察看文件属性:canRead(),isFile(),lastModified()
创建或删除目录:mkdir(),delete()
列出目录下的所有文件:list()
判断文件是否存在:exists()
重新命名文件:renameTo()
范例:
import java.io.*;
impirt java.io.Date;
public class DirUtil{
public static void main(String args[])throws Exception{
File dir1=new File("D:\\dir1");
if(!dir1.exists())dir1.mkdir();
File dir2=new File(dir1,"dir2");
if(!dir2.exists())dir2.mkdirs();
File dir4=new File(dir1,"dir3\\dir4");
if(!dir4.exists())dir4.mkdirs();
File file=new File(dir2,"test.txt");
if(!file.exists())file.createNewFile();
listDir(dir1);
deleteDir(dir1);
}
/** 察看目录信息 */
public static void listDir(File dir){
File[] lists=dir.listFiles();
//打印当前目录下包含的所有子目录和文件的名字
String info="目录:"+dir.getName()+"(";
for(int i=0;i<lists.length;i++)
info+=lists[i].getName()+" ";
info+=")";
System.out.println(info);
//打印当前目录下包含的所有子目录和文件的详细信息
for(int i=0;i<lists.length;i++){
File f=lists[i];
if(f.isFile())
System.out.println("文件:"+f.getName()
+" canRead:"+f.canRead()
+" lastModified:"+new Date(f.lastModified()));
else //如果为目录,就递归调用listDir()方法
listDir(f);
}
}
/** 删除目录或文件,如果参数file代表目录,会删除当前目录以及目录下的所有内容*/
public static void deleteDir(File file){
if(file.isFile()){ //如果file代表文件,就删除该文件
file.delete();
return;
}
//如果file代表目录,先删除目录下的所有子目录和文件
File[] lists=file.listFiles();
for(int i=0;i<lists.length;i++)
deleteDir(lists[i]); //递归删除当前目录下的所有子目录和文件
file.delete(); //最后删除当前目录
}
}
5. java.nio.file包中类的用法
从JDK7开始,引入了java.nio.file包,封装了一些操纵文件和目录的细节,提供了一组功能强大的实用方法。最常用的类包括:
- Files类:提供了一组操纵文件和目录的静态方法,如移动文件的move()方法,拷贝文件的copy()方法,按照指定条件搜索目录树的find()方法等。此外,Files类的newDirectoryStream()方法会创建一个目录流,程序得到这个目录流之后,就能方便地遍历整棵目录树。Files类的walkFileTree()方法也可以遍历目录树,而且能在参数中指定遍历目录树中每个文件时的具体操作。
- Path接口:表示文件系统中的一个路径。这个路径可以表示一棵包含多层子目录和文件的目录树。
- Paths类:提供了创建Path对象的静态方法。它的get(String first, String… more)
返回一个Path对象,这Path对象所代表的路径以first参数作为根路径,以more可变参数作为子路径。例如调用Paths.get (“/root”,“dir1”,“dir2”) 方法,将返回一个Path对象,它表示的路径为“/root/dir1/dir2”。 - FileSystem类:表示文件系统。
- FileSystems类:提供了创建FileSystem对象的静态newFileSystem()方法。
复制、移动文件以及遍历、过滤目录树
import java.io.IOException;
import java.nio.file.*;
public class FilesTool {
public void copyFile(String fromDir,String toDir,String file)throws IOException {
Path pathFrom = Paths.get(fromDir,new String[]{file});
Path pathTo = Paths.get(toDir,new String[]{file});
//调用文件拷贝方法,如果目标文件已经存在就将其覆盖
Files.copy(pathFrom, pathTo, StandardCopyOption.REPLACE_EXISTING);
}
}
public void moveFile(String fromDir,String toDir,String file)throws IOException {
Path pathFrom = Paths.get(fromDir,new String[]{file});
Path pathTo = Paths.get(toDir,new String[]{file});
//文件的大小bytes
System.out.println(Files.size(pathFrom));
//调用文件移动方法,如果目标文件已经存在就将其覆盖
Files.move(pathFrom, pathTo, StandardCopyOption.REPLACE_EXISTING);
}
public void createAndShowDir(String dir)throws IOException{
Path path = Paths.get(dir);
//创建文件夹
if(Files.notExists(path)){
Files.createDirectories(path);
System.out.println("create dir");
}else{
System.out.println("dir exists");
}
//遍历文件夹下面的文件
DirectoryStream<Path> paths = Files.newDirectoryStream(path);
for(Path p : paths)
System.out.println(p.getFileName());
System.out.println("以下是以java、txt、bat结尾的文件");
//创建一个带有过滤器的目录流,过滤条件为文件名以java txt bat结尾
DirectoryStream<Path> pathsStream = Files.newDirectoryStream(path, "*.{java,txt,bat}");
for(Path p : pathsStream)
System.out.println(p.getFileName());
}
public static void main(String[] args) throws IOException{
FilesTool tool=new FilesTool();
//把D:目录下的hello.txt文件拷贝到C:目录下
tool.copyFile("D:\\","C:\\","hello.txt");
//把D:目录下的test.txt文件移动到C:目录下
tool.moveFile("D:\\","C:\\","test.txt");
//遍历循环C:\\dollapp目录下的内容
tool.createAndShowDir("C:\\dollapp");
}
}
6. 课堂小结
Java I/O类库具有两个对称性,它们分别是:
(1)输入-输出对称,例如:
InputStream和OutputStream对称。
FilterInputStream和FilterOutputStream对称。
DataInputStream和DataOutputStream对称。
Reader和Writer对称。
InputStreamReader和OutputStreamWriter对称。
BufferedReader和BufferedWriter对称。
(2)字节流和字符流对称,例如InputStream和Reader分别表示字节输入流和字符输入流,OutputStream和Writer分别表示字节输出流和字符输出流。
File类不是用于输入和输出,而是用于管理文件系统。File类的名字容易让人误以为它仅仅代表文件,而实际上File对象既可以表示文件系统中的一个文件,也可以表示一个目录。
当一个File对象被创建后,它所代表的文件或目录有可能在文件系统中存在,也有可能不存在,可以用File类的exists()方法来判断它是否存在。如果File对象代表文件,并且在文件系统中不存在,可以用File类的createNewFile()方法来创建该文件。如果File对象代表目录,并且在文件系统中不存在,可以用File类的mkdir()方法来创建该目录。
java.nio.file包中Files、Paths和FileSystem等类操纵文件系统的功能比File类更加强大,可以方便地实现对文件的复制、移动和遍历等。
7. 练习题
1.以下哪些属于File类的功能?
a) 改变当前目录
b) 返回根目录的名字
c) 删除文件
d) 读取文件中的数据
[答案] b,c
2.以下哪些是合法的构造RandomAccessFile对象的代码?
a) RandomAccessFile(new File(“D:\myex\dir1\…\test.java”), “rw”)
b) RandomAccessFile(“D:\myex\test.java”, “r”)
c) RandomAccessFile(“D:\myex\test.java”)
d) RandomAccessFile(“D:\myex\test.java”,“wr”)
[答案] a,b
3.以下哪段代码能够向文件中写入UTF-8编码的数据?
a)
public void write(String msg) throws IOException {
FileWriter fw = new FileWriter(new File(“file”));
fw.write(msg);
fw.close();
}
b)
public void write(String msg) throws IOException {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream(“file”), “UTF-8”);
osw.write(msg);
osw.close();
}
3.以下哪段代码能够向文件中写入UTF-8编码的数据?
c)
public void write(String msg) throws IOException {
FileWriter fw = new FileWriter(new File(“file”));
fw.setEncoding(“UTF-8”);
fw.write(msg);
fw.close();
}
d)
public void write(String msg) throws IOException {
FileWriter fw = new FileWriter(new File(“file”));
fw.write(msg, “UTF-8”);
fw.close();
}
[答案] b
4.java.nio.file包中的哪个类具有拷贝文件的copy()方法?
a) Path b) Paths c) Files d)FileSystem
[答案] c
5.以下哪些类的构造方法具有InputStream类型的参数?
a) BufferedInputStream b) DataInputStream
c) SequenceInputStream d) FileInputStream
[答案] a,b,c