黑马程序员—JAVA基础之I/O

I/O流概述

I/O流主要用来处理设备之间的数据传输。就是说通过java的语言来处理设备上的数据,比如硬盘上的文件,内存中的数据。早期这些操作数据的方法通过封装,便形成了I/O流对象。纵多的I/O对象就形成了IO包。按照流向分为两大类:一个是输入流,一个是输出流。按照操作的数据分类又分为:字符流,字节流。(所有的数据原始的状态还是字节数据。字符的出现主要是通过一串的字节数据来表示字符,最后形成了一个字节和字符的映射表。字符流内部可以指定码表,融合了码表,方便了对文本数据的处理。)

I/O流对象中,基本的规律就是,流对象的前缀名是这个流的功能,后缀名是这个流对象的父类类名。

字符流的抽象基类:

Reader(读取)

Reader读取流和写入流不同,读取流没有flush刷新操作,创建文件读取流对象要和指定文件名的文件关联。读取流对象关联文件后,文件中的数据被存放到内存中的流对象中。

例如:

Reader r = new FileReader("demo.txt");//要保证这个文件要已经存在。如果不存在,会发生文件未找到异常。

 

相对于Writer写入流的write()方法,读取流Reader也有相应的读取方法read()方法,这个方法只能读取字符,或者字符数组。不能读取字符串。read()方法返回的是一个int型数据,返回的都是文件里每个字符相对应的码表数字,每次读取都不会停留在一个字符上,会自动的读取已经读取的字符的下一个字符,如果读到文件末尾,就会返回一个-1。返回-1也是windows系统为了让硬盘上的文件的数据能够区分开来,在每个文件的末尾部分加上了结束标识,当java程度读取到这个结束标识时,并不返回这个结束标识,而是返回-1。

读取流读取文件内容的第一种方式:(每次只读取一个字符)

int num = 0;

while ((num = r.read())!=-1){

读取文件内容的代码块;

}

 

读取文件内容的第二种方式:(将读到的字符存入到数组中,那么read方法返回的是读取到字符的个数。下次读取的时候会将新读取的数据存入到数组中,并且覆盖这个数组原先的数据。当读取文件的结尾处时,read方法就会返回-1)

char[] ch = new char[1024];

int num = 0;

while ((num = r.read(ch))!=-1){

读取文件内容的代码块;

}

 

Writer(写入)

这个体系中所有的字符流对象在关闭(close)之前都首先要刷新(flush)流的缓冲。

 

这个体系中的共性方法write(),作用是将数据写入到流中,并不会马上写入到文件中而是写到了内存中而流对象是在内存当中,所以要写入到目的文件中要先刷新流中的缓冲。

 

close()和flush()的区别:流资源在关闭之前会自动的刷新一次内部缓冲中的数据,之后就不能再往流中写入数据,否则会发生异常。刷新则是将流中的数据刷新到目的文件中,流资源并没有被关闭,还可以再往流对象中写入数据。

其实系统自身也可以创建文件,并往这个文件中写入数据,java语言执行同样的操作只是调用了系统的资源,操作结束之后,这些系统流资源就需要被释放出来,但是java在调用完系统资源,程序结束后,系统的资源并不会随着java程序的结束而被关闭,系统资源只有系统自己才能关闭,所以java语言就要用close方法调用系统的关闭方法,来关闭系统底层的资源。

 

专门用来操作文件的流对象是:FileWriter (extends OutputStreamWriter 这个流对象是字符流通向字节流的桥梁)

这个类没有无参数的构造函数,因为这个流对象是专门操作文件的,要给文件写入数据,所以在这个流对象被初始化之前需要操作的文件必须存在。如果这个文件在初始化之前不存在,那么在初始化的同时,会在指定的目录下创建一个指定的同名文件。

例如:

FileWriter fw = new FileWriter("demo.txt");初始化时明确要操作的文件,如果文件不存在会马上创建一个。如果目录中有同名文件存在,那么这个文件会被覆盖。简明的说,这步就是要明确数据要存放的目的地。

如果要不覆盖文件而且对文件内容进行续写,就需要判断构造函数中append的布尔值是否为true,true即可对文件末尾进行续写。

例如:

FileWriter fw = new FileWriter("demo.txt",true);


