0
点赞
收藏
分享

微信扫一扫

Java中的IO流

唯米天空 2022-01-31 阅读 74

以下为本人观看尚硅谷Java学习视频所做的笔记

目录

File类的使用

  • java.io.File类: 文件和文件目录路径的抽象表示形式,与平台无关
  • File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流
  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录
  • File对象可以作为参数传递给流的构造器

File类常用构造器

  • public File(String pathname)
    以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
    • 绝对路径: 是一个固定的路径,从盘符开始
    • 相对路径: 是相对于某个位置开始
  • public File(String parent, String child)
    以parent为父路径,child为子路径创建File对象
  • public File(File parent, String child)
    根据一个父File对象和子文件路径创建File对象

路径分隔符

路径中的每级目录之间用一个路径分隔符隔开。

路径分隔符和系统有关:

  • windows和DOS系统默认使用 “ \ ” 来表示(往往写的是"\\",是因为转义字符)
  • UNIX和URL使用 “ / ” 来表示

Java程序支持跨平台运行,因此路径分隔符要慎用。

为了解决这个隐患,File类提供了一个常量: public static final String separator。根据操作系统,动态的提供分隔符。

举例:

File file1 = new File( "d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File( "d:/atguigu");

File类常用方法

File类的获取功能:

  • public String getAbsolutePath(): 获取绝对路径
  • public String getPath(): 获取路径
  • public String getName(): 获取名称
  • public String getParent(): 获取上层文件目录路径。若无返回null
  • public long length(): 获取文件长度(即:字节数)。不能获取目录的长度
  • public long lastModified(): 获取最后一次的修改时间,毫秒值
  • public String[] list(): 获取指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles(): 获取指定目录下的所有文件或者文件目录的File数组

File类的重命名功能

  • public boolean renameTo(File dest): 把文件重命名为指定的文件路径
    如果调用 file1.rename(file2),要保证返回true,需要file1在硬盘中存在且file2在硬盘中不存在

File类的判断功能

  • public boolean isDirectory(): 判断是否是文件目录
  • public boolean isFile(): 判断是否是文件
  • public boolean exists(): 判断是否存在
  • public boolean canRead(): 判断是否可读
  • public boolean canWrite(): 判断是否可写
  • public boolean isHidden(): 判断是否隐藏

File类的创建功能

  • public boolean createNewFile(): 创建文件。若文件存在,则不创建,返回false
  • public boolean mkdir(): 创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建
  • public boolean mkdirs(): 创建文件目录。如果上层文件目录不存在,一并创建

注意事项: 如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下

File类的删除功能

  • public boolean delete(): 删除文件或者文件夹

删除注意事项:
Java中的删除不走回收站
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

 

IO流原理及流的分类

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯

Java程序中,对于数据的输入/输出操作以 “流(stream)” 的方式进行

java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据

流的分类

  • 按操作数据单位不同分为: 字节流(8 bit,一个字节的大小),字符流(16 bit,即一个char型变量的大小)
  • 按数据流的流向不同分为: 输入流,输出流
  • 按流的角色的不同分为: 节点流,处理流
抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个
    抽象基类派生的
  2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

流的体系结构

抽象基类节点流(文件流)缓冲流(处理流的一种)
InputStreamFileInputStream
(read(byte[] buffer))
BufferedInputStream
(read(byte[] buffer))
OutputStreamFileOutputStream
(write(byte[] buffer,0,len))
BufferOutputStream
(write(byte[] buffer,0,len))/ flush()
ReaderFileReader
(read(char[] cbuf))
BufferedReader
(read(char[] cbuf) / readLine())
WriterFileWriter
(write(char[] cbuf,0,len))
BufferedWriter
(write(char[] cbuf,0,len))/ flush()

 

节点流(或文件流)

对于文本文件(如.txt, .java, .c, .cpp),使用字符流处理
对于非文本文件(如.jpg, .mp3, .mp4, .avi, .doc, .ppt),使用字节流处理

字符流

注意不能使用字符流来处理图片等非文本文件数据,而应该用字符流来处理文本文件

FileReader读入数据的基本操作

示例:将当前Module目录下的hello.txt文件内容读入到程序中,并输出到控制台
说明点:
1、异常的处理:为了保证流资源一定可以执行关闭操作,需要使用try-catch-finally来处理
2、读入的文件一定要存在,否则就会报FileNotFoundException

使用read():
read():返回读入的一个字符。如果达到文件末尾,返回-1

import org.junit.Test;

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

public class Sample {
    @Test
    public void testFileReader() {
        FileReader fr = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");//Test下的文件路径是相较于当前Module,而在main函数中是相较于当前工程
            //2.提供具体的流
            fr = new FileReader(file);
            //3.数据的读入
            //read(): 返回读入的一个字符。如果达到了文件末尾,返回-1
            //方式一:
//            int data = fr.read();
//            while (data != -1) {
//                System.out.print((char) data);
//                data = fr.read();
//            }
            //方式二:语法上针对于方法一的修改
            int data;
            while ((data = fr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流的关闭操作
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用read(char[] cbuf):
read(char[] cbuf): 返回每次读入cbuf数组中的字符个数,如果达到文件末尾,返回-1

import org.junit.Test;

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

public class Sample {
    @Test
    public void testFileReader() {
        FileReader fr = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");//Test下的文件路径是相较于当前Module,而在main函数中是相较于当前工程
            //2.提供具体的流
            fr = new FileReader(file);
            //3.数据的读入
            //read(char[] cbuf): 返回每次读入cbuf数组中的字符个数,如果达到文件末尾,返回-1
            char[] cbuf = new char[5];
            int len;
            while ((len = fr.read(cbuf)) != -1) {
                //方式一:
                //错误的写法:因为可能会输出最后一次字符数组赋值后未被覆盖的末尾的老数据
//                for (char c : cbuf) {
//                    System.out.print(c);
//                }
                //正确的写法:
                for (int i = 0; i < len; i++) {
                    System.out.print(cbuf[i]);
                }
                //方式二:
                //错误的写法,对应着方式一的错误的写法
//                String s = new String(cbuf);
//                System.out.print(s);
                //正确的写法:
                String s = new String(cbuf, 0, len);
                System.out.print(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流的关闭操作
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileWriter写出数据的基本操作

  1. 输出操作,对应的File可以不存在的,并不会报异常
  2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件
    File对应的硬盘中的文件如果存在:
      如果流使用的构造器是 FileWriter(file, false) / FileWriter(file):对原有文件的覆盖
      如果流使用的构造器是 FileWriter(file, true):不会对原有文件覆盖,而是在原有文件上追加内容
import org.junit.Test;

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

public class Sample {
    @Test
    public void testFileReader() {
        FileWriter fw = null;
        try {
            //1.提供File类的对象,指明写出到的文件
            File file = new File("hello.txt");
            //2.提供FileWriter的对象,用于数据的写出
            fw = new FileWriter(file);
            //3.写出的操作
            fw.write("I have a dream! \n");
            fw.write("You need to have a dream! ");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流资源的关闭
            if (fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字节流

使用字节流处理文本文件,可能出现乱码,最好还是用字符流
不过如果仅仅只是进行文本文件的复制,是可以使用字节流的(但是要注意,不可以使用字符流来复制非文本文件)

使用FileInputStream和FileOutputStream读写非文本文件

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Sample {
    @Test
    public void testFileReader() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            File srcFile = new File("原图片.jpg");
            File destFile = new File("复制文件.jpg");
            //2.提供具体的流
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //3.数据的读入
            byte[] buffer = new byte[5];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                //4.关闭资源
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

缓冲流

缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter

缓冲流的作用:提高流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
可以显式地调用flush方法来刷新缓冲区,不过在缓冲流中一般也不用显式写,因为在缓冲流中如果缓冲区满了的时候会自动调用flush方法

处理流,就是“套接”在已有的流的基础上,来对原有的流进行改造(就像缓冲流是对性能进行提升)

使用BufferedInputStream和BufferedOutputStream

import org.junit.Test;

import java.io.*;

public class Sample {
    @Test
    public void testFileReader() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //1.造文件
            File srcFile = new File("源文件.jpg");
            File destFile = new File("复制文件.jpg");
            //2.造流
            //2.1造节点流
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            //3.复制的细节:读取、写入
            byte[] buffer = new byte[10];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if(bos!=null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bis!=null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //说明:关闭外层流的同时,内层流也会自动地进行关闭,关于内流层的关闭,我们其实可以省略
//          fos.close();
//          fis.close();
        }
    }
}

使用BufferedReader和BufferedWriter

import org.junit.Test;

import java.io.*;

public class Sample {
    @Test
    public void testFileReader() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流
            br = new BufferedReader(new FileReader(new File("原文件.txt")));
            bw = new BufferedWriter(new FileWriter(new File("复制文件.txt")));

            //读写操作
            //方式一:使用char[]数组
//            char[] cbuf = new char[1024];
//            int len;
//            while ((len = br.read(cbuf)) != -1) {
//                bw.write(cbuf, 0, len);
//                //bw.flush();//也可以忽略,不进行显式调用
//            }
            //方式二:使用String
            String data;
            while ((data = br.readLine()) != null) {
                //data中不包含换行符
                //方式一:
                //bw.write(data+"\n");
                //方式二:
                bw.write(data);
                bw.newLine();//提供换行的操作
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

转换流

  • 转换流提供了在字节流和字符流之间的转换,属于字符流(规律:类名以Reader和Writer结尾)
  • Java API提供了两个转换流:
    • InputStreamReader: 将InputStream转换为Reader(字节输入流转换为字符输入流)
    • OutputStreamWriter: 将Writer转换为OutputStream(字符输出流转换为字节输出流)
  • 字节流中的数据都是字符时,转成字符流操作更高效
  • 很多时候我们使用转换流来处理文件乱码问题,实现编码和解码的功能
    • 解码:字节、字节数组 → 字符数组、字符串
    • 编码:字符数组、字符串 → 字节、字节数组

补充:字符集

  • 编码表的由来
    计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表——这就是编码表
  • 常见的编码表:
    • ASCII: 美国标准信息交换码,用一个字节的7位可以表示
    • ISO8859-1: 拉丁码表、欧洲码表,用一个字节的8位表示
    • GB2312: 中国的中文编码表。最多两个字节编码所有字符
    • GBK: 中国的中文编码表升级,融合了更多的中文文字符号,最多两个字节编码
    • Unicode: 国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示
    • UTF-8: 变长的编码方式,可用1~4个字节来表示一个字符

Unicode不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用一个字节表示就够了,第二个问题是如何才能区别Unicode和ASCIl? 计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢? 第三个,如果和GBK等双字节编码方式一样,用最高位是1或O表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现。

面向传输的众多UTF (UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。 推荐的Unicode编码是UTF-8和UTF-16。

InputStreamReader的使用

import org.junit.Test;

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

public class Sample {
    @Test
    public void testFileReader() throws IOException {
        //这里为了省事没写try-catch-finally,应该得写的
        FileInputStream fis = new FileInputStream("原文件.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
        //参数2: 指明了字符集,具体使用哪个字符集,取决于文件保存时使用的字符集
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        char[] cbuf = new char[20];
        int len;
        while ((len = isr.read(cbuf)) != -1) {
            String str = new String(cbuf, 0, len);
            System.out.print(str);
        }
        isr.close();
    }
}

OutputStreamWriter的使用

import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Sample {
    @Test
    public void testFileReader() throws IOException {
        //这里为了省事没写try-catch-finally,应该得写的
        //1.造文件、造流
        FileInputStream fis = new FileInputStream("原文件.txt");
        FileInputStream fos = new FileInputStream("复制文件.txt");

        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        OutputStreamWriter osw = new OutputStreamWriter("gbk");

        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while ((len = isr.read(cbuf)) != -1) {
            osw.write(cbuf, 0, len);
        }
        
        //3.关闭资源
        isr.close();
    }
}

 

标准输入、输出流(了解)

  • System.inSystem.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"时,退出程序

方法一:使用Scanner实现,调用next()返回一个字符串
方法二:使用System.in实现,System.in → 转换流 → BufferedReader的readLine()

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Sample {
    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(data.equalsIgnoreCase("e")||data.equalsIgnoreCase("exit")){
                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();
                }
            }
        }
    }
}

 

打印流(了解)

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

 

数据流(了解)

  • 为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流
  • 数据流有两个类: (用于读取和写出基本数据类型、String类的数据)
    • DatalnputStream和 DataOutputStream
    • 分别“套接”在InputStream和 OutputStream子类的流上
  • DatalnputStream中的方法
    • 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即可

写入:

import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class Sample {
    public static void main(String[] args) throws IOException {
        //处理异常的话,仍然应该使用try-catch-finally
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));

        dos.writeUTF("刘德华");
        dos.flush();//刷新操作,将内存中的数据写入到文件中
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();

        dos.close();
    }
}

读取:注意读取不同类型的数据的顺序要与当初写入文件时保存数据的顺序一致,否则会报异常

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

public class Sample {
    public static void main(String[] args) throws IOException {
        //处理异常的话,仍然应该使用try-catch-finally
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));

        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();

        dis.close();
    }
}

 

对象流

  • ObjectInputStream和OjbectOutputSteam
    用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
  • 序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制(将内存中的一个java对象转换为磁盘文件中的对象)
  • 反序列化: 用ObjectlnputStream类读取基本类型数据或对象的机制(将磁盘文件中的对象还原为内存中的一个java对象)
  • ObjectOutputStream和ObjectInputStream不能序列化statictransient修饰的成员变量

对象的序列化

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流(序列化),从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象(反序列化)
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
  • 序列化是 RMI (Remote Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是 JavaEE的基础。因此序列化机制是JavaEE平台的基础
  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
    • Serializable(这是个标识接口,因为接口没有需要实现的方法)
    • Externalizable
  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
    • private static final long serialVersionUID;
    • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
    • 如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。**若类的实例变量做了修改,serialVersionUID可能发生变化。**故建议,显式声明。
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与木地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

序列化过程:使用ObjectOutPutStream实现

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Sample {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new String("我爱北京天安门"));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

反序列化过程:使用ObjectlnputStream实现

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

public class Sample {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            Object obj = ois.readObject();
            String str = (String) obj;
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

自定义类实现序列化和反序列化

想让一个java对象可序列化,需要满足要求:
1、需要实现接口:Serializable
2、需要当前类提供一个全局常量:serialVersionUID
3、内部的所有属性都可序列化(默认情况下,基本数据类型可序列化)

示例:让Person类可序列化

class Person implements Serializable {
    public static final long serialVersionUID = 475463534532L;//这个值可以随意取
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

 

随机存取文件流

  • RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了Datalnput、DataOutput这两个接口,也就意味着这个类既可以读也可以写,既可以做输入流也可以做输出流。
  • RandomAccessFile类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
    • 支持只访问文件的部分内容
    • 可以向已存在的文件后追加内容
  • RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。
  • RandomAccessFile类对象可以自由移动记录指针:
    • long getFilePointer(): 获取文件记录指针的当前位置
    • void seek(long pos): 将文件记录指针定位到pos位置
  • 构造器:
    • public RandomAccessFile(File file, String mode)
    • public RandomAccessFile(String name, String mode)
  • 创建RandomAccessFile类实例需要指定一个mode参数,该参数指
    定RandomAccessFile的访问模式:
    • r: 以只读方式打开
    • rw: 打开以便读取和写入
    • rwd: 打开以便读取和写入;同步文件内容的更新
    • rws: 打开以便读取和写入;同步文件内容和元数据的更新
  • 如果模式为只读r,则不会创建文件,而是会去读取一一个已经存在的文件,如果读取的文件不存在则会出现异常
  • 如果模式为rw读写,RandomAccessFile作为输出流时:
    • 如果写出到的文件如果不存在,则在执行过程中自动创建
    • 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Sample {
    public static void main(String[] args) {
        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try {
            raf1 = new RandomAccessFile(new File("图片.jpg"), "r");
            raf2 = new RandomAccessFile(new File("图片.jpg"), "rw");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = raf1.read(buffer)) != -1) {
                raf2.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf1 != null) {
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (raf1 != null) {
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

RandomAccessFile作为输出流时,默认情况下为从头覆盖,注意以下几种情况

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

public class Sample {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
        raf1.write("abcdefg".getBytes());
        raf1.write("xyz".getBytes());
        raf1.close();
        //得到的hello.txt内容是abcdefgxyz
    }
}
import java.io.IOException;
import java.io.RandomAccessFile;

public class Sample {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
        RandomAccessFile raf2 = new RandomAccessFile("hello.txt", "rw");
        raf1.write("abcdefg".getBytes());
        raf2.write("xyz".getBytes());
        raf1.close();
        raf2.close();
        //得到的hello.txt内容是xyzdefg
    }
}

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

示例:在abc后面插入xyz

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

public class Sample {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
        RandomAccessFile raf2 = new RandomAccessFile("hello.txt", "rw");
        raf1.write("abcdefg".getBytes());
        raf2.seek(3);//将指针调到角标为3的位置
        //保存指针3后面的所有数据到StringBuilder中
        StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        byte[] buffer = new byte[20];
        int len;
        while ((len = raf2.read(buffer)) != -1) {
            builder.append(new String(buffer, 0, len));
        }
        raf2.seek(3);//将指针调回到角标为3的位置
        raf2.write("xyz".getBytes());
        //将StringBuilder中的数据写入到文件中
        raf2.write(builder.toString().getBytes());
        raf1.close();
        raf2.close();
        //得到的hello.txt内容是abcxyzdefg
    }
}

我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能,用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能。

 

NIO.2中Path、Paths、File类的使用(了解)

Java NIO概述

  • Java NIO (New lO,Non-Blocking lO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作
  • Java API中提供了两套NIO,一套是针对标准输入输出NIO另一套就是网络编程NIO
    |------java.nio.channels.Channel
      |-----FileChannel: 处理本地文件
      |-----SocketChannel: TCP网络编程的客户端的Channel
      |-----ServerSocketChannel: TCP网络编程的服务器端的Channel
      |-----DatagramChannel: UDP网络编程中发送端和接收端的Channel

NIO.2

随着JDK7的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为NIO.2。
因为NIO提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

Path、Paths和Files核心API

  • 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
  • NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
  • 在以前IO操作都是这样写的:
      import java.io.File;
      File file = new File(“index.html”);
  • 但在Java7中,我们可以这样写:
      import java.nio.file.Path;
      import java.nio.file.Paths;
      Path path = Paths.get(“index.html”);
  • 同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件; Paths则包含了两个返回Path的静态工厂方法。
  • Paths类提供的静态 get() 方法用来获取Path对象:
    • static Path get(String first, String … more): 用于将多个字符串串连成路径
    • static Path get(URl uri): 返回指定uri对应的Path路径
举报

相关推荐

0 条评论