Java(25)IO流和File类

IO流+File类

File类

讲IO流之前先来讲以下File类。Java的标准库Java.io提供了File类来操作文件和目录。操作可以有:新建、删除、重命名等,但是不能访问文件本身的内容,如果想要访问,需要使用IO流。

新建File对象:

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args) {
        File a=new File("C:\\Users\\97464\\Desktop\\test\\File1.txt");

        File b=new File("C:\\Users\\97464\\Desktop","test\\File1.txt");

        /*
        1.上面两种方式创建的FIle对象是一个意思,File类的不同构造方法导致可以有不同的参数。
        2.构造File对象的时候,既可以传入绝对路径,也可以传入相对路径。
        3.注意Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符。传入相对路径时,相对路径前面加上当前目录就是绝对路径。
        4.   .代表当前目录..代表上级目录
         */
    }
}

获取文件或者目录的路径和名字等操作

package day01;

import java.io.File;
import java.io.IOException;

class Battery{
    public static void main(String[] args){
        File tt=new File("F:\\test\\test\\..\\hello.txt");
        //获取文件或者目录的路径:
        System.out.println(tt.getPath());  //返回构造方法传入的路径
        System.out.println(tt.getAbsolutePath());  //返回绝对路径
        try {
            System.out.println(tt.getCanonicalPath());  
            //返回规范路径(C:\\Users\\..----->C:\\)
        }catch (IOException e){
            e.printStackTrace();
        }
        //获取文件或者目录的名字:
        System.out.println(tt.getName());

        //返回一个用当前文件的绝对路径构建的File对象;
        File gg=tt.getAbsoluteFile();
        System.out.println(gg.getName());

        //返回文件或目录的父级目录:
        System.out.println(tt.getParent());

        //重命名文件或目录:
        tt.renameTo(new File("F:\\test\\test\\..\\hello2.txt"));
    }
}
/*运行结果为:
F:\test\test\..\hello.txt
F:\test\test\..\hello.txt
F:\test\hello.txt
hello.txt
hello.txt
F:\test\test\..
*/

/*----------------补充:--------------------------------
上面的getCanonicalPath()方法,如果去查看它的源码可以发现它抛出了IOException异常,如果想要使用它,需要在调用的时候try...catch捕获异常,或者由main()继续抛出异常,交给JVM处理。
*/

文件检测

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello2.txt");
        //判断文件或者目录是否存在:
        System.out.println(a.exists());

        //判断文件是否可读或可写:
        System.out.println(a.canRead());
        System.out.println(a.canWrite());

        //判断当前File对象是不是文件或者目录:
        System.out.println(a.isFile());
        System.out.println(a.isDirectory());
    }
}
/*运行结果为:
true
true
true
true
false
*/

获取文件大小和文件的最后修改时间

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello2.txt");
        System.out.println(a.length()); //返回文件的大小,以字节为单位
        System.out.println(a.lastModified());  //返回最后修改时间,是一个毫秒数
    }
}
/*运行结果为:
11
1580719765617
*/

新建和删除文件或目录

package day01;

import java.io.File;
import java.io.IOException;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello.txt");
        if(!a.exists()){ //判断文件是否不存在
            try{
                a.createNewFile();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        a.delete(); //删除文件

        File b=new File("F:\\test2");
        b.mkdir(); //创建单层目录

        File c=new File("F:\\test3\\kobe\\number24");
        c.mkdirs();//创建多层目录
    }
}

遍历目录下的文件和子目录

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File d=new File("F:\\test3");
        System.out.println(d.list().getClass());
        //可以看到d.list()返回的是数据类型是class [Ljava.lang.String;
        //[:表示返回的类型是数组,L:表示数组元素是一个对象实例 
        // Java.land.String表示对象是String类型
        //下面进行循环输出目录下的文件或者子目录:
        String []strArr=d.list();
        for (String i:strArr){
            System.out.println(i);
        }

        File e=new File("F:\\test3");
        File [] arrFile=e.listFiles(); 
        //e.listFiles()返回test3目录下的文件或子目录的file对象
        for (File i: arrFile){
            System.out.println(i);
        }
    }
}
/*运行结果为:
class [Ljava.lang.String;
kobe
F:\test3\kobe
*/

上面遍历文件和子目录是单层遍历,如果想要进行多层遍历(子目录的子目录也会被遍历),可以进行递归遍历。

  • 案例:
package day01;

import java.io.File;

class Rabbit{
    public void fds(File a){
        if(a.isFile()){
            System.out.println(a.getName());
        }else {
            System.out.println(a.getName());
            File []fileArr=a.listFiles();
            for (File i:fileArr){
                fds(i); //递归
            }
        }
    }
}
class RunClass{
    public static void main(String[] args) {
        Rabbit test=new Rabbit();
        File tf=new File("F:\\test3");
        test.fds(tf);
    }
}

IO流

IO概念:

IO是指Input/Output,即输入和输出,以内存为中心:

  • Input指从外部读入数据到内存。----例如把文件从磁盘读取到内存,从网络读取数据到内存等
  • Output指把数据从内存输出到外部。----例如把数据从内存写入文件,把数据从内存输出到网络等

为什么要以内存为中心?

------因为数据被读取到内存中才能被处理,代码是在内存中运行的,数据也必须内存。

IO流概念:

IO流是一种顺序读写数据的模式,它的特点是单向流动。

------例如: 想要把一张图片放入一个文件夹,不能整张直接塞进去,而是需要把图片转化为一个数据集(例如二进制),把这些数据一点一点传到文件夹,这个数据的传递类似于自来水在水管中的流动,称为IO流。

同步和异步

同步IO是指,读写IO时代码必须等待数据返回后才继续执行后续代码,它的优点是代码编写简单,缺点是CPU执行效率低。

而异步IO是指,读写IO时仅发出请求,然后立刻执行后续代码,它的优点是CPU执行效率高,缺点是代码编写复杂。

Java标准库的包java.io提供了所有的同步IO,而java.nio则是异步IO。

下面我们即将讨论的InputStreamOutputStreamReaderWriter都是同步IO的抽象类的具体实现类

流的分类

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)

按数据流的流向不同分为:输入流,输出流

按流的角色不同分为:节点流,处理流

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

Java的IO流共有40多个相关类,实际上都是从上面的四种抽象基类派生的。

image-20200204105414414

字节流

IO流以byte(字节)为最小单位,称为字节流。字节流非常通用,不仅可以用来操作字符型文档,还可以操作任何地其它类型文件(图片、压缩包等),因为字节流本身使用地就是二进制。

╔════════════╗
║   Memory   ║ 内存
╚════════════╝
       ▲
       │0x48
       │0x65
       │0x6c
       │0x6c
       │0x6f
       │0x21
 ╔═══════════╗
 ║ Hard Disk ║ 硬盘
 ╚═══════════╝
 //上面,内存从硬盘(磁盘)读入了6个字节的数据,是按顺序读入的,是输入字节流。
 //反过来就是输出字节流:
 ╔════════════╗
 ║   Memory   ║ 内存
 ╚════════════╝
       │0x21
       │0x6f
       │0x6c
       │0x6c
       │0x65
       │0x48
       ▼
 ╔═══════════╗
 ║ Hard Disk ║ 硬盘
 ╚═══════════╝
InputStream

InputStream是Java标准库提供的最基本的输入字节流。位于Java.io这个包里。InputStream是一个抽象类,是所有输入流的父类,这个抽象类定义的最重要的方法是int read()。源代码如下:

public abstract int read() throws IOException;

构建InputStream对象:

import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) {
        InputStream is=new InputStream() {
            @Override  //可以看到新建InputStream输入流对象必须重写抽象方法int read()
            public int read() throws IOException {
                return 0;
            }
        };
    }
}

read()这个方法会读取输入流的下一个字节,并返回字节表示的int(0-255)ascii码),读到末尾就会返回-1

read()方法还可以传递参数——read(byte [] b),作用是一次读取一个字节数组,把输入流读取到的内容存放到这个字节数组中,并返回存放到数组的内容的字节数,同样是到末尾就返回-1byte数组是作为一个缓冲区,每次存入数组大小为byte.length的数据,存入的数据是一个int

FileInputStream

FileInputStreamInputStream的一个子类,用来从文件中读取数据。FileInputStream类的构造方法有:

  • FileInputStream(File file): 传递一个文件的File类对象
  • FileInputStream(String name): 传递一个String类型的文件路径

案例1: int read()的使用

F:\\test\\hello.txt的文件内容:
abclove
package day01;

import java.io.FileInputStream;
import java.io.IOException;

class MainClass{
    public static void main(String[] args) throws IOException {  //所有与IO操作相关的代码都必须正确处理IOException
        FileInputStream fis=new FileInputStream("F:\\test\\hello.txt");  //创建流对象
        for(;;){  //无限循环
            int n = fis.read();  //反复调用read()方法,直到n=-1
            if (n==-1){
                break;
            }
            System.out.print(n+":"); //打印read()返回的byte值
            System.out.println((char)n);
        }
        fis.close(); //关闭流,释放对应的底层资源
    }
}
/*运行结果为
97:a
98:b
99:c
108:l
111:o
118:v
101:e
 */
//从运行结果可知,read()返回的单字节整数值,是对应字符的acsii码。通过(char)就可以转换为对应字符。

案例2:int read(byte [] b)的使用(缓冲)

package day01;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) throws IOException {
        InputStream cup=new FileInputStream("F:\\test\\hello.txt");
        byte [] b=new byte[3];  
        //b数组是作为一个缓冲区,在下面的循环中,每循环一次,b就会更新一次,输入流把读取到的内容放到b中
        int len=0; //初始化len为0
        while((len=cup.read(b))!=-1){  //len代表存放到数组b的数据的字节数
            System.out.print(len+":");
            System.out.println(new String(b,0,len)); //用法看下面的补充
        }
        cup.close();
    }
}
/*运行结果为:
3:abc
3:lov
1:e
 */
/*-------------------------补充:----------------------------------------
String类的构造方法public String(byte bytes[], int offset, int length)的用法:
	作用:将字节数组的某部分转为字符串
	参数:byte bytes[]表示要被转的数组
		int offset表示偏移量,即从数组的第几个位置开始转为字符串,
		int length表示总共要转化的字节数

read()方法是阻塞的(blocking)。读取IO流相比执行普通代码,速度要慢很多。

package day01;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) throws IOException {
      InputStream a=new FileInputStream("F:\\test\\hello.txt");
        System.out.println("kobe");
        int b=a.read();  // 必须等待read()方法返回才能执行下一行代码
        System.out.println("gigi");
    }
}
OutputStream

OutputStream是Java标准库提供的最基本的输出流。OutputStream和InputStream一样,也是抽象类,是所有输出流的父类。抽象类定义的一个重要的方法是void write(int b),源代码如下:

public abstract void write(int b) throws IOException;

write()还有重载方法:

  • write(byte b[]):用于一次性写入多个字节
  • write(byte b[], int off, int len) :将数组boff位置开始,把长度为len字节的数据写入到文件中

InputStream一样,OutputStreamwrite()方法也是阻塞的。

FileOutputStream

FileOutputStreamOutputStream的子类。FileOutputStream类的构造方法有:

  • FileOutputStream(File file): 传递一个文件的File类对象
  • FileOutputStream(String name): 传递一个String类型的文件路径

案例1:一次写入一个字节

package day01;

import java.io.*;

class MainClass{
    public static void main(String[] args) throws IOException {
        OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
        ops.write(97); //往文件写入97ascii码代表的字符a
        ops.write(98); //往文件写入98ascii码代表的字符b
        ops.write(99); //往文件写入99ascii码代表的字符c
        ops.flush();
        ops.close();
    }
}
//运行结果是:往F:\\test\\outPut.txt这个文件写入“abc",如果这个文件不存在,则会新建文件。

从案例1可以看到,OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。

为什么要有flush()?因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出。

通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputStream会自动调用它,并且,在调用close()方法关闭OutputStream之前,也会自动调用flush()方法。

但是,在某些情况下,我们必须手动调用flush()方法。举个例子:

小明正在开发一款在线聊天软件,当用户输入一句话后,就通过OutputStreamwrite()方法写入网络流。小明测试的时候发现,发送方输入后,接收方根本收不到任何信息,怎么肥四?

原因就在于写入网络流是先写入内存缓冲区,等缓冲区满了才会一次性发送到网络。如果缓冲区大小是4K,则发送方要敲几千个字符后,操作系统才会把缓冲区的内容发送出去,这个时候,接收方会一次性收到大量消息。

解决办法就是每输入一句话后,立刻调用flush(),不管当前缓冲区是否已满,强迫操作系统把缓冲区的内容立刻发送出去。

实际上,InputStream也有缓冲区。例如,从FileInputStream读取一个字节时,操作系统往往会一次性读取若干字节到缓冲区,并维护一个指针指向未读的缓冲区。然后,每次我们调用int read()读取下一个字节时,可以直接返回缓冲区的下一个字节,避免每次读一个字节都导致IO操作。当缓冲区全部读完后继续调用read(),则会触发操作系统的下一次读取并再次填满缓冲区。

案例2:一次性写入若干字节

package day01;

import java.io.*;

/**
 * 一次性写入若干字节,通过void write(byte [])来实现
 */
