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

黑马程序员——Java之IO(上)

2015-12-19 20:45 537 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

内容提要:
IO流概述;
字符流;
字节流;

IO流概述

1. IO:意为输入输出,是Input和Output的缩写。

2. Java中IO的特点:

IO流用来处理设备之间的数据传输,为什么会在IO之后增加一个字:流?
Java对数据的操作是通过流的方式进行处理的
流按照操作数据可以分为两种:字节流(二进制数据)和字符流(由一个个字符组成)。
流按照流向分为两种:输入流和输出流。



输入机制,允许程序读取外部数据、用户输入;输出机制,允许程序记录允许状态,将程序数据输出到存储设备中,站在JVM的角度考虑数据流向;

Java用于操作流的对象都定义在了IO包中,通过java.io包下的类和接口来支持。其中字节流的抽象基类:InputStream,OutputStream;字符流的抽象基类:Reader,Writer。





由这四个基类派生出来的子类名称都是以其父类名作为子类名的后缀。
需要注意的是:流只能操作数据,不能操作文件

字符流

一、基本概念描述

1. 字符流中的对象融合了编码表的查询动作,使用的是默认编码表:当前系统编码,能让计算机知道该查看什么类型的码表。

2. 编码表——比如美国信息标准交换码ASCII。Unicode无论什么字符都用两个字节(16位二进制)表示;UTF-8是对Unicode码表的优化;此外各个国家都对应有自己的语言查询码表。
3. 字符流只用于处理文字数据,而字节流可以处理媒体数据,如图片、语音等。

4. IO流是操作数据的,数据的最常见体现形式是:文件,因此应该从字符流Reader和Writer中着手,需要注意的是,该流对象一被初始化,就必须有被操作的文件存在。

二、FileWriter字符流
比如:在硬盘上创建一个文件并写入一些数据。找到一个专门用于操作文件的Writer子类对象:FileWriter,后缀名是父类名,前缀名是该流对象的功能。
import java.io.FileWriter;

public class FileDemo {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
FileWriter fw = new FileWriter("demo.txt");
// 调用write()方法,将字符串写入到流中(其实是内存中)
fw.write("zhangfeng");
fw.flush(); // 刷新流对象中的缓冲的数据,将数据刷到目的地中
fw.close(); // 关闭流资源,但关闭之前会刷新一次内部缓冲中的数据
}
}

代码分析:FileWrite字符流是用来操作文件的输出字符流,该对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件将被覆盖。其有两个特点:用于操作文件、输出(写的方式)、流中单位是字符。
需要注意的是:
1. Java实际是调用底层操作系统内容,以此完成数据操作,因此close()必须执行;

2. 创建一个FileWriter对象,该对象被初始化就必须要明确被操作的文件,而且该文件会被创建到指定目录下;如果该目录有同名文件,将被覆盖,目的:明确数据要存放的目的地。

close()和flush()的区别:

共同点:刷新流的数据缓冲区;区别在于:使用flush()方法,该流对象还在;而close()方法执行后,流将消失。

二、FileWriter字符流操作中的IO异常处理,及文件续写
IO异常会在IO中经常出现。由于在创建对象时,需要指定创建文件位置,如果指定的位置不存在,就会发生IOException异常,所以整个步骤中,需要对IO异常进行try处理。
/*
* 程序演示FileWriter,及其文件续写操作;
* 特别需要注意在文件操作中的IO异常处理;
* */
import java.io.*;

class FileWriterDemo {
public static void main(String[] args) {
// 该fw指针即可在finally语句中使用
FileWriter fw = null;
try {
fw = new FileWriter("d:\\demo.txt",true); // 创建具有盘符的文件,并续写文件
fw.write("zhangfeng");
} catch (IOException e) // 抛出IO异常
{
System.out.println(e.toString());
} finally // finally中的内容一定要执行,一般用于关闭资源
{
try // 一定要关闭IO异常
{
if (fw != null) // 判断是否创建了fw所指向的文件
fw.close();
} catch (IOException e) {
System.out.println(e.toString());
}
}
System.out.println("Hello World!");
}
}

