IO流概述
一、IO即Input、Output的组合
二、IO流的特点:
- 处理设备之间的数据传输
- java通过流的方式对数据进行处理
三、IO流分类
1、按照操作数据:字符流和字节流
字节流:字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的(如图片,音频)
字符流:字符流只能操作纯字符数据,比较方便(不能操作音频和图片)。
2、按照流向分:输入流和输出流
四、字节流和字符流框架
1、字符流的抽象基类
Reader(输入流)、Writer(输出流)
2、 字节流的抽象基类
InputStram(输入流),OutputSteam(输出流)
字符流
一、字符流是什么?
1、字符流是可以直接读取字符的IO流
2、字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.
二、FlieReader与FileWriter
- FileReaderDemo
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args)
{
/*FileReader是IO流中的字符输入流,是从硬盘上读取数据用的*/
//步骤1
FileReader fr = null;
try{
fr = new FileReader("d:\\file.txt"); //会抛出FileNotFoundException异常
/*因为是读取文件,所以要指定文件以及文件所在的路径,必须保证文件存在 如果要读取得文件不存在,则会发生FileNotFoundException文件找不到异常
*/
//步骤2
int Num = 0;
//调用read方法,循环读取文件
while((Num = fr.read())!=-1){ //read会抛出IOExcepion异常
System.out.print((char)charNum);
}
}
catch(IOException e){
System.out.println(e.toString());
}
finally{
//流是必须要关闭的,所以放在finally语句块中
try
{
if(fr!=null)
fr.close(); //会抛出IOException异常
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
2.FileWriter
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)
{
/*FileWriter是IO流中的字符输出流,是将读取到的数据写入指定文件中*/
//步骤1
FileWriter fw = null;
try{
fw = new FileWriter("d:\\file_Copy.txt"); //如果文件不存在,会创建一个,创建不成功或其他原因会抛出IOException异常
//步骤2
fw.write("abcdefg");
}
catch(IOException e){
System.out.println(e.toString());
}
finally{
//流是必须要关闭的,所以放在finally语句块中
try
{
if(fw!=null)
fw.close(); //会抛出IOException异常
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
注意:flush(),close()是用来刷新缓冲区
三、什么情况下使用字符流
- 字符流可以拷贝纯文本文本文件(但不建议使用,因为读取数据时会将字节流转变为字符流,写入时会将字符流转变成字符流)
读取一段文本数据
思考可以用字符流读取非纯文本:不可以,因为字符流在读取字节流的时候会将字节流转变成字符流,写入的时候将字符流转变成字节流可能会因为编码的不同,二导致找不到对应的字符,造成乱码,导致文件被破坏。
四、文件的拷贝
import java.io.*;
class FileReaderFileWriterDemo
{
public static void main(String[] args)
{
//定义输入流
FileReader fr = null;
//定义输出流
FileWriter fw = null;
try
{
fr = new FileReader("d:\\file.txt");
fw = new FileWriter("d:\\file_copy.txt");
//定义数组存放字符
char [] ch = new char[1024*4]; //这里一般把数组的长度定义成1024*4也就是4kB
int index = 0;
//把读到的字符存入数组
while((index = fr.read(ch))!=-1){
//从字符中读取数据写入文件中
fw.write(ch,0,index);
fw.flush();
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally{
//分别关闭输入输出流
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
}
五、带缓冲区的文件拷贝
几个方法:
1、read():读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率
2、write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率
3、readerLine():用于读取一行文本,还有就是用readerLine读取的数据不会自动换行,那是因为readerLine方法返回的时候只返回回车符之前的数据内容,并不返回回车符
4、 newLine():是一个跨平台的换行符(代替‘\r\n’)
import java.io.*;
class BufferedReaderBufferedWriterDemo
{
public static void main(String[] args)
{
BufferedReader br = null;
BufferedWriter bw = null;
try
{
br = new BufferedReader(new FileReader("d:\\file.txt"));
bw = new BufferedWriter(new FileWriter("d:\\file_copy.txt"));
/*缓冲区的作用是为了提高流的操作效率而出现的,所以在创建缓冲之前,必须要先有流对象,所以要把流对象作为实际参数传递给缓冲区的构造函数*/
String str = "";
while((str=br.readLine())!=null){
bw.write(str);
bw.newLine();
bw.flush();
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally{
//分别关闭输入输出流
try
{
if(br!=null)
br.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally{
try
{
if(bw!=null)
bw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
}
自定义readLine:
class MyReadLine {
private FileReader r;
public MyReadLine(FileReader r) {
this.r = r;
}
public String myReadLine() throws Exception{
StringBuilder sb = new StringBuilder();
int len = 0;
while((len =r.read())!=-1) {
//判断回车符
if(len=='\r')
continue;
if(len=='\n')
return sb.toString();
else
sb.append((char)len);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public void myClose() throws Exception {
r.close();
}
六、装饰类(上面的BufferedReader也是个装饰类)
1、装饰设计模式:定义一个类,通过构造方法接收已有对象,并基于已有的功能,提供增强功能,这个类被称为装饰类
例如:
class Person {
public void eat() {
System.out.println("吃饭!");
}
}
class SuperPerson {
private Person p;
//定义构造函数,接收被装饰的对象
public SuperPerson(Person p) {
this.p = p;
}
//提供功能更强的方法
public void SuperEat() {
System.out.println("吃点点心");
p.eat();
System.out.println("喝点酒!");
}
}
2、装饰类和继承的区别:
1、装饰设计模式比继承更加灵活,避免了继承体系的臃肿,而且降低了类于类之间的关系
2、装饰类具有的功能与已有的是相同的,只不过提供了更强的功能。
3、装饰类与被装饰类一般是一个体系的
七、.LineNumberReader
特有方法:
getLineNumber() 获取行号
setLineNumber() 设置行号
自定义LineNumberReader:
/**的、自定义行号:
* 定义一个类,传递一个FileReader对象,定义一个lineNumber变量
* 分别实现myReadLine,mySetLineNumber,myGetLineNumber方法
*
*/
class MyLineNumber {
private Reader r;
int lineNumber = 0;
public MyLineNumber(Reader r) {
this.r = r;
}
public String MyReadLine() throws Exception {
int len;
lineNumber++;
StringBuffer sb = new StringBuffer();
while((len=r.read())!=-1) {
if(len=='\r')
continue;
if(len=='\n')
return sb.toString();
else
sb.append((char)len);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public void MySetLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int MyGetLineNumber() {
return lineNumber;
}
public void myClose() throws Exception {
r.close();
}
}
字节流
一、概述
1、字节流和字符流的基本操作是相同的,但字节流还可以操作其他媒体文件(如音、视频、图片文件)。
2、由于媒体文件数据中都是以字节存储的,所以字节流对象可直接将数据写入到文件中,而不用再刷新缓冲区。
3、常用方法:
InputStream:
1、read():读取一个字节,返回 0 到 255 范围内的 int 字节值
2、read(byte[] b): 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
3、available():可以获取读取文件中的所有字节数
4、close() :关闭流,释放资源
OutputStream:
1、write(int b) 写出一个字节
2、write(byte[]) 写出数组中的所有字节
3、writer(byte[],int strat,int end) 从指定位置写出数组中的字节
4、close() 关闭流,释放资源
二、读、写文件
1、定义字节数组拷贝文件
class BytesCopyMp3
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
FileInputStream fis =null;
FileOutputStream fos =null;
try
{
fis = new FileInputStream("D:\\KuGou\\陈奕迅 - 斗战神.mp3");
fos = new FileOutputStream("D:\\KuGou\\陈奕迅 - 斗战神_copy.mp3");
byte [] bys = new byte[1024];
int by = 0;
while((by=fis.read(bys))!=-1){
fos.write(bys,0,by);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败");
}
finally{
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("文件读取流关闭失败");
}
finally{
try
{
if(fos!=null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("文件输出流关闭失败");
}
}
}
long end = System.currentTimeMillis();
System.out.println("拷贝使用的毫秒数:"+(end-start));
}
}
注意:为什么read方法返回的是int而不是byte类型呢?
原因:因为如果读取的是视频文件或音频文件或图片文件,在读取过程中很有可能会遇到11111111,也就是byte类型的-1,那么遇到-1程序就会停止读取,会漏掉文件,为了防止这种情况出现,把byte类型提升为int类型,在这个字节前补上24个0,把遇到的-1变成255,这样可以保证将整个文件读完。
2、带缓冲区的拷贝文件
class CopyMp3Buffered
{
public static void main(String[] args)
{
FileInputStream fis =null;
FileOutputStream fos =null;
try
{
fis = new FileInputStream("D:\\KuGou\\陈奕迅 - 斗战神.mp3");
fos = new FileOutputStream("D:\\KuGou\\陈奕迅 - 斗战神_copy.mp3");
//创建缓冲区
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int by = 0;
while((by=bis.read())!=-1){
bos.write(by);
}
}
catch (IOException e)
{
throw new RuntimeException("文件复制失败");
}
finally{
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("文件读取流关闭失败");
}
finally{
try
{
if(fos!=null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("文件输出流关闭失败");
}
}
}
}
}
3、自定义缓冲区
class MyBufferedInputStream
{
private byte [] bys = new byte[1024*4]; //缓存区
private int count = 0; //用于记录数组中数据的长度
private int pos=0; //指针
private FileInputStream fis;
public MyBufferedInputStream(FileInputStream fis){
this.fis = fis;
}
public int myRead()throws IOException{
//如果count为0,从文件中读取一批数据存入数组中
if(count==0)
{
//给count赋值
count = fis.read(bys);
//如果成立表示没有文件中没有数据,直接返回-1
if((count==-1))
return -1;
//数组中的元素被读完,把pos初始化
pos = 0;
//用b记录bys数组中的pos位置上的元素
byte b = bys[pos];
//每调用一次myRead方法,条件成立,让count-1,当count等于0时,再从文件中读取一批数据
count--;
//每调用一次myRead方法,条件成立,让pos+1,知道把bys数组中的元素读完
pos++;
/*因为read方法返回的是int类型,而b是byte类型,类型提升.由原先的8位变成了32位,而b的二进制有可能是11111111的情况,也就是byte类型的-1,那么遇到-1程序就会停止读取,会漏掉文件,为了防止这种情况出现,把byte类型提升为int类型,需要在这个字节前补上24个0,把遇到的-1变成255,所以需要&255*/
return b&255;
}
else if(count>0)
{
byte b = bys[pos];
count--;
pos++;
return b&0xff;
}
return -1;
}
public void myClose()throws IOException{
fis.close();
}
}
打印流
一、概述
该流提供了打印方法,可以将各种数据都原样打印
二、分类
1、 PrintWriter:字符打印流
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
4、字符输出流 Writer
2、 PrintStream:字节打印流
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
练习:
使用打印流实现键盘录入
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//打印在控制台
// PrintWriter pw = new PrintWriter(System.out);
//接收字节输出流对象
PrintWriter pw = new PrintWriter(new FileOutputStream("c:\\abc.txt"));
//接收字符输出流对象
//PrintWriter pw = new PrintWriter(new FileWriter("c:\\abc.txt"));
String str = null;
while((str=br.readLine())!=null) {
pw.println(str);
pw.flush();
}
}
序列流
一、概述:
1.SequenceInputStream:将多个输入流合并成一个输入流,也被称为合并流。
2、常用构造函数:
SequenceInputStream(Enumeration e)
序列流的使用:
public static void main(String[] args) throws Exception {
// 1、创建集合,并将流对象添加进集合
Vector<InputStream> v = new Vector<InputStream>();
v.add(new FileInputStream("c:\\a.txt"));
v.add(new FileInputStream("c:\\ab.txt"));
v.add(new FileInputStream("c:\\abc.txt"));
//2、创建Enumeration对象,将集合元素加入。
Enumeration<InputStream> elements = v.elements();
// 3、创建SequenceInputStream对象,合并流对象
SequenceInputStream sis = new SequenceInputStream(elements);
// 4、创建写入流对象,FileOutputStream关联写入文件
FileOutputStream fos = new FileOutputStream("C:\\AA.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1) {
fos.write(buf,0,len);
}
}
但是,上面的Vector效率低下,下面我们用ArrayList来操作。
public static void main(String[] args) throws Exception {
ArrayList<FileInputStream> arrayList = new ArrayList<FileInputStream>();
arrayList.add(new FileInputStream("c:\\a.txt"));
arrayList.add(new FileInputStream("c:\\ab.txt"));
arrayList.add(new FileInputStream("c:\\abc.txt"));
final Iterator<FileInputStream> iterator = arrayList.iterator();
//创建Enumeration对象,实现其方法
Enumeration<FileInputStream> e = new Enumeration<FileInputStream>() {
public FileInputStream nextElement() {
return iterator.next();
}
public boolean hasMoreElements() {
return iterator.hasNext();
}
};
SequenceInputStream sis = new SequenceInputStream(e);
FileOutputStream fos = new FileOutputStream("C:\\AAA.txt");
byte[] b = new byte[1024];
int len = 0;
while((len=sis.read(b))!=-1) {
bos.write(b, 0, len);
}
sis.close();
bos.close();
}
对象持久化存储
一、概述
|—- ObjectInputStream
|—- ObjectOutputStream
1、对象存在与堆内存中,当程序运行结后,内存释放,对象就不存在了。而流可以将对象(中的特有数据)写到硬盘文件中,当想要操作该对象中特有数据时,直接创建流与其相关联即可。
2、需要被序列化(持久化)的对象必须要实现 Serializable 接口,标记接口。
该接口中没有方法,只是给序列化的类一个ID标识:UID。
static final long serialVersionUID = 42L;
UID是根据成员计算出来的,如果在更改成员的属性或者个数时,UID会发生变化。因此将UID设定为一个固定值是最优的。
3、注意事项
1)只能序列化堆内存中的成员,不能序列化静态成员。
2)若不想序列化非静态成员,则加关键字 transient 修饰,保证该值只在堆内存中存在而不在硬盘文件中存在。
二、特有方法:
输入流:
int readInt() 读取一个 32 位的 int 值(还可以操作其他基本数据类型)
Object readObject() 从 ObjectInputStream 读取对象
输出流:
void writeInt(int val) 写入一个 32 位的 int 值(还可以操作其他基本数据类型)
void writeObkect(Object obj) 将指定的对象写入 ObjectOutputStream
管道流
一、概述
|—- pipedInputStream
|—- PipedOutputStream
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
注意:read方法是阻塞式方法,没有读到数据就会一直等待。
构造函数:
PipedInputStream() 创建尚未连接的 PipedInputStream
PipedOutputStream() 创建尚未连接到管道输入流的管道输出流
注意:未连接的管道流需要通过connect方法连接
PipedInputStream(PipedOutputStream src) 创建 PipedInputStream,使其连接到管道输出流 src
PipedOutputStream(PipedInputStream snk) 创建连接到指定管道输入流的管道输出流
RandomAccessFile
一、概述
1、此类的实例支持对随机访问文件的读取和写入
随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。
2、RandomAccessFile不算IO体系中的子类,而是直接继承至Object
该类内部封装了字节流,让其具备了读写功能。
二、方法
1、构造方法:
RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定
RandomAccessFile(String name, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称
从构造方法可以看出:
1)该类只能操作硬盘上的文件。其他流对象还可以操作内存,标准输入输出等。
2)操作文件有模式,mode值与含义如下:
注意:
1)若mode为“r”,不会在硬盘中创建文件,会去读取一个已存在的文件。若该文件不存在,则会发生异常。
2)若mode为“rw”,文件不存在的会创建,文件存在则不会被覆盖,而其他输出流在创建时会覆盖。
2、常用方法:
write(int val) 写入该数的最低8位(1个字节)
writeInt(int val) 写入该数的32位(4个字节)(还可以操作其他数据类型)
int read() 从文件中读取1个字节
int readInt() 从文件中读取4个字节(还可以操作其他数据类型,读写得对应)
void seek(long pos) 设置指针到指定位置(可以任意移动)
int skipBytes(int n) 指针跳过n个字节(不能忘回跳,只能往前跳)
long getFilePointer() 返回此文件中的当前偏移量
Data流
操作基本数据类型的流对象
|—- DataInputStream
|—- DataOutputStream
一、概述
1、简介
DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
2、构造方法
DataInputStream(InputStream in) 使用指定的底层 InputStream 创建一个 DataInputStream
DataOutputStream(OutputStream out) 创建一个新的数据输出流,将数据写入指定基础输出流
二、特有方法
由于流中的read方法读取的是一个整数的1个字节,而write方法写的也是一个整数的1个字节。当需要写入所有字节时,用以下方法:
1、DataInputStream( 操作基本数据类型)
boolean readBoolean()
byte readByte()
char readChar()
double readDouble()
float readFloat()
int readInt()
long readLong()
short readShort()
String readUTF() 使用 UTF-8 修改版编码将从流中读取数据
2、DataOutputStream(操作基本数据类型)
void writeBoolean(boolean v)
void writeByte(int v)
void writeChar(int v)
void writeDouble(double v)
void writeFloat(float v)
void writeInt(int v)
void writeLong(long v)
void writeShort(int v)
void writeUTF(String str) 使用 UTF-8 修改版编码将一个字符串写入基础输出流
操作数组流
|—- ByteArrayInputStream(操作字节数组)
|—- ByteArrayOutputStream
|—- CharArrayReader(操作字符数组)
|—- CharArrayWriter
|—- StringReader(操作字符串)
|—- StringWriter
一、概述
ByteArrayInputStream :包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
ByteArrayOutputStream:此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
关闭上述流 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
由与上述流对象均未操作系统资源,故不用进行关闭。
二、构造方法
源:buf
ByteArrayInputStream(byte[] buf) 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组
ByteArrayInputStream(byte[] buf, int offset, int length) 创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组
目的:该对象内部封装的数组
ByteArrayOutputStream() 创建一个新的 byte 数组输出流。
ByteArrayOutputStream(int size) 创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)
其他方法:
byte[] toByteArray() 创建一个新分配的 byte 数组
String toString() 使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串
void writeTo(OutputStream out) 将此 byte 数组输出流的全部内容写入到指定的输出流(只有该方法会抛IOException)
三、操作字符数组与字符串
二者的输入流在构造时,需要接收相同类型的数组或字符串;输出流在构造时,可以不用定义数据目的地,因为输出流对象中已经封装了可变长度的数组,这就是数据的目的地。