您的位置:首页 > 职场人生

黑马程序员--IO(一)--概述、字符流、字节流、流操作规律

2015-11-10 14:09 429 查看
黑马程序员--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、BufferedReader

a 作用:在流的基础上对流的功能进行了增强。所以缓冲区创建之前,必须有对应的流对象。

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 体系图

字节流继承体系图:



字符流继承体系图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: