写在前面:
先看几组图片吧。
输入输出流是相对内存而言的。
一,常识
1.1,java有几种流
字符流和字节流。字节流继承inputStream和OutputStream,字符流继承自InputSteamReader和OutputStreamWriter。
1.2,字符流和字节流有什么区别?
要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为IO流,对应的抽象类为OutputStream和InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。
在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?
计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。
底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO设别写入或读取字符串提供了一点点方便。
缓大多数情况下使用字节流会更好,因为字节流是字符流的包装,而大多数时候 IO 操作都是直接操作磁盘文件,所以这些流在传输时都是以字节的方式进行的(图片等都是按字节存储的)。如果对于操作需要通过 IO 在内存中频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能
1.3,什么是缓冲区?有什么作用?
缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就显著提升了性。
对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。
1.4,InputStream里的read(),read(byte[] data)以及OutputStream里面的write(int b),write(byte b[], int off, int len)
read() 返回的是所读取的字节的int型(范围0-255)
read(byte [ ] data)将读取的字节储存在这个数组。返回的就是传入数组参数个数。相当于read(data, 0, data.length)
read(byte[] data, int off, int len) 将读取的字节存储到数组data里面,存储的位置是date[off - (off+len+1)]
write(int b)将指定字节(b)传入数据源
write(byte b[], int off, int len) b[off]是传入的第一个字符、b[off+len-1]是传入的最后的一个字符 、len是实际长度。吧b[off - (off+len-1)]写入到输出流
1.5,节点流和处理流
节点流:
可以从或向一个特定的地方(节点)读写数据。
如:FileReader、FileWriter、FileInputStream、FileOutputStream等文件进行处理的节点流。
处理流:
是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。
处理流的构造方法总是要带一个其他的流对象做参数。
一个流对象经过其他流的多次包装,称为流的链接。
BufferedImputStrean
BufferedOutputStream
BufferedReader
BufferedWrite
二,流的基本操作
所有流都需要关流操作close(),一般写在finally里面。这里做测试,先不写了。
①,inputstream/outputstream:
public static void main(String[] args) throws IOException {
File file = new File("F:\\camera\\test.txt");
InputStream inputStream = new FileInputStream(file);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(System.out, "utf-8"), true);
byte[] bs = new byte[1024];
while (inputStream.read(bs) != -1){
printWriter.write(new String(bs));
}
printWriter.flush();
}
打印:
第一行
第二行
第三行
②,FileReader/FileWriter
public static void testFileRw() throws IOException {
File file = new File("F:\\camera\\test.txt");
FileReader reader = new FileReader(file);
File file2 = new File("F:\\camera\\test2.txt");
FileWriter writer = new FileWriter(file2);
char[] cs = new char[1024];
while (reader.read(cs) != -1){
System.out.println("读取: "+new String(cs));
//写入文件
writer.write(cs);
}
writer.flush();
}
③,StringReader/StringWriter
public static void testStringRw() throws IOException {
StringReader reader = new StringReader("这是要读取的内容");
StringWriter writer = new StringWriter();
char[] cs = new char[1024];
while (reader.read(cs) != -1){
writer.write(cs);
}
writer.flush();
//实际写入的是内不的一个stringbuffer
System.out.println(writer.getBuffer());
}
④,字节流转换为字符流
public static void changeByte2Character() throws IOException {
File file = new File("F:\\camera\\test.txt");
InputStream inputStream = new FileInputStream(file);
//字节流inputstream 转成 字符流 bufferreader
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
char[] cs = new char[1024];
while (reader.read(cs) != -1){
//读取的内容
System.out.println(cs);
}
}
⑤,randomaccseefile
public static void testRandomAccseeFile() throws IOException {
File file = new File("F:\\camera\\test.txt");
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
long length = randomAccessFile.length();
randomAccessFile.seek(length); //定位游标到最后
randomAccessFile.write("\n换行追加的内容".getBytes());
randomAccessFile.close();
InputStream inputStream = new FileInputStream(file);
byte[] bs = new byte[1024];
while (inputStream.read(bs) != -1){
System.out.println(new String(bs));
}
}
运行结果:
第一行
第二行
第三行
换行追加的内容
⑥,管道流
管道流哟成对使用
public static void testPipStream() throws IOException {
PipedOutputStream pipedOutputStream = new PipedOutputStream();
PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
new Thread(()->{
int i = 0;
for(;;){
i++;
try {
pipedOutputStream.write(("写入数字: " + i).getBytes() );
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
try {
byte[] bs = new byte[1024];
while (pipedInputStream.read(bs) != -1){
System.out.println(new String(bs));
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
打印结果如下:
写入数字: 1 写入数字: 2 写入数字: 3
消费者pipedInputStream进行堵塞读取消费
⑦,SequenceInputStream将多个输入流当成一个输入流依次读取
按照顺序,先读第一个流,读完继续读第二个流进行追加。
public static void testSequenceStream() throws IOException {
File file1 = new File("F:\\camera\\test.txt");
File file2 = new File("F:\\camera\\test2.txt");
InputStream inputStream1 = new FileInputStream(file1);
InputStream inputStream2 = new FileInputStream(file2);
//定义两个流
SequenceInputStream sequenceInputStream = new SequenceInputStream(inputStream1, inputStream2);
byte[] bs = new byte[1024];
while (sequenceInputStream.read(bs) != -1){
System.out.println(new String(bs));
}
}
结果如下:
文本1:第一行
文本1:第二行
文本1:第三行 文本2:第一行
文本2:第二行
文本2:第三行
⑧,PushBackInputStream/PushBackReader回退输入流
简单点,支持回退的流。12345,读到3的时候,往回走一步。下一次读取的还是3
public static void testPushStream() throws IOException {
PushbackReader pushbackReader = new PushbackReader(new FileReader("F:\\camera\\test2.txt"));
int p;
while ((p = pushbackReader.read()) != -1){
//读取到3
if(p == '3'){
System.out.println("现在读取到3了");
pushbackReader.unread(p);
}else {
System.out.println("读取: " + (char)p);
}
}
}
运行结果:
到3了
现在读取到3了
现在读取到3了......
当检测到读取‘3’时,进行了回退操作,后面陷入了死循环,因为接下来会一直读取3。
我们看看内部时如何实现的。
unread(p):
/** Pushback buffer */
private char[] buf;
/** Current position in buffer */
private int pos;
public void unread(int c) throws IOException {
synchronized (lock) {
ensureOpen();
if (pos == 0)
throw new IOException("Pushback buffer overflow");
buf[--pos] = (char) c;
}
}
内部维护了buf字符数组和当前坐标pos。我们的每次读取或者回退,都是操作的pos。