代码分析:需要指出的是,在操作文件后需要与之相关的资源,如fw.close()。
import java.io.*;
/*
演示对已有文件的数据续写
*/
class FileDemo2 {
public static void main(String[] args) throws IOException {
// 创建文件指针,并指明文件名;第二个参数表示文件续写
FileWriter fw = new FileWriter("demo.txt", true);

// 将以下内容续写到指定文件中
fw.write("\r\ning"); // 在windows中\r\n代表的是换行
fw.close();
System.out.println("Hello World!");
}
}
文件数据的续写是通过构造函数FileWriter(String s,
boolean append),在创建对象时,传递一个true参数,代表不覆盖已有文件,并在已有文件的末尾处进行数据续写。

三、FileReader读取数据流
有两种方式读取文件中的字符数据:单次只读取一个字符,单次读取多个字符。
文件结尾处都有windows定义的文件结束标识,JVM将调用windows的文件读取方式,并得到文件结束标记。
import java.io.*;

class FileReaderDemo {
public static void main(String[] args) {
singleCharReader(); // 每次读取一个字符
arrayCharReader(); // 每次读取多个字符
System.out.println("Hello World!");
}

public static void arrayCharReader() {
FileReader fr = null;
try {
fr = new FileReader("d:\\demo.txt");
char[] buf = new char[5]; // 一般定义数组长度为1024的整数倍

int num = 0;
// 将读取的字符数组存入buf中,并返回读取的字符数;若没有数据了,则返回-1
// 如果读到的内容超出了数组长度,再次读取时,会从数组首部开始存入
while ((num = fr.read(buf)) != -1) {
System.out.println("num = " + num);
System.out.println(new String(buf)); // 打印全部buf的全部内容
System.out.println(new String(buf, 0, num)); // 打印当次读取到的字符
}
} catch (IOException e) {
throw new RuntimeException("读取失败");
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {

}
}

}

public static void singleCharReader() {
// 创建一个文件读取流对象,并和指定名称的文件相关联
// 需要保证该文件是已经存在的;如果不存在,会发生异常FileNotFoundException
FileReader fr = null;
try {
fr = new FileReader("d:\\demo.txt");
/*
* //调用读取流中的,read()方法一次读一个字符,而且会自动往下读(记录位置)
* //为什么返回的是int类型的数据?
*/
// 循环读取文件内容,结束标记:返回-1
int ch = 0;
while ((ch = fr.read()) != -1) // 返回的是int类型的数据
System.out.println("ch = " + (char) ch); // 以字符数据显示
} catch (IOException e) {
throw new RuntimeException("读取失败");
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {

}
}
}

}

单次读取多个字符,实际上是使用了数据缓冲区,将读取到的数据存入buf中,然后记录读取到的字符数,并输出读取到的字符。
四、练习使用FileReader和FileWriter拷贝文本文件
import java.io.*;