class MainClass{
    public static void main(String[] args) throws IOException {
        OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
        ops.write("hello Krystal".getBytes("utf-8") );
        ops.flush();
        ops.close();
    }
}
/*----------------------补充:------------------------------------------
 String.getBytes(String decode)方法会根据指定的decode编码返回某字符串在该编码下的byte数组
*/

案例3:复制文件

package day01;

import java.io.*;

class MainClass{
    public static void main(String[] args) throws IOException {
        Stero st=new Stero();
        st.fun1("F:\\test\\outPut.txt","F:\\test\\outPut2.txt");
        //把outPut.txt复制为outPut2.txt
    }
}
class Stero{
    void fun1(String intputPath,String outputPath) throws IOException {
        InputStream ips=new FileInputStream(intputPath);
        OutputStream ops=new FileOutputStream(outputPath);
        byte[]a=new byte[3];
        int len;
        while((len=ips.read(a)) != -1){
            ops.write(a,0,len);
        }
        ops.close(); //先关闭输出流
        ips.close(); //后关闭输入流
        //最早开的最晚关
    }
}

正确关闭流

上面的流的案例,存在一个潜在的问题,如果读取或者写入过程中,发生了IO错误,流就没法及时关闭,资源也无法释放。 Java7引入了新的try(resource)语法,只需要编写try语句,就可让编译器自动为我们关闭资源。

案例:自动正确地关闭流

class Cable{
    public static void main(String[] args) throws IOException{
        //把新建流对象地语句放在try()里面即可
        try(FileInputStream sf=new FileInputStream("F:\\test\\hello.txt")){
            int n;
            while((n=sf.read())!=-1){
                System.out.println((char)n);
            }
        }
    }
}

字符流

如果我们需要读写的是字符(不是图片什么的),并且字符不全是单字节表示的ASCII字符,那么按照char来读写更方便,这种流称为字符流。字符流传输的最小数据单位是char,Java提供了ReaderWriter两个基类来操作字符流。

ReaderWriter本质上是一个能自动编解码的InputStreamOutputStream

使用Reader,数据源虽然是字节,但我们读入的数据都是char类型的字符,原因是Reader内部把读入的byte做了解码,转换成了char。使用InputStream,我们读入的数据和原始二进制数据一模一样,是byte[]数组,但是我们可以自己把二进制byte[]数组按照某种编码转换为字符串。究竟使用Reader还是InputStream,要取决于具体的使用场景。如果数据源不是文本,就只能使用InputStream,如果数据源是文本,使用Reader更方便一些WriterOutputStream是类似的。

Reader

Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取,Reader是所有字符输入流的父类。Reader类的主要方法是int read(),源代码是:

public int read() throws IOException;
FileReader

FileReaderReader的子类,FileReader的用法和FileInputStream的用法极其相似。

案例1:

//hello.txt的内容如下(注意编码格式要为utf-8,要不然会出错,另存为就可以改编码格式)
武汉,加油。
China,add oil. 
package day01;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

class RunClas{
    public static void main(String[] args) {
        Water w=new Water();
        try {
            w.fun1("F:\\test\\hello.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class Water{
    public void fun1(String rPath) throws IOException {
        try(Reader fr=new FileReader(rPath)){
            char[] a=new char[4]; //创建用于缓冲的字符数组
            int len;
            while((len=fr.read(a))!=-1){
                System.out.println(new String(a,0,len));
            }
        }
    }
}
Writer

Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出。

Writer是所有字符输出流的超类,它提供的方法主要有:

  • 写入一个字符(0~65535)void write(int c)
  • 写入字符数组的所有字符:void write(char[] c)
  • 写入String表示的所有字符:void write(String s)
FileWriter

FileWriterWriter的一个子类。FileWriter的用法和FileOutputStream的用法很相似。

案例1:使用void write(String s)方法

package day01;

import java.io.*;

class RunClas {
    public static void main(String[] args) {
        Desk d=new Desk();
        try {
            d.funF("F:\\test\\FileWriterOutput.txt","Hello,krystal,i'm missing you.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class Desk {
    public void funF(String oPath, String content) throws IOException {
        try (Writer a = new FileWriter(oPath)) {
            a.write(content);
            a.flush();
            a.close();
        }
    }
}

案例2:用字符流进行复制文件

package day01;

import java.io.*;

class RunClas {
    public static void main(String[] args) throws IOException {
        Desk d = new Desk();
        d.funF("F:\\test\\hello.txt", "F:\\test\\hello3.txt");
    }
}

class Desk {
    public void funF(String iPath, String oPath) throws IOException {
        Reader ra = new FileReader(iPath);
        Writer wa = new FileWriter(oPath);
        char[] charArr = new char[100];
        int len;
        while ((len = ra.read(charArr)) != -1) {
            wa.write(charArr);
        }
        wa.flush();
        wa.close();
    }
}

流对文件的操作注意事项:

  • 在写入一个文件时,目录下的同名文件会被覆盖
  • 在读取一个文件时,必须保证改文件是存在的,否则报异常

缓冲流

为了提高 数据读写的速度,Java提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组。(基于内存的)。

BufferedInputStream-->FileInputStream
BufferedOutputStream-->FileOutputStream
BufferedReader-->FileReader
BufferedWriter-->FileWriter

缓冲流先把数据缓冲到内存里,然后在内存中做io操作,基于内存的io操作比基于硬盘的操作快7500倍。

案例1:BufferedInputStream的使用
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        //创建File流对象:
        FileInputStream a=new FileInputStream("F:\\test\\hello.txt");
        //创建缓冲流对象:
        BufferedInputStream b=new BufferedInputStream(a); //需要传入FIle流作为参数
        byte[] bArr=new byte[100];
        int len;
        while((len=b.read(bArr))!=-1){
            System.out.println(new String(bArr,0,len));
        }
    }
}
案例2:BufferedOutputStream的使用
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        FileOutputStream a=new FileOutputStream("F:\\test\\BOutput.txt");
        BufferedOutputStream b=new BufferedOutputStream(a); 
        b.write("I love you,krystal".getBytes());
        b.flush();
        b.close();
    }
}
案例3:使用字节流+缓冲流实现文件复制
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        //创建File流对象:
        FileInputStream ia=new FileInputStream("F:\\test\\hello.txt");
        FileOutputStream oa=new FileOutputStream("F:\\test\\Chello.txt");

        //创建缓冲流对象:
        BufferedInputStream ib=new BufferedInputStream(ia);
        BufferedOutputStream ob=new BufferedOutputStream(oa); 

        byte[] bArr=new byte[100];
        int len;
        while((len=ib.read(bArr))!=-1){
            ob.write(bArr);
        }
        ob.flush();
        ob.close();
        ib.close();
        oa.close();
        ia.close();
    }
}
案例4:BufferedReader的使用
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        FileReader a=new FileReader("F:\\test\\hello.txt");
        BufferedReader b=new BufferedReader(a);
        char []cArr=new char[100];
        int len;
        while((len=b.read(cArr))!=-1){
            System.out.println(new String(cArr,0,len));
        }
        b.close();
        a.close();
    }
}
案例5:BufferedWriter的使用
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        FileWriter a=new FileWriter("F:\\test\\brout.txt");
        BufferedWriter b=new BufferedWriter(a);
        b.write("Hello,krystal!");
        b.flush();
        b.close();
        a.close();
    }
}
案例6:使用字符流+缓冲流实现文件的复制
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new FileReader("F:\\test\\hello.txt"));
        BufferedWriter bw=new BufferedWriter(new FileWriter("F:\\test\\em.txt"));
        char [] cArr=new char[100];
        int len;
        while((len=br.read(cArr))!=-1){
            bw.write(cArr);
        }
        bw.flush();
        bw.close();
        br.close();
    }
}

