https://www.jianshu.com/p/e5bc7ea5f948
最近帮学姐写爬虫的时候遇到奇怪的问题,同样的程序在Mac上可以正常运行而在Windows上返回结果错误,最后经排查发现是Linux与Windows的默认编码方式不同,而自己的程序没有设置编码方式自动采用了默认的编码方式,所以导致错误发生。之后尝试了多种编码方式均告失败,最后发现是由于自己对输入输出流的认识不到位,没有正确使用的原因,故进行整理学习。
首先认识一下字节流与字符流。程序中的输入输出都是通过流的形式保存的,流中保存的全是字节文件。根据处理数据类型不同可以分为字节流和字符流。字节流是字符流的基础。
字节流:字节流处理单元为一个字节,操作字节和字节数组。如果是音频、图片等建议用字节流。
字符流:字符流处理单元为两个字节的UNICODE字符,操作字符家、字符数组和字符串,对多国语言支持性较好,如果是文本建议用字符流。
基于字节流的Stream:通常以OutputStream和InputStream结尾,DataOutputStream、DataInputStream、FileOutputStream……
**基于字符流的Stream:通常以Writer和Reader结尾,PrintWriter、FileWriter、FileReader、StringWriter……
可以发现绝大部份流都是成对出现的,包括输入流和输出流。可以这样理解输入输出流
输入流(InputStream和Reader)可以看作一个出水的水龙头,具有流出水流的功能,即向程序产生数据的功能,read便相当于打开开关,之后便会流出水流(数据)。
输出流(OutputStream和Writer)可以看作一个进水的水龙头,具有储存水流的功能,即接收程序产生的数据,write后也相当于打开开关,水流(数据)流进进水水龙头。
介绍完了基本概念,现在来看一下基本用法。
InputStream | |
---|---|
从流中读取数据 | |
public abstract int read() throws IOException | 从输入流中读取下一字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回-1。 |
public int read(byte[] b) throws IOException | 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。以整数形式返回实际读取的字节数。等同于read(byte[],int,int) |
public int read(byte[] b, int off, int len) throws IOException | 将输入流中最多len个数据字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值。将读取的第一个字节存储在元素b[off]到b[off+k-1]的元素中,以此类推。 |
public long skip(long n) throws IOException | 跳过和丢弃此输入流中数据的 n 个字节。出于各种原因,skip 方法结束时跳过的字节数可能小于该数,也可能为 0。导致这种情况的原因很多,跳过 n 个字节之前已到达文件末尾只是其中一种可能。返回跳过的实际字节数。如果 n 为负,则不跳过任何字节。此类的 skip 方法创建一个 byte 数组,然后重复将字节读入其中,直到读够 n 个字节或已到达流末尾为止。建议子类提供此方法更为有效的实现。例如,可依赖搜索能力的实现。 |
public int available() throws IOException | 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数(流中尚未被读取的字节数)。下一个调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。注意,有些 InputStream 的实现将返回流中的字节总数,但也有很多实现不会这样做。试图使用此方法的返回值分配缓冲区,以保存此流所有数据的做法是不正确的。如果已经调用 close() 方法关闭了此输入流,那么此方法的子类实现可以选择抛出 IOException。类 InputStream 的 available 方法总是返回 0。此方法应该由子类重写。 |
关闭流 | |
public void close() throws IOException | 关闭输入流并释放与该流关联的所有系统资源 |
使用输入流中的标记 | |
public void mark(int readlimit) | 在此输入流中标记当前位置。对 reset 方法的后续调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。readlimit 参数表示读取readmit个字节数后标记失效。 |
public void reset() throws IOException | 将读指针重新指向mark方法记录的位置。 |
public boolean markSupported() | 测试此输入流是否支持mark()和reset()方法。 |
OutputStream | |
---|---|
输出数据 | |
public abstract void write(int b) throws IOException | 将指定的字节写入输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 |
public void write(byte[] b) throws IOException | 将b.length个字节从指定的byte数组写入此输出流。与write(b, 0, b.length)等同 |
public void write(byte[] b, int off, int len) throws IOException | 将指定数组中从偏移量off开始的len个字节写入此输出流。 |
刷新流 | |
public void flush() throws IOException | 刷新此输出流并强制写出所有缓冲的字节。如果此流的预期目标是由基础操作系统提供的一个抽象(如一个文件),则刷新此流只能保证将以前写入到流的字节传递给操作系统进行写入,但不保证能将这些字节实际写入到物理设备(如磁盘驱动器)。 |
关闭流 | |
public void close() throws IOException | 关闭此输出流并释放与此流有关的所有系统资源 |
通过输入输出流复制图片的例子:
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
InputStream is = new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg"));
OutputStream os = new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg"));
int i = 0;
while(i != -1){
i = is.read();
os.write(i);
}
is.close();
os.close();
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
//输出结果为:程序运行时间40231ms
通过缓冲流提高复制速度
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg")));
BufferedOutputStream bos = new BufferedOutputStream(new
FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg")));
int i = 0;
while(i != -1){
i = bis.read();
bos.write(i);
}
bos.flush();
bis.close();
bos.close();
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
//输出结果为:程序运行时间486ms
文件较大时,做一个缓冲处理
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
byte[] tmp = new byte[1024];
InputStream is = new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg"));
OutputStream os = new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg"));
int i = 0;
while(i != -1){
i = is.read(tmp);
os.write(tmp);
}
is.close();
os.close();
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
//输出结果为:程序运行时间61ms
双缓冲
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
byte[] tmp = new byte[1024];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg")));
int i = 0;
while(i != -1){
i = bis.read(tmp);
bos.write(tmp);
}
bos.flush();
bis.close();
bos.close();
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
//输出结果为:程序运行时间29ms
可以看到第一种情况效率最低,所以若非特殊要求可以放弃这种方法。