/*
复制原理:
将E盘下的文件数据存储到D盘下

思路:
1.在D盘下创建,用于存储E盘下的文件内容;
2.定义读取流和E盘文件关联
3.通过不断的读写完成数据存储
4.关闭资源。
*/
class CopyTextDemo {
public static void main(String[] args) throws IOException {
copytxt_2();
System.out.println("Hello World!");
}

// 第二种方式:使用异常机制,使用数据缓冲区进行读写
public static void copytxt_2() {
FileWriter fw = null;
FileReader fr = null;
try {
fw = new FileWriter("d:\\demo_copy.txt");
fr = new FileReader("e:\\demo.txt");

char[] buf = new char[5]; // 定义数据缓冲区
int len = 0;

// 在还没有关闭文件时,使用上次的文件指针循环多次拷贝数据到指定文件中
// 读文件操作和写文件操作都包含有指针
while ((len = fr.read(buf)) != -1) {
System.out.println("len--->" + len); // 输出读到的字符数
fw.write(buf, 0, len); // 将读取到的字符数据,写入到目的文件中
}
} catch (IOException e) {
throw new RuntimeException("读写异常"); // 抛出RuntimeException异常
} finally {
try {
if (fr != null) // 如果fr关联了文件
fr.close();
} catch (IOException e) {
}

try {
if (fw != null) // 如果fw关联了文件
fw.close();
} catch (IOException e) {
}
}
}

// 第一种方式:硬盘磁头在来回读写操作,拷贝效率低
public static void copytxt_1() throws IOException {
// 不使用IOException的异常机制,但必须保证源文件存在
FileWriter fw = new FileWriter("d:\\demo_copy.txt");
FileReader fr = new FileReader("e:\\demo.txt");

int ch = 0;
while ((ch = fr.read()) != -1) // 读取方式:一次读取一个字符,来回读写
fw.write(ch);

fw.close();
fr.close();
}
}

代码分析:拷贝文件有两个步骤:先读取文件,后写入文件;以此比较两种文件拷贝的方式。
五、字符流缓冲区:BufferedWriter和BufferedReader
缓冲区区(就是用于存储数据的区域)的出现提高了对数据的读写效率,对应类:BufferedWrited和BufferedReader。缓冲区要结合流才可以使用,所以在创建缓冲区之前,必须要先有流对象;
缓冲技术的原理在于,在对象中封装了数组,将数据存入数组中,然后一次性操作该数组中的数据。在流的基础上对流的功能进行了增强,避免了硬盘磁头的来回切换。
/*
* 程序演示缓冲区技术,使用BufferedReader和BufferedWriter
* 需要注意的是:在创建数据缓冲区之前,必须有数据流
* */

import java.io.*;

class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// bufferedWriter();
bufferedReader();
System.out.println("Hello World!");
}

public static void bufferedReader() throws IOException {
// 创建一个读取流对象和文件相关联
FileReader fr = new FileReader("d:\\buf.txt");

// 为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数
BufferedReader buffr = new BufferedReader(fr);

String line = null;
// 读取一行数据(有效数据,不包括换行),并返回字符串;如果为null,则表示末尾
while ((line = buffr.readLine()) != null)
System.out.println(line);

// 关闭缓冲区
buffr.close();
}

public static void bufferedWriter() throws IOException {
FileWriter fw = new FileWriter("d:\\buf.txt");

// 为了提高字符写入流的效率,加入了缓冲技术
// 只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可
BufferedWriter bufw = new BufferedWriter(fw);

// 使用缓冲区的方法进行五次数据写入操作
for (int index = 0; index < 5; index++) {
bufw.write("abcd" + index);
bufw.newLine(); // newLine()实现了函数的跨平台,换行在Windows和Linux下不同
bufw.flush(); // 每写一次就刷新缓冲区
}
// 只要用到缓冲区,就需要数据刷新
// bufw.flush();

// 其实关闭缓冲区,就是在关闭缓冲区的流对象
bufw.close(); // 不需要进行fw.close()
}
}

代码分析:
1. 只要使用了数据缓冲技术,需要进行flush()操作;关闭流同样会刷新,但为了排除意外事故,保证数据准确性,建议写入一次就刷新一次。关闭缓冲区,就是在关闭缓冲区中的流数组。
2. 缓冲区一行一行地读readLine()方法,方便于文本数据的获取,是最为高效的。该方法返回null时,表示读到了文件的末尾,不返回回车符。
六、通过缓冲区复制文本文件
/*
* 使用数据缓冲区技术复制文件
* */

import java.io.*;

