Java IO

Java输入输出流

Ω文件可以认为是相关记录或存放在一起的数据的集合;

文件一般是存放在磁盘上的,例如:硬盘、软盘和光盘等等;

可断电式存储设备中,能够识别的只有两类东西;目录和文件;

File类

ΩFile类的对象不但可以表示文件,还可以表示目录,在程序中一个File类对象可以代表一个文件或目录;

Ω当创建一个文件对象后,就可以利用它来对文件或目录的属性进行操作,如:文件名、最后修改日期、文件大小等等;

Ω需要注意的是,File对象并不能直接对文件进行读/写操作,只能查看文件的属性;

 

方法描述
File(String path) 构造方法,根据路径得到File的对象,这个路径可以是文件,也可以是目录
exists():boolean 判断File对象对应路径的目录或文件在磁盘系统中是否存在
isDirectory():boolean 判断File对象是否为一个目录
isFile():boolean 判断File对象是否为一个文件
isHidden():boolean 判断File对象是否隐藏
canRead():boolean 判断File对象是否可读
canWrite():boolean 判断File对象是否可写
lastModified():long 返回File对象最后一次修改时间
length():long 返回文件中具备的字节数
createNewFile():boolean 根据虚拟路径创建文件的对象
delete():boolean 删除File对象表示的目录和文件;注意:文件可以直接删,但是删除目录时,必须保证目录时空的;
list():String[] 当File对象表示一个目录时,list方法能够列出这个目录下所有的直接子目录及文件名称
listFiles():File[] 得到指定目录下所有的子文件和目录的File对象
mkdir():boolean 根据虚拟路径创建目录的对象

如上图片所示,为一个文件具备的部分属性;

File类中虽然没有提供读取文件的功能,但是提供了其他丰富个功能;

  • 读取某个路径下所包含的子目录以及文件

  • 操作路径下的文件以及目录(创建目录,创建文件,删除,)

  • 读取文件以及目录所具备的属性

