相对于内存来说,从文件或网络连接中读取数据到内存称为输入流(InputStream和Reader),从内存中读取数据往文件中写称为输出流(OutputStream和Writer)。其中InputSream和OutputStream针对字节流(8bit),Reader和Writer针对字符流(2个字节)。所以一般纯文本可以用字符流来处理,如果含有图片、视频等一些非文字的流数据文件就必须按字节流来处理。
一、java.io.InputStream
1.1、read()
/**
* 从数据中读取一个字节并返回该字节,流的结尾返回-1
*/
abstract int read();
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
int line = 0;
while((line = bis.read())!=-1){
System.out.println(line);
}
fis.close();
bis.close();
测试结果:97,文件中内容是a,97正好是a的ASCII编码对应的十进制数字。
1.2、read(byte[] b)
/**
* 读入一个字节数组,并返回实际读入的字节数,流的结尾返回-1,最多返回b.length-1个字节
*/
int read(byte[] b);
下面我们用实例来看一下:
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
byte[] bytes = new byte[3];
int line = 0;
StringBuilder result = new StringBuilder();
while((line = bis.read(bytes))!=-1){
String str = new String(bytes,"UTF-8");
System.out.println(line + ":" + str);
result.append(str);
}
System.out.println("最后得出的结果:" + result.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
读取结果:
3:abc
3:def
1:gef
最后得出的结果:abcdefgef
为什么我们源文件里面“abcdefg”,我们读出来的是“abcdefgef”,这是因为我们最后一次读出来的虽然只有一个字节,但是由于bytes数组中之前的值是“def”,当我们再读出来一个字节覆盖了原来的字节,其余两个字节还保持着历史数据,就会出现这种情况了。有两种解决方法
每读取一次清理一次缓冲区:
while((line = bis.read(bytes))!=-1){
String str = new String(bytes,"UTF-8");
System.out.println(line + ":" + str);
result.append(str);
bytes = new byte[3];
}
用available()方法:
byte[] bytes = new byte[bis.available()];
识别能读出来的所有字节,直接一次就读出来。所以我们在用以上方法读取文件的时候要注意这种情况。
1.3、read(byte[] b, int off, int len)
/**
* 读入一个字节数组,并返回实际读入的字节数,流结尾返回-1
* @param b 数据读入的数组
* @param off 第一个读入字节应该被放置的位置在b中的offset
* @param len 读入字节的最大长度
* 一般都用read(byte[] b)方法读取
*/
int read(byte[] b, int off, int len);
1.4、skip(long n)
/**
* 在输入流中跳过n个字节,返回实际跳过的字节数(如果到流的结尾,则可能小于n)
*/
long skip(long n)
该方法跳过的是流即将读取的n个字节,例如,上例中我们源文件为“abcdefg”,现在我们想跳过前面两个字符
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
byte[] bytes = new byte[3];
int line = 0;
StringBuilder result = new StringBuilder();
bis.skip(2);
while((line = bis.read(bytes))!=-1){
String str = new String(bytes,"UTF-8");
System.out.println(line + ":" + str);
result.append(str);
bytes = new byte[3];
}
System.out.println("最后得出的结果:" + result.toString());
3:cde
2:fg
最后得出的结果:cdefg
1.4、available()
/**
* 返回在不阻塞的情况下可获取到的字节数
*/
int available()
1.5、close()
/**
* 关闭这个输入流
*/
void close();
如果一个应用程序打开了过多的流而没有关闭,那么系统资源将被耗尽,所以我们一定不要忘记关闭。
1.6、mark(int readlimit)
/**
* 在输入流的当前位置打一个标记,如果从输入流中已经读入的字节多于readlimit个,则这个流允许忽略这
* 个标记
*/
void mark(int readlimit)
1.7、reset()
/**
* 返回到最后一个标记,随后对read的调用将重新读入这些字节,随后对read的调用将重新读入这些字节,如
* 果当前没有任何标记,则这个流不被重置。
*/
void reset();
1.8、markSupported()
/**
* 如果这个流支持打标记,则返回true
*/
boolean markSupported();
二、java.io.OutputStream
2.1、void write(int n)
/**
* 写出一个字节的数据
*/
abstract void write(int n)
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(6);
bos.close();
fos.close();
文件中没有任何内容,测试结果:
ACK
查ASCII表:
他会把输入值6根据ASCii表转换成相应的字符写入文件中。
因为此方法只能一次读取一个字节,因此只能读取char类型和int类型数字。
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write('c');
bos.close();
fos.close();
输出结果:c,且每次写入文件都是全量写入,覆盖之前的内容。
2.2、void write(byte[] b)
/**
* 写出一个字节数组b
*/
void write(byte[] b);
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "abcdef";
bos.write(str.getBytes());
bos.close();
fos.close();
测试结果:abcdef全部写入文件
2.3、void write(byte[] b,int off,int len)
/**
* 写出所有字节或者某个范围的字节到数组b中
* 参数:b 数据写出的数组
* off 第一个写出字节在b中的偏移量
* len 写出字节的最大数量
*/
void write(byte[] b, int off, int len)
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "abcdef";
bos.write(str.getBytes(),3,2);//本次写出的字节数组中,从第3个字节开始,写2个字节
bos.close();
fos.close();
测试结果:de
2.4、void close()
/**
* 冲刷并关闭输出流
*/
void close();
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "abcdef";
bos.write(str.getBytes());
//bos.close();
//fos.close();我们先不关闭输出流试一下
测试结果:文件中什么都没有。因为我们用了输出缓冲流,不关闭bos的话是不会将输出缓冲流里面的数据刷新到磁盘上的。如果我们不用缓冲流,直接fos.write(str.getBytes());这样是由文件输出流直接按字节写入到文件中并没有先缓冲到内存中,这样不关闭流是可以成功写入到文件中的。但是我们在实际应用中,如果文件很大,我们以字节为单写入文件的话IO频繁,性能很低,因为磁盘读取速度远远小于内存读取速度,所以最好用缓冲区来读取。所以千万别忘了关闭流。
2.4、void close()
/**
* 冲刷输出流,也就是将所有的缓冲数据发送到目的地
*/
void flush();
将缓冲区中的流数据刷新到磁盘,此时不执行close()方法数据也是可以被正确写入到磁盘的。
File file = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "abcdef";
bos.write(str.getBytes());
bos.flush();
//bos.close();
//fos.close();我们先不关闭输出流试一下
测试结果:abcdef,但还是要关闭,因为我们之前说过不关闭流是要耗费系统资源的。
最后,如果我们想把数据追加到文件中,而不是每次都覆盖,我们应该怎么实现呢?
FileOutputStream fos = new FileOutputStream(file,true);
参考文献:《java核心技术卷二》