(1)概念
文件在程序中是以流
的形式来操作的。
流:是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两个存储位置之间的传输称为流。流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
另外,在下文中演示IO的时候,会用到文件路径的获取。我特地整理了一篇博客Android之获取内、外置存储器路径
(2)分类
- 按流向分类:输入流和输出流
输入流:用于读文件,并将读到的数据暂存到内存。
输出流:用于写文件,将内存中的数据存入到文件。
- 按传输单位分类:字节流和字符流
字节流:可以用于读写二进制文件及任何类型文件。
字符流:可以用于读写文本文件。
(3)字节流和字符流的区别?
- 读写单位不同
字节流以字节(1 byte,1byte=8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同
字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
- 缓存
字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件。
总结:优先选用字节流。因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。
(4)超类
字节流:
InputStream(读入流) OutputStream(写出流)
字符流:
Reader(读入流) Writer (写出流)
它们都是抽象类。
(5)文件流
为了操作文件,所以出现了文件流,用文件流来对文件进行读写操作。
字节流
:
-----文件输入流:FileInputStream:
使用read方法读取文件
-----文件输出流:FileOutputStream :
使用write方法将二进制写到文件
字符流
:
-----文件输入流:FileReader:
使用read方法读取文件
-----文件输出流:FileWriter:
使用write方法将内容写到文件
例子一:
使用FileOutputStream
将字符串写入文件,在使用 FileInputStream
将文件的内容读出来。
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fos = null;
FileInputStream fis = null;
String text = "abcdefg";
try {
fos = new FileOutputStream(filePath);
fos.write(text.getBytes());//必须将字符串转成字节数组才能写入输出流
fos.flush();//写完之后刷新一下才能真正将字符串写入文件
fis = new FileInputStream(filePath);
byte[] cache = new byte[text.getBytes().length];
StringBuilder sb = new StringBuilder();
fis.read(cache);//由于字符串确定长度,那么cache的长度也确定了,所以这里只要读一次就能全部读完
sb.append(new String(cache));
Log.d("aaa", sb.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子二:
使用FileInputStream
和FileOutputStream
实现文件的拷贝。
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String copy_filePath = "";
if (isExternalStorageExist && isRootDirExist) {
copy_filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
copy_filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath);
fos = new FileOutputStream(copy_filePath);
byte[] cache = new byte[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字节的临时缓存
int index = 0;
while ((index = fis.read(cache)) != -1){//如果读完就会返回-1
fos.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
fos.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子三:
使用FileReader
和FileWriter
实现文件的拷贝,和上面字节流写法类似。
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String copy_filePath = "";
if (isExternalStorageExist && isRootDirExist) {
copy_filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
copy_filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
FileWriter fw = null;
FileReader fr = null;
try {
fr = new FileReader(filePath);
fw = new FileWriter(copy_filePath);
char[] cache = new char[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字符数的临时缓存
int index = 0;
while ((index = fr.read(cache)) != -1){//如果读完就会返回-1
fw.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
fw.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(6)缓冲流
缓冲流是对文件流的操作的功能的加强,提高了数据的读写效率。既然缓冲流是对流的功能和读写效率的加强和提高,所以在创建缓冲流的对象时应该要传入要加强的流对象。
BufferedXxx是对其他流的封装,符合装饰设计模式
。
字节缓冲流:
-----缓冲输入流:BufferedInputStream:
使用read读取文件
-----缓冲输出流:BufferedOutputStream :
使用write将二进制写入文件
字符缓冲流:
-----缓冲输入流:BufferedReader:
使用read或者readLine读取文件,使用readLine可能会出现中文乱码的问题。
-----缓冲输出流:BufferedWriter:
使用write将内容写入文件
例子一:
使用BufferedInputStream
和BufferedInputStream
实现文件的拷贝,和FileInputStream
+FileOutputStream
实现文件拷贝的代码基本一致。使用装饰设计模式,在FileInputStream
的基础上再次封装,变成了BufferedInputStream
,在FileOutputStream
的基础上再次封装,变成了BufferedInputStream
。
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String filePath_copy = "";
if (isExternalStorageExist && isRootDirExist) {
filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(filePath));
bos = new BufferedOutputStream(new FileOutputStream(filePath_copy));
byte[] cache = new byte[1024];//由于读取的文件长度不确定,所以就暂定一个大小为1024字节数的临时缓存
int index = 0;
while ((index = bis.read(cache)) != -1){//如果读完就会返回-1
bos.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
bos.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子二:
使用BufferedReader
和BufferedWriter
实现文件拷贝
BufferedReader
新增了一个readLine方法,请谨慎使用,因为容易导致编码问题,比如“中文乱码”。
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String filePath_copy = "";
if (isExternalStorageExist && isRootDirExist) {
filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(filePath));
bw = new BufferedWriter(new FileWriter(filePath_copy));
String temptext;
while ((temptext = br.readLine()) != null){//如果读完就会返回-1
bw.write(temptext);//将缓存cache的[0, index]范围的内容写入文件
bw.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上需要注意的是:
br = new BufferedReader(new FileReader(filePath));
bw = new BufferedWriter(new FileWriter(filePath_copy));
以上两句话的默认编码是GBK或者gb2312,如果我们读取一个“UTF-8”格式的文件时,就会出现乱码。
有关中文乱码
的问题,我有必要重点提一下:
- IO操作的默认编码是UTF-8
以下两句代码的默认编码就是UTF-8
//默认编码是UTF-8
br = new BufferedReader(new FileReader(filePath));
//默认编码是UTF-8
bw = new BufferedWriter(new FileWriter(filePath_copy));
相当于
//可以指定一个编码
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
//可以指定一个编码
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"));
- 当读取文件时,如果文件的编码是GBK,我们读取的时候必须指定一个编码,这个时候我们必须使用
转换流
来指定编码格式。(有关转换流
的只是下面会讲)
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "GBK"));
- 当我们将数据写到文件时,默认编码依然是UTF-8,如果需要指定编码格式,就使用下面的代码
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath_copy), "GBK"));
- 假如我们指定“UTF-8”编码将数据写入文件,此时,当前文件的编码就是“UTF-8”,当我们使用三方工具打开文件时,如果出现乱码,那么表明该工具的默认打开的编码不是“UTF-8”。
(7)转换流
顾名思义,转换流就是讲字节流转成字符流,从构造方法就可以看出来了
InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter(OutputStream out, String charsetName)
完整的代码如下:
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String filePath_copy = "";
if (isExternalStorageExist && isRootDirExist) {
filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
isr = new InputStreamReader(new FileInputStream(filePath), "UTF-8");
osw = new OutputStreamWriter(new FileOutputStream(filePath_copy), "GBK");
char[] cache = new char[1024];
int index;
while ((index = isr.read(cache, 0, cache.length)) != -1){//如果读完就会返回-1
osw.write(cache, 0, index);//将缓存cache的[0, index]范围的内容写入文件
osw.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(8)对象流
主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了。
对象的输出流:
ObjectOutputStream
对象的输入流:
ObjectInputStream
对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。
代码如下:
public class UserInfo implements Serializable{//必须实现Serializable接口
private String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//是否存在外置存储器
boolean isExternalStorageExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
String filePath = "";
if (isExternalStorageExist && isRootDirExist) {
filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext.txt";
} else {
filePath = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext.txt";
}
File file = new File(filePath);
if(!file.exists()){
//如果文件不存在,则创建一个文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
String filePath_copy = "";
if (isExternalStorageExist && isRootDirExist) {
filePath_copy = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "filetext_copy.txt";
} else {
filePath_copy = FileDirUtil.getInstance().getFilesDir() + File.separator + "filetext_copy.txt";
}
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(filePath));
UserInfo userInfo1 = new UserInfo("张三", 14);
UserInfo userInfo2 = new UserInfo("李四", 15);
oos.writeObject(userInfo1);
oos.writeObject(userInfo2);
oos.flush();
ois = new ObjectInputStream(new FileInputStream(filePath));
oos = new ObjectOutputStream(new FileOutputStream(filePath_copy));
Object object;
while ((object = ois.readObject()) != null){//如果获取的对象不为空
if(object instanceof UserInfo){
UserInfo userInfo = (UserInfo) object;
Log.d("aaa", "name:"+userInfo.getName()+"===age:"+userInfo.getAge());
oos.writeObject(userInfo);
}
oos.flush();//写完之后刷新一下才能真正将字符串写入文件
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上代码体现了对象的序列化和反序列化的过程。主要注意的是,对象必须实现Serializable接口。
(9)ByteArrayInputStream
包含一个内部缓冲区,其中包含可以从流中读取的字节。 内部计数器跟踪由read方法提供的下一个字节。关闭一个ByteArrayInputStream没有任何效果。 该流中的方法可以在流关闭后调用,而不生成IOException 。意思就是说,比如文件流的处理对象的外存中的文件,而它的处理对象是内存中的缓冲区。它是从一个内存读到另一个内存方式。
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
try {
String text = "我是中国人!123!abc!";
//第一个数组
byte[] textBytes = text.getBytes();
bis = new ByteArrayInputStream(textBytes);
bos = new ByteArrayOutputStream();
//第二个数组
byte[] tempText = new byte[1024];
int index;
while ((index = bis.read(tempText)) != -1){
bos.write(textBytes, 0 , index);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
(10)数据流
DataInputStream和DataOutputStream
(11)打印流
PrintStream和PrintWriter
(12)随机访问文件流
RandomAccessFile
(13)结构图
结构图文件已保存到百度网盘:
链接:https://pan.baidu.com/s/16_BpUSdwfalGR7zezU2-uw
提取码:bu70
说明:
这篇写到最后也懒得多写了,因为我自己都看的比较乱,等后期有时间了我会重新整理下。