黑马程序员--IO(一)--概述、字符流、字节流、流操作规律
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、概述
1、IO流(Input Output):用来处理设备之间的数据传输。java对数据的操作通过流的方式。java用于操作流的对象都在IO包中。分类:
流按流分为:输入流和输出流。将外设中的数据读取到内存中:输入。将内存中数据写入到外设中:输出。
流按操作数据分为:字节流和字符流。
PS:
流只能操作数据,不能操作文件。
2、常用基类
字节流的抽象基类:InputStream OutputStream
字符流的抽象基类:Reader Writer
PS:
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:Reader的子类FileReader。
二、字符流
1、简述由于字节流是单字节处理方式,当处理汉字这样双字节字符时,它就显得不太方便,这时我们通过字节流+编码表的方式获取字符流。
即让字节流读取文字字节数据后,不直接操作而是先查指定的编码表,获取对应的文字。来便捷文字操作。
字符流的抽象基类:Reader Writer
2、字符流的读写
读取方式有两种:
read():一次读一个字符。而且会自动往下读。
read(char[] cbuf):将字符读入数组。更优。
//字符流读取数据方式有两种
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args)throws IOException
{
//创建一个文件读取流对象和指定名称的文件相关联。
//要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
int ch = 0;
//第一种方式:调用读取流对象的read()方法,它一次读一个字符。而且会自动往下读。
while((ch=fr.read())!=-1)
{
System.out.println("ch="+(char)ch);
}
FileReader fr2 = new FileReader("demo.txt");
//第二种方式:read(char[] cbuf):将字符读入数组。以数组形式读取,一次性输出。更优。
char[] buf = new char[1024];
int num = 0;
while((num=fr2.read(buf))!=-1)
{
System.out.println(new String(buf,0,num));
}
fr.close();
}
}
/*
字符流:写入、续写、异常处理。
需求:在硬盘上,创建一个文件并写入一些文字数据。处理文字数据首先要考虑字符流。
FileWriter:专门用于操作文件的Writer子类对象。后缀名是父类名;前缀名是流对象功能。
FileWriter会将文件创建到指定目录下,如果该目录下已有同名文件,将被覆盖。
flush():用于对文件刷新。作用:将写入字符流输出到指定文件中。
close和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
*/
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)
{
FileWriter fw = null;
try
{ //创建一个FileWriter对象。初始化时必须明确被操作文件。在构造函数中加入true,可实现对文件续写。
fw= new FileWriter("demo.txt",true);
//调用Write方法,字符串写入到流中。
fw.write("---www\r\neeeee");
}
//处理异常
catch(IOException e)
{
System.out.println("wrong:"+e.toString());
}
//用finally关闭流,可防止代码异常时流无法关闭的问题。
finally
{
try
{
if(fw!=null)
//关闭流资源,但是关闭前会刷新一次内部的缓冲的数据。
fw.close();
}
catch(IOException e)
{
System.out.println("wrong too:"+e.toString());
}
}
}
}
3、字符流的缓冲区-->BufferedWriter、BufferedReadera 作用:在流的基础上对流的功能进行了增强。所以缓冲区创建之前,必须有对应的流对象。
b 原理:将数据以数组形式储存在内部,最后一次性取出。减少了数据的在内存上的读取频率,提高效率。
newLine():写入换行,返回数据及回车符。属于BufferedWriter类。可以跨平台。
readLine():读取一行数据。属于BufferedReader类。
原理:使用read方法,缓冲读取到的字符并判读换行标记,将标记前的缓冲数据变成字符串返回。
//缓冲区的应用:通过缓冲区复制一个java文件
import java.io.*;
class CopyByBuf
{
public static void main(String[] args)
{
//缓冲区要先初始化,不然作用域只在try{}范围内。
BufferedWriter bufw = null;
BufferedReader bufr = null;
try
{
//创建字符流缓冲区并加载字符流文件
bufr = new BufferedReader(new FileReader("helloworld.java"));
bufw = new BufferedWriter(new FileWriter("hello_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
{
if(bufr!=null)
bufr.close();
}
catch(IOException e)
{
throw new RuntimeException("读取关闭失败!");
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch(IOException e)
{
throw new RuntimeException("写入关闭失败!");
}
}
}
}
}
LineNumberReader:跟踪行号的缓冲字符输入流。LineNumberReader是BufferedReader的子类。此类定义了方法setLineNumber(int)和getLineNumber(),它们可分别用于设置和获取当前行号。
import java.io.*;
class LineNumberReaderDemo
{
public static void main(String[] args)throws IOException
{
FileReader fr = new FileReader("helloworld.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();
}
}
PS:1 缓冲区要结合流才可以使用。
2 无论是读一行还是多个字符,最终在硬盘上仍是一个一个读取。
3 read()和readLine()区别:
read():读取字符数据,它覆盖了父类的read方法。
readLine():另外开辟一个缓冲区,存储的是原缓冲区一行的数据,不包含换行符。
4、装饰设计模式
装饰设计模式:对原有类进行功能的改变、增强。装饰类和被装饰类通常都属于一个体系中。
装饰和继承比较:
//装饰设计模式
class Person
{
public void chifan()
{
System.out.println("吃饭");
}
}
//继承方法-->覆盖
class NewPerson extends Person
{
public void chifan()
{
System.out.println("漱口");
super.chifan();
System.out.println("甜点");
}
}
//装饰类-->补充
class SuperPerson
{
private Person p;
SuperPerson(Person p)
{
this.p = p;
}
public void superChifan()
{
//基于原来方法并给予丰富
System.out.println("漱口");
p.chifan();
System.out.println("甜点");
}
}
class DecorateDemo
{
public static void main(String[] args)
{
Person p = new Person();
NewPerson np = new NewPerson();
np.chifan();
System.out.println("-------------------");
SuperPerson sp = new SuperPerson(p);
sp.superChifan();
}
}
继承的体系:Reader 专门用于读取数据的类。
|--TextReader:用于读取文本
|--MediaReader:用于读取媒体
功能扩展:提高读取效率,加入缓冲技术
Reader
|--TextReader
|--BufferTextReader
|--MediaReader
|--BufferMediaReader
加入再进行功能扩展,就会发现这个体系会越来越臃肿,不够灵活。
装饰体系思路:
既然加入的都是同一种技术--缓冲。继承是让缓冲和自己的流对象相结合。根据劳动分工,提高效率的特点。
我们可以将缓冲(同一属性的扩展功能)进行单独封装,各司其职,当需要时再针对性调用。
1、将缓冲进行单独封装
class Buffer
{
Buffer(TextReader text){}
Buffer(MediaReader media){}
}
2、优化。用多态封装,提高扩展性。
class BufferReader extends Reader
{
private Reader r;
BufferReader(Reader r){}
}
3、装饰体系
Reader 专门用于读取数据的类。
|--TextReader
|--MediaReader
|--BufferReader
由上面可知装饰和继承的异同:
相同点:都是用于功能的扩展。
不同点:
a 装饰扩展性更强;b 装饰避免了继承臃肿的体系;c 装饰降低了类之间的关联性。
d 装饰是对原有类的补充,继承是覆盖或者说是替代。
三、字节流
1、简述a 字节流:基本操作和字符流类相同。但它不仅可以操作字符,还可以操作其他媒体文件。
b 由于媒体文件数据都是以字节存储的,所以字节流对象可直接将媒体文件的数据写入到文件中,不用进行刷新动作。
c 字节流基类:InputStream(读)、OutputStream(写)
//读取字节流方式
import java.io.*;
class FileStream
{
public static void main(String[] args)throws IOException
{
writerFile();
readFile_1();
readFile_2();
readFile_3();
}
//方法一:单个字节获取
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)//循环是为了获取数据的大小
{
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();
//定义一个恰好的缓冲区,不用循环操作。
byte[] buf = new byte[fis.available()];
fis.read(buf);
//System.out.println("num="+num);
System.out.println(new String(buf));
fis.close();
}
public static void writerFile()throws IOException
{
//输出字节流到指定文件
FileOutputStream fos = new FileOutputStream("fos.txt");
//不涉及任何转换,所以不用刷新。
fos.write("abcde".getBytes());
fos.close();
}
}
PS:1 FileOutputStream、FileInputStream的flush方法内容为空,没有任何实现,调用没有意义。
2 int available():获取对象的数据大小。它是InputStream的特有方法。因为字节流可以操作媒体文件,所以直接获取数据大小,
可能因为获取的数据太大造成内存溢出。虚拟机默认内存是64M。
2、字节流的缓冲区:提高字节流读写效率。
2.1 读写存在类型提升和强转现象
read():会将字节byte类型提升为int类型。原因:防止直接返回byte类型值,造成read误判为程序结束标记的情形。
write():会将int类型强转为byte类型。原因:有始有终,保障数据唯一性。
/*
自定义字节流的缓冲区:
1,定义数组
2,定义指针
3,定义计数器。
*/
import java.io.*;
//装饰类
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf = new byte[1024];
private int pos = 0, count = 0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//返回数据类型是int类型不是byte类型,这样避免了值为-1,正常情形下程序的停止。
public int myRead() throws IOException
{
//每句第一次读取数据的情形。通过in对象读取硬盘上数据,并存储在buf中。并计数。
if(count==0)
{
count = in.read(buf);
if(count<0)
return -1;
pos = 0;
//数组以byte类型存储。
byte b = buf[pos];
count--;
pos++;
//b&255,此时的b已经是int类型,它&255会只获取一个8位,补充位补0,防止-1的出现。
return b&255;
}
//每句话第二次读取数据的情形
else if(count>0)
{
byte b = buf[pos];
count--;
pos++;
//0xff=255
return b&0xff;
}
return -1;
}
public void myClose() throws IOException
{
in.close();
}
}
PS:它们的转变原理是:
因为数据的字节流形式是二进制:0,1;
该文件中,因为字节流中会存在一个字节全是1的情形:1111-1111
定理:负数的二进制等于整数二进制取反+1。
0000-0001 1的二进制
1111-1110 取反
0000-0001 +1
1111-1111 -1的二进制
因为返回指针值是byte类型,但是类的返回值是int类型,所以返回值存在类型提升问题。
这样定义其实是为了解决直接返回值为-1,造成read方法误判断为程序结束标记的情形。即:while((ch=fis.read())!=-1)
byte:-1 ----> int: -1;
00000000 00000000 00000000 11111111类型提升时,若补位为0,则值为 255
11111111 11111111 11111111 11111111 类型提升时,若补位为1,则值为 -1
当byte:-1提升到int类型,若补位为1,其值仍为-1。read方法以读到-1为停止标记,造成程序停
止,无法满足需求。
所以我们要在前面补0,这样就可以避免-1的出现。
补0方式:&255
11111111 11111111 11111111 11111111
& 00000000 00000000 00000000 11111111
------------------------------------------------------------
00000000 00000000 00000000 11111111
同时这也意味着,我们读取完后有4个字节需要写入,这样其实改变了原数据,
所以write实际执行时,会执行一个强转动作,只执行最后一个有效字节。
四、流操作规律
1、键盘录入1.1 标准输入输出流
System.in:对应的标准输入设备:键盘。 它是InputStream类型。
System.out:对应的标准的输出设备,控制台。它是PrintStream类型。
1.2整行录入
当使用输入流进行键盘录入时,只能单字节录入。为了提高效率,可以自定义一个数组将一行字节进行储存。当一行录入完毕,
再放回本行录入内容。这种方式正是字符流的readLine方法的原理。
那我们为什么不直接使用readLine方法呢?但由于readLine方法是字符流BufferedReader类中方法。而键盘录入的read方法是字节流
InputStream的方法。这时我们必须将字节流转换成字符流再使用readLine方法。这样我们就用到了转换流。
1.3 转换流:InputStreamReader、OutputStreamWriter
作用:当字节流中数据都是字符时,将字节流转换成字符流。
1.3.1 InputStreamReader:将字节流转成字符流步骤:
a 获取键盘录入对象
InputStream in = System.in;
b 将字节流对象转成字符流对象,使用转换流。
InputStreamReader isr = new InputStreamReader(in);
c 为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReader br = new BufferedReader(isr);
以上步骤可简化为:最常用方式
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
1.3.2 OutputStreamWriter:将字符流转成字节流。
以字符形式录入,以字节形式存储到硬盘。步骤和InputStreamReader相似。
//转换流演示
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象
//InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流--> InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//加入缓冲区
//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);
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
2、流操作的基本规律:2.1
源:键盘录入。
目的:控制台。
2.2 需求:想把键盘录入的数据存储到一个文件中。
源:键盘。
目的:文件。
文件是字符形式。使用InputStreamReader。
2.3 需求:想要将一个文件的数据打印在控制台上。
源:文件。
目的:控制台。
控制台获取的是字节形式。使用OutputStreamWriter。
2.4 如何选择流对象?
通过三个明确,确定流对象的使用:
a 源和目的。
源:读取流。InputStream Reader
目的:输出流。OutputStream Writer
b 明确体系:操作的数据是否是纯文本。
是:字符流
否:字节流
c 明确要使用的具体对象。通过设备来进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
2.5 事例分析:
2.5.1 需求:将一个文件中数据存储到另一个文件中。复制文件
1)源:读取流。InputStream Reader
明确体系:数据类型:纯文本:Reader
明确对象:硬盘上的一个文件。Reader体系中可以操作文件的对象是:FileReader。
FileReader fr = new FileReader("a.txt");
提高效率
BufferedReader bufr = new BufferedReader(fr);
2)目的:输出流:OutputStream Writer
明确体系:数据类型:纯文本:Writer
明确对象:硬盘上的一个文件。Writer体系中可以操作文件的对象FileWriter。
FileWriter fw = new FileWriter("b.txt");
提高效率
BufferedWriter bufw = new BufferedWriter(fw);
2.5.2 需求:将键盘录入的数据保存到一个文件中。
1)源:InputStream Reader
明确体系:数据类型:纯文本:Reader。
明确对象:键盘。对应的是System.in。
但Reader是字符流,System.in是字节流。为了操作键盘的文本数据,所以字符流最方便。
所以既然明确了Reader,那么就将System.in转换成Reader。用Reader体系中的转换流:InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
提高效率
BufferedReader bufr = new BufferedReader(isr);
2)目的:OutputStream Writer
明确体系:数据类型:纯文本:Writer。
明确对象:硬盘上的一个文件:FileWriter。
FileWriter fw = new FileWriter("c.txt");
提高效率
BufferedWriter bufr = new BufferedWriter(fw);
扩展:想要把录入的数据按照指定的编码表(utf-8)存到文件中。
2)目的: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);
/*
练习:将键盘录入的数据按照utf-8编码表存入文件中。
思路:
1)源:InputStream Reader
明确体系:数据类型:纯文本:Reader。
明确对象:键盘:System.in。因为是文本数据,Reader最好操作,
所以需要借助Reader体系中的转换流:InputStreamWriter。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
2)目的:OutputStream Writer
明确体系:数据类型:纯文本:Writer
明确对象:硬盘上的文件:FileWriter。但是FileWriter是使用的默认编码表:GBK。因为存储时需要加入
指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。
而该转换流对象要接受一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream。
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(),"UTF-8"));
*/
import java.io.*;
class TransStreamDemo1
{
public static void main(String[] args) throws IOException
{
//键盘录入的最常见写法:
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("lady.txt"),"UTF-8"));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
运行结果:
PS:
1 转换流什么时候使用?
通常在涉及到字符编码转换时使用。它是字符和字节之间的桥梁。
2 异常日志信息
当程序在执行出现不希望直接给用户看的问题时,我们需要以异常日志的形式储存信息,方便程序员查看、调整。
//异常的日志信息独立反映。
import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfoDemo
{
public static void main(String[] args)throws IOException
{
try
{
int[] arr = new int[2];
System.out.println(arr[3]);
}
catch(Exception e)
{
try
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//H:24小时制
String s = sdf.format(d);
PrintStream ps = new PrintStream("exeception.log");
ps.println(s);
System.setOut(ps);
}
catch(IOException ex)
{
throw new RuntimeException("日志文件创建失败");
}
e.printStackTrace(System.out);
}
}
}
3 系统属性信息文本Properties getProperties():获取系统信息。
void list(PrintStream out):将信息输出到指定输出流中。
new PrintStream("sysinfo.txt"):将输出流中数据存入指定文件中。
// getProperties():确定当前的系统属性。
import java.util.*;
import java.io.*;
class SystemInfo
{
public static void main(String[] args)throws IOException
{
Properties prop = System.getProperties();
prop.list(new PrintStream("sysinfo.txt"));
}
}
4 通过System类的setIn、setOut方法可以对默认设备进行改变。System.setIn(new FileInputStream("1.txt")):将源改成文件1.txt。
System.setOut(new FileOutputStream("2.txt")):将目的改成文件2.txt。
5 体系图
字节流继承体系图:
字符流继承体系图: