黑马程序员---java基础------------------IO字符流、字节流、流转换
字符流的两个基类:Writer Reader.
在两个基类的下面分别对应的子类对象有FileWriter、FileReader。
加入缓冲区的有BufferdWriter和BufferdReader,缓冲区的技术原理就是数组的概念。
需求:在硬盘上,创建一个文件并写入一些文字数据
import java.io.*;
class FileWriterDemo {
public static void main(String[] args) throws IOException {
//创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件;而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。
//其实该步就是在明确数据要存放的目的地。
FileWriter fw = new FileWriter("demo.txt");
//调用write方法,将字符串写入到流中。
fw.write("abcde");
//将数据刷到目的地中。
//fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。 //将数据刷到目的地中。
//close和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
}
演示对已有文件的数据续写
import java.io.*;
class FileWriterDemo3 {
public static void main(String[] args) throws IOException {
//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter("demo.txt",true);
fw.write("nihao\r\nxiexie");
fw.close(); //close的时候已经执行了刷新的操作
}
}
import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException { //对于这些操作中,为了代码简便,我们直接进行了异常抛出,实际开发中不应该这样,应该try
FileReader fr = new FileReader("demo.txt"); //catch--finally去处理异常,finally关闭资源的时候要分开判断if(xx!=null)....xx.close()
//read():一次读一个字符。而且会自动往下读。当返回的是-1的时候说明已经读完了
int ch = 0;
while((ch=fr.read())!=-1) {
System.out.println((char)ch);
}
fr.close();//资源一定要关闭啊!!!
}
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt");
//该read(char[])返回的是读到字符个数。
char[] buf = new char[1024];//这里的字符长度定义成1024或者它的整数倍是最合适的。
int num = 0;
while((num=fr.read(buf))!=-1) {
System.out.println(new String(buf,0,num)); //这边这样写可以读取有效的字符数,从而减少循环次数
}
fr.close();
}
}
加入缓冲技术
import java.io.*;
class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//创建一个字符写入流对象。
FileWriter fw = new FileWriter("buf.txt");
BufferedWriter bufw = new BufferedWriter(fw); //进行缓冲处理的时候是不是应该有要被处理的对象呢?就在这里了!!
for(int x=1; x<5; x++) {
bufw.write("abcd"+x);
bufw.newLine(); //这是一个跨平台的换行符,我们也可以自己判断来实现,后面将有介绍
bufw.flush();
}
//bufw.flush(); //只要用到缓冲区,就要记得刷新。
bufw.close(); //其实关闭缓冲区,就是在关闭缓冲区中的流对象。
}
}
import java.io.*;
class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null) { //这里是读取一行的判断条件 readLine方法返回的是不包含换行符的内容。
System.out.print(line);
}
bufr.close(); //再次的提醒,关闭了缓冲区就是关闭了流对象,它已经操作了刷新的动作
}
}
装饰设计模式:自定义一个类中包含一个功能和readLine的方法
import java.io.*;
class MyBufferedReader extends Reader { //extends Reader后面要将两个方法复写
private Reader r;
MyBufferedReader(Reader r) {
this.r = r;
}
public String myReadLine()throws IOException { //这也是一种装饰设计模式的体现,将原来的对象传进来,对其功能进行增强,具有了读取一行的
StringBuilder sb = new StringBuilder(); //功能
int ch = 0;
while((ch=r.read())!=-1) {
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf,off,len) ;
}
public void close()throws IOException {
r.close();
}
public void myClose()throws IOException {
r.close();
}
}
class LineNumberReaderDemo {
public static void main(String[] args)throws IOException {
FileReader fr = new FileReader("PersonDemo.java");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
lnr.setLineNumber(100); //设置行号从哪个值开始
while((line=lnr.readLine())!=null) {
System.out.println(lnr.getLineNumber()+":"+line); //得到行号
}
lnr.close();
}
}
自定义的lineNumberReader
import java.io.*;
class MyLineNumberReader extends MyBufferedReader {
private int lineNumber;
MyLineNumberReader(Reader r) {
super(r);
}
public String myReadLine()throws IOException {
lineNumber++;
return super.myReadLine();
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
}
通过缓冲区复制一个.java文件。这里用了正规的异常处理
import java.io.*;
class CopyTextByBuf {
public static void main(String[] args) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try { 匿名对象: FileReader fr=new FileReader("BufferedWriterDemo.java");
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));
String line = null;
while((line=bufr.readLine())!=null) {
bufw.write(line); 读取到一行就打印
bufw.newLine(); 换行了
bufw.flush();
}
}catch (IOException e) {
throw new RuntimeException("读写失败");
}finally {
try { //对资源要判断后再关闭, 分开的两个资源,分开try
if(bufr!=null)
bufr.close();
} catch (IOException e) {
throw new RuntimeException("读取关闭失败");
} try {
if(bufw!=null)
bufw.close();
} catch (IOException e) {
throw new RuntimeException("写入关闭失败");
}
}
}
}
字节流的两个基类:InputStream OutputStream
字节流的子类对象:FileInputStream FileOutputStream
加入缓冲的子类对象:BufferedInputStream BufferedOutputStream
import java.io.*;
class FileStream {
public static void main(String[] args) throws IOException {
readFile_3();
}
public static void readFile_3()throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
// int num = fis.available(); //这个方法返回的是文件的字符数
byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区。好处是:不用在循环了,但这也有局限性,当一个文件很大超出内存时,可能会冒烟的
fis.read(buf);
System.out.println(new String(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) { //.read(buf),当读取数组元素完成时,返回-1
System.out.println(new String(buf,0,len));
}
fis.close();
}
public static void readFile_1()throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
int ch = 0;
while((ch=fis.read())!=-1) { //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());
fos.close();
}
}
演示mp3的复制。通过缓冲区。
import java.io.*;
class CopyMp3 {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copy_2();
long end = System.currentTimeMillis();
System.out.println((end-start)+"毫秒");
}
public static void copy_2()throws IOException {
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
int by = 0;
//System.out.println("第一个字节:"+bufis.myRead());
while((by=bufis.myRead())!=-1) {
bufos.write(by);
}
bufos.close();
bufis.myClose();
}
//通过字节流的缓冲区完成复制。
public static void copy_1()throws IOException {
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
int by = 0;
while((by=bufis.read())!=-1) {
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
import java.io.*;
class MyBufferedInputStream {
private InputStream in;
private byte[] buf = new byte[1024*4];
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;
}
else if(count>0) {
byte b = buf[pos];
count--;
pos++;
return b&0xff;
}
return -1;
}
public void myClose() throws IOException {
in.close();
}
}
...................................这个原理要弄清楚.................................................................
11111111-111111110000000000101001001010100101010010101001010
byte: -1 ---> int : -1; 00000000 00000000 00000000 11111111 255
11111111 11111111 11111111 11111111
11111111 -->提升了一个int类型 那不还是-1吗?是-1的原因是因为在8个1前面补的是1导致的。 那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。 怎么补0呢?
11111111 11111111 11111111 11111111 &00000000 00000000 00000000 11111111 ------------------------------------ 00000000 00000000 00000000 11111111
0000-0001 1111-1110 000000001 1111-1111 -1
结论: 字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。 因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1. 那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。 所以,为了避免这种情况将读到的字节进行int类型的提升。 并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在写入数据时,只写该int类型数据的最低8位。
............................................................................................................
读取键盘录入:
import java.io.*;
class ReadIn {
public static void main(String[] args) throws IOException {
InputStream 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("over".equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());
}
else
sb.append((char)ch);
}
}
}
流转换:
import java.io.*;
class TransStreamDemo {
public static void main(String[] args) throws IOException {
//获取键盘录入对象。
//InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader bufr = new BufferedReader(isr);
//键盘的最常见写法。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); 使用了匿名对象简化了代码
// OutputStream out = System.out;
// OutputStreamWriter osw = new OutputStreamWriter(out); //红色的部分改掉之后就可以改变标准输入输出设备了比如吧输入该成一个文件
// BufferedWriter bufw = new BufferedWriter(osw); //异常的日志信息就是将输出改成一个文件PrintStream ps = new PrintStream("exeception.log");
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); //System.setOut(ps);设置输出
String line = null;
while((line=bufr.readLine())!=null) {
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
流操作的基本规律:
通过三个明确来完成:
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer。
2,操作的数据是否是纯文本。 是:字符流。 不是:字节流。
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘。键盘
目的设备:内存,硬盘,控制台。
分析案例:
1,将一个文本文件中数据存储到另一个文件中。复制文件。
源:因为是源,所以使用读取流。InputStream Reader
是不是操作文本文件。 是!这时就可以选择Reader 这样体系就明确了。
接下来明确要使用该体系中的哪个对象。
明确设备:硬盘。 一个文件。 Reader体系中可以操作文件的对象是 FileReader
是否需要提高效率:是!。加入Reader体系中缓冲区 BufferedReader.
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream Writer
是否是纯文本。 是!Writer。
设备:硬盘,一个文件。 Writer体系中可以操作文件的对象FileWriter。
是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
2,需求:将键盘录入的数据保存到一个文件中。
源:InputStream Reader
是不是纯文本?是!Reader
设备:键盘。对应的对象是System.in. 不是选择Reader吗?System.in对应的不是字节流吗? 为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方
便的。 所以既然明确了Reader,那么就将System.in转换成Reader。 用了Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率吗?需要。
BufferedWriter bufw = new BufferedWriter(fw);
扩展,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。 但是FileWriter是使用的默认编码表。GBK. 但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以
指定。 所以要使用的对象是OutputStreamWriter。 而该转换流对象要接收一个字节输出流。FileOutputStream 而且还可以操作的文件的字节输出
流。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
转换流什么时候使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时, 需要用到转换流。