一.IO流简介:
1.1 基本概念:
IO:用于处理设备之间的数据传输的技术。
流:可以理解数据的流动,就是一个数据流,是一种系统资源,它的功能由操作系统来实现,Java的IO调用了操作系统底层,实现输入输出功能。
1.2 IO流分类:
根据功能:输入流(读)和输出流(写)。
根据处理数据:分为字节流和字符流。
1.3 字节流:
处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都是以二进制形式进行存储的。二进制存储的最小单位是以一个8位(bit)数据单元,即一个字节(Byte)。字节流可以处理设备上的所有数据,包括字符数据。
1.4 字符流:
因为每个国家的字符都不一样,所以涉及到了字符编码问题,例如GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时还要获取指定的编码表,这样才可以正确解析数据。为了方便于文字的解析,将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流。
1.5 字符流和字节流体系:
输入输出流的体系功能不同,但是有共性内容,将这些共性内容不断抽取,形成了输入输出流的体系。
该体系一共有四个基类,都是抽象类。
类型 | 对应此类型的java实现类 |
---|---|
字节流 | InputStream,OutputStream |
字符流 | Reader,Writer |
在这四个子体系中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名则和这个子类的功能有关。
二.File类:
将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和方法可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。
2.1 File类的部分属性:
不同的系统文件路径的分隔符不一样,Windows的分隔符是“\”,Linux的分隔符则是“/”,所以File类将路径的分隔符封装成了一个静态属性,pathSeparator和pathSeparatorChar,两个属性功能相同数据类型不同
属性 | 功能 |
---|---|
static String pathSeparator | 与系统有关的路径分隔符,为了方便,它被表示为一个字符串 |
static char pathSeparatorChar | 与系统有关的路径分隔符 |
2.2 File类的部分方法:
方法 | 功能 |
---|---|
boolean createNewFile( ) | 在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,则会覆盖 |
boolean mkdir( ) | 创建此抽象路径名指定的目录,相似的方法mkdirs( )用来创建多级目录 |
boolean delete( ) | 删除此抽象路径名表示的文件或目录,在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除,否则会返回false删除失败 |
boolean renameTo(File dest) | 可以实现移动的效果。可以实现剪切和重命名两个功能 |
String[ ] list( ) | 获取指定目录下的当前的文件和文件夹的名称。包含隐藏文件。如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效 |
File[ ] listFiles( ) | 获取指定目录下的所有文件对象 |
boolean isDirectory( ) | 测试此抽象路径名表示的文件是否是一个目录,使用mkdir( )方法创建的文件夹返回true |
boolean isFile( ) | 测试此抽象路径名表示的文件是否是一个标准文件, 使用createNewFile( )方法创建的文件对象返回true |
三.递归:
函数自身调用自身,一个功能被重复使用,每一次使用该功能时的参数由上次使用该功能得出的结果来确定,其实递归就是在栈数据结构中不断的加载同一个函数
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
递归的使用案例:
编写一个函数,能够删除一个含有子文件夹及子文件的文件夹
由于delete( )函数不能在文件夹内还含有内容时删除一个文件夹,所以需要使用递归将文件夹内的内容全部删除,然后删除该文件夹
public class DeleteDir {
public static void main(String[] args) {
File file = new File("D:\\总结文档");
deleteDir(file);
}
public static void deleteDir(File dir){
//1.使用listFiles()获取文件夹下所有文件对象
File[] files = dir.listFiles();
//2.遍历获取到的文件对象
for (File file : files){
//2.1如果文件对象是一个文件夹
if (file.isDirectory()){
//使用递归,调用deleteDir()
deleteDir(file);
}else{
//2.2如果文件对象不是一个文件夹(则是一个文件),执行删除
System.out.println(file+"。。。。。"+file.delete());
}
}
//3.删除完文件夹下的所有文件后,再将此文件夹删除
System.out.println(dir+"。。。。。。"+dir.delete());
}
}
四.字节流
字节流体系:
4.1 字节输出流
4.1.1 OutputStream抽象类:
此抽象类输出字节流的所有类的超类。
OutputStream去掉注释的源码:
public abstract class OutputStream implements Closeable, Flushable {
//抽象方法,继承此类必须要实现的方法,各OutputStream子类该方法的实现不同
public abstract void write(int b) throws IOException;
//以字节数组的方式写数据
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException{
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
//调用了write(int b)方法实现功能
write(b[off + i]);
}
}
public void flush() throws IOException {
}
public void close() throws IOException {
}
close( )和flush( )的区别:
flush( ):将缓冲区的数据刷到目的地中,不关闭流,流还可以继续使用。
close( ):将缓冲区的数据刷到目的地中,然后关闭流。该方法主要用于结束调用的底层资源,使用流操作后一定要调用此方法关闭流。
4.1.2 FileOutputStream类:
文件输出流是用于将数据输出到File 或 FileDescriptor 的输出流。
FileOutputStream类和数据输出有关的部分源码:
public class FileOutputStream extends OutputStream{
private final String path;
private final boolean append;
//调用系统底层方法实现数据写出
private native void write(int b, boolean append) throws IOException;
//覆盖了OutputStream中的抽象方法write(int b)
public void write(int b) throws IOException {
Object traceContext = IoTrace.fileWriteBegin(path);
int bytesWritten = 0;
try {
//调用使用了系统底层的方法
write(b, append);
bytesWritten = 1;
} finally {
IoTrace.fileWriteEnd(traceContext, bytesWritten);
}
}
.....
}
FileOutputStream类进行数据输出的案例:
public class InputDemo1 {
private File file = new File("D:\\1.txt");
@Test
public void outputMethod1() throws IOException {
//创建流对象,需要指定文件
FileOutputStream fileOutputStream = new FileOutputStream(file);
//调用写数据的方法
fileOutputStream.write("abcde".getBytes());
//关闭流对象
fileOutputStream.close();
}
4.1.2 BufferedOutputStream类
该类使用缓冲区(缓冲区的概念和实现稍后会讲)进行数据输出。通过设置这种输出流,应用程序就可以调用一次底层系统资源将多个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
BufferedOutputStream类的一些操作缓冲区的方法:
void flush( ) 刷新此缓冲的输出流,即调用该方法后不管缓冲区中有多少数据,都将缓冲区中的数据全部写入硬盘
4.1.3 OutputStream的其他子类
FilterOutputStream:此类是过滤输出流的所有类的超类
4.2 字节输入流
4.2.1 InputStream抽象类:
字节输入流的所有类的超类
InputStream类去掉注释的源码:
public abstract class InputStream implements Closeable {
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
public int available() throws IOException {
return 0;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
4.2.2 FileInputStream类:
从文件系统中的某个文件中获得输入字节,常用于读取诸如图像数据之类的原始字节流
FileInputStream的使用案例:
//读取数据方式一
public void inputMethod1() throws IOException {
FileInputStream fileInputStream = new FileInputStream(file);
//以字节为单位,一个字节一个字节的读入数据
int by = 0;
while((by = fileInputStream.read()) != -1){
System.out.println(by);
}
fileInputStream.close();
}
//读取数据方式二
public void inputMethod2() throws IOException {
FileInputStream fileInputStream = new FileInputStream(file);
//开辟了一个大小为1024字节的空间作为缓冲区,缓冲区大小一般设置为1024的倍数
byte[] buffer = new byte[1024];
int length = 0;
//先将数据读入缓冲区
while((length = fileInputStream.read(buffer)) != -1){
//再对缓冲区中的数据进行操作
System.out.println(new String (buffer,0,length));
}
fileInputStream.close();
}
对比以上两种方式,方式二效率更高,因为方式一是读取一个字节接着便操作,方式二则是将一段数据读入缓冲区,然后再从缓冲区中将这一段数据拿出来对这些数据进行集中操作,省去了很多CPU取数据的操作,所以效率更高
方式二的实现和BufferedInputStream类的功能非常相似,但是BufferedInputStream类对缓冲区的使用更加详细规范
4.2.3 BufferedInputStream类
BufferedInputStream 可以为另一个输入流添加一些功能(使用了装饰者设计模式),即缓冲输入。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。
BufferedInputStream类的部分源码:
public
class BufferedInputStream extends FilterInputStream {
//创建一个静态变量用来设置缓冲数组的默认大小,BufferedInputStream类内部缓冲区数组的默认大小是8192字节
private static int defaultBufferSize = 8192;
.....
}
4.2.4 InputStream的其他子类:
FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
五.编码表
编码表 | 描述 |
---|---|
ASCII码表 | 美国标准信息交换码,用一个字节的7位表示 |
ISO8859-1码表 | 拉丁码表,欧洲码表用一个字节的8位表示 |
GB2312码表 | 中国的中文编码表 |
GBK码表 | 中国的中文编码表升级,融合了更多的中文文字符号 |
Unicode码表 | 国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode |
UTF-8码表 | unicode码表的升级版,最多用三个字节来表示一个字符 |
六.字符流
字符流将是将字节流和编码表一起封装起来的数据流,它的出现是为了解决文字因各种编码不同而出现的乱码问题
6.1 InputStreamReader类及OutputStreamWriter类
是字节流通向字符流的桥梁,InputStreamReader使用指定的 charset 读取字节并将其解码为字符,OutputStreamWriter可使用指定的 charset 将要写入流中的字符编码成字节,它们使用的字符集可以由名称指定也可以显式给定,或者使用平台默认的字符集。
使用案例:
@Before
public void outputMethod2() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
//使用FileOutputStream累的对象创建OutputStreamWriter类的对象
//创建对象时可以设置以哪种编码向文件进行写操作
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"UTF-8");
outputStreamWriter.write("你好");
fileOutputStream.close();
}
@Test
public void readMethod2() throws IOException {
FileInputStream fileInputStream = new FileInputStream(file);
//InputStreamReader类创建对象的方式和OutputStreamWriter类相同
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"UTF-8");
//按码表读取一个字符长度单位的数据并转换为字符类型然后输出
System.out.println((char)inputStreamReader.read());
fileInputStream.close();
}
由上面的例子可以看出,字符流封装了字节流和编码方式,可以说 字符流=字节流+编码表