递归算法(英语:recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此在很多函数编程语言(如Scheme)中习惯用递归来实现循环。

递归和java中循环相似,循环具有循环三要素,这三要素控制了循环的循环次数。递归为了能够在有限次数内结束递归,因此需要一个结束的条件;

 

如果当一个递归算法无法正常退出,则类似于死循环,无休止的向方法栈中不断的压入栈帧,直到达到了JVM参数配置的内存上限时则会出现StackOverflowError;

 

栈是一种先进后出的数据结构,因此如果递归能够正常结束,必定是后压入栈的方法栈帧弹出栈以后最后才轮到第一次压入栈的方法弹出栈;

public class Test2 {
    static int count = 0;
    public static void main(String[] args) {
        test();
    }
​
    private static void test(){
        count++;
        if (count < 5) { //控制递归执行次数
            System.out.println("准备将test方法压入栈" + count);
            test(); //调用自身形成递归,直到这个方法弹出栈以后,才会执行弹出栈的输出语句
            System.out.println("已经将栈顶的test方法弹出栈" + --count);
        }
    }
}

 

准备将test方法压入栈1 准备将test方法压入栈2 准备将test方法压入栈3 准备将test方法压入栈4 已经将栈顶的test方法弹出栈1 已经将栈顶的test方法弹出栈2 已经将栈顶的test方法弹出栈3 已经将栈顶的test方法弹出栈4

输入输出流

输入流(InputStream)和输出流(OutputStream)用来处理设备之间的数据传输。Java程序中,对于数据的输入/输出操作以”流(stream)” 的方式进行。java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

 

流的分类

按流向:输入流和输出流

按单位:字节流和字符流

按角色:节点流和处理流

抽象类字节流字符流
输入流 InputStream Reader
输出流 OutputStream Writer

所有的有关输出输入流的类都直接或间接的继承了以上4个父类;在这4个类中定义通用的读以及写的方法;

由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

java.io.FileInputStream这个类是InputStream的子类

java.io.FileOutputStream是OutputStream的子类

java.io.FileReader是Reader字符输入流的子类

java.io.FileWriter是Writer字符输出流的子类

 

 

字节流和字符流的区别

l字节流是指8位的通用字节流,以字节为基本单位,在java.io包中,对于字节流进行操作的类大部分继承于InputStream(输入字节流)类和OutputStream(输出字节流)类;

l字符流是指16位的Unicode字符流,以字符(两个字节)为基本单位,非常适合处理字符串和文本,对于字符流进行操作的类大部分继承于Reader(读取流)类和Writer(写入流)类。

能用记事本打开的且文件不大一般直接用字符流

其他的图片,压缩文件,可执行文件等等都采用字节流

InputStream中的通用方法

方法描述
read():int 从输入流中读取下一个字节,如果读到流的末尾,则返回-1
read(byte[]):int 从输入流中读取n个字节到byte数组,返回流中实际读到的字节数。如果读到流的末尾,则返回-1
read(byte[] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组。返回流中实际读到的字节数。如果读到流的末尾,则返回-1
close():void 关闭输入流,重要,每次用完流之后必须手动关闭

OutputStream中的通用方法

方法描述
write(int b):void 将一个字节写入输出流
write(byte[] b):void b.length字节从指定的字节数组写入此输出流。
write(byte[] b,int off,int len):void 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
flush():void 如果输出流带有缓存,则需要调用flush方法将缓存的字节写入输出流
close():void 关闭输出流,重要,每次用完流之后必须手动关闭

Reader中的通用方法

方法描述
read():int 从流中读取一个字符;
read(char[] buff):int 从流中读取buff.length长度的字符数组,返回实际读到的字符个数,如果达到流的末尾,则返回-1
read(char[] cbuf, int off, int len) 从流中读取buff.length长度的字符数组,指定off下标开始接收,len个字符
ready():boolean 如果下一个read()保证不阻止输入,则为True,否则为false。 请注意,返回false并不能保证下一次读取将被阻止。
close():void 关闭字符输入流

Writer中的通用方法

方法描述
write(int c):void 将一个字符写入此字符输出流
write(String str):void 将一个字符串写入此输出流
write(char[] buff):void 将一个字符数组写入输出流
write(char[] b,int off,int len):void 将一个指定范围的字符数组写入输出流
flush():void 刷新缓存流,在关闭前刷新
close():void 关闭流

FileInputStream

关于字节输入流Inputstream的直接子类,代表着通过字节输入流读取一个文件;

方法描述
FileInputStream(String name) throws FileNotFoundException 按照指定路径得到字节输入流的对象
FileInputStream(File file) throws FileNotFoundException 按照指定的File对象得到字节输入流的对象

当使用字节输入流读取英文字母,英文符号,0-9的数字时一切正常;但是在读取UTF-8编码的中文符号或者汉字时,会出现乱码的情况;因为UTF-8编码的汉字采用UNICODE字符集,每个字符有2-3个字节组成;

public static void main(String[] args) {
    String path = "F:\\java43\\javaSE\\ch15\\abc.txt";
    FileInputStream in = null;
    try {
        File file = new File(path);
        //FileInputStream in = new FileInputStream(path);
        in = new FileInputStream(file);
​
        int i; //用来接收每次读的每个字节
        StringBuilder builder = new StringBuilder();
        while ((i = in.read()) != -1) {//如果i的值为-1,说明读到流的末尾
            //System.out.println(i);
            char c = (char) i;
            //System.out.println(c);
            builder.append(c);
        }
        System.out.println(builder.toString());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally { //不管程序是否出现异常都会执行的代码块
        if (in != null) {
            try {
                in.close(); //读完之后关闭流
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

FileOutputStream

关于OutputStream抽象类的直接子类,负责将内存中的数据输出到指定文件;

方法描述
FileOutputStream(String name) throws FileNotFoundException 将数据输出到指定路径的文件中。默认会覆盖原始文件
FileOutputStream(String name,boolean append) throws FileNotFoundException 将数据输出到指定路径的文件中。如果append为true,则实现在原始文件的末尾追加输出的数据
FileOutputStream(String File) throws FileNotFoundException 将字节输出流的数据输出到File对象对应的文件中,默认覆盖原始数据。
FileOutputStream(String File,boolean append) throws FileNotFoundException 将字节输出流的数据输出到File对象对应的文件中,append参数为true,则实现原始数据末尾追加的功能
private static void write2File(String params) {
    String path = "F:\\java43\\javaSE\\ch15\\abc.txt";
    OutputStream out = null;//之所以提出out对象,因为在finally中需要关闭流
    try {
        //保留原始数据,在原始数据的末尾追加
        out = new FileOutputStream(path,true);
​
        byte[] bytes = params.getBytes();//得到字符串对应的字节数据
//每次写一个字节
        /*for (byte aByte : bytes) {
            out.write(aByte); //捕获异常时的快捷键:alt + entry
        }*///直接将字节数组写入文件
        //out.write(bytes);
//按照范围写字节数组
        out.write(bytes, 0, bytes.length);
​
        System.out.println("写入文件完毕===============");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (out != null) { //关闭之前判断out是否为null
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

FileReader

方法描述
FileReader(File file) 根据file对象初始化字符输入流的对象
FileReader(String fileName) 根据文件的路径名初始化字符输入流的对象

FileWriter

方法描述
FileWriter(File) 得到File对象的字符输出流,默认覆盖原始数据
FileWriter(File,boolean) 得到File对象的字符输出流,true代表末尾追加
FileWriter(String fileName) 得到fileName对应的文件字符输出流,默认覆盖
FileWriter(String fileName,boolean) 得到字符输出流,true代表末尾追加
String str = "我爱编程,编程使我快乐!";
Writer writer = null;
String fileName = "F:\\java43\\javaSE\\ch15\\abc.txt";
try {
    writer = new FileWriter(fileName, true);
​
    //将字符串转换为字符的数组
    /*char[] chars = str.toCharArray();
    for (char aChar : chars) {
        writer.write(aChar);
    }*///直接将一个字符串写入字符输出流
    /*StringBuilder builder = new StringBuilder(str).reverse();
    writer.write("\r\n"); //换行
    writer.write(builder.toString());*//*直接写一个char的数组*/
    char[] chars = str.toCharArray();
    writer.write("\r\n"); //换行
    writer.write(chars);
​
    System.out.println("写入文件完毕。。。。。。。");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (writer != null) {
        try {
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

节点流和处理流

1.定义

(1)节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader。

(2)处理流(用来包装节点流):是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

 

2.区别与联系

(1)节点流是低级流,直接跟数据源相接。

   处理流(也叫包装流)把节点流包装了一层,属于修饰器设计模式,不会直接与数据源相连,通过处理流来包装节点流既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。

(2)处理流的功能主要体现在以下两个方面:

1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。

2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的内容,而不是输入/输出一个或多个水滴处理流可以嫁接在任何已存在的流的基础上

BufferedInputStream

BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

BufferedOutputStream

BufferedOutputStream本质上是通过一个内部缓冲区数组实现的。例如,在新建某输出流对应的BufferedOutputStream后,当我们通过write()向输出流输出数据时,BufferedOutputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据存满之后做一次刷新缓存的操作(将缓存区内容输出到文件)。如此反复,直到我们输出所有的数据到文件。

BufferedReader

带有缓存的字符输入流,其内部包装的是一个字符输入流;

BufferedReader遵循装饰设计模式,内部维护了一个特定长度的字符数组(默认8192)。每次读取之前直接从数据源中读取8192个字符填充到缓存的字符数组中。后续的多次读取操作直接从缓存中读取,若缓冲中的数据读取完毕,则再次从数据源中读取数据到缓存数组。这样做的好处在于减少了直接读取文件的次数,增加了读取的效率。

BufferedWriter

带有缓存的字符输出流。内部维护了一个默认长度(8192)用于缓存的字符数组。当用户调用write方法时,直接向缓存数组中写入。当缓存数组写满后,则调用flush()做一次向数据源写入的操作。这样做的好处在于减少了用户直接将数据多次写入文件的操作。增加写入效率。

转换流

将字节流转换为字符流

  • 字节输入流转换为字符输入流: InputStreamReader

  • 字节输出流转换为字符输出流:OutputStreamWriter

序列化和反序列化

为了实现java 对象在不同的操作系统或者通过网络的形式能够传输java对象,就可以通过序列化和反序列化实现。 为了实现序列化和反序列化操作,类必须要实现java.io.Serializable接口。 结合ObjectInputStreamObjectOutputStream就能够实现对对象的序列化和反序列化操作。

序列化和反序列一般在分布式系统中应用的比较多。

posted @ 2022-09-09 16:44  岁月记忆  阅读(28)  评论(0)    收藏  举报