其实平时的项目里面接触过不少java的io操作,但那毕竟是前人封装好的工具类,真要自己写起来,还真是云里雾里的。网上很多优秀的博客对于这个部分的内容都有很好的描述,在此,本文只记录个人的学习小结。PS:动手很重要!!!!!
数据流
所谓的数据流,在计算机的底层不过就是一串只有0和1的二进制数据。如果用1表示男性,0代表女性,那么这些数据流其实像极了春运火车站检票口排队进站的队伍。如:
←←←
010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001...
字节流
字节流是以字节为单位的数据流,相当于把排队的人,以八个为一组来分组。如:
← ← ←
01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100 00100001 ...
字符流
每个字符根据不同的字符编码,占用的字节位数不一样,如在ASCII码表中,每个字符占用的是1个字节,01000001十进制值为65对应字母A,如此,上面的字节流根据ASCII码进行转换,则为
← ← ←
H e l l o W o r l d !...
输入流
字节输入流
InputSrteam是所有字节输入流的超类,包含三种read方法,均是返回int类型,默认值-1。
方法 | 返回值 | 备注 |
---|---|---|
read() | int | 从输入流中读取数据的下一个字节。返回该字节十进制值。 |
read(byte[] b) | int | 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 返回读取的字节数组的长度。 |
read(byte[] b, int off, int len) | int | 将输入流中最多 len 个数据字节读入 byte 数组。 返回读取的字节数组的长度。 len不能大于byte[]的长度,不然会报错。 |
InputStream 的子类有AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream。常见于项目中的有ByteArrayInputStream,FileInputStream以及FilterInputStream中的子类BufferedInputStream。如:
从字符串中获取字节流:InputStream inputStream = new ByteArrayInputStream(“String”.getBytes());
从文件中获取字符流:InputStream inputStream = new FileInputStream(new File(path));
从网络中获取字节流:InputStream inputStream = ((HttpURLConnection)new URL(urlStr).openConnection()).getInputStream();
BufferedInputStream继承于FilterInputStream,对InputStream进行一层封装(装饰),InputStream作为其内部一个变量,并提供一段缓冲区而具备内存缓存功能,提高了读取的效率。
字符输入流
对于字符流输入流的处理,JDK提供了Reader这个类。它有四个read方法,其中的三个其形参及返回值与InputStream中的read方法一致。第四个为 int read(CharBuffer target) 试图将字符读入指定的字符缓冲区。这个用的也比较少,忽略。
Reader的子类有BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader。常见的有InputStreamReader及其子类FileReader,BufferedReader(其他几个貌似看起来挺容易的,目前没接触过,先放一边)。
InputStreamReader是字节流通向字符流的桥梁,它封装了InputStream,并可根据指定字符编码将字节流转换成字符流。而它的子类FileReader更是进一步封装了从文本到字符流转换的这个过程,即FileReader(FilePath)等价于InputStreamReader(new FileInpustStream(new File(FilePath)))。
BufferedReader的作用跟BufferedInputSteam的作用类似,封装(装饰)了Reader,提高了字符流的读取效率。不同的是,BufferedReader中提供了一个readLine()方法,对于文本的逐行读取提供了很大的便利。
输出流
字节输出流
字节输出流的超类是OutputStream,对应InputStream中read方法的是write,无返回值。
方法 | 返回值 | 备注 |
---|---|---|
write(int b) | void | 将指定的字节写入此输出流。 |
write(byte[] b) | void | 将 b.length 个字节从指定的 byte 数组写入此输出流。 |
write(byte[] b, int off, int len) | void | 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
OutputStream的子类有ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream。常见的有ByteArrayOutputStream,FileOutputStream及FilterOutputStream中的子类BufferedOutputStream。
ByteArrayOutputStream 常用于收集网络中的数据,然后一次性把数据发送出去。
FileOutputStream 常用于文件的输出。
BufferedOutputStream 用于缓存输出,装饰OutputStream。
字符输出流
Writer 子类BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter。常见的有OutputStreamWriter及其子类FileWriter,BufferedWriter。
复制
从字节输入流中读取一个字节,返回该字节对应的十进制值,当字节不存在或者已读取完毕时返回-1,对应了OutputStream中的write()方法。通常在一些操作(如文件拷贝)过程中,并不需要知道具体每个字节的含义,只需要判断资源是否读写完整即可。使用其相应子类中的read方法和write()方法即可完成操作。文件拷贝核心代码如下:
InputStream in = new FileInputStream(oldFilePath);
FileOutputStream fs = new FileOutputStream(newFilePath);
byte [] buffer = new byte[1024];
int len;
while((len = in.read(buffer)) != -1){
fs.write(buffer, 0, len);
}
in.close();
fs.close();
对于上面的代码,刚开始有两个疑问:一是,为什么要用byte数组作缓存,直接用fs.wirte(in.read())不就OK了吗?二是,为什么要用fs.write(buffer,0,len),而不是fs.wirte(buffer)?
然而实践之后终有所发现。
第一个问题,引用byte[]缓存必然是涉及效率问题。so,将程序改写一下使用单字节读写,拷贝含有1000行“hello world!”的文本,耗时为943毫秒,而使用以上的代码耗时1毫秒,结果不言而喻。网上有这么一句话形容这个过程,恰到好处:把一个水缸的水倒到另外一个水缸里去,用吸管太慢,可以用水瓢。说到这里,可能会想到,如果使用BufferedInpustream,那么它内部的缓存,跟这个buffer的作用有什么区别呢?BufferedInputStream中的缓存区只针对读取做缓存,如在文本的读取的操作中旨在减少内存与磁盘的IO操作,而buffer中的数据则在这个缓存区中读取。整个过程相当于存在二级缓存。
第二个问题,其实只需要debug一下就会发现问题所在。只有一行“hello world!”的文本,总共12个字节,若字节数组缓冲区大小设置为5,那么将是三次循环读取完毕,in.read(buffer)返回的是读取的字节数组的长度,将分别是5、5、2。使用fs.write(buffer)会将buffer里面的所有字节都写入。由于每次循环都是替换前一次循环的数据,第二次循环读取在buffer中的数据是”[ ,w,o,r,l]”,第三次循环读取只替换了前面两个字节,即[d,!,o,r,l]。那么最终拷贝输出的是“hello world!orl”,这是不正确的。fs.write(buffer,0,wirter)根据读取的偏移量写入正好解决了该问题。
从文本中逐行读取
BufferedReader bf = new BufferedReader(new FileReader("E:\\1.txt"));
String str=bf.readLine();
/**
* 读取UTF-8格式的文本,需要注意去掉BOM头,避免读取错误,推荐以下方法,需引入common.io.jar
* BufferedReader reader = new BufferedReader(new InputStreamReader(new BOMInputStream(new FileInputStream(file))));
*/
while(null != str){
System.out.println(str);
str=bf.readLine();
}
bf.close();
读取网络资源
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
conn.setRequestMethod("GET");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connecion", "Keep-Alive");
if(conn.getResponseCode() == 200){
is = conn.getInputStream();
//使用ByteArrayOutputStream收集网络信息
baos = new ByteArrayOutputStream();
int len = -1;
byte[] buf = new byte[1024];
while((len = is.read(buf)) != -1){
baos.write(buf,0,len);
}
baos.flush();
//将从网络收集到的信息,一次性输出,ByteArray转字符串时,需注意其字符编码
return new String(baos.toByteArray(),"UTF-8");
}else{
throw new RuntimeException("responseCode is not 200...");
}
将字节流/字符流输出到文本
将流输出到文本中,是流读取的逆向操作。如果数据源是字节流,可直接使用FileOutputStream直接输出,为提高效率,可使用BufferedOutputStream对FileOutputStream进行装饰。如果数据源是字符流,则需要使用FileOutStreamReader将其转换成字节流,再进行进一步的处理。
//字节流输出到文本
InputStream in=new ByteArrayInputStream("hello world!".getBytes());
FileOutputStream fos= new FileOutputStream("E:\\3.txt");
byte[] buffer = new byte[1024];
int len;
while((len=in.read(buffer)) != -1){
fos.write(buffer, 0, len);
}
fos.close();
bis.close();
//字符流输出
FileOutputStream fos= new FileOutputStream("E:\\4.txt");
OutputStreamWriter writer = new OutputStreamWriter(fos,"utf-8");
BufferedWriter bw = new BufferedWriter(writer);
bw.write("helloword1!");
bw.newLine(); //添加换行符
bw.write("helloword2");
bw.flush();
bw.close();