在平时的开发中,我们经常需要和系统I/O机制打交道。通常来说底层的数据交换都是通过二进制形式进行交换的,二进制是个好东西,但只是对于机器而言。对于我们人类而言,一串的数字太晦涩难懂了,所以Java给开发者封装了大量用于操作字符流和字节流的类,其中输出字符流和输出字节流是writer和outputStream,输入字符流和输入字节流是reader和inputStream。仔细了解它们的特征,有助于我们更好的操作数据的输入和输出,所以接下来会综合讲解outputStream、inputStream、writer、reader,接着在这基础上继续分析outputStreamWriiter,BufferWriter.....比较重要且日常较多使用的类。
一、字节流、字符流的综述:
1、OutputStream
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
public void flush() throws IOException
public void close() throws IOException
2、inputStream
InputStream是一个抽象类,他是所有字节输入流的超类,它的作用在于读取目标字节并返回,任何它的子类都必须实现的一个方法是读取下一字节并返回的方法,该方法原型如下:
public abstract int read() throws IOException
public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException
public int available() throws IOException
此方法用于预测当前输入流可读数据的长度,在调用此方法时,并不会影响其它线程对此输入流的操作,因此并不会造成线程堵塞。但要注意的是此方法一般是由具体的输入流的子类来实现的,有些实现是返回整个可读数据的长度,有的是直接返回0,因此如果想要用此方法来获取一个数值来申请缓存空间的大小是不可取的,因为这个方法返回的数据并不可靠。
public void close() throws IOException
public boolean markSupported()
public synchronized void mark(int readlimit)
用于标记当前inputStream的位置,以至于后续调用reset方法的时候,可以恢复标记位置之后的数据。其中readlimit参数比较复杂,不同的子类有不同的含义,这里不进行解释了。即当我们读取到当前字节流的某个位置时,调用mark方法,那么在此方法之后所有被读取的字节数据都会被记住,这样,当我们调用reset方法的时候,就可以重新回到标志位,读取相同的数据。
public synchronized void reset() throws IOException
这个方法的功能在上面已经讲到了,就是用于将mark位置之后的字节数据进行恢复。在讲完综合这一章节,会继续给大家一个例子
3、writer
abstract public void write(char cbuf[], int off, int len) throws IOException;
public void write(int c) throws IOException
此方法用于将单个字符写入字符流并且会进行缓存。同时还有:
public void write(char cbuf[]) throws IOException
此方法等同于调用write(cbuf,0,cbuf.length)。但是用的最多的是输出字符串的方法,此功能函数有两个重载:
public void write(String str, int off, int len) throws IOException
public void write(String str) throws IOException
abstract public void flush() throws IOException;
此方法和outputStream的特征一样,但是不同的地方在于,如果此字符输出流还包含着下层的字节输出流或者字符输出流的话,那么连同下层缓存的字节内容和字符内容都会一并冲刷至缓存区域。对于需要操作系统提供支持的写操作,此方法只保证将数据冲刷给操作系统,并不保证将数据写入硬件。
abstract public void close() throws IOException
此方法用于关闭字符输出流,不同的地方在于,在关闭字符输出流的时候,所缓存的数据是会被冲刷掉的。这个和字节输出流(比如outputStream并没有进行缓存字节的操作)有所不同。
abstract public int read(char cbuf[], int off, int len) throws IOException
其中cbuf表示存储读取的数据的字符数组,off,len分别表示存储的起始位置。
public int read(char cbuf[]) throws IOException
public int read() throws IOException
此方法会读取下一个字符并返回。调用read的方法都会导致线程堵塞,除非返回了可读数据或者是抛出了异常又或者都到了文件的末尾。
public boolean markSupported()
public void mark(int readAheadLimit) throws IOException
abstract public void close() throws IOException
5、总结
package cn.com.chinaweal.stream; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import java.io.ByteArrayInputStream; import java.io.IOException; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String content = "lalalalayonggandekaiba"; ByteArrayInputStream bios = new ByteArrayInputStream(content.getBytes()); StringBuffer sb = new StringBuffer(); int i = 0; boolean isReapter = false; while ((i = bios.read()) != -1) { char temp=(char)i; sb.append(temp); //当调用reset之后,会恢复y后面的字符 if ((temp == 'y') && bios.markSupported() && !isReapter) bios.mark(bios.available()); if ((temp == 'e') && bios.markSupported() && !isReapter) { bios.reset(); isReapter = true; } } try { bios.close(); } catch (IOException e) { e.printStackTrace(); } Log.i("test", sb.toString()); } }
输出结果如下:
在第一部分呢,讲解了字符流和字节流的超类,通过对这些超类的基本了解,可以知道,对于所有的字节流和字符流都有哪些基本操作。通过第一部分的了解,也得出了reader,writer,inputStream,outputStream都是抽象类,这意味着,它们并不能直接被我们开发者使用。同时,因为这些超类的基本读写方法都是抽象的,所以这就要求我们对于它们的子类有一定的了解,才能结合具体的场景,去搭配不同的子类一提供更加便利高效的读写方法。所以很有必要对字符流和字节流中比较特殊的子类有一定的了解。
二、更好的具有缓存功能的字符流和字节流
接下来,会介绍用于提高读写效率的具有缓冲数据作用的子类,通常来说,如果我们需要和字符流或者字节流打交道,最好就是用含有缓冲功能的子类进行对字符流或者字节流的包裹,这样对于我们的读写操作会有更高的表现性能以及更多的便捷操作方式。接下来将介绍FilterInputStream,FilterOuputStream,BufferedInputStream,BufferedOutPutStream,BufferedReader,BufferedWriter这几个常用的具有高效率的读写方法的类。
1、FilterInputStream,FilterOutputStream
2、BufferedOutputStream
这是一个具有缓存输出的字节数据作用的字节输出流,它的存在,意义在于当开发者调用write方法进行写数据的时候,不用每次都调用I/O系统,而是会将数据线缓存起来,等到到达了一个限度之后,在一次性调用系统的I/O操作,这样就大大提高了写数据的效率。要了解它是怎么起到缓存效果的,我们需要了解一下几个方面:
①、缓存数组以及缓存大小。
protected byte buf[];
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int size)
在此类中含有一个Byte数组用于缓存读取的字节数据,等到达到一定的缓存闲置之后,才会调用底层的输出流进行写操作。缓存闲置默认是1024*8(8kb)个字,当然,从上面的两个构造方法可以看出,通过调用第二个构造函数,就可以通过size来指定buf数组的大小,即缓存数据的大小。
②、起到缓存数据达到限制后,将数据交由底层输出流进行写操作的方法:
private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count); count = 0; } }
此方法适用于将数据交由底层输出流的,其中count表示当前buf里面缓存的数据的大小,可以看到,每次调用此方法后,count都变为0了,意味着,调用此方法之前的缓存数据都会被底层输出流操作,此方法之后的缓存数据会从头开始进行保存,即覆盖旧的缓存数据。
此类中,还含有两个write方法以及一个flush方法,前者用于书写要输出的数据,都是先判断当前的缓存数据是否达到了限制,达到的话就会调用上面的方法进行将缓存的数据输出并将现在要输出的数据进行缓存。没有达到的话,就直接进行缓存。flush方法就是直接将缓存的数据交由底层输出流操作。
3、BufferdInputStream
BufferedInputStream具有将数据源的数据缓存的作用。缓存的目的在于,此类给我们提供了mark和reset方法的支持,以至于后续的操作我们可以读取重复的数据。这个类会包含一个底层输入流,以及一个缓存数组,还有很多的常量用于进行标识作用。使用此类对于读取操作的最大用处就是,可以进行数据的重复读取,接下里就了解一下,这个过程怎么实现的。
①、缓冲数组及缓存数据的容量
protected volatile byte buf[]
public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)
其中buf用于缓存读取的数据,便于后续进行重复读取,缓存的大小默认是8kb,但是同样的,可以通过调用第二个构造函数的size来指定缓存容量的大小。
②、具有标记功能的常量
protected int count;
这是一个用于指示当前缓存数组中已缓存的数据的大小,通常它是介入0-buf.length之间。
protected int pos;
这是一个用于标记下一个应该读取的缓存数据的索引值。比如pos=1,表示下一个应该读取的数据是buf[1]。
protected int markpos
③、起到将数据进行缓存的方法
private void fill() throws IOException
④、重复读取数据
在第一部分已经提到过了,要想重复读取一样的字节数据,必须结合使用mark,reset以及markSupported方法,在BufferInputStream中,markSupported方法是返回true的。因此,此类是支持重复读取数据的操作的。剩余的方法和签名提到的一样。另外,此类的avaliable方法返回的结果是,当前输入流可被读取的字节的总数。
4、BufferedWriter
此类是具有缓存字符功能的字符输出流,同样的,它的缓存容量大小可以通过构造方法来制定。这个类中有一个特殊的方法是newLine方法,作用是进行换行,为什么说它特殊,是因为,这个方法返回的值,并不是单纯的\n换行字符,而是一个当前的操作平台获取的具有换行效应的属性值。如果开发者需要进行字符的血操作的话,是很强烈要求推荐用这个类来包括其他的字符输出流,从而来提高我们输出的字符的效率的。
同样的,此类中和BufferedOuputStream类一样,都具有一个起到缓存作用的数组,不同的是這里的是char数组,另外就是,此类的write方法不仅可以写char数组,还可以直接写string类型的值。剩余的,关于这个类的基本操作和writer是一样的,不明白的朋友去看我写的第一部分。
5、BufferedReader
此类一样是一个具有缓存作用的字符输入流,使用此类的目的在于提高读取效率,如果我们要操作的字符输入流的对象的读取方法是很耗时的,就应该用此类来进行包裹,这样在每次我们调用read方法的时候,就不是从底层的输入流对象数据而是从当前的缓存字符数组中读取,从而提高了效率。此类中的大部分方法都是和bufferdInputStream的类似,不同的是操作的对象不同,前者操作的是字节数据,后者是字符数据。在此类中比较特殊的是readLine方法,此方法的作用是读取输入流中的数据,读取的方式是一行一行的都,因此如果此方法返回的是null,表面以及读取到了内容的结尾处。此方法也有重复读取数据的功能,方法和上面的一样,就不再累赘了。
下面通过一个例子来使用上面的知识。例子中将一串字符写入文件然后在读取出来。关注点是如何搭配这些输入输出对象。
package cn.com.chinaweal.stream; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.util.Log; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * Created by Myy on 2016/8/6. */ public class BufferActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String content = "content to be write,content to be write,content to be write,content to be write"; File file = new File(Environment.getExternalStorageDirectory().getPath() + "/buffer.txt"); PrintWriter stringWriter = null; try { //BufferedWriter的作用是用于提高输出效率。 //PrinterWriter的做用在于提供了类似println等操作的便捷方式 stringWriter = new PrintWriter(new BufferedWriter(new FileWriter(file))); stringWriter.println(content); } catch (IOException e) { e.printStackTrace(); } finally { stringWriter.close(); } BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); StringBuilder sb = new StringBuilder(); String temp = null; while ((temp = reader.readLine()) != null) { sb.append(temp); } Log.i("test", sb.toString()); } catch (Exception e) { e.printStackTrace(); } finally { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }
还有第三部分,用于讲解具有不同功能的输入流和输出流。
---------文章写自:HyHarden---------
--------博客地址:http://blog.csdn.net/qq_25722767-----------