IO流(二) 字节流
一、OutputStream类
1.概述OutputStream
类可以在硬盘上创建一个文件,并写入或添加数据。该类的子类还能实现写入过程中的不同功能。
2.FileOutputStream类FileOutputStream
类用于在硬盘上创建文件,并以字节的形式写入数据。其使用方式和FileWriter
类相似,只是以字节的形式操作数据。下面的代码在指定目录创建一个文件并写入自定义数据。
示例代码:
package com.heisejiuhuche.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
/* 创建FileOutputStream对象并关联文件 */
fos = new FileOutputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");
/* 写入数据,通过String类的getBytes方法将字符串转换为字节数组 */
fos.write("abcdefg".getBytes());
} catch(FileNotFoundException e) {
throw new RuntimeException("文件不存在");
} catch(IOException e) {
throw new RuntimeException("操作失败");
} finally {
try {
if(fos != null)
fos.close();
} catch(IOException e) {
throw new RuntimeException("输出流关闭失败");
}
}
}
}
该程序在指定目录创建fos.txt
并写入abcdefg
注意:
字节输出流在直接使用的时候不需要调用flush()方法,与字符流不用。由于字符流底层操作的也是字节,同时用的是字节流的缓冲区;该缓冲区中有个数组,用于临时存储数据。要将数组中的数据写入目的地文件,字符流就需要调用flush()方法进行刷新。而字节输出流是对最小单位字节直接进行操作,没有使用具体缓冲区,所以不需要刷新,直接往目的地文件写入数据。
二、InputStream类
1.概述InputStream
类以字节形式读取硬盘上的文件数据。该类的子类还能实现读取过程中的不同功能。
2.FileInputStream类FileInputStream
类用于以字节形式读取文件数据。其特有的方法使创建字节数组有了明确的大小。该类的使用方式和FileReader
相似。下面的代码读取文件中的数据。
示例代码:
package com.heisejiuhuche.io;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
read_1();
System.out.println();
read_2();
read_3();
}
private static void read_3() throws IOException {
FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");
/* available方法返回文件的大小 */
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println("read_3: " + new String(buf));
}
private static void read_2() throws IOException {
FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) {
System.out.println("read_2: " + new String(buf, 0, len));
}
}
private static void read_1() throws IOException {
FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");
int ch = 0;
System.out.print("read_1: ");
while((ch = fis.read()) != -1) {
System.out.print((char)ch);
}
}
}
程序输出结果:
read_1: abcdefg
read_2: abcdefg
read_3: abcdefg
注意:
如果使用字节输入流读取的文件较大,建议使用1024字节整数倍方法读入数据,而不要使用创建available()方法返回值大小的数组;后者可能造成内存溢出
三、字节流练习
1.拷贝图片到指定目录
示例代码:
package com.heisejiuhuche.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class PicCopy {
public static void main(String[] args) {
picCopy();
}
private static void picCopy() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
/* 创建输入输出流并关联文件 */
fos = new FileOutputStream("C:/Users/jeremy/Documents/me.jpg");
fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/me.jpg");
/* 创建缓冲区 */
byte[] buf = new byte[1024];
int len = 0;
/* 复制文件 */
while((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
} catch(FileNotFoundException e) {
throw new RuntimeException("文件不存在");
} 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.缓冲区对应的类
字节流缓冲区对应BufferedOuputStream
类和BufferedInputStream
类。
2.缓冲区应用
1)用缓冲区拷贝一个Mp3文件
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyMp3Demo {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copy();
long end = System.currentTimeMillis();
System.out.println((end - start) + "毫秒");
}
private static void copy() throws IOException {
/* 创建Buffered缓冲区对象并关联文件 */
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/back.mp3"));
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(
"C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));
int ch = 0;
/* 复制文件 */
while((ch = bufis.read()) != -1) {
bufos.write(ch);
}
}
}
程序输出结果:136毫秒
2)自定义缓冲区
假设内存中字节数组的大小定义为1024
字节;那么字节流缓冲区在工作时,首先由FileInputStream
从硬盘抓取1024
字节的数据存入字节数组,然后由BufferedInputStream
的read()
方法依次一个字节一个字节读取。缓冲区中有两个控制读取过程的变量,分别是数组的索引指针,和一个计数器。下标用于控制不断读取下一个字节,计数器用于控制下一次从硬盘抓数据存入缓冲区数组的时间。read()
方法每读取一个字节,指针右移一位,计数器自减1
;当计数器减至0的时候,意味着数组中已经没有字节可读,这时再由FileInputStream
从硬盘抓取1024
个字节存入数组,指针归零,计数器回到1024
,再次进行以上步骤的循环,直至硬盘数据全部被抓取。
要自定义缓冲区,需要定义一个字节数组,两个变量(指针和计数器)。
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyBufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copy();
long end = System.currentTimeMillis();
System.out.println((end - start) + "毫秒");
}
private static void copy() throws IOException {
/* 创建Buffered缓冲区对象并关联文件 */
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/back.mp3"));
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream(
"C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));
int ch = 0;
/* 复制文件 */
while((ch = bufis.read()) != -1) {
bufos.write(ch);
}
bufos.close();
bufis.close();
}
}
class MyBufferedInputStream {
private FileInputStream fis;
private int pos;
private int count;
private byte ch;
private byte[] buf = new byte[1024];
MyBufferedInputStream(FileInputStream fis) {
this.fis = fis;
}
public int read() throws IOException {
/* 如果count=0了,继续抓取数据到缓冲区数组 */
if(count == 0) {
/* 抓取数据后,count回到1024 */
count = fis.read(buf);
/* 读到文件末尾,fis的read()方法返回-1,必须判断如果count < 0,说明读到了最后 */
if(count < 0) {
return -1;
}
/* 抓取数据后指针归0 */
pos = 0;
/* 取出第一个字节 */
ch = buf[pos];
/* 每取一个数据,指针+1,右移 */
pos++;
/* 每取一个数据,count-1 */
count--;
/* 返回第一个字节 */
return ch;
} else if(count > 0) {
ch = buf[pos];
pos++;
count--;
/* 返回第一个字节后的每一个字节 */
return ch;
}
/* 读完文件,返回-1 */
return -1;
}
public void close() throws IOException {
fis.close();
}
}
问题:
上面的代码运行结果只拷贝了8K
到目的文件。
原因:
是因为媒体文件在硬盘上的数据以二进制形式存在;read()
方法在读取第一个字节的时候,有可能会读到:11111111
;这样8个1的情况;而8个1的的二进制就是十进制的-1
;程序中while
循环的跳进等于-1
时,循环停止;因此只复制了8K大小。
理解BufferedInputStream的read()方法:
细看BufferedInputStream类的read()方法,其返回的是int类型。而方法中读到的字节都是byte类型;这样做的原因,就是为了解决读取字节读到8个1的情况。read()方法在返回byte类型字节数据的时候,将byte类型提升为int类型,存储位数由1个8位,变为4个8位。为了确保返回的数据与原数据相同而不产生-1的情况,read()方法在类型提升之后,补了3个8位的0在原byte数据前面。过程如下图:
在自定义缓冲区中,虽然方法返回了int
类型,进行了数据类型提升,但是没有进行补0
的操作,意味着当读到8个1组成的byte
数据时,返回了一个由32个1组成的int
类型数据,结果还是-1
。那么,如果要完成相同功能,只需取32个1的最后8位
即可。取最后8位,将原数据与上255
。
将每个return ch;语句改为return ch & 255;即可。
五、键盘录入
1.System标准输出输入System
类中对应的成员out
和in
分别是:
System.out-标准输出流
System.in-标准输入流
System.in
用于读取键盘录入。
2.接收键盘录入
从键盘接收输入,并打印在控制台。
示例代码:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.InputStream;
public class SysteminDemo {
public static void main(String[] args) {
/* 创建标准输入对象 */
InputStream in = System.in;
try {
/* 录入一个字符 */
int ch = in.read();
/* 打印该字符的ASCII码 */
System.out.println(in.read());
} catch(IOException e) {
throw new RuntimeException("运行出错~");
}
}
}
程序输出结果:
a
97
3.练习
接收键盘录入,当回车时打印整行内容;当输入over
回车时,结束输入。
示例代码:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.InputStream;
public class SysteminDemo {
public static void main(String[] args) {
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
int ch = 0;
try {
/* 一直读入字符,每输入一个字符,就往StringBuilder添加一个;
* 如果读到回车符,继续读入;
* 如果读到换行符,将StringBuilder中的字符组成字符串
* 判断该字符串是否等于over,如果是,结束程序;
* 如果不是,就打印该字符串,并将StringBuilder容器清空
*/
while(true) {
ch = in.read();
if(ch == 13)
continue;
if(ch == 10) {
String str = sb.toString();
if(str.equals("over")) {
break;
}
System.out.println(str);
sb.delete(0, sb.length());
} else
sb.append((char)ch);
}
} catch(IOException e) {
throw new RuntimeException("运行出错~");
}
}
}
六、转换流
1.InputStreamReader类InputStreamReader
类用于将字节流转换为字符流。该类可以将读取到的字节数据转换为字符数据。其使用的编码表可以由开发者指定,也可以使用系统默认。利用转换流将字节流转换为字符流,就意味着该字节流可以使用字符流的缓冲区技术,调用其readLine()
方法,使键盘录入的读取过程更加高效便捷。
利用转换流修改键盘录入并打印的代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) {
/* 创建标准输入对象 */
InputStream in = System.in;
/* 利用转换流将字节流转换为字符流 */
InputStreamReader isr = new InputStreamReader(in);
/* 转换后的字节流,就可以像字符流一样使用字符流的缓冲技术 */
BufferedReader bufr = new BufferedReader(isr);
/* 调用BufferedReader的readLine()方法整行获取键盘录入 */
String line = null;
try {
while((line = bufr.readLine()) != null) {
/* 如果输入over 结束键盘录入 */
if(line.equals("over"))
break;
System.out.println(line.toUpperCase());
}
} catch(IOException e) {
throw new RuntimeException("Exception occured...");
}
}
}
将字节流转换为字符流,相当于在字节流上套了两根管子;一根使字节流变成字符流;另一根使字节流可以使用字符流的缓冲技术。
2.OutputStreamWriter类OutputStreamWriter
类用于将字符流转换为字节流。该类的编码表同样可以指定或使用系统默认。
用OutputStreamWriter类修改上面的代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class InputStreamReaderDemo {
public static void main(String[] args) {
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader bufr = new BufferedReader(isr);
/* 创建标准输出对象 */
OutputStream out = System.out;
/* 用转换流将字符流转换为字节流 */
OutputStreamWriter osw = new OutputStreamWriter(out);
/* 使用缓冲字符输出流增强字符输出流 */
BufferedWriter bufw = new BufferedWriter(osw);
String line = null;
try {
while((line = bufr.readLine()) != null) {
/* 如果输入over 结束键盘录入 */
if(line.equals("over"))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
throw new RuntimeException("Exception occured...");
} finally {
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
throw new RuntimeException("资源关闭失败");
}
}
}
}
可以将字节流转字符流的三个步骤简化为一行代码:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
3.转换流的作用
转换流可以指定读写时使用的字符编码集;如不指定,将使用系统默认的编码集,本机默认使用GBK
。下面的代码演示了用UTF-8
写入,用GBK
读取会发生乱码的情况。
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class CharSetDemo {
public static void main(String[] args) {
write();
}
private static void read() {
BufferedReader bufr = null;
try {
/* 使用默认GBK字符集读取数据,结果会出现乱码 */
bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/demo.txt"));
System.out.println(bufr.readLine());
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
private static void write() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
BufferedReader bufr = null;
BufferedWriter bufw = null;
String line = null;
try {
bufr = new BufferedReader(new InputStreamReader(System.in));
/* 指定使用UTF-8字符集写入数据 */
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));
while((line = bufr.readLine()) != null) {
if(line.equals("over")) {
break;
}
bufw.write(line);
bufw.newLine();
bufw.flush();
}
/* 将写入的数据读取打印在控制台 */
read();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
程序输出结果:
你好,这里是虹桥镇~
over
浣犲ソ锛岃繖閲屾槸铏规ˉ闀噡
为了正常显示,用InputStreamReader指定读取时编码集为UTF-8即可
bufr = new BufferedReader(new InputStreamReader(new FileInputStream(
"C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));
程序输出结果:
你好,这里是虹桥镇~
over
你好,这里是虹桥镇~
小扩展
使用System类的方法setIn()和setOut()改变标准输入输出。示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class SystemSetDemo {
public static void main(String[] args) throws IOException {
BufferedReader bufr = null;
BufferedWriter bufw = null;
/* 改变标准输入源,从键盘变为指定目录的文件 */
System.setIn(new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt"));
/* 改变标准输出目的地,从控制台变为指定目录的文件 */
System.setOut(new PrintStream("C:/Users/jeremy/Documents/javaTmp/fos1.txt"));
bufr = new BufferedReader(new InputStreamReader(System.in));
bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = bufr.readLine();
bufw.write(line);
bufw.flush();
bufr.close();
bufw.close();
}
}