InputStream
就是Java标准库提供的最基本的输入流。它位于java.io
这个包里。java.io
包提供了所有同步IO的功能。
要特别注意的一点是,InputStream
并不是一个接口,而是一个抽象类,它是所有输入流的超类。
FileInputStream
是InputStream
的一个子类。FileInputStream
就是从文件流中读取数据。
完整地读取一个FileInputStream
的所有字节:
public void readFile() throws IOException {
// 创建一个FileInputStream对象:
InputStream input = new FileInputStream("src/readme.txt");
for (;;) {
int n = input.read(); // 反复调用read()方法,直到返回-1
if (n == -1) {
break;
}
System.out.println(n); // 打印byte的值
}
input.close(); // 关闭流
}
在读取流的时候,一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。InputStream
提供了两个重载方法来支持读取多个字节:
int read(byte[] b)
:读取若干字节并填充到byte[]
数组,返回读取的字节数int read(byte[] b, int off, int len)
:指定byte[]
数组的偏移量和最大填充数利用上述方法一次读取多个字节时,需要先定义一个byte[]
数组作为缓冲区,read()
方法会尽可能多地读取字节到缓冲区, 但不会超过缓冲区的大小。read()
方法的返回值不再是字节的int
值,而是返回实际读取了多少个字节。如果返回-1
,表示没有更多的数据了。
private static void testFileInputStream() throws IOException {
File file = new File("haha.txt");
FileInputStream fis = new FileInputStream(file);
//一次读取一个字节 读完之后并跳转到下一个字节 直到读完为止 -1
// int ch = 0;
// while ((ch = fis.read()) != -1) {
// System.out.println((char) ch);
// }
//一次读取一组字节
byte[] buf = new byte[1024]; // 1024 byte = 1kb
int len = 0;
while ((len = fis.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
File file = new File("haha.txt");
//文件若不存在 则自动创建
//append false 覆盖
//append true 追加
FileOutputStream fos = new FileOutputStream(file, true);
//写出单个字节数据
fos.write(97);
fos.write(98);
fos.write(99);
//写出多个字节数据 字节数组
String s = "Hello World!";
fos.write(s.getBytes());
//必须要关掉流:操作完毕一个文件后 要保存并退出
fos.close();
}
在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。
InputStream
和OutputStream
都是通过close()
方法来关闭流。关闭流就会释放对应的底层资源。
在读取或写入IO流的过程中,可能会发生错误,例如,文件不存在导致无法读取,没有写权限导致写入失败,等等,这些底层错误由Java虚拟机自动封装成IOException
异常并抛出。因此,所有与IO操作相关的代码都必须正确处理IOException
。
用try ... finally
来保证InputStream
在无论是否发生IO错误的时候都能够正确地关闭:
try {
//关联源文件的字节输入流
FileInputStream fis = new FileInputStream(source);
//关联字节输入流缓冲流 缓冲区默认8192字节大小
bis = new BufferedInputStream(fis);
//关联目标文件的字节输出流
FileOutputStream fos = new FileOutputStream(aim);
//关联字节输出流缓冲流 缓冲区默认8192字节大小
bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
//bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
和InputStream
相反,OutputStream
是Java标准库提供的最基本的输出流。
和InputStream
类似,OutputStream
也是抽象类,它是所有输出流的超类。
和InputStream
类似,OutputStream
也提供了close()
方法关闭输出流,以便释放系统资源。要特别注意:OutputStream
还提供了一个flush()
方法,它的目的是将缓冲区的内容真正输出到目的地。
向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]
数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream
有个flush()
方法,能强制把缓冲区内容输出。
通常情况下,我们不需要调用这个flush()
方法,因为缓冲区写满了OutputStream
会自动调用它,并且,在调用close()
方法关闭OutputStream
之前,也会自动调用flush()
方法。
实际上,InputStream
也有缓冲区。例如,从FileInputStream
读取一个字节时,操作系统往往会一次性读取若干字节到缓冲区,并维护一个指针指向未读的缓冲区。然后,每次我们调用int read()
读取下一个字节时,可以直接返回缓冲区的下一个字节,避免每次读一个字节都导致IO操作。当缓冲区全部读完后继续调用read()
,则会触发操作系统的下一次读取并再次填满缓冲区。
复制一个文件
public class IODemo02 {
public static void main(String[] args) throws IOException {
int count = 0;
//源文件
File source = new File("C:\\Users\\HENG\\Desktop\\周杰伦 - 暗号.m4a");
//目标文件
File aim = new File(source.getName());
//关联源文件的字节输入流
FileInputStream fis = new FileInputStream(source);
//关联目标文件的字节输出流
FileOutputStream fos = new FileOutputStream(aim);
byte[] buf = new byte[1024 * 1024]; // KB
int len = 0;
while ((len = fis.read(buf)) != -1) {
count++;
fos.write(buf,0, len);
}
System.out.println("count = " + count);
fis.close();
fos.close();
}
}
字节缓冲输入流/输出流
public class IODemo03 {
public static void main(String[] args) {
//源文件
File source = new File("C:\\Users\\jameth\\Desktop\\周杰伦 - 暗号.m4a");
//目标文件
File aim = new File(source.getName());
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//关联源文件的字节输入流
FileInputStream fis = new FileInputStream(source);
//关联字节输入流缓冲流 缓冲区默认8192字节大小
bis = new BufferedInputStream(fis);
//关联目标文件的字节输出流
FileOutputStream fos = new FileOutputStream(aim);
//关联字节输出流缓冲流 缓冲区默认8192字节大小
bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
//bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileOutputStream
实现了文件流输出;ByteArrayOutputStream
在内存中模拟一个字节流输出。某些情况下需要手动调用OutputStream
的flush()
方法来强制输出缓冲区。
总是使用try(resource)
来保证OutputStream
正确关闭。