字符流缓冲区:(为了提高流对数据的读写效率,需要结合流来使用,增强了流功能)

BufferedReader(在创建之前必须先要有流的出现,不然没有意义。这个也是为什么没有无参数构造函数的原因)

BufferedWritter

缓冲区的概念:缓冲区主要是对数据进行临时的存储,当需要使用的时候一次性的将数据输出。具体是要联系到硬盘上数据的读写,如果没有缓冲区,那么硬盘就要没一个字节都要进行一次磁头的扫描,效率非常的低。有了缓冲区,可以一次性的将大量的数据通过磁头来存储数据在硬盘上。缓冲区的最终原理:其实缓冲区对象中封装了数组。

缓冲区在使用的时候需要和需要提高效率的流进行关联。

例如:

BufferedWriter bufw = new BufferedWriter(fw);

bufw.close();(缓冲区在关闭的时候,其实关闭的是这个缓冲区关联的读写流对象。因为缓冲区仅仅提供的只是提高读写流的效率,而真正在进行读写操作的还是读写流。所以,缓冲区对象调用关闭流方法的时候,其实调用的是读写流的关闭流资源close()方法。所以在缓冲区对象调用关闭流方法后,就不需要再次的调用流对象的关闭方法了。)

 

缓冲区相对于父类流对象,有一个特殊的方法,newLine()这个方法是写入一个行分隔符,相对于windows中的\r\n,但是特殊的地方就是这个方法会根据不同的系统来表示行分隔符。不如linux中的行分隔符就是\n,跟windows系统的不一样。使用这个方法可以避免在不同系统中使用时发生不支持的错误。实现跨平台。

例如:

bufw.write(line+"\r\n");

相同于

bufw.write(line);

bufw.newLine();//但是这种编写方法更好,具备跨平台性。

 

BufferedReader对象提供的readLine()方法,可以读取文件中一行的数据。返回的是一个字符串。如果读取文件中的行,到行的末尾处,这个方法会返回null。需要注意的是,这个方法读到行末尾处并没有读取行分隔符。也就是说在读取一行数据之后,如果要将数据写入新的文件,那么写入时要带着newLine()这个方法。

readLine()方法的底层原理其实还是最初的read()方法将一个一个的字符读取。这个方法是阻塞式方法,没有读到数据,或者数据中没有回车符,线程都会处在等待状态。

原理:StringBuilder sb = new StringBuilder();

 

int ch = 0;

while ((ch = fr.read())!= -1)

{

if (ch == '\r')

{

continue;

}

else if (ch == '\n')

{

return sb.toString();

}

else

{

sb.append((char)ch);

}

}

if (sb.length()!=0)

{

return sb.toString();

}

return null;

 

LineNumberReader extends BufferedReader也是一个装饰类,这个类提供返回行号功能的方法,setLineNumber(int num),getLineNumber()这个两个方法。

 

装饰设计模式:

字符流缓冲区对象其实用的设计模式就是装饰设计模式。比如readLIne()方法的出现其实就是增强read()方法的功能。当想要对已有的对象实现其功能的增强时,可以定义类,将已有的类对象传入,基于已有类对象的功能,并实现这个功能的增强功能。那么自定义的类就被称为装饰类。这种设计模式就叫做装饰设计模式。

装饰设计模式和继承的区别:

在体系设计之初,一个体系的根类可以根据不同的实现功能,衍生出不同的子类,而这些二级子类就是通过复写根类的某一功能产生的,当这个体系中,每个子类的这项功能明显的效率太低,那么还要对这个体系中所有子类的这项功能进行增强,于是,根据每个子类的功能,都将这个功能的代码复写来增强这个功能的效率,于是就产生了三级子类,而且后期如果因为扩展再添加某个二级子类,那么都要产生一个三级子类来增强这项功能。明显后期的这个体系会非常的臃肿。

 

然后装饰设计模式的出现能够彻底的解决体系臃肿的问题,这个装饰类封装了对这项功能增强的实现步骤,当这个装饰类对象初始化的时候,将二级子类的实例对象当做参数传递给装饰类的构造函数,这样就能省略掉很多二级子类的三级子类。后期进行体系扩展的时候也变的十分的方便。当要使用到增强的功能时,只要将二级子类的实例对象传递给装饰类的构造函数就可以了。要注意的是,装饰类和这个体系的二级子类同属于这个体系,就是说都是属于根类的衍生,这样减少了类与类之间的联系。同时极大的提高了功能扩展。

 

 

 

