前言
作为初学者,很多人都很容易被所谓的“流”搞晕,IO在应用有好几个方式,代码都差不多,今天我来详细区分讲解下
IO的 分类
我们来借用一位兄弟的图
常用的流区分
节点流
通俗的理解就是:单纯的用来传送数据,并不对数据进行额外处理如下:
父 类 InputStream OutputStream Reader Writer
文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流
处理流
通俗的理解就是:在节点流外附加一层处理,增加效率如:
BufferedImputStrean BufferedOutputStream BufferedReader BufferedWrite等
IO常用的类对应的方法
字节流常用方法
InputStream
(1)从流中读取数据
int read() 读取一个字节,返回值为所读得字节
int read(byte b[]) 读取多个字节,放置到字节数组b中,通常读取的字节数量为b的长度,返回值为实际独取的
字节的数量。
int read(byte b[] ,int off,int len)读取len个字节,放置到以下标off开始字节数组b中,返回值为实际
读取的字节的数量。
int available() 返回值为流中尚未读取的字节的数量。
long skip(long n);读指针跳过n个字节不读,返回值为实际跳过的字节数量
(2)关闭流
close() 流操作完毕后必须关闭
(3)使用输入流中的标记
void mark(int readlimit)纪录当前指针的所在位置.readlimit表示读指针读出的readlimit个字节后
所标记的指针位置才实效。
void reset() 把读指针重新指向用mark方法所记录的位置
boolean markSupported() 当前的流是否支持读指针的记录功能。
OutputStream
(1)输出数据
void write(int b)往流中写一个字节b
void write(byte b[])往流中写一个字节数组b
void write(byte b[],int off,int len)把字节数组b中从下标off开始,长度为len的字节写入流中
(2)
flush()刷空输出流,并输出所有被缓存的字节
由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
(3)关闭流
close()流操作完毕后必须关闭。
相对应的FileInputStream /FileOutputStream
FileInputStream 和 FileOutputStream分别是InputStream 和 OutputStream的子类,往往使用FileInputStream 和 FileOutputStream这两个子类去就可以从一个指定文件中读取或者向某一个文件中写入数据。
** public FileInputStream openFileInput (String name)** :打开输入流文件名
** public FileOutputStream openFileOutput (String name, int mode)** :打开输出流文件名
mode参数主要有:
MODE_PRIVATE :指定改数据只能被本程序读写
MODE_APPEND :以追加方式打开,可向该文件夹中追加内容
MODE_WORLD_READABLE:可以被其他程序读,但不能写
MODE_WORLD_WRITEABLE:可被其他读写。
字符流常用方法
Reader常用方法:
int read()从输入流中读取单个字符,返回读取的字符
int read(byte[] c) 从输入流中读取c.length长度的字符,保存到字符数组c中,返回实际读取的字符数
read(char[] c,int off,int len) 从输入流中读取最多len的长度字符,保存到字符数组c中,保存的位置从off位置开始,返回实际读取的字符长度
void close() 关闭流
Writer常用方法:
write(String str)将str字符串里的字符输出到指定输出流中
write(String str,int off,int len) 将str字符串里从off位置开始长度为len的字符输出到输出流中
void close() 关闭输出流
void flush() 刷新输出流
相对应的FileReader/FileWriter
FileReader fr = new FileReader(String fileName);//使用带有指定文件的String参数的构造方法。
FileWriter fw = new FileWriter(String fileName);//创建字符输出流类对象和已存在的文件相关联。文件不存在的话,并创建。
FileWriter fw = new FileWriter(String fileName,boolean append);//创建字符输出流类对象和已存在的文件相关联,并设置该该流对文件的操作是否为续写。
需要在AndroidMainfset.xml文件中进行权限的配置:
1.SDCard中创建与删除文件权限:
2.SDCard写入数据权限
例子
可以根据例子中代码来区分,顺序是:
writeFileOutputStream()方法写入文件,readFileInputStream()读取文件。
FileReader()方法写入文件, FileWriter()读取文件。
//保存的时候是在文件内容中连续写入,也就是在之前保存的数据基础上再次写入。
private void writeFileOutputStream() {
try {
FileOutputStream fos = openFileOutput("file.txt", Context.MODE_PRIVATE);
String inputFileContext = write_edt.getText().toString();
fos.write(inputFileContext.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取文件是把整个文件的内容进行读取。如果要加入解析,则保存的时候保存为特殊格式。
private void readFileInputStream() {
try {
FileInputStream fis = openFileInput("file.txt");
//注意这个方法,可以换成 byte[] buffer = new byte[1024];
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
read_edt.setText(new String(buffer));
fis.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void FileReader() {
String content = "";//读取到的内容
String temp = "";
FileReader fr = null;
int ch = 0;
File extDir = Environment.getExternalStorageDirectory();
String filename = "downloaded.txt";
File fullFilename = new File(extDir, filename);
try {
fr = new FileReader(fullFilename);
//只能读取一段分行就读取不到了
// while ((ch = fr.read()) != -1) {
// temp += (char) ch;
// }
BufferedReader br = new BufferedReader(fr);
//一行一行读取
while ((content = br.readLine()) != null) {
temp += content + "\r\n";
}
read_edt.setText(temp);
fr.close();
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void FileWriter() {
//2,以字符的形式直接写入文件
File extDir = Environment.getExternalStorageDirectory();
String filename = "downloaded.txt";
File fullFilename = new File(extDir, filename);
try {
FileWriter fw = new FileWriter(fullFilename, false);//第二个参数:是否以追加的模式将字符写入
// fw.write(write_edt.getText().toString());//直接将字符写入文件
BufferedWriter bw = new BufferedWriter(fw);//又包裹一层缓冲流 增强IO功能
bw.write(write_edt.getText().toString());
bw.flush();//将内容一次性写入文件 ,否则会出现短缺字符
bw.close();
fw.close();//BufferedWriter,再关闭FileWriter
} catch (IOException e) {
e.printStackTrace();
}
}
遇到需要注意的问题
第一个问题:
//注意这个方法,可以换成 byte[] buffer = new byte[1024];
byte[] buffer = new byte[fis.available()];
要一次读取多个字节时,经常用到InputStream.available()方法,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
如果这样写代码:
int count = in.available();
byte[] b = new byte[count];
in.read(b);
在进行网络操作时往往出错,因为你调用available()方法时,对发发送的数据可能还没有到达,你得到的count是0。
需要改成这样:
int count = 0;
while (count == 0) {
count = in.available();
}
byte[] b = new byte[count];
in.read(b);
第二个问题:
我们可以看一下以下代码,随便选一组:openFileOutput构造方法和FileReader构造方法
FileOutputStream fos = openFileOutput("file.txt", Context.MODE_PRIVATE);
File extDir = Environment.getExternalStorageDirectory();
String filename = "downloaded.txt";
File Filename = new File(extDir, filename);
fr = new FileReader(Filename);
构造方法里面的参数FileOutputStream()可以随意定一个"file.txt"放进去即可,不用指定路径(应该是系统默认了,读写/data/data/<应用程序名>目录下的文件),而FileReader()却是放入的要么是有指定路径的File,要么是路径名,若是随意放一个"file.txt"会报无法找到路径的错误。
第三个问题:
FileWriter fw = new FileWriter(fullFilename, false);//第二个参数:是否以追加的模式将字符写入
// fw.write(write_edt.getText().toString());//直接将字符写入文件
BufferedWriter bw = new BufferedWriter(fw);//又包裹一层缓冲流 增强IO功能
bw.write(write_edt.getText().toString());
bw.flush();//将内容一次性写入文件 ,否则会出现短缺字符
bw.close();
fw.close();//BufferedWriter,再关闭FileWriter
一定要先关闭BufferedWriter缓存再关闭FileWriter,否则会报错。顺带提一下BufferedWriter这里就是处理流,简单的加入这段代码既可以增加效率。
相应的文件目录写