IO流的原理

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

输入input:读取外部数据(磁 盘、光盘等存储设备的数据)到 程序(内存)中。

输出output:将程序(内存) 数据输出到磁盘、光盘等存储设 备中。

流(stream)的分类

概括

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

io流开发 java java io流原理_数组


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

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

节点流:直接从数据源或目的地读写数据

io流开发 java java io流原理_数组_02

处理流:不直接连接到数据源或目的地,而是“连接”在已存 在的流(节点流或处理流)之上,通过对数据的处理为程序提 供更为强大的读写功能。

io流开发 java java io流原理_开发语言_03

流的分类图例

io流开发 java java io流原理_java_04

IO流体系

 流的基础类

输入流基础类

前言

InputStream 和 Reader 是所有输入流的基类。 

程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资 源,所以应该显式关闭文件 IO 资源。

FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader 

InputStream

int read() 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值 -1。

int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已 经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取 的字节数。

int read(byte[] b, int off,int len) 将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取 的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于 文件末尾而没有可用的字节,则返回值 -1。

public void close() throws IOException 关闭此输入流并释放与该流关联的所有系统资源。

Reader

int read() 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1 

int read(char[] cbuf) 将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

int read(char[] cbuf,int off,int len) 将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

public void close() throws IOException 关闭此输入流并释放与该流关联的所有系统资源。

 输出流基础类

前言

outputStream 和 writer 是所有输入流的基类。 

因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数 :void write(String str); / void write(String str, int off, int len);

FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

OutputStream

void write(int b) 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写 入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。

void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。