字节流的抽象基类:

字节流和字符流的区别:

字符流其实也是基于字节流,在字符流操作文本文件的时候,都是根据系统默认的码表来返回每个文本文件中字符数据的字节码,这其中的转换过程需要有一个缓冲区来存放字节码,所以在字符流输出的数据的时候,都必须进行刷新动作。

 

而字节流不同,字节流处理的数据中间不需要进行任何的转换,所以在操作字节流数据的时候,可以不进行刷新动作。要注意的是,不管是处理什么数据的流对象最后都需要被关闭,因为都有调用系统的资源。

 

如果用字符流去读取媒体文件其实是可以的,读取数据的时候,字符流会根据自带默认的码表来返回数据,如果码表里有对应的数据,那么原先的字节数据不变,但是如果码表里找不到,那么就是到码表的位置数据区中寻找接近原先数据的字符数据返回,这样原先的字节数据就变了,最后会导致原先的媒体文件打不开了。

 

InputStream(读取)

InputStream中的read()和字符流中的read()方法其实没什么区别,字符流有内置的默认码表,而字节流则没有,但是方法操作的数据其实都是字节数据,字符流的出现只是更加方便的处理文本数据而已。而如果字节流去操作字符数据,返回的其实还是字节数据,如果有些软件内记录的数据不是字节数据的话,原因是软件内有内置的码表,就会自动的对照码表来输出数据,其实底层记录的数据还是字节数据。

 

字节流特有的方法available(),这个方法返回的是一个文件的字节数,也就是文件的大小。

 

OutputSream(输出)

字节输出流中相对于字符输出流不同之处就是字节输出流输出的时候可以不用刷新。其他的基本和字符流的方法相似。

 

字节流缓冲区:

BufferedInputStream 

 

文件开头出现字节数据模拟文件末尾结束标记的问题:

在读取媒体文件的时候,文件的开头的一个字节可能就是-1,所以做一些复制操作就会失败。所以当缓冲区从自身内部数组读取流对象读取的字节数据的时候,会将原先8位的字节数据&255或者0xff提升到32位int类型的字节数据,将-1转换成255,当进行复制的操作时,再通过write()方法内部的强转动作将前面的三个字节砍掉,保留最后一个字节,再输出到指定目的地的文件中。这样既可以避免开头为-1的错误,也有有效保护了原数据不被破坏。

原理:

class MyBufferedInputStream

{

private InputStream in;

private int count;

private int pos;

private byte[] buf = new byte[1024];

 

MyBufferedInputStream(InputStream in)

{

this.in = in;

}

 

public int myRead() throws IOException

{

if (count==0)//数据无数据

{

count = in.read(buf);

 

if (count<0)//数据读完了

{

return -1;

}

pos = 0;

byte b = buf[pos];

pos++;

count--;

 

return b&255;

}

else if (count>0)//数组已有数据

{

byte b = buf[pos];

pos++;

count--;

 

return b&255;

}

else

return -1;

}

 

public void myClose() throws IOException

{

in.close();

}

}

 

 

BufferedOutputStream

 

字符流和字节流缓冲区的原理:

字节流缓冲区的读取方法read()读取的不是文件中的字节码,而是通过字节流对象关联文件通过字节流对象的read()方法,一个一个的读取字节码后存储进缓冲区的数组中,然后缓冲区的read()方法再一个一个的从数组中读取字节码。这个是字节流缓冲区读取文件字节码的原理。这个原理也适用字符流的缓冲原理。因为字符流的缓冲区中也有一个数组来进行字符的存储,不同的是,字符流缓冲区中用的是字符数组,字节流缓冲区中用的是字节数组。

 

 

标准输入输出设备:

System.in 标准输入设备:键盘(键盘录入并打印的原理和BufferedReader的readLine()方法的原理是一样的)

System.setIn();这个方法能改变输入源。接收一个字节输入流对象。

 

读取转换流:

字节流---->字符流:InputStreamReader (extends Reader)这个对象初始化时,接收一个键盘录入对象。源头是键盘录入

 

