首页 > 代码库 > 黑马程序员-IO流
黑马程序员-IO流
IO概述
IO流用来处理设备之间的数据传输,java 对数据操作的是通过流的方式
java用于操作流的对象都在IO包中。
流按操作数据分为两种:字节流与字符流
流按流向分为:输入流和输出流
字符流对象中融合了编码表。只有文字涉及到编码
字节流的抽象基类:
inputStream,OutputStream
字符流的抽象基类:
Reader Writer
Writer 方法:
write(String str) 写入字符串。
write(int c) 写入单个字符。
write(String str, int off, int len)
写入字符串的某一部分。
write(char[] cbuf)
写入字符数组。
write(char[] cbuf, int off, int len) //从off开始,len表示要写入的个数。
flush() 刷新该流的缓冲。
先学习下字符流的特点:
既然IO流是用于操作数据的,那么数据最常见的体现形式:文件。
Reader:数据的读取,要么读字节,要么读字符, 要么读取字节。没有读取字符串什么的东东。
BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader
read(char[])方法:
‘1234567‘ ,char[3] read(char[]),第一次读取缓冲区内容[123],第二次[456],
第三次[756],但是我们第三次取的时候只能取7,5和6没有被取代
FileReader:
FileWriter(String fileName, boolean append) :对已有文件的续写,boolean判断是否续写。
字符流缓冲区 缓冲区作用:提高效率(里面封装了数组)
|--提高了对数据的读写效率
|--对应类:
BufferWriter
BufferReader
|--缓冲区要结合流才可以使用
|--在流的基础上对流的功能进行了加强
装饰设计模式:
当要对自己已有的对象进行增强时,可以定义类,
将已有对象传入,基于已有的功能,并提供增强功能。
那么该自定义类称为装饰类。
装饰类通常会通过构造方法接受被装饰的对象。
并基于被装饰的对象的功能,提供更强的功能。
装饰类的由来
应用:去年的功能,要增强,用装饰设计,既可以用增强的方法,也可以用以前的方法
通过多态的形式,提高扩展性
class buffered extends myReader{//该类只是增强版,但仍然是myReader的子类或者接口
buffered(myReader,my){//将其父类传入
}
}
MyReader//专门用于读取数据
|-MyTextReader
|-MyMediaReader
|-MyDataReader
|-MyBufferedReader
装饰模式比继承模式灵活,避免了继承体系的臃肿。为了几个方法而去继承降低了类与类之间的关系。
装饰类:对已有的类进行增强 将已有的类传入,定义需要的增强的方法。然后再使用装饰类就可以使用该增强的方法了。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能所以装饰类和被装饰类通常都属于一个体系中,从继承结构变成了组合结构(我里面有你)。
BufferedReade类中特有方法readLine,做一个自定义myReadLine
思路;
1.定义一个my类
2.定义一个myreadline方法
3.定义一个myclose方法
ps;父类有抽象方法,子类不覆盖无法new对象
import java.io.*;
class lianxi{
public static void main(String[] args) throws Exception{
myBufferedReader mybu = new myBufferedReader(new FileReader("fi.txt"));
String line = null;
while ((line=mybu.myReadLine())!=null){
System.out.println(line);
}
mybu.myclose();
}
}
class myBufferedReader{
private FileReader fr ;
myBufferedReader(FileReader fr) throws Exception{
this.fr= fr;
}
public String myReadLine() throws Exception{
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch=fr.read())!=-1){
if (ch==‘\r‘){//ch不用强转,因为换行字符也被转成了int型\r=13,\n=10
continue;
}
if(ch==‘\n‘)
return sb.toString();
else
sb.append((char)ch);
}
if (sb.length()!=0)
return sb.toString();//有条件的return 不能算吧!
return null;
}
//覆盖抽象方法
public void myclose() throws Exception{
fr.close();
}
public void read(char[] cbuf, int off, int len){
//直接返回其子类(调用本方法的对象)
return fr.read(cbuf, off, len)
}
}
lineNumberReader BufferedReader的子类:跟踪行号
int getLineNumber();获取当前行号
void setLineNumber(int lineNumber)
自定义字节流InputStream缓冲区
思路:定义read方法,使用数组,返回低八位
1.抓一票数据放在byte数组中,并且返回数组的长度count
2.从数组取出来,定义指针pos,到结尾后从零位再重新来
3.count-- 等于零,在抓一票
class myBufferedInputStream{
private InputStream in;
int count =0 ;
int pos=0;
byte[] by = new byte[1024];
myBufferedInputStream(InputStream in) throws Exception{
this.in=in;
}
public void myclose() throws Exception{
in.close();
}
public int myread()throws Exception{
if (count==0){//count=0,表示数组取完
pos=0;
//一次读一个字节,从缓冲区(字节数组)获取,这个数组封装在bufferedReader内部
count =in.read(by);//将流存入1024的数组中
byte b = by[pos];//取出一个字节
count--;
pos++;
return b&255;//返回调用者读取的一个字符。
}
if (count>0){//第二次调用read(),count=1023
byte b =by[pos];
pos++;
count--;
return b&255;
}
if (count<0){
return -1;
}
return -1;
}
}
在读取二进制的时候,可能会读到11111111 ,-1字节。但是read方法返回的是int型。所以会进行类型提升。
为防止-1与我们的结束标记相同,导致读取失败。会在提升为int型是,将高24位全部补零,即%255.
键盘录入:最常见写法
BufferedReader bur= new BufferedReader(new InputStreamReader(System.in));
它与System.in的区别,前者转成了【字符流】,可以直接读取一行,
而后者是inputStream流只能读一个【字节】
读取键盘录入
System.in:对应标准输入设备:键盘
System.out.:对应标准输出设备,控制台
转换流:
InputStreamReader:字节流转字符流,读取的时候按【字符流】读取。用字符更方便的读取
InputStreamReader(【InputStream】 in)
创建一个使用默认字符集的 InputStreamReader。
OutputStreamWtriter:【注意这个还得flush】为什么要转?
本身仍是字符流。但是存储的时候一字节的形式存储
OutputStreamWriter(OutputStream out)
创建使用默认字符编码的 OutputStreamWriter。
转换流:真正的应用:使用指定的码表进行读取和存储。
* 1.InputStreamReader(InputStream in, Charset cs)
* 2.OutputStreamWriter(OutputStream out, Charset cs)
记住;转换流什么时候使用,通常,涉及到字符编码转换的时候。
字符串默认:GBK
InputStreamReader的子类FileReader:使用默认编码表,当使用其他的编码表
就只能使用父类来制定码表。
区别:
1,
转换流:字节流+编码表
转换流的子类:FileReader&FileWriter:字节流+本地默认码表(GBK)。
2,
转换流可以指定任意码表。
而转换流子类需要构造一个其父类的对象有字节和字符。
PrintWriter在什么情况下可以自动刷新?
构造函数中,如果参数是输出流,那么可以通过指定另一个参数true完成自动刷新,该true对println方法有效。
PrintWriter对象的println自动刷新图
注意,自动刷新功能只对字节流对象和字符流对象有效。
字节流为什么不用刷新?
字符流需要刷新是因为,例如一个字节,因为一个汉字有两个字节,读完一个字节
要临时存起来,而字节流不需要缓冲,可以直接将字节输出。因为是对字节最小单位操作
实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,
而字符流在操作时使用了缓冲区,通过缓冲区再操作文件
什么叫缓冲区:简单理解为一段内存区域。
某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,
此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,
以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,
所以使用了缓冲区暂存数据。例如一个汉字,又两个字符组成,必须读完两个字节之后,才能去查表,
找出对应的汉字,所以第一个字节就要先存放在内存中(缓存)。
而字节流,获取一个字节就可以查表,然后存到指定的地方。
OutputStream 特有方法:
int available() ;返回流中(即关联文件的字节数)的字节数,包括换行。
用于定义刚刚好的缓冲区的长度 byte[] chs = new byte[is.available()];
有局限,慎用,还是定义1024的整数倍最好。虚拟机内存。
BufferedOutputStream
该类通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
缓冲区原理:通过底层流对象,将数据存到内存的数组中(如:存了[1024]),然后再从内存中取。
如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,
并将其转换为字符后返回,而这是极其低效的。
流操作的基本规律:
明确三个完成。
1.明确源和目的。
源:输入流。Reader InputStream
目的:输出流。Writer OutputStream
2.明确,操作的数据是否是纯文本。
是:字节流
否:字符流
3.体系明确后,明确使用哪个具体对象
通过设备进行区分:
源设备:内存,硬盘,键盘
目的设备:内存,键盘,控制台。
4. 明确需要额外功能吗?如指定编码表,高效等.
实例:1.将一个文本文件数据存储到另一个文件中。复制文件分析;
(1):从哪来来
源: 因为是源,使用输入流。
是否是纯文本:是选择;reader,体系明确
设备: 源设备:硬盘上的文件,使用FileReader
FileReader fr = new FileReader("a.txt");
是否需要提高效率:是,加入BufferedReader
..........
(2):到哪儿去
目的: 因为是目的,使用输出流。
是否是纯文本:是,writer
设备:硬盘,一个文件。FileWriter
FileWriter fr = new FileWriter("b.TXT");
是否需要提高效率:是,加入BufferedWriter
练习:异常日志的信息 建立异常日志文件
如何将异常信息存到文件中:
throwble类的方法 (包括erro和exception
void printStackTrace(PrintStream s)
将此 throwable 及其追踪输出到指定的输出流。
Reader:
--|int read():返回要读取的字符,以Int型表示
--|int read(char[] ch):返回的字符数量
InputStream:
--|read();下一个数据字节;如果到达流的末尾,则返回 -1。
Writer:
--|write(int), writr(char[]),write(String )返回值是:void,可写入String,要刷新
--|写入单个字符。要写入低16位。
OutputStream :
--|wirte(byte[]),返回值是:void,无法写入字符串,可用String.getBytes(),不需要flush()
--|write(int) 写入字节,即低八位。
BufferedInputStream:
--|
BufferedReader:
--|eadLine() 读取一个文本行。
BufferedOutputStream:
--|write(),write(char[])没有返回值
BufferedWriter:
--|newLine() 写入一个行分隔符。跨平台