void write(byte[] b,int off,int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。

public void flush()throws IOException 刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立 即写入它们预期的目标。

public void close() throws IOException 关闭此输出流并释放与该流关联的所有系统资源。

Writer

void write(int c) 写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。 

void write(char[] cbuf) 写入字符数组。

void write(char[] cbuf,int off,int len) 写入字符数组的某一部分。从off开始,写入len个字符

void write(String str) 写入字符串。

void write(String str,int off,int len) 写入字符串的某一部分。

void flush() 刷新该流的缓冲,则立即将它们写入预期目标。

public void close() throws IOException 关闭此输出流并释放与该流关联的所有系统资源。

节点流(文件流)

注:以下代码演示均为字符流 (FileReader、FileWriter)的操作,处理字节流文件请更换为字节流(FileInputStream、FileOutputStream)

读取文件

步骤

//1.建立一个流对象,将已存在的一个文件加载进流。
FileReader fr = new FileReader(new File("E:\\JAVA\\java2\\src\\day8\\hello.png"));

//2.创建一个临时存放数据的数组。
char[] c = new char[1024];

//3.调用流对象的读取方法将流中的数据读入到数组中。
fr.read(c);

//4.关闭流的资源
fr.close();

 代码演示

读取的时候使用:int read()方法

FileReader fr = null;
try {
    //1.建立一个流对象,将已存在的一个文件加载进流。
    fr = new FileReader(new File("E:\\JAVA\\java2\\src\\day8\\hello.png"));

    //2.调用流对象的读取方法将流中的数据读入到数组中。
    int len;
    while ((len = fr.read()) != -1){
        System.out.print((char) len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //3.关闭流的资源
    try {
        if (fr != null)
        fr.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

读取的时候使用:int read(char[] cbuf)方法

FileReader fr = null;
try {
    //1.建立一个流对象,将已存在的一个文件加载进流。
    fr = new FileReader(new File("E:\\JAVA\\java2\\src\\day8\\hello.png"));

    //2.创建一个临时存放数据的数组。
    char[] cfr = new char[1024];

    //3.调用流对象的读取方法将流中的数据读入到数组中。
    int len;
    while ((len = fr.read(cfr)) != -1){
        //方式1:
        for (int i = 0; i < len; i++) {
            System.out.print(cfr[i]);
        }
        
        //方式2:
        String str = new String(cfr,0,len);
        System.out.print(str);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //4.关闭流的资源
    try {
        if (fr != null)
        fr.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

写入文件

步骤

//1.创建流对象,建立数据存放文件
 FileWriter fw = new FileWriter(new File("E:\\JAVA\\java2\\src\\day8\\hello.png"));
//2.调用流对象的写入方法,将数据写入流
 fw.write("写入的数据。。。");
//3.关闭流资源,并将流中的数据清空到文件中。
 fw.close();

代码演示

FileWriter fw = null;
try {
    //1.创建流对象,建立数据存放文件
    fw = new FileWriter(new File("E:\\JAVA\\java2\\src\\day8\\helloCopy.png"));

    //2.调用流对象的写入方法,将数据写入流
    fw.write("I am a superman\n");
    fw.write("you are a superman too");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //3.关闭流资源,并将流中的数据清空到文件中。
    if (fw != null){
        try {
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读入文件和写入文件组合

FileReader srcFile = null;
FileWriter destFile = null;
try {
    //1.创建输入输出流的对象,指明读入和写出的文件
    srcFile = new FileReader(new File("E:\\JAVA\\java2\\src\\day8\\hello.png"));
    destFile = new FileWriter(new File("E:\\JAVA\\java2\\src\\day8\\hello1.png"));

    //2.数据的读入和写出操作
    char[] cbuf = new char[1024];
    //记录每次读入到cbuf数组中的字符的个数
    int len;
    while ((len = srcFile.read(cbuf)) != -1){
        //每次写出len个字符
        destFile.write(cbuf,0,len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //3.关闭流资源
    /*//方式1:
    try {
        if (destFile != null)
        destFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            if (srcFile != null)
            srcFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

    //方式2:
    try {
        if (destFile != null)
        destFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        if (srcFile != null)
        srcFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

节点流(或文件流):注意点

1.定义文件路径时,注意:可以用“/”或者“\\”。
2.在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
3.如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。
4.在读取文件时,必须保证该文件已存在,否则报异常。
5.字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
6.字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

缓冲流

说明

1.缓冲流是处理流的一种,内部提供了一个缓冲区,提供流的读取、写入的速度。

2.为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。

io流开发 java java io流原理_java_05


3.缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

 操作字节数据类型的:BufferedInputStream 和 BufferedOutputStream

 操作字符数据类型的:BufferedReader 和 BufferedWriter

4.当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

5.向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流

6.flush()方法的使用:手动将buffer中内容写入文件

7.关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流

8.如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

9.当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区 

io流开发 java java io流原理_io流开发 java_06

缓冲流处理字节类型数据

/**
     * @description 使用缓冲流复制字节类型的数据
     * @author Alvin
     * @param srcFile:需要复制的字节文件地址
     * @param destFile:复制到的文件地址
     * @date 2022年8月17日15:19:39
     * @version
     */
    private static void copyFileWithBuffered(String srcFile,String destFile) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //1.创建文件和相应的节点流
            FileInputStream fis = new FileInputStream(new File(srcFile));
            FileOutputStream fos = new FileOutputStream(new File(destFile));

            //2.创建缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.读取、写入
            byte[] bbuf = new byte[1024];
            int len;
            while ((len = bis.read(bbuf)) != -1){
                bos.write(bbuf,0,len);
                bos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流资源关闭(要求:先关闭外层的流,再关闭内层的流)
            try {
                if (bos != null)
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }
    }

缓冲流处理字符类型的数据

/**
 * @description 使用缓冲流复制字符类型的数据
 * @author Alvin
 * @param srcFile:需要复制的字符文件地址
 * @param destFile:复制到的文件地址
 * @date 2022年8月17日16:05:04
 * @version
 */
public static void copyCharFileWithBuffered(String srcFile,String destFile){
    BufferedReader bd = null;
    BufferedWriter bw = null;
    try {
        //1.创建文件和相应的节点流+缓冲流
        bd = new BufferedReader(new FileReader(new File(srcFile)));
        bw = new BufferedWriter(new FileWriter(new File(destFile)));

        //2.读写操作
        /*//方式1:使用char[]数组
        char[] cbuf = new char[1024];
        int len;
        while ((len = bd.read(cbuf)) != -1){
            bw.write(cbuf,0,len);
        }*/

        //方式2:使用String
        String data;
        while ((data = bd.readLine()) != null){
            //方法1:
            bw.write(data + "\n");//data中不包含换行符
            //方法2:
            bw.write(data);
            bw.newLine();//提供换行的操作
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //3.关闭流资源
        if (bw != null) {
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            if (bd != null)
            bd.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

转换流

说明

1.转换流:属于字符流。
2.转换流提供了在字节流和字符流之间的转换
3.Java API提供了两个转换流:
 InputStreamReader:将InputStream转换为Reader(解码:字节、字节数组  --->字符数组、字符串)
 OutputStreamWriter:将Writer转换为OutputStream(编码:字符数组、字符串 ---> 字节、字节数组)
4.字节流中的数据都是字符时,转成字符流操作更高效。
5.很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。 

io流开发 java java io流原理_开发语言_07

代码演示:将utf-8的文件格式转化为gbk文件格式

InputStreamReader isr = null;
OutputStreamWriter osr = null;
try {
    FileInputStream fis = new FileInputStream(new File("E:\\JAVA\\java2\\src\\day8\\hello.txt"));
    FileOutputStream fos = new FileOutputStream(new File("E:\\JAVA\\java2\\src\\day8\\hello1.txt"));

    isr = new InputStreamReader(fis,"utf-8");//该“utf-8“参数指明了字符集,具体使用哪个字符集,取决于文件保存时使用的字符集
    osr = new OutputStreamWriter(fos,"GBK");//再次编码的时候,可以指定新的字符集

    char[] cbuf = new char[1024];
    int len;
    while ((len = isr.read(cbuf)) != -1){
        osr.write(cbuf,0,len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (osr != null) {
        try {
            osr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    try {
        if (isr != null)
            isr.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

标准输入、输出流

前言

System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)

代码演示

/* 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续
进行输入操作,直至当输入“e”或者“exit”时,退出程序。*/
public static void main(String[] args) {
    BufferedReader br = null;
    try {
        // 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
        InputStreamReader isr = new InputStreamReader(System.in);
        br = new BufferedReader(isr);

        while (true){
            System.out.println("请输入字符串:");
            String data = br.readLine(); // 读取用户输入的一行数据 --> 阻塞程序
            if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
                System.out.println("程序结束!");
                break;
            }
            String upperCase = data.toUpperCase();
            System.out.println(upperCase);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

public static void setIn(InputStream in)

public static void main(String[] args) {
    OutputStreamWriter osw = null;
    BufferedReader br = null;
    try {
        //创建新的文件输入流
        InputStream ps = new FileInputStream("java2\\\\src\\\\dayExercise\\\\Files\\\\log.txt");
        //创建标准输出流
        osw = new OutputStreamWriter(System.out);
        //将标准输入流替换成新的文件输入流
        System.setIn(ps);
        br = new BufferedReader(new InputStreamReader(System.in));
        char[] cbuf = new char[1024];
        //此时读入内存的为新的文件输入流内容,非控制台输入的内容
        int i = br.read(cbuf);
        //输出在控制台的为文件内容如
        osw.write(cbuf,0,i);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (br != null)
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (osw != null)
            osw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public static void setOut(PrintStream out) 

public static void main(String[] args) {
    try {
        //创建输出流,保存键盘输出的内容
        PrintStream ps = new PrintStream("java2\\src\\dayExercise\\Files\\log.txt");
        //创建标准程序输出流,用来输出提示语
        PrintStream out = System.out;
        //键盘录入数据
        Scanner scanner = new Scanner(System.in);
        String line = "";
        System.out.println("请输入:");
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();
            if (line.equalsIgnoreCase("exit")){
                System.out.println("程序结束!输入的信息已存储到对应文件中。");
                break;
            }
            //创新指定输出流
            System.setOut(ps);
            System.out.println(line);
            //还原为标准输出流,输入提示语句
            System.setOut(out);
            System.out.println("输入的信息已存储到对应文件中。请继续输入(输入exit结束程序):");
        }
    } catch (FileNotFoundException e) {
        // TODO: handle exception
        e.printStackTrace();
    }
}

 打印流

前言

实现将基本数据类型的数据格式转化为字符串输出
打印流:PrintStream和PrintWriter
 1.提供了一系列重载的print()和println()方法,用于多种数据类型的输出
 2.PrintStream和PrintWriter的输出不会抛出IOException异常
 3.PrintStream和PrintWriter有自动flush功能
 4.PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
 5.System.out返回的是PrintStream的实例 

代码演示

//打印流可以使用System.setOut()方法将本来通过[System.out.print()]方法输入在控制台的文件给储存起来
PrintStream ps = null;
try {
    FileOutputStream fos = new FileOutputStream(new File("E:\\JAVA\\java2\\src\\day8\\hello1.txt"));

    // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
    ps = new PrintStream(fos, true);
    if (ps != null) {// 把标准输出流(控制台输出)改成文件存储
        System.setOut(ps);
    }
    //打印ASCII的字符
    for (int i = 0; i < 255; i++) {
        System.out.print((char) i);
        if (i % 50 == 0) {// 每50个数据一行
            System.out.println();//换行
        }
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        if (ps != null)
            ps.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

数据流

前言

为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
 DataInputStream 和 DataOutputStream
 分别“套接”在 InputStream 和 OutputStream 子类的流上

DataInputStream中的方法
     boolean readBoolean()
     byte readByte()
     char readChar()
     float readFloat()
     double readDouble()
     short readShort()
     long readLong()
     int readInt()
     String readUTF()
     void readFully(byte[] b)


DataOutputStream中的方法
 将上述的方法的read改为相应的write即可。

代码演示

内存的基本数据类型的变量或字符串(自定义对象需要下面对象流处理)写出到文件中

//将内存中的字符串、基本数据类型的变量写出到文件中。
DataOutputStream dos = null;
try {
    dos = new DataOutputStream(new FileOutputStream("E:\\JAVA\\java2\\src\\day8\\data.dat"));
    dos.writeUTF("我是中国人!");
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeBoolean(true);
    dos.flush();
    dos.writeLong(12121212L);
    dos.flush();
    System.out.println("写入成功");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (dos != null)
            dos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 将文件中储存的基本数据类型的变量或字符串(自定义对象需要下面对象流处理)写入到内存中

 注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!

//将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
//注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
DataInputStream dis = null;
try {
    dis = new DataInputStream(new FileInputStream("E:\\JAVA\\java2\\src\\day8\\data.dat"));

    String str = dis.readUTF();
    boolean b = dis.readBoolean();
    Long l = dis.readLong();
    System.out.println(str);
    System.out.println(b);
    System.out.println(l);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (dis != null)
            dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

对象流

前言

1.ObjectInputStream 和 ObjectOutputStream

2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

3.ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

4.序列化:用ObjectOutputStream类保存基本类型数据或对象的机制

   反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

5.序列化机制:

  5.1对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。  当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

  5.2序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原

  5.3序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是JavaEE 平台的基础

6.要想一个java对象是可序列化的,需要满足相应的要求。

io流开发 java java io流原理_数组_08


  6.1如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一(Serializable、Externalizable  )。否则,会抛出NotSerializableException异常

  6.2凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID; serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

  6.3简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

  6.4如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化 

 序列化代码演示

序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去,使用ObjectOutputStream实现

ObjectOutputStream oos = null;
try {
    //创建一个 ObjectOutputStream
    oos = new ObjectOutputStream(new FileOutputStream("object.dat"));

    //调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
    oos.writeObject(new String("我爱我得祖国"));
    //注意写出一次,操作flush()一次
    oos.flush();
    oos.writeObject(new Person("张三",23,001));
    oos.flush();
    oos.writeObject(new Person("李四",24,002,new Account(1000)));
    oos.flush();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (oos != null)
        oos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

反序列化代码演示

反序列化:将磁盘文件中的对象还原为内存中的一个java对象,使用ObjectInputStream来实现

ObjectInputStream ois = null;
try {
    //创建一个 ObjectInputStream
    ois = new ObjectInputStream(new FileInputStream("object.dat"));

    //调用 readObject() 方法读取流中的对象
    Object o = ois.readObject();
    String str = (String) o;

    Person p1 = (Person) ois.readObject();
    Person p2 = (Person) ois.readObject();
    Person p3 = (Person) ois.readObject();

    System.out.println(str);
    System.out.println(p1);
    System.out.println(p2);
    System.out.println(p3);
} catch (IOException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        if (ois != null)
        ois.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

谈谈对java.io.Serializable接口的理解

1.我们知道它用于序列化,是空方法接口
2.实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
3.由于大部分作为参数的类如String、Integer等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。 

随机存取文件流

前言

RandomAccessFile的使用
1. RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
2. RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
3. 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
4. RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:
 long getFilePointer():获取文件记录指针的当前位置
 void seek(long pos):将文件记录指针定位到 pos 位置
5. 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
构造器
 public RandomAccessFile(File file, String mode)
 public RandomAccessFile(String name, String mode)
< 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
 r: 以只读方式打开
 rw:打开以便读取和写入
 rwd:打开以便读取和写入;同步文件内容的更新
 rws:打开以便读取和写入;同步文件内容和元数据的更新
< 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
< JDK1.6上面写的每次write数据时,“rw”模式,数据不会立即写到硬盘中;而“rwd”,数据会别立即写入硬盘,如果写数据过程发生异常,“rwd”模式中已被write的数据被保存在硬盘中,而“rw”则全部丢失。

代码演示

RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
    //创建读入文件流
    raf1 = new RandomAccessFile(new File("E:\\JAVA\\java2\\正能量.png"), "r");
    //创建写出文件流
    raf2 = new RandomAccessFile(new File("E:\\JAVA\\java2\\正能量1.png"), "rw");

    byte[] bbuf = new byte[1024];
    int len;
    while ((len = raf1.read(bbuf)) != -1){
        raf2.write(bbuf,0,len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (raf1 != null)
        raf1.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        if (raf2 != null)
        raf2.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

使用RandomAccessFile实现数据的插入效果

使用StringBuffer存储插入位置后边的内容:

RandomAccessFile raf = null;
try {
    //需求:在hello.txt文件角标为3的位置插入”abc”
    raf = new RandomAccessFile("E:\\JAVA\\java2\\src\\day8\\hello.txt","rw");

    //将指针调到角标为3的位置
    raf.seek(3);
    //保存指针3后面的所有数据放到StringBuilder中
    StringBuffer StrBuf = new StringBuffer((int) new File("E:\\JAVA\\java2\\src\\day8\\hello.txt").length());
    byte[] bbuf = new byte[1024];
    int len;
    while ((len = raf.read(bbuf)) != -1){
        StrBuf.append(new String(bbuf,0,len));
    }
    //调回指针,写入“abc”
    raf.seek(3);
    raf.write("abc".getBytes());
    //将StringBuilder中的数据写入到文件中
    raf.write(StrBuf.toString().getBytes());
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (raf != null){
        try {
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 使用ByteArrayOutputStream存储插入位置后边的内容:

@Test
public void test1() throws Exception {
	RandomAccessFile raf = new RandomAccessFile("E:\\JAVA\\java2\\src\\day8\\hello.txt","rw");
	//将指针调到角标为3的位置
	raf.seek(3);
	//保存指针3后面的所有数据放到ByteArrayOutputStream对象info中
	String info = readStringFromInputStream(raf);
	//调回指针,写入“hhhhh”
	raf.seek(3);
	raf.write("hhhhh".getBytes());
	//将info中的数据写入到文件中
	raf.write(info.getBytes());
}

/**
 * @description  使用ByteArrayOutputStream存储插入位置后边的内容
 * @param raf 随机存取流对象
 * @return 返回存储数据的ByteArrayOutputStream对象
 * @throws IOException
 */
private String readStringFromInputStream(RandomAccessFile raf) throws IOException {
	 ByteArrayOutputStream baos = new ByteArrayOutputStream();
	 byte[] buffer = new byte[10];
	 int len;
	 while ((len = raf.read(buffer)) != -1) {
	 baos.write(buffer, 0, len);
	 }

	 return baos.toString();
}

NIO.2中Path、 Paths、Files类的使用

Java NIO概述

1. Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
2. Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
3. 随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。
4. 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
5. NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
6. 同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。
7. Paths 类提供的静态 get() 方法用来获取 Path 对象(如何实例化Path:使用Paths):
 static Path get(String first, String … more) : 用于将多个字符串串连成路径
 static Path get(URI uri): 返回指定uri对应的Path路径

Path类 

Path类的实例化

Path path1 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");//相当于:new File(String filepath)
Path path2 = Paths.get("E:\\JAVA\\java2\\src\\day8", "hello1.txt");//相当于new File(String parent,String filename);

Path中的常用方法

一下代码演示用到的实例化对象path和path1

Path path = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");
Path path1 = Paths.get("hello1.txt");

Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象 

Path absolutePath = path.toAbsolutePath();
System.out.println(absolutePath);//E:\JAVA\java2\src\day8\hello1.txt
Path absolutePath1 = path1.toAbsolutePath();
System.out.println(absolutePath1);//E:\JAVA\java2\src\day8\hello1.txt

File toFile(): 将Path转化为File类的对象  《------》Path toPath(): 将Flie转化为Path类的对象

File file = path.toFile();//Path--->File的转换
Path path2 = file.toPath();//File--->Path的转换

Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象

Path resolve = path.resolve("day9\\hello2.txt");
System.out.println(resolve);//E:\JAVA\java2\src\day8\hello1.txt\day9\hello2.txt
Path resolve1 = path1.resolve("day9\\hello2.txt");
System.out.println(resolve1);// hello1.txt\day9\hello2.txt

Path getName(int idx) : 返回指定索引位置 idx 的路径名称

Path name = path.getName(2);
System.out.println(name);//src
//Path name1 = path1.getName(2);
//System.out.println(name1);//java.lang.IllegalArgumentException

int getNameCount() : 返回Path 根目录后面元素的数量

int nameCount = path.getNameCount();
System.out.println(nameCount);//5
int nameCount1 = path1.getNameCount();
System.out.println(nameCount1);//1

Path getFileName() : 返回与调用 Path 对象关联的文件名

Path fileName = path.getFileName();
System.out.println(fileName);//hello1.txt
Path fileName1 = path1.getFileName();
System.out.println(fileName1);//hello1.txt

Path getRoot() :返回调用 Path 对象的根路径

Path root = path.getRoot();
System.out.println(root);//E:\
Path root1 = path1.getRoot();
System.out.println(root1);//null

Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径

Path parent = path.getParent();
System.out.println(parent);//E:\JAVA\java2\src\day8
Path parent1 = path1.getParent();
System.out.println(parent1);//null

boolean isAbsolute() : 判断是否是绝对路径

boolean absolute = path.isAbsolute();
System.out.println(absolute);//true
boolean absolute1 = path1.isAbsolute();
System.out.println(absolute1);//false

boolean endsWith(String path) : 判断是否以 path 路径结束

boolean endsWith = path.endsWith("hello1.txt");
System.out.println(endsWith);//true

boolean startsWith(String path) : 判断是否以 path 路径开始

boolean startsWith = path.startsWith("E:\\JAVA");
System.out.println(startsWith);//true

String toString() : 返回调用 Path 对象的字符串表示形式

System.out.println(path.toString());//toString可是省略 //E:\JAVA\java2\src\day8\hello1.txt

Files类

 java.nio.file.Files 用于操作文件或目录的工具类。

Files类常用方法

 Path copy(Path src, Path dest, CopyOption … how) : 文件的复制

Path path1 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");
Path path2 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");
//要想复制成功,要求path1对应的物理上的文件存在。path2对应的文件没有要求。
Path pathCopy = Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);

Path createDirectory(Path path, FileAttribute … attr) : 创建一个目录

Path path3 = Paths.get("E:\\JAVA\\java2\\src\\day8");//该文件磁盘中存在
Path path4 = Paths.get("E:\\JAVA\\java2\\src\\day9");//该文件磁盘中不存在
//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
//Path directory = Files.createDirectory(path3);//java.nio.file.FileAlreadyExistsException
Path directory1 = Files.createDirectory(path4);

Path createFile(Path path, FileAttribute … arr) : 创建一个文件

Path path5 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");//该文件磁盘中存在
Path path6 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//该文件磁盘中不存在
//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
//Path file = Files.createFile(path5);//java.nio.file.FileAlreadyExistsException
Path file1 = Files.createFile(path6);

void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错

Path path7 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//该文件磁盘中存在
Path path8 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello4.txt");//该文件磁盘中不存在
//删除一个文件/目录,如果不存在,执行报错
Files.delete(path7);
//Files.delete(path8);//java.nio.file.NoSuchFileException

void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束

Path path9 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//该文件磁盘中存在
Path path10 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello4.txt");//该文件磁盘中不存在
//Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
Files.deleteIfExists(path9);
Files.deleteIfExists(path10);//文件不存在时不会报错

Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置

Path path11 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");//该文件磁盘中存在
Path path12 = Paths.get("E:\\JAVA\\java2\\src\\day9\\hello1.txt");//该文件磁盘中不存在
//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
Path move = Files.move(path11, path12, StandardCopyOption.ATOMIC_MOVE);

long size(Path path) : 返回 path 指定文件的大小

Path path13 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");//该文件磁盘中存在
Path path14 = Paths.get("E:\\JAVA\\java2\\src\\day9\\hello1.txt");//该文件磁盘中不存在

long size = Files.size(path13);
System.out.println(size);
//long size1 = Files.size(path14);//java.nio.file.NoSuchFileException

用于判断的方法

 boolean exists(Path path, LinkOption … opts) : 判断文件是否存在

Path path1 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello1.txt");//该文件磁盘中存在
Path path2 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//该文件磁盘中不存在
boolean exists1 = Files.exists(path1,LinkOption.NOFOLLOW_LINKS);
System.out.println(exists1);//true
boolean exists2 = Files.exists(path2,LinkOption.NOFOLLOW_LINKS);
System.out.println(exists2);//false

boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在 

Path path11 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件存在
Path path12 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//文件不存在
boolean notExists1 = Files.notExists(path11,LinkOption.NOFOLLOW_LINKS);
System.out.println(notExists1);//false
boolean notExists2 = Files.notExists(path12,LinkOption.NOFOLLOW_LINKS);
System.out.println(notExists2);//true

boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录

Path path3 = Paths.get("E:\\JAVA\\java2\\src\\day8");//目录
Path path4 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件
boolean directory1 = Files.isDirectory(path3, LinkOption.NOFOLLOW_LINKS);
System.out.println(directory1);//true
boolean directory2 = Files.isDirectory(path4,LinkOption.NOFOLLOW_LINKS);
System.out.println(directory2);//false

boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

Path path5 = Paths.get("E:\\JAVA\\java2\\src\\day8");//目录
Path path6 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件
boolean regularFile1 = Files.isRegularFile(path5,LinkOption.NOFOLLOW_LINKS);
System.out.println(regularFile1);//false
boolean regularFile2 = Files.isRegularFile(path6,LinkOption.NOFOLLOW_LINKS);
System.out.println(regularFile2);//true

 boolean isHidden(Path path) : 判断是否是隐藏文件

Path path7 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件存在
Path path8 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello3.txt");//文件不存在
//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
boolean hidden = Files.isHidden(path7);
System.out.println(hidden);//false
//boolean hidden1 = Files.isHidden(path8);//java.nio.file.NoSuchFileException

boolean isReadable(Path path) : 判断文件是否可读

Path path9 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件存在
boolean readable = Files.isReadable(path9);
System.out.println(readable);//true

boolean isWritable(Path path) : 判断文件是否可写

Path path10 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");//文件存在
boolean writable = Files.isWritable(path10);
System.out.println(writable);//true

用于操作内容的方法

 SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连 接,how 指定打开方式。

/*StandardOpenOption.READ:表示对应的Channel是可读的。
StandardOpenOption.WRITE:表示对应的Channel是可写的。
StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常*/
Path path1 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");
SeekableByteChannel Channel = Files.newByteChannel(path1, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录

Path path2 = Paths.get("E:\\JAVA\\java2\\src");
DirectoryStream<Path> paths = Files.newDirectoryStream(path2);
Iterator<Path> iterator = paths.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象

Path path3 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");
InputStream inputStream = Files.newInputStream(path3,StandardOpenOption.READ);

OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象

Path path4 = Paths.get("E:\\JAVA\\java2\\src\\day8\\hello2.txt");
OutputStream outputStream = Files.newOutputStream(path4,StandardOpenOption.WRITE,StandardOpenOption.CREATE);