System.out 标准输出设备:控制台

System.setOut();这个方法改变输出目的。接收一个打印输出流对象PrintStream。

 

输出转换流:

字符流---->字节流:OutputStreamWriter(exteads Writer)这个对象初始化时,接收一个控制台输出对象。目的地是控制台输出

 

转换流对象在初始化的时候,除了要传进流对象之外,还可以指定码表,通过码表可以读取指定码表的文本内容,或者按照码表来打印输出。

 

流操作的基本规律:

通过两个明确来确定使用什么流对象。

1 明确源和目的

源:InputStream 和 Reader  

目的:OutputStream 和 Writer

 

2 明确操作的文件是否是纯文本。

 是:用字符流

 不是:用字节流

 

3 明确用什么设备

源:键盘(System.in),硬盘(FileInputStream),内存(ByteArrayInputStream):

目的:控制台(System.out),硬盘(FileOutputStream),内存(ByteArrayOutputStream):

 

 

打印流

该流提供了打印方法,可以将各种数据类型的数据都原样打印。

 

字节打印流

PrintStream(OutputStream out, boolean autoFlush)

PrintStream:构造函数可以接收的参数类型File对象,字符串路径String,字节输出流OutputStream。

 

字符打印流(在Web开发中常用)

PrintStream(OutputStream out, boolean autoFlush) ;

PrintWriter;构造函数可以接收的参数类型File对象,字符串路径String,字节输出流OutputStream,字符输出流,Writer。

可以用来取代BufferedWriter,Write()方法可以用print()来代替,这个方法搭配构造函数中自动刷新的功能,不用flush()就可以换行就刷新,但是这个功能只在操作流对象的时候使用。使用println();方法会自动换行,和BufferedWriter的newLine()方法一样。

 

合并序列流:

SequanceInputStream:对多个流进行合并。

构造函数接收一个Enumeration<? extends InputStream>对象。所以多个输入流对象要被存储在Vector集合中。

 

对象操作流:(被操作的对象必须实现Serializable)

ObjectInputStream:读取持久化对象的流,读取方法是readObject();

ObjectOutputStream:

相对其他的输出流,这个对象输出流有一个方法能输出int型数据,writeInt()这个方法能做到这点,还能操作其他的基本数据类型。

对象的持久化,或者对象的序列化,总之就是将对象存储到硬盘上,当要使用对象中的数据时,可以再次使用这个对象。

要被写入硬盘持久化存储的对象要实现一个借口,Serializable 这个接口没有要被实现的方法,所以这种接口被称为标记接口。

 

一点特殊的是,对象序列化时,都有一个UID号,public static final long serialVersionUID这个号是根据对象内部的成员变量和成员函数来定义的,如果要想在对象内容改动之后还能继续读取原先已经序列化的对象,就需要自行定义UID号码。但是有一点就是对象中的静态成员的内容是不能被序列化的,不论静态的内容怎么改变,在后期读取时,只能读取最初定义的序列化的值。只能序列化堆内存中对象的内容,不能序列化其他内存中的内容。

 

如果不想让对象中的某些非静态成员进行序列化,那么可以再成员前面加上transient关键字进行修饰。修饰的作用就是让这个被修饰的成员在堆内存中存在,而不在持久化之后的文件中存在。一般被序列化对象存储的文件命名后缀都是.Object文件。

 

管道流:

管道流的作用:读取流和输出流对接在一起。这个流对象涉及到多线程技术。管道流对象在初始化的时候可以接收一个管道流对象,这样两个流对象就能进行连接。或者使用connect()方法。

 

 

(多看看)随机访问文件的类:RandomAccessFile:

这个类和其他的流对象不同,这个类不继承根类流对象,而是直接继承Object。但是它是IO包中的成员,它内部封装了数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置。

同时可以通过seek改变指针的位置,并且通过这个方法可以随意的前后移动指针。通过对指针的操作,就能随意的在任意位置写入或读取数据。这个就是实现分段写入数据的原理,下载软件就是这种原理。

 

此类的实例支持对随机访问文件的读取和写入。而且这个对象只能操作文件,还有模式。如果模式为“rw”而且访问的文件不存在,会自动创建,如果已经存在也不会覆盖原来的文件。如果模式为“r”,那么只能读取已经存在的文件,如果文件不存在就会抛出异常。