class CopyTextBufferDemo {
public static void main(String[] args) {
BufferedReader buffr = null;
BufferedWriter buffw = null;
try {
buffr = new BufferedReader(new FileReader("d:\\FileReaderDemo.java"));
buffw = new BufferedWriter(new FileWriter("d:\\buf_copy.txt"));

String line = null; // 数据的中转站
// readLine()方法只返回回车符之前的数据内容,并不返回回车符
while ((line = buffr.readLine()) != null) {
buffw.write(line);
buffw.newLine(); // 写入换行符
buffw.flush();
}
} catch (IOException e) {
throw new RuntimeException("读写失败");
} finally {
try {
if (buffr != null) {
buffr.close();
}
} catch (IOException e) {
throw new RuntimeException("读取关闭失败");
}

try {
if (buffw != null) {
buffw.close();
}
} catch (IOException e) {
throw new RuntimeException("写入关闭失败");
}
}

System.out.println("Hello World!");
}
}

代码分析:通过使用数据缓冲,实现高效。
readLine()方法是获取文件一行数据,而无论是读一行获取多个数据,还是只获取一个字符,最终都是在硬盘上一个个读取,所以最终使用的还是read()方法一次读一个的方法,只是readLine()方法将每读到的一行数据存储到数据缓冲区内。
七、MyBufferedReader及[b]readLine的原理实现[/b]
自定义一个数据缓冲读取流,实现BufferedReader的readLine()功能。
/*自定义一个类,包含一个功能和readLine()功能一致的方法
来模拟BufferedReader
*/

import java.io.*;

class MyBufferReader extends Reader // 使用装饰设计模式
{
private Reader r;

MyBufferReader(Reader r) {
this.r = r;
}

// 自定义方法myReadLine()
public String myReadLine() throws IOException {
// 定义一个临时容器,原BufferReader封装的是字符数组
StringBuilder sb = new StringBuilder(); // StringBuilder线程不安全
int ch = 0;
while ((ch = r.read()) != -1) { // 使用Reader类中的read()方法,一次读一个字符
if (ch == '\r') // 判断是否到一行末尾
continue;
if (ch == '\n')
return sb.toString(); // 将sb转化为String类型
else
sb.append((char) ch); // 将字符添加到sb的末尾
}

if (sb.length() != 0) // 缓冲区只要有数据,就将该数据返回
return sb.toString();

return null; // 假如源文件中没有任何数据,将返回null
}

public void myClose() throws IOException {
r.close();
}

// 覆盖Reader类中的抽象方法
public void close() throws IOException {
r.close();
}

// 覆盖Reader类中的抽象方法
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf, off, len);
}

public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("d:\\FileWriterDemo.java");

MyBufferReader mybuffer = new MyBufferReader(fr);

String str = null;
// 调用自己封装的类的myReadLine()
while ((str = mybuffer.myReadLine()) != null)
System.out.println(str);

mybuffer.myClose();
}
}

代码分析:自定义代码,实现BufferedReader。
八、装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类时将已有对象传入,基于已有的功能提供加强功能,那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接受被装饰的对象,并基于被装饰的对象的功能提供更强的功能。
装饰设计模式的由来:比如,read()方法到readLine()的转变。
装饰模式比继承要灵活,避免了继承体系的臃肿,其中使用多态提高程序的扩展性;此外降低了类与类之间的关系。装饰类因为增强已有对象,具备的功能和已有对象是相同的,只不过提供了更强功能,所以装饰类和被装饰类通常都属于一个体系中。从继承结构转变成了组合结构。
九、装饰类之MyLineNumberReader
在BufferedReader中有一个直接子类LineNumberReader,其中有特有的方法能够获取和设置行号。
跟踪行号的缓冲字符输入流,可以设置和获取当前行号,也是一个装饰类。默认情况下,是从零开始的。
import java.io.*;

class LineNumberReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("MyBufferReader.java");
LineNumberReader lnr = new LineNumberReader(fr);

String line = null;
lnr.setLineNumber(100); // 将第一行默认为第100行,读取时依次增加行数
while ((line = lnr.readLine()) != null) { //该类中的方法readLine()和BufferedReader类似
// 基于readLine()功能,增加了输出行号
System.out.println(lnr.getLineNumber() + "::" + line);
}
System.out.println("Hello World!");
}
}