转换流

转换流是指InputStreamReaderOutputStreamWriter,上面讲了,流的数据都是字符时,转成字符流更高效,那么转换流就是用来将字节流转换成字符流的,并且可以指定字符集的编码解码格式。

案例1:转换字节输入流为字符输入流

//"F:\\test\\aa.txt"文件的编码为GBK,文件内容如下:
中国我爱你。
I love you,China.
package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        //创建文件字节输入流对象
        FileInputStream fis=new FileInputStream("F:\\test\\aa.txt");
        //创建转换流对象
        InputStreamReader isr=new InputStreamReader(fis,"utf-8");//指定字符集编码
        char []cArr=new char[10];
        int len;
        while((len=isr.read(cArr))!=-1){
            System.out.println(new String(cArr,0,len));
        }
        isr.close();
        fis.close();
    }
}
/*运行结果为:
�й��Ұ��㡣

I love you
,China.
*/
/*出现了乱码,是因为代码中指定的字符集编码与读取的文件的数据的编码格式不一致,
指定编码的一行代码改成:InputStreamReader isr=new InputStreamReader(fis,"GBK");即可避免乱码*/

案例2:转换字节输出流为字符输出流

package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("F:\\test\\ors.txt");
        OutputStreamWriter osw=new OutputStreamWriter(fos,"utf-8");
        osw.write("中国,我爱你");
        osw.flush();
        osw.close();
        fos.close();
    }
}

标准输入输出流

System.outSystem.in是系统标准的输入和输出设备(键盘和显示器)

System.in的类型是InputStream

System.out的类型是PrintStream。

  • 案例1:创建一个接受键盘输入的标准输入流
package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        RedPap rd=new RedPap();
        rd.fun();
    }
}
class RedPap{
    public void fun() throws IOException {
        //创建接受键盘输入的输入流
        InputStreamReader isr=new InputStreamReader(System.in);
        //把输入流放到缓冲流里:
        BufferedReader bfr=new BufferedReader(isr);
        //创建临时接受数据的字符串:
        String str="";
        while((str=bfr.readLine())!=null){  //readLine()是缓冲输入字符流提供的按行读取终端数据的方法,每一次调用会以字符串形式返回一行内容,读取完会返回null
            System.out.println(str);
        }
    }
}
//运行结果如下图:

image-20200205223122589

案例2:将控制台的输入内容输出到文件krystal.txt中,控制台出现”over“时结束输出。

package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        Krystal cf=new Krystal();
        cf.fun("F:\\test\\Krystal.txt");
    }
}
class Krystal{
    public void fun(String kPath) throws IOException {
        BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bfw=new BufferedWriter(new FileWriter(kPath));
        String str="";
        while((str=bfr.readLine())!=null){
            if(str.equals("over")){
                break;
            }
            bfw.write(str);
        }
        bfw.close();
        bfr.close();
    }
}

image-20200206025400074

序列化与反序列化

首先思考两个问题:

1、如果想把一个类的实例化对象存到电脑的硬盘上,要怎么做?

  硬盘存储的基础是二进制,需要把对象转化为一个二进制的字节流,然后把流保存到硬盘上。如果要用存到硬盘里的对象,又得把流转化为对象再使用。

2、如果想把一个对象通过网络传到另一台机器上,要怎么做?

  网络通信的基础也是二进制,需要把对象转化为二进制的数据流,然后通过网络传输流。接收者如果想要使用接收的对象得先把对象的流转为对象。

因为上面两类问题的存在,产生了对象的输入与输出流。

序列化(Serialize):序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组,用ObjectOutputStream类将一个对象写入IO流中。

反序列化(Deserialize):用ObjectInputStream类从IO流中恢复对象。