其实这个对象内部封装了字节输入和输出流。

 

 

 

数据流对象:DataInputStream  DataOutputStream

这个流对象是专门用来处理基本数据类型的。其中方法readUTF()和writeUTF()在读取和写入的时候都是对应的,如果写入是用writeUTF()方法来来操作的,那么读取数据的时候也需要用readUTF()方法来读取,不然会出现数据没读完的异常。因为这两个方法写入的数据字节数和其他的码表的字节数不同,相对较多。

 

 

操作字节数组的流对象:ByteArrayInputStream和ByteArrayOutputStream

ByteArrayInputStream:

包含一个内部缓冲区,该缓冲区包含从流中读取的字节。这个流对象没有调用底层资源,不会产生IO异常。

对象在出事化的时候,需要接受数据源,而且数据源是一个字节数组。

 

ByteArrayOutputStream:

此类实现一个输出流,其中的数据被写入一个byte数组。缓冲区会随着数据的不断写入而自动增长。通过toByteArray()和toString()来获取数据。writeTo()方法提供将内部数据写入指定目的的操作。就这个方法会报异常。

 

对象在初始化的时候,不用定义数据目的。因为该对象中已经内部封装了可变长度的字节数组。这个就是数据目的。

 

这两个流对象不用调用关闭操作,调用了这个流对象还是存在的,还可以继续使用,因为这两个流对象没有调用底层的资源。

 

操作字符数组的流对象:CharArrayReader   和   CharArrayWriter

 

操作字符串的流对象:StringReader   和   StringWriter

以上三种流对象都是对内存进行操作的流对象。

 

IO流异常的处理方式:

 

import java.io.*;

class IOExceptionDemo 

{

public static void main(String[] args) 

{

//流对象初始化,这个步骤会发生IO异常。

//由于这个异常处理的步骤由多个代码块组成,所以必须要定义变量在函数范围内都能访问的到。

Writer w = null;

try

{

w = FileWriter("demo.txt");

w.write("aegubaiubegf");

}

catch (IOException e)

{

 处理代码块;

}

finally

{

try

{

if (w!=null)

{

w.close();

}

 

}

catch (IOException e)

{

处理代码块;

}

}

}

}

 

File文件操作对象

IO流对象只能对文件的数据进行操作,很多文件本身的数据IO流对象操作不了,于是就对文件进行了描述产生了File类对象。

File对象在初始化的时候封装一个文件或者目录。例如:File f = new File("e:\\java","a.java");这里同时封装了文件的父目录和文件。

打印File对象,那么在控制台上显示的就是File对象封装的路径或者文件,封装什么打印什么。

File类常见方法:

创建 

createNewFile()  特点:在指定位置创建文件,如果该文件已经存在,则不创建,返回false。

和输出流不一样,输出流对象一建立创建文件,而且文件已经存在会被覆盖。

boolean mkdir();创建目录,但只能创建一级目录。多级目录就不行了。

boolean mkdirs();创建多个目录。 

 

删除

delete();删除失败返回false

deletOnExit();在程序退出时删除文件

renameTo();重命名功能,如果绝对路径不一样,功能会类似剪切

 

判断

exists();判断文件是否存在

isFile();判断是否为一个文件,判断之前要先判断文件是否存在

isDirectory();判断是否为一个目录,判断之前要先判断文件是否存在

isHidden();判断是否为一个隐藏文件

isAbsolute();判断是否为绝对路径,文件不存在的话只要路径带盘符,就返回true

canExecute();判断文件件是否能被执行

 

获取信息

getName();

length();返回的是这个文件对象的大小。

getPath();获取路径,封装什么路径就返回什么路径。

getParent();//该方法返回的是绝对路径中的父目录。如果获取的是相对路径的话返回null,如果相对路径中有上一层目录,该目录就会返回结果。

getAbsolutePath();获取绝对路径。不论封装的是什么路径。返回的都是绝对路径。

lastModified();//最后修改的时间。

String[]   list();返回一个某个目录中所有的文件包括文件夹。调用这个方法的file对象必须封装一个目录,该目录必须存在。

File[]   listFIles();真实的开发中用这个方法相对list()方法较多。返回的是对象,可以再对对象进行细致的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值