一、字节流(InputStream/OutputStream)
1、字节流:是主要用于操作媒体文件(例如:视频、图片、音乐......)的流。也可以操作文本文件(因为字符流是从字节流当中分离出来的)。
2、字节输出流 (写入):OutputStream —— 所有字节输出流的父类。
①、OutputStream类声明:
public abstract class OutputStream
extends Object
implements Closeable, Flushable
是一个抽象类,需要用其子类进行实例化对象。
②、OutputStream类中的常用方法:
void close():关闭流资源。
void flush():刷新流。
void write(byte[] b):将一个字节数组写入到输出流。
void write(byte[] b, int off, int len):将字节数组中指定的字节数据写入到输出流中。
abstract void write(int b):将指定的字节写到输出流中。
3、字节输入流 (读取):InputStream ——→所有字节输入流的父类。
①、InputStream类声明:
public abstract class InputStream
extends Object
implements Closeable
②、InputStream类中的常用方法:
abstract int read():一次读单个字节。
int read(byte[] b):读取一个字节数组。
int read(byte[] b, int off, int len):读取一个字节数组当中指定长度的字节数据。
int available():InputStrea类的特有方法,可以获取被读取文件的长度。可用于定义一个长度刚刚好的字节数组,但是要慎用。
3、FileInputStream类/FileOutputStream类
与字符流当中的FileReader和FileWriter类似。用于操作字节数据,进行读写操作。
通过InputStream类中的available()方法读取字节数据的这种方式一定要慎重使用,因为如果读取一个大容量的文件的时候,可能在内存中开辟的空间就会超过JVM的内容容量,从而导致内存溢出的情况。
所以,开发当中建议还是使用读取字节数组read(byte[])的方式进行字节读取流的操作。
代码示例:
import java.io.*;
class FileStream
{
public static void main(String[] args) throws IOException
{
//writeFile();
//readFile_1();
//readFile_2();
readFile_3();
}
public static void readFile_3() throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[fis.available()]; //通过available()方法定义一个刚刚好的缓冲区,不用再循环了。
fis.read(buf);
System.out.println(newString(buf));
fis.close();
}
//通过定义字节数组的方式读取。
public static void readFile_2() throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
System.out.println(new String(buf,0,len));
}
fis.close();
}
//通过read方法,一个一个字节的方式读取数据。
public static void readFile_1() throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
int ch = 0;
while((ch = fis.read()) != -1)
{
System.out.println((char)ch);
}
fis.close();
}
public static void writeFile() throws IOException
{
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("abcde".getBytes()); //由于OutputStream当中没有提供写入字符串的方法,所以可以将字符串通过getBytes()方法转成字节之后再写入到流当中。
fos.close();
}
}
4、图片复制
①、需求:使用字节流复制一个图片。
②、步骤:
1) 用字节读取流对象和图片关联。
2) 用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
3) 通过循环读写了,完成数据的存储。
4) 关闭资源。
③、实现代码:
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{
fos = new FileOutputStream("2.bmp"); //用字节输出流创建一个图片文件存储读取到的数据。目的文件
fis = new FileInputStream("1.bmp"); //字节读取流关联一个图片文件。源文件
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) //通过字节数组读取数据。
{
fos.write(buf,0,len);
}
}
catch(IOException e)
{
throw new RuntimeException("复制文件失败");
}
finally
{
try
{
if(fis != null)
fis.close();
}
catch(IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(fos != null)
fos.close();
}
catch(IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
④、用字符流能否实现图片复制?
可以。但是复制了之后的图片有可能看不了,因为编码表不一样,所以在读取的过程中可能会发生查到的编码表内容不一致从而导致最终的图片文件无法观看或者无法打开。
二、字节流缓冲区
1、字节读取流缓冲区:BufferedInputStream
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
2、字节写入流缓冲区:BufferedOutputStream
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
字节流缓冲区的的使用和字符流的缓冲区使用非常类似。
代码示例:通过字节流缓冲区复制MP3文件。
import java.io.*;
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start = System.currentTimeMillis();
copy_1();
long end = System.currentTimeMillis();
System.out.println((end-start)+"毫秒"); //获取复制MP3文件消耗的时间。
}
//通过字节流的缓冲区完成复制。
public static void copy_1() throws IOException
{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("1.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("2.mp3"));
int by = 0;
while((by = bufis.read()) != -1)
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
3、自定义字节读取流的缓冲区
import java.io.*;
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf = new byte[1024*4];
private int pos = 0,count = 0; //定义数组指针变量pos和计数器变量count。
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//一次读一个字节。从缓冲区(字节数组)获取
public int myRead() throws IOException
{
if(count == 0) //判断当计数器为0的时候,表示数组为空,可以往数组中存数据。
{
count = in.read(buf); //通过in对象读取硬盘上的数据并存储到buf中。
if(count<0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&255; //&上255使返回的b由byte型被提升为int型的值之后将前面的24位全部变成0。既可以保留原字节数据不变,又可以避免-1的出现。
}
else if(count>0)
{
byte b = buf[pos];
count--;
pos++;
return b&0xff; //&上255的十六进制形式。和&上255没什么区别。
}
return -1;
}
public void myClose() throws IOException
{
in.close();
}
}
4、键盘录入
System.out:对应标准的输出设备。控制台。
System.in:对应标准的输入设备。键盘。
①、字段in的声明:
public static final InputStream in
代码示例:
import java.io.*;
class ReadIn
{
public static void main(String[] args) throws IOException
{
InputStream in = System.in;
int ch = 0;
while((ch = in.read()) != -1) //循环输出从键盘录入的字符。一次只输出一个字符。
{
System.out.println(ch);
}
in.close();
}
}
输出结果发现无论输入什么字符,始终无法终止打印(即使输入-1也一样无法停止打印,因为-1是两个字符),但是却可以通过Ctrl+C快捷键来终止键盘录入,原因是Ctrl+C操作传递给键盘录入的指令就相当于是让read方法读取到了-1,从而可以终止键盘录入。
②、需求:当录入一行数据后,就将该行数据进行打印。当录入字符串“over”的时候就终止键盘录入。
import java.io.*;
class ReadIn
{
public static void main(String[] args) throws IOException
{
InputStream in = System.in;
StringBuilder sb = new StringBuilder(); //定义StringBuilder缓冲区临时存储录入的数据。
while(true)
{
int ch = in.read();
if(ch == '\r')
continue;
if(ch == '\n')
{
String s = sb.toString();
if("over".equals(s)) //当录入到over的时候就终止循环。
break;
System.out.println(s.toUpperCase()); //将录入的内容全部转换成大写字母。
sb.delete(0,sb.length()); //当输出一行字符之后就清空缓冲区,通过StringBuilder中的delete方法。
}
else
sb.append((char)ch);
}
}
}
三、转换流(InputStreamReader/OutputStreamWriter)
转换流是字符流体系当中的功能。作用是在字符流和字节流之间做相互转换。
1、读取转换流:InputStreamReader
①、InputStreamReader是Reader的直接子类。是字节流通向字符流的桥梁(将字节流转换成字符流)。
构造方法:InputStreamReader(InputStreamin)
②、读取转换流演示:
需求:将字节流转成字符流,再使用字符流缓冲区的readLine方法,当读取到over的时候就终止键盘录入。
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象。
InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStramReader
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符流进行缓冲区技术的高效操作。使用BufferedReader。
BufferedReader bufr = new BufferedReader(isr);
String line = null;
while((line = bufr.readLine()) != null) //调用BufferedReader缓冲区中的readLine方法。
{
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
2、写入转换流:OutputStreamWriter
①、OutputStreamWriter是Writer的直接子类。是字符流通向字节流的桥梁(将字符流转换成字节流)。
构造方法:OutputStreamWriter(OutputStreamout)
②、写入转换流演示:
需求:将字节流转成字符流,再使用字符流缓冲区的readLine方法,当读取到over的时候就终止键盘录入。
将通过键盘录入的字符输出打印在控制台上,输出之后自动添加一个换行动作。
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = bufr.readLine()) != null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine(); //调用BufferedWriter缓冲区中的newLine方法进行换行。
bufw.flush(); //将键盘录入的字符输入到流中以后要刷新之后才能显示到控制台上。
}
bufr.close();
}
}
四、流操作基本规律
IO流的操作类太多,实际应用时不明确具体应该使用哪一个来达到预期的目的。可以通过“三个明确”来确定到底应该使用哪一个流对象。
三个明确:
1、明确源和目的。
源:输入流 —— InputStream Reader
目的:输出流 —— OutputStream Writer
2、操作的数据是否是纯文本。
是:用字符流
不是:用字节流
3、当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存、硬盘、键盘
目的设备:内存、硬盘、控制台
具体示例说明—1:
1、需求:将一个文本文件中的数据存储到另一个文件中。
【明确1】源:因为是源,所以使用读取流。InputStream Reader
【明确2】是不是操作文本文件?
是。这时就可以选择Reader
【明确3】明确设备:硬盘,一个文件。
Reader体系中可以操作文件的对象是 FileReader
是否需要提高效率?
是。加入Reader体系中的缓冲区 BufferedReader
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
【明确1】目的:OutputStream Writer
【明确2】目的是否是纯文本?
是。Writer
【明确3】设备:硬盘,一个文件。
Writer体系中可以操作文件的对象FileWriter
是否需要提高效率?
是。假如Writer体系中的缓冲区 BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
具体示例说明—2:
2、需求:将键盘录入的数据保存到一个文件中。
【明确1】源:InputStream Reader
【明确2】是不是纯文本?
是,Reader
【明确3】设备:键盘。对应的对象是System.in
问题:不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文本数据方便,转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成Reader。
用到了Reader体系中的转换流:InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
是否需要提高效率?
是。加入Reader体系中的缓冲区 BufferedReader
BufferedReader bufr = new BufferedReader(isr);
【明确1】目的:OutputStream Writer
【明确2】是否是纯文本?
是,Writer
【明确3】设备:硬盘。一个文件。使用FileWriter.
FileWriter fw = newFileWirter("c.txt");
是否需要提高效率?
是。
BufferedWriter bufw = new BufferedWriter(fw);
扩展知识点:
将录入的数据按照指定的编码表存入文件中。
【明确1】目的:OutputStream Writer
【明确2】是否是纯文本?是,Writer
【明确3】设备:硬盘。一个文件。
使用FileWriter。但是FileWriter是使用的默认编码表:GBK
但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用OutputStreamWriter.
而该转换流对象要接收字节输出流。而且还是可以操作文件的字节输出流。FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(newFileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
转换流什么时候使用?
转换流是字符和字节之间的桥梁,当涉及到字符编码转换时,需要用到转换流。
转换流中指定编码表的构造方法:
InputStreamReader:字节流转字符流
InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter:字符流转字节流
OutputStreamWriter(OutputStream out, String charsetName)
五、标准输入输出设备
Java中使用System类中的in和out两个字段分别代表标准输入、输出。其中默认的标准输入设备是:键盘,标准输出设备是:显示器(或者控制台)。
可以通过System类中提供的两个方法setIn,setOut改变默认输入输出设备(或者称之为改变流的数据源和数据目的地)。
代码示例:
class Demo
{
public static void main(String[] args) throws IOException
{
//设置源。
System.setIn(new FileInputStream("1.txt")); //输入的源位置设置为一个文件1.txt
//设置目的。
System.setOut(new PrintStream("2.txt")); //输出的目的地设置成一个文件2.txt
}
}