自定义类模拟一个带有行号计数的缓冲区。
/*
* 演示装饰器设计模式
* 自定义的MyLineNumberReader类没有继承Reader,而是接受了Reader对象
* 并提供增强功能
* */

import java.io.*;

class MyLineNumberReader {
private Reader r;
private int lineNumber; //自定义属性:行号,默认值为0

MyLineNumberReader(Reader r) {
this.r = r; //传入装饰对象
}

//以下操作和MyBufferedReader中的readLine()方法类似
public String myReadLine() throws IOException {
lineNumber++; // 每读取一行,lineNumber自增

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; // 假如源文件中没有任何数据,将返回null
}

public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}

public int getLineNumber() {
return lineNumber;
}

public void myClose() throws IOException {
r.close();
}

public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("LineNumberReaderDemo.java");
MyLineNumberReader mylnr = new MyLineNumberReader(fr);

String line = null;
while ((line = mylnr.myReadLine()) != null) {
System.out.println(mylnr.getLineNumber() + ":" + line);
}
System.out.println("Hello World!");

mylnr.myClose();
}
}

代码分析:自定义代码演示装饰类。
字节流
一、字节流概述
字节流和字符流的基本操作是相同的,但字节流还可以操作其他类型文件,如媒体文件等。

媒体文件中的数据都是以字节形式存储的,所以字节流对象可直接对媒体文件的数据写入到文件中,而且不需要再进行“刷流”动作。字节流操作的是字节,即数据最小单位,不需要像字符流一样进行转换为字节,所以可直接将字节数据写入到指定文件中。

字节输入流:InputStream,用于读;字节输出流:OutputStream,用于写。

二、File读写操作:FileInputStream和FileOutputStream
和字符流相同,只是操作的数据是字节。
字符流实际上也是走的字节流,只是将字节临时存储下来了,然后去查码表。
在操作字节流中,不显式使用缓冲区是不需要缓冲的,不管是什么数据,均当作字节操作,所以不需要刷新。
/*
* 演示字节操作:输入流和输出流
* FileInputStream和FileOutputStream操作
* */

import java.io.*;

class FileStreamDemo {
public static void main(String[] args) throws IOException {
// writeFile();
// readFile_1();
// readFile_2();
readFile_3();
System.out.println("Hello World!");
}

// 字节输出流操作,进行写操作
public static void writeFile() throws IOException {
FileOutputStream fos = new FileOutputStream("d:\\fos.txt");
// 字节流操作的都是字节或者是字节数组
fos.write("abcde".getBytes()); // 将字符串转换成字节数组
fos.write("\r\n".getBytes());
fos.close(); // 关闭资源
}

public static void readFile_1() throws IOException {
FileInputStream fis = new FileInputStream("d:\\fos.txt");
int ch = 0;
while ((ch = fis.read()) != -1) // 循环访问文件数据,进行字节读取
{
System.out.print((char) ch);
}

fis.close();
}

public static void readFile_2() throws IOException {
FileInputStream fis = new FileInputStream("d:\\fos.txt");

byte[] buf = new byte[1024]; // 定义一个大容量的字节缓冲区
int len = 0;

// read(byte[] buf)方法返回的是读取字节数目
while ((len = fis.read(buf)) != -1) { // 返回-1,表示文件结束
System.out.println(new String(buf, 0, len));
}

fis.close();
}

public static void readFile_3() throws IOException {
FileInputStream fis = new FileInputStream("d:\\fos.txt");

// 使用FileInputStream的特有方法available(),返回文件字节大小
byte[] buf = new byte[fis.available()]; // 定义个刚刚好的字节缓冲区

fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
}

代码分析:FileInputStream和FileOutputStream用于字节流的文件输入输出操作。
其中可利用available()方法指定读取方式中传入数组的长度。此方法在文件不大时,可以使用;JVM启动分配的默认内存一般为64M,如果文件过大,数组长度所占内存空间会溢出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: