因为工作事宜,又有一段时间没有写博客了,趁着今天不是很忙开始IO之路;IO往往是我们忽略但是却又非常重要的部分,在这个讲究人机交互体验的年代,IO问题渐渐成了核心问题。
一、File类
在讲解File类之前,我们先认识和了解一下流的概念;流的概念可能比较抽象,可以想象一下水流的样子。
1.流
IO在本质上是单个字节的移动,而流可以说是字节移动的载体和方式,它不停的向目标处移动数据,我们要做的就是根据流的方向从流中读取数据或者向流中写入数据。
想象下倒水的场景:倒一杯水,水是连成一片往地上流动,而不是等杯中的水全部倒出悬浮在空中,然后一起掉落地面。最简单的Java流的例子就是下载电影,肯定不是等电影全部下载在内存中再保存到磁盘上,本质上是下载一个字节就保存一个字节。
一个流,必有源和目标,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是Internet上的某个URL。流的方向是重要的,根据流的方向,流可分为两类:输入流和输出流。我们从输入流读取数据,向输出流写入数据。
2.流的分类
根据流向:输入流(input)和输出流(output)
根据处理数据:字节流(二进制,可以处理一切文件,文本,音频等) 字符流(文本文件,只能是纯文本)
根据功能:节点流包裹源头 处理流:增强功能,提供性能
3.文本文件 text-files
有些文件被当作字符序列,并拥有一些使二进制数字对程序和编辑器来说就像字符一样的流和方法,这样的文件就称之为文本文件。
4.二进制文件 binary-files
有些文件内容必须作为二进制数字序列处理的文件则称之为二进制文件
文本文件是为人类使用而设计的,而二进制文件是为程序读取计算机使用而设计的。
5.File 操作
在整个io包中,唯一表示与文件本身有关的类就是File类。使用File类可以进行创建或删除文件等常用操作,要想使用File类,则首先要观察File类的构造方法,此类的常用构造方法如下:
File类中的主要方法和常量:
方法或常量 |
类型 |
描述 |
public static final String pathSeparator |
常量 |
表示路径的分隔符(windows:‘;’) |
public static final String separator |
常量 |
表示路径分隔符(windows:‘\’) |
public File(String pathname) |
构造 |
创建File类对象,传入完整的路径 |
public boolean createNewFile() throws IOException |
普通 |
创建新文件 |
public boolean exists() |
普通 |
判断文件是否存在 |
public boolean delete() |
普通 |
删除文件 |
public boolean isDirectory() |
普通 |
判断给定的路径是否是一个目录 |
public long length() |
普通 |
返回文件的大小 |
public String[] list() |
普通 |
列出指定目录的全部内容,只是名称 |
public File[] listFiles() |
普通 |
列出指定目录的全部内容,会列出路径。 |
public boolean mkdir() |
普通 |
创建一个目录 |
public boolean renameTo(File dest) |
普通 |
为已有的文件重命名 |
下面我们将针对主要方法进行讲解和代码示例演练:
1)File 常量
package com.pony1223.file; import java.io.File; /**
* 两个常量
* 1、路径分隔符 ;
* 2、名称分隔符 /(windows) /(linux 等)
* @author Pony
*
*/
public class Demo01
{
public static void main(String[] args)
{
System.out.println(File.pathSeparator);
System.out.println(File.separator);
String path = "E:\\study\\java\\HelloWorld.java";
String path1 = "E:"+File.separator+"study"+File.separator+"java"+File.separator+"HelloWorld.java";
String path2 = "E:/study/java/HelloWorld.java";//推荐方式
} }
2)使用绝对路径和相对路径构造文件File类
package com.pony1223.file; import java.io.File; /**
* 两个常量
* 1、路径分隔符 ;
* 2、名称分隔符 /(windows) /(linux 等)
* @author Pony
*
*/
public class Demo02
{
public static void main(String[] args)
{
String parentPath = "E:/study/java";
String name = "HelloWorld.java";
//相对路径
File src =new File(parentPath,name);
src =new File(new File(parentPath),name);
//输出
System.out.println(src.getName());
System.out.println(src.getPath());
//绝对路径
src =new File("E:/study/java/HelloWorld.java");
System.out.println(src.getName());
System.out.println(src.getPath());
//没有盘符: 以 user.dir构建
src =new File("test.txt");//使用相对路径,注意如果在路径中没有盘符,文件则放在工程项目下
//src =new File(".");
System.out.println(src.getName());//test.txt
System.out.println(src.getPath());//test.txt
System.out.println(src.getAbsolutePath());//G:\DevelopeHome\MyEclipseWorkSpace\Collections\test.txt /**
* getPath:如果构建文件路径是绝对路径则返回完整路径,否则返回相对路径
* getAbsolutePath:返回绝对路径(完整路径)
* getCanonicalPath:不但是全路径,而且把..或者.这样的符号解析出来。
*/
}
}
3)判断文件(true/false)
//判断文件是否存在
System.out.println(f.exists());//true //判断文件是否可读,可写canWrite()
System.out.println(f.canRead());//true //判断文件路径是否为绝对路径,有盘符则为绝对路径
System.out.println(f.isAbsolute());//true //判断是文件isFile还是文件夹isDirectory
System.out.println(f.isDirectory());//false
4)返回文件长度,以字节为单位
假设文件内容为aswdwdad,长度为8个字节,注意如果是文件夹,字节数不为0,这里也返回0 即 只有文件才能读出长度,文件夹不可
System.out.println(f.length());//8
5)创建和删除文件
(1)创建文件createNewFile()
若文件已经存在,则创建会返回false,若文件名为操作系统关键字,比如con,也会返回false
(2)删除文件delete()
File f=new File("F:/Java/test.txt");
if(!f.exists()){
try {
boolean flag = f.createNewFile();
System.out.println(flag?"success":"fail");//fail
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} f.delete();
6)两个静态方法创建临时文件
File f1=File.createTempFile("wees", ".temp",new File("F:/JAVA"));
//F:\JAVA\wees7063380687067821023.temp
System.out.println(f1.getAbsolutePath()); File f2=File.createTempFile("wes", ".temp");
//C:\Users\wwyDEPP\AppData\Local\Temp\wes8047158297297613408.temp
System.out.println(f2.getAbsolutePath());
注意点;
当我们创建一个新的File对象,所给的路径不是根盘路径时,文件会自动放在项目文件夹下;但是使用静态方法创建一个临时文件,所给路径不是根盘路径时,文件是放在C盘下的某文件夹下的
7)操作目录
(1)mkdir()创建目录,确保父目录存在,如果不存在,创建失败
(2)mkdirs()创建目录,如果父目录链不存在,一同创建
(3)list()输出目录文件夹下所包括的文件名
(4)listFiles()输出目录文件夹下所包括的文件
(5)listFiles(filter)输出目录文件夹下所包括的特定文件(.txt)
String path="F:/Picture/test";
File file=new File(path);
file.mkdir(); System.out.println("输出目录文件夹下所包括的文件名");
String path2="F:/Picture";
File file2=new File(path2);
if(file2.isDirectory()){
String[] strs=file2.list();
for(String s:strs){
System.out.println(s);
}
} System.out.println("输出目录文件夹下所包括的文件");
if(file2.isDirectory()){
File[] files=file2.listFiles();
for(File f:files){
System.out.println(f.getAbsolutePath());
}
} System.out.println("输出目录文件夹下所包括的特定文件(.txt),命令设计模式");
if(file2.isDirectory()){
File[] files=file2.listFiles();
files=file2.listFiles(new FilenameFilter(){ @Override
public boolean accept(File dir, String name) { return new File(dir,name).isFile()&&name.endsWith(".txt");
} }); for(File f:files){
System.out.println(f.getAbsolutePath());
}
}
结果:
输出目录文件夹下所包括的文件名
EFI.png
NTFS.png
test
test.txt
输出目录文件夹下所包括的文件
F:\Picture\EFI.png
F:\Picture\NTFS.png
F:\Picture\test
F:\Picture\test.txt
输出目录文件夹下所包括的特定文件(.txt),命令设计模式
F:\Picture\test.txt
二、字节流与字符流
字节流包括输入流InputStream和输出流OutputStream。字符流包括输入流Reader 输出流Write
InputStream相关类图如下,只列举了一级子类:
InputStream提供了一些read方法供子类继承,用来读取字节。
OutputStream相关类图如下:
OutputStream提供了一些write方法供子类继承,用来写入字节。
Reader相关类图如下:
Reader提供了一些read方法供子类继承,用来读取字符。
Writer相关类图如下:
Writer提供了一些write方法供子类继承,用来写入字符。
每个字符流子类几乎都会有一个相对应的字节流子类,两者功能一样,差别只是在于操作的是字节还是字符。例如CharArrayReader和 ByteArrayInputStream,两者都是在内存中建立数组缓冲区作为输入流,不同的只是前者数组用来存放字符,每次从数组中读取一个字符;后者则是针对字节。主要的:
ByteArrayInputStream、CharArrayReader | 为多线程的通信提供缓冲区操作功能。常用于读取网络中的定长数据包 |
ByteArrayOutputStream、CharArrayWriter | 为多线程的通信提供缓冲区操作功能。常用于接收足够长度的数据后进行一次性写入 |
FileInputStream、FileReader | 把文件写入内存作为输入流,实现对文件的读取操作 |
FileOutputStream、FileWriter | 把内存中的数据作为输出流写入文件,实现对文件的写操作 |
StringReader | 读取String的内容作为输入流 |
StringWriter | 将数据写入一个String |
SequenceInputStream | 将多个输入流中的数据合并为一个数据流 |
PipedInputStream、PipedReader、PipedOutputStream、PipedWriter | 管道流,主要用于2个线程之间传递数据 |
ObjectInputStream | 读取对象数据作为输入流,对象中的 transient 和 static 类型的成员变量不会被读取或写入 |
ObjectOutputStream | 将数据写入对象 |
FilterInputStream、FilterOutputStream、FilterReader、FilterWriter | 过滤流通常源和目标是其他的输入输出流,大家可以看到有众多的子类,各有用途,就不一一介绍了 |
字符流和字节流的区别:
字节流就是按照byte单位来读取,可以用来读取其他格式的文件
字符流是在字节流的基础上实现的,用来读取文本文件,一个字符一个字符的读取
如果字节流是一滴水一滴水的转移,那么字符流是用勺子一勺一勺水的转移,速度明显加快了
当然使用缓冲Buffer以后,就是一桶一桶水的转移了
一个字节占8位,java采用unicode编码,占两个字节,即16位,也就是java一个字符是2byte,16位,
那么在文本copy的时候,用字节流就是一byte-byte的copy,字符流就是一个字符一个字符的copy
1)文件的读取和写出
要读取一个文件,有以下几个步骤:
1.建立与文件的联系:File对象,文件必须存在
2.选择流:按字节流读取,文件输入流 InputStream FileInputStream
3.操作:byte[] car=new byte[1024]+read
4.释放资源,注意jdk1.7后会自动关闭了
InputStream是一个抽象类,不能new一个新的对象,所以这里使用多态
InputStream is=new FileInputStream(......);
选择构造方法,第一个和第三个比较常用,本质上来说这两种方法是一致的,通过String name 创建一个新对象,
它内部还是会通过name包装成File
在读取文件时,使用循环不断进行读取,定义一个制定长度的byte数组,则这个数组就是每次读取文件的长度,
如果要输出,就创建一个String对象,String info = new String(car, 开始, 结束);
样例:
package com.pony1223.byteio; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; public class Demo01 {
public static void main(String[] args)
{
// 1、建立联系 File对象
File src = new File("E:/study/java/HelloWorld.java");
// 2、选择流
InputStream is = null; // 提升作用域
try
{
is = new FileInputStream(src);
// 3、操作 不断读取 缓冲数组
byte[] car = new byte[1024];
int len = 0; // 接收 实际读取大小
// 循环读取
StringBuilder sb = new StringBuilder();
while (-1 != (len = is.read(car)))
{
// 输出 字节数组转成字符串
String info = new String(car, 0, len);
sb.append(info);
}
System.out.println(sb.toString()); }
catch (FileNotFoundException e)
{
e.printStackTrace();
System.out.println("文件不存在");
} catch (IOException e)
{
e.printStackTrace();
System.out.println("读取文件失败");
}
finally
{
try
{
// 4、释放资源
if (null != is)
{
is.close();
}
}
catch (Exception e2)
{
System.out.println("关闭文件输入流失败");
}
}
} }
文件写出,有以下几个步骤:
1.建立与文件的联系:File对象,文件可不存在
2.选择流:按字节流写出,文件输出流 OutputStream FileOutputStream
3.操作:write+flush
4.释放资源,注意jdk1.7后会自动关闭了
FileOutputStream的构造方法
FileOutputStream(File file,boolean append) 如果选择true,则是追加,false则覆盖
构造方法中没有append参数的,则默认false,覆盖
package com.pony1223.byteio; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream; public class Demo02
{
public static void main(String[] args)
{
File dest=new File("E:/study/java/test.txt");
OutputStream os=null;
try {
os=new FileOutputStream(dest);
String str="hahahaha";
//字符串转字节数组
byte[] data=str.getBytes(); os.write(data,0,data.length); //强制刷新出去
os.flush(); }
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}finally
{ try {
if(null!=os){
os.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2)文件的拷贝
文件拷贝需要以下几个步骤:
1.建立联系:两个File对象,源头和目的地
2.选择流:
文件输入流:InputStream FileInputStream
文件输出流:OutputStream FileOutputStream
3.操作:拷贝
byte[] car=new byte[1024];+read
whrite+flush
4.释放资源:关闭两个流
File是可以创建文件和文件夹的,但是注意,文件我们是可以通过流去读取的,文件夹不可以;拷贝过程其实很简单,我们在文件读写操作时,都用到了byte数组和String字符串。
读取操作是用byte数组接收,转化为String字符串便于输出
假设我的数组长度为8,即8字节 byte[] buffer=new byte[8];
定义一个int变量,得到实际读取的字节数 int len=0;
用一个循环来不断的读取数据,当文件中数据都读完以后,is.read会返回(-1),那么只要没返回-1,就会一直不停的读取数据,每次读取的长度就是设置的byte数组的长度,也就是这里的8
while(-1!=(len=is.read(buffer))){
String info=new String(buffer,0,len);
}
也就是这个buffer数组,是会一直往里面装东西的,如果我的文件是18个字节,那么就需要用到3次这个数组,数组中的数据是会不断被覆盖的,当然定义一个大空间的byte数组可以避免这种问题,一般读取设置byte数组大小为1024
读取操作就完成了,可以输出到控制台便于查看
写出操作是将String转化为byte,写入文件
String str="qaz";
byte[] buffer=str.getBytes();
os.write(buffer,o,buffer.length);
这里的byte数组不设置大小
那么读写操作放在一起,其实代码更加精简
byte[] buffer=new byte[1024];
int len=0;
while(-1!=(len=is.read(buffer))){
os.write(buffer,0,buffer.length);
//os.write(buffer);也可行
}
样例:
public class Demo02 {
public static void main(String[] args) throws IOException { File src=new File("F:/Picture/1.jpg");
File dest=new File("F:/Picture/copy1.jpg"); InputStream is=null;
OutputStream os=null; byte[] buffer=new byte[1024];
int len=0; try {
is = new FileInputStream(src);
os = new FileOutputStream(dest); //从文件夹读取读取
while (-1 != (len=is.read(buffer))) {
//写出到文件夹
os.write(buffer, 0, len);
}
//强制刷出
os.flush(); }finally{
//先打开的后关闭
if(os!=null){
os.close();
} if(is!=null){
is.close();
}
}
}
}
3)文件夹的拷贝
文件夹用来把文件包裹起来,褪去这些外衣,说到底拷贝文件夹也就是拷贝文件
模拟实例:将F:/Picture/test 文件夹 拷贝到 F:/Picture/dir文件夹
该实例中test文件夹下只包含了test.txt文件
步骤分析:
1.通过路径得到File对象
2.递归查找子孙级文件夹或者文件
3.复制文件(同文件拷贝)
那么重点是在第二个步骤,我们可以通过File对象的listFiles方法得到目标文件夹下所包括的文件,listFiles方法返回一个泛型为File的集合list,由此我们就得到了test文件夹下所有的文件,通过foreach循环语句遍历这个list,得到的每一个File对象,首先要做的就是判断这个File对象是文件还是文件夹,如果是文件就可直接copy,如果是文件夹,则需要再通过listFiles方法得到目标文件夹下所包括的文件,步骤与上面一致,这也就是递归的思想
需要注意的一点是,我们需要把整个test文件夹拷贝到dir文件夹,那么当遍历到test文件夹下的test.txt文件时,我们在拷贝的时候,需要重新创建一个新的目标文件,dir/test/text.txt.,这就需要File的另一个构造方法
File(File parent, String child)
根据 parent 抽象路径名和 child 路径名字符串创建一个新 File
实例
在得到dir这个文件夹的时候,也应该用上述构造方法,得到dir/testFile新对象
在拷贝文件的时候,使用了不同的流,
之前拷贝文件使用的FileInputStream与FileOutputStream,
这里使用了BufferedInputStream与BufferedOutputStream,使用方法相似
InputStream is =new BufferedInputStream(new FileInputStream(src));
OutputStream os =new BufferedOutputStream(new FileOutputStream(dest));
样例代码:
public class Demo03 {
/**
* @param args
*/
public static void main(String[] args) {
// 源目录
String srcPath = "F:/Picture/test"; // 目标目录
String destPath = "F:/Picture/dir"; //进行拷贝
try {
copyDir(srcPath, destPath);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} /**
* 通过路径获得File对象
*
* @param src源路径
* @param dest目标路径
* @throws IOException
* @throws FileNotFoundException
*/
public static void copyDir(String srcPath,String destPath) throws FileNotFoundException, IOException{
//拒绝自己拷贝给自己
if(srcPath.equals(destPath)){
return ;
}
File src=new File(srcPath);
File dest =new File(destPath);
copyDir(src,dest);
} /**
* 拷贝文件夹
* @param src 源File对象
* @param dest 目标File对象
* @throws IOException
* @throws FileNotFoundException
*/
public static void copyDir(File src,File dest) throws FileNotFoundException, IOException{
if(src.isDirectory()){ //文件夹
dest =new File(dest,src.getName());
if(dest.getAbsolutePath().contains(src.getAbsolutePath())){
System.out.println("父目录不能拷贝到子目录中");
return;
}
}
copyDirDetail(src,dest);
} /**
* 拷贝文件夹细节
* @param src
* @param dest
*/
public static void copyDirDetail(File src,File dest) throws FileNotFoundException,IOException{
if(src.isFile()){ //文件
copyFile(src, dest);
}else if(src.isDirectory()){ //文件夹
//确保目标文件夹存在
dest.mkdirs();
//获取下一级目录|文件
for(File sub:src.listFiles()){
copyDirDetail(sub,new File(dest,sub.getName()));
}
}
} /**
* 文件的拷贝,得到File对象
* @param 源文件路径
* @param 目录文件路径
* @throws FileNotFoundException,IOException
* @return
*/
public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
//1、建立联系 源(存在且为文件) +目的地(文件可以不存在)
copyFile(new File(srcPath),new File(destPath));
}
/**
* 文件的拷贝
* @param 源文件File对象
* @param 目录文件File对象
* @throws FileNotFoundException,IOException
* @return
*/
public static void copyFile(File src,File dest) throws FileNotFoundException,IOException {
if(! src.isFile()){ //不是文件或者为null
System.out.println("只能拷贝文件");
throw new IOException("只能拷贝文件");
}
//dest为已经存在的文件夹,不能建立于文件夹同名的文件
if(dest.isDirectory()){
System.out.println(dest.getAbsolutePath()+"不能建立于文件夹同名的文件");
throw new IOException(dest.getAbsolutePath()+"不能建立于文件夹同名的文件");
} //2、选择流
InputStream is =new BufferedInputStream(new FileInputStream(src));
OutputStream os =new BufferedOutputStream(new FileOutputStream(dest)); //3、文件拷贝 循环+读取+写出
byte[] flush =new byte[1024];
int len =0; //读取
while(-1!=(len=is.read(flush))){
//写出
os.write(flush, 0, len);
}
os.flush(); //强制刷出 //关闭流
os.close();
is.close();
}
}
4)用字符流进行纯文本的读取和写出
纯文本的读取,步骤:
1.建立联系 file对象
2.选择流: Reader FileReader
3.读取:char[] flush=new char[1024];
4.关闭资源
思路和读取文件基本是一致的,下面比较一下:
字节流读取文件VS字符流读取纯文本
1.使用流不同,前者使用“stream”,后者是“reader”
2.读取使用数组不同,前者是byte数组,后者是char数组
3.速度不同,后者速度要比前者快
代码:
public class Demo05 {
public static void main(String[] args) {
/**
* 纯文本读取
*/ //1.建立联系
File src=new File("F:/Picture/test/test.txt"); //2.选择流
Reader reader=null;
try {
reader=new FileReader(src); //3.char数组读取
char[] flush=new char[1024];
int len=0; while(-1!=(len=reader.read(flush))){
String str=new String(flush,0,len);
System.out.println(str);
} } catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//4.关闭资源
if(reader!=null){
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
纯文本的写出,步骤:
1.建立联系 file对象
2.选择流:writer FileWriter
3.读取while(字符数组,0,长度)+flush
4.关闭资源
之前文件写出的时候,需要把String字符串转化成byte数组,才可以使用writer方法写出,
但是Writer提供了不同的writer方法,可以直接写出字符串,如下:
代码:
public class Demo05 {
public static void main(String[] args) { /**
* 纯文本写出
*/ //1.获取File对象
File dest=new File("F:/Picture/test/test2.txt"); //2.选择流
Writer writer=null; try {
//true代码追加文件,false代码覆盖,默认false覆盖
writer=new FileWriter(dest,true);
String str="我们都是好孩子!"; //3.写出,强制刷出
writer.write(str); //可追加
writer.append("hahaahaha"); writer.flush(); } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} }
}
字符流拷贝:
public class Demo05 {
public static void main(String[] args) { /**
* 纯文本的拷贝
*/ File src=new File("F:/Picture/test/test.txt");
File dest=new File("F:/Picture/test/test3.txt"); Reader re=null;
Writer wr=null; try {
re=new FileReader(src);
wr=new FileWriter(dest); char[] buffer=new char[1024];
int len=0;
while(-1!=(len=re.read(buffer))){
wr.write(buffer);
wr.flush();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally { try {
if (wr != null) { wr.close();
}
if (re != null) {
re.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
}
三、字节流和字符流转换
任何数据的持久化和网络传输都是以字节形式进行的,所以字节流和字符流之间必然存在转换问题。字符转字节是编码过程,字节转字符是解码过程。io包中提供了InputStreamReader和OutputStreamWriter用于字符和字节的转换。
来看一个小例子:
char[] charArr = new char[1];
StringBuffer sb = new StringBuffer();
FileReader fr = new FileReader("test.txt");
while(fr.read(charArr) != -1)
{
sb.append(charArr);
}
System.out.println("编码:" + fr.getEncoding());
System.out.println("文件内容:" + sb.toString());
FileReader类其实就是简单的包装一下FileInputStream,但是它继承InputStreamReader类,当调用read方法时其实调用的是StreamDecoder类的read方法,这个StreamDecoder正是完成字节到字符的解码的实现类。如下图:
InputStream 到 Reader 的过程要指定编码字符集,否则将采用操作系统默认字符集,很可能会出现乱码问题。上例代码输出如下:
编码:UTF8
文件内容:hello�����Dz����ļ�!
再来看一个例子,换一个字符集:
char[] charArr = new char[1];
StringBuffer sb = new StringBuffer();
//设置编码
InputStreamReader isr = new InputStreamReader(
new FileInputStream("D:/test.txt")
, "GBK");
while(isr.read(charArr) != -1)
{
sb.append(charArr);
}
System.out.println("编码:" + isr.getEncoding());
System.out.println("文件内容:" + sb.toString());
输出正常:
编码:GBK
文件内容:hello!我是测试文件!
编码过程也是类似的,就不再说了。