字符流:FileReader、BufferedReader
FileWriter、BufferedWriter
字节流:InputStream :读
OutputStream :写
字符流操作字符数据,多用于文本文件。字节流操作字节数据,例如图片文件等。
字符流读入字符数组char[],字节流读入字节数组byte[]。
代码示例:
import java.io.*;
class FileStreamDemo {
public static void main(String[] args) throws IOException{
writeFile();
readFile_1();
readFile_2();
readFile_3();
}
public static void writeFile() throws IOException{
FileOutputStream fos = new FileOutputStream("fos.txt");//文件不存在则创建。
//String类的getBytes()方法获取字节数组。
fos.write("FileStream".getBytes());
/* 字节流的写入不需要刷新。字符流需要刷新是因为,先逐个字节的将数据从流对象写入到一个缓冲区,
再查表,有匹配的字符时,再刷新写入目的地。 */
fos.close();
}
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 readFile_2() throws IOException{
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1){ //读取到buf数组中
System.out.println(new String(buf,0,len));
}
fis.close();
}
public static void readFile_3() throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
//int num = fis.available();
//available方法获取流对象中的字节数,回车符'\r'和换行符'\n'各占一个。
byte[] buf = new byte[fis.available()]; //定义一个刚刚好的缓冲区,不用再循环了。
//但是如果这个数组长度过长,内存就溢出了。
fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
}
复制一个图片文件,用到的流对象:FileInputStream、FileOutputStream。
思路:
1,字节流读取对象InputStream和图片文件相关联。
2,用字节流输出对象OutputStream创建一个文件,用于存储读取到底的图片数据。
3,通过循环读写,完成数据的存储。
代码示例:
import java.io.*;
class CopyPictureDemo{
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("e:\\EVE.jpg"); //读
fos = new FileOutputStream("e:\\EVE_副本.jpg"); //写
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1){ //读取到数组中
fos.write(buf,0,len);
}
}
catch(IOException e){
System.out.println(e.toString());
}
finally{
try{
if(fis!=null)
fis.close();
}
catch(IOException e){
System.out.println(e.toString());
}
try{
if(fos!=null)
fos.close();
}
catch(IOException e){
System.out.println(e.toString());
}
}
}
}
通过复制MP3文件,演示带缓冲区的字节流:BufferedInputStream、BufferedOutputStream。
代码示例:
import java.io.*;
class CopyMp3Demo {
public static void main(String[] args) throws IOException{
long start = System.currentTimeMillis(); //获取时间
copy_1();
long end = System.currentTimeMillis(); //获取时间
System.out.println((end-start)+"毫秒"); //执行时间
}
//通过字节流的缓冲区完成复制
public static void copy_1() throws IOException{
BufferedInputStream bufis =
new BufferedInputStream(new FileInputStream("e:\\GetUp.mp3"));
BufferedOutputStream bufos =
new BufferedOutputStream(new FileOutputStream("e:\\GetUp-副本.mp3"));
byte[] bt = new byte[1024];
int len = 0;
while((len=bufis.read(bt))!=-1){
bufos.write(bt,0,len);
}
bufis.close();
bufos.close();
}
//MyBufferedInputStream 的演示。
public static void copy_2() throws IOException{
MyBufferedInputStream bufis =
new MyBufferedInputStream(new FileInputStream("e:\\GetUp.mp3"));
BufferedOutputStream bufos =
new BufferedOutputStream(new FileOutputStream("e:\\GetUp-副本.mp3"));
int by = 0;
while((by=bufis.myRead())!=-1){
bufos.write(by);
}
bufis.myClose();
bufos.close();
}
}
上一节代码CopyMp3Demo中copy_2的演示。
代码示例:
import java.io.*;
class MyBufferedInputStream {
private InputStream in = null;
private byte[] buf = new byte[1024];
//pos指针从数组中取元素,count计数器存储数组中的元素个数。
private int pos = 0, count = 0;
MyBufferedInputStream(InputStream in){
this.in = in;
}
//一次读一个字节,从缓冲区(字节数组)获取。
public int myRead() throws IOException{
//通过in对象读取硬盘上的数据,并存储到buf中。
if(count==0){
count = in.read(buf);
if(count<0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&255; //从byte类型提升到int类型
}
else if(count>0){
byte b = buf[pos];
count--;
pos++;
return b&255; //从byte类型提升到int类型
}
return -1;
}
public void myClose() throws IOException{
in.close();
}
}
-1的二进制表示:补码表示
0000 0001
1111 1110
1111 1111 -1 读到8个1,就返回了-1,所以没有执行read方法。
为什么read()返回是int类型,而不是byte类型?
因为返回byte类型的话,遇到连续8个1时,返回-1,误以为是结束标记,就不执行whlie循环。
提升到int类型后,32个1才表示“-1”,才是结束标记。
byte: -1 ----> int: -1 //一个int类型是32个二进制位,而一个字节8个二进制位,所以被提升了。
11111111 11111111 11111111 11111111 -1 //提升时若要还是-1,应在前面补1。
---> 00000000 00000000 00000000 11111111 255 //但为了避免遇到8个1返回-1,被误以为是结束标记。
//应该在前面补0。
11111111 --->提升了一个int类型,那不还是-1吗?是-1的原因是因为在8个1前面补的是1导致的。
那么我只要在前面补0,既可以保留原字节数据不变,又可以避免-1的出现。
怎么补0呢?
11111111 11111111 11111111 11111111
& 00000000 00000000 00000000 11111111 255 //和255做与运算,补0.
--------------------------------------------
00000000 00000000 00000000 11111111
事实上,read方法返回int类型时,进行补0的操作,
而write方法先进行去0的操作,再把11111111写入目的地,最终写入的数据还是这一个字节11111111。
System.out:对应标准的输出设备,控制台。
System.in:对应标准的输入设备,键盘。
需求:
1,通过键盘读取录入。
2,当录入一行数据后,就将该行数据进行打印。
3,如果录入“over”的话,那么停止录入。
其实就是带缓冲区的IO流中readLine()的原理。
代码示例:
import java.io.*;
class ReadInDemo{
public static void main(String[] args) throws IOException{
InputStream in = System.in; //System.in对象,读取键盘录入。
StringBuilder sb = new StringBuilder();
while(true){
int ch = in.read();
if(ch=='\r')
continue;
if(ch=='\n'){ //读到换行符,就打印缓冲区中的数据
String s = sb.toString();
if(s.equals("over"))
break;
System.out.println(s);
sb.delete(0,sb.length()); //清空缓冲区。
}
else
sb.append((char)ch);
}
}
}