注意事项:

  • 序列化和反序列化针对的是实例化对象的各种属性,不包括类的属性。
  • 一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口
  • 实现Serializable接口的类的对象的是可序列化的
  • Serializable接口没有定义任何方法,它是一个空接口。
  • 我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

案例1:

package day01;

import java.io.*;

class AbleObj implements Serializable {
    String name;
    int age;
    String school;
    double weigth;
}
class ObjOut{  //序列化类
    public void funO(String oosPath) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(oosPath)); //定义对象输出流
        AbleObj ao=new AbleObj();
        ao.name="krystal";
        ao.age=20;
        ao.school="SongSanHu";
        oos.writeObject(ao);
        oos.flush();
        oos.close();
    }
}
class ObjIn{  //反序列化类
    public void funI(String oisPath) throws IOException, ClassNotFoundException {
        ObjectInputStream ois =new ObjectInputStream(new FileInputStream(oisPath));
        Object a=ois.readObject();
        AbleObj b=(AbleObj)a; //强制转换为AbleObj类型
        /*对象的序列化和反序列化使用的类要严格一致,序列化是什么类反序列化就用什么类,
        包名、类名、类结构等等所有都要一致。*/
        System.out.println(b.name);
        System.out.println(b.school);
        ois.close()
    }
}
public class Test01{  //运行类
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjOut tt=new ObjOut();
        tt.funO("F:\\test\\KrystalInfo.txt");
        ObjIn kk=new ObjIn();
        kk.funI("F:\\test\\KrystalInfo.txt");
    }
}
/*运行结果为:
krystal
SongSanHu
 */
安全性

因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。

实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

随机存取流

RandomAccessFile类支持"随机访问"的方式,即程序可以直接跳到文件的任意地方进行读写操作RandomAccessFile对象包含一个记录指针,用来标记当前读写开始位置,通过void seek(long pos)方法可以将记录指针定位到pos位置。

案例1:随机访问文件(任意位置读取)

//F:\\test\\AccessTest.txt文件的内容为:
123456789I love you,krystal. Where are you?
package day01;

import java.io.FileInputStream;
import java.io.RandomAccessFile;

public class Test01{
    public static void main(String[] args) throws Exception {
        RandomIn ii=new RandomIn();
        ii.Rfi("F:\\test\\AccessTest.txt");
    }
}
class RandomIn{
    public void Rfi(String path1) throws Exception{
        RandomAccessFile raf=new RandomAccessFile(path1,"rw");
        /*参数2是mode模式:
        "r":以只读模式打开文件
        "rw":打开以便读取和写入(常用)
        "rwd":打开以便读取和写入,同步文件内容的更新
        "rws":打开以便读取和写入,同步文件内容和元数据的更新
        */
        raf.seek(5);  //设置读取文件内容的起点
        byte [] bArr=new byte[100];
        int len;
        while((len=raf.read(bArr))!=-1){
            System.out.println(new String(bArr,0,len));
        }
        raf.close();
    }
}
/*运行结果为:
6789I love you,krystal. Where are you?
*/

案例2:随机写入

//F:\\test\\AccessTest.txt文件的内容为:
123456789I love you,krystal. Where are you?
package day01;

import java.io.IOException;
import java.io.RandomAccessFile;

public class Test01{
    public static void main(String[] args) throws Exception {
        RandomOut oo=new RandomOut();
        oo.rof("F:\\test\\AccessTest.txt");
    }
}
class RandomOut{
    public void rof(String path2) throws IOException {
        RandomAccessFile rdaf=new RandomAccessFile(path2,"rw");
        rdaf.seek(0); 
        rdaf.write("Hi,this is Jimmy! ".getBytes());
        rdaf.seek(rdaf.length());  //这个相当于给文件末尾追加内容
        rdaf.write(" I miss you so much!".getBytes());
        rdaf.close();
    }
}
/*运行完成后,F:\\test\\AccessTest.txt文件的内容变化如下:
123456789I love you,krystal. Where are you?
Hi,this is Jimmy! u,krystal. Where are you? I miss you so much!
可以看到,在开头或者中间写入内容时,会覆盖掉等长度的原内容。
*/
posted @ 2020-08-29 09:37  Whatever_It_Takes  阅读(223)  评论(0编辑  收藏  举报