天天看点

Java学习笔记——Java语言基础(二十)(随机访问流、序列化和反序列化、Properties类、SequenceInputStream、切割合并文件练习)

一、随机访问流RandomAccessFile

RandomAccessFile

RandomAccessFile:最大的特点就是可读可写。

RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能,这个对象可以用来读取数据也可以用来写数据,可以操作任意类型的数据。

构造方法

RandomAccessFile(File file, String mode)  创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定
RandomAccessFile(String name, String mode)  创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
           

第一个参数为文件或者文件的路径名。创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定

第二个参数为打开文件的访问模式,其可以选择的值如下:

值        		含意
  
"r" 		以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。  
"rw" 		打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。  
"rws" 		打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。  
"rwd"   	打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。  

           

写入数据

可以写入指定类型的数据

public class Demo01 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile rw = new RandomAccessFile("random.txt", "rw");
        rw.writeInt(1000);
        rw.writeBoolean(true);
        rw.writeDouble(3.1415);
        rw.writeUTF("你好");
    }
}
           

读取数据

读取上面写入的数据,**注意:**读取顺序应当相同。

我们可以通过getFilePointer方法获取文件指针,并且可以通过seek方法设置文件指针

public class Demo02 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile rw = new RandomAccessFile("random.txt", "rw");
        //通过getFilePointer方法获取文件指针 指针位置与所存放数据类型占得字节数有关
        int i = rw.readInt();
        long filePointer = rw.getFilePointer();
        System.out.println("文件指针的位置:"+filePointer);//4
        boolean b = rw.readBoolean();
        filePointer = rw.getFilePointer();
        System.out.println("文件指针的位置:"+filePointer);//5
        double v = rw.readDouble();
        filePointer = rw.getFilePointer();
        System.out.println("文件指针的位置:"+filePointer);//13
        String s = rw.readUTF();
        filePointer = rw.getFilePointer();
        System.out.println("文件指针的位置:"+filePointer);//24
        System.out.println(i);//1000
        System.out.println(b);//true
        System.out.println(v);//3.1415
        System.out.println(s);//你好
        //可以通过seek方法设置文件指针 重新读取字符串,将指针设置为字符串前面数据类型的指针位
        rw.seek(13);
        String s1 = rw.readUTF();
        System.out.println(s1);//你好;
    }
}
           

这里面,使用编码为UTF-8,一个汉字为三个字节,但移动指针的位数为8,这是为什么?

当在调用readUTF()方法时,会把两个字节写入文件中,移动的位数是实际的字节数,而不是字符串的长度。在该长度之后,按顺序输出该字符串的每个字符,并对每个字符使用 UTF-8 修改版编码。

使用RandomAccessFile断点下载

public class downLoad {
    public static void main(String[] args) throws IOException {
        RandomAccessFile read = new RandomAccessFile(new File("mixMusic.java"), "rw");
        RandomAccessFile write = new RandomAccessFile(new File("mixMusicdowmLoad.java"), "rw");
        /*
        read.seek();
        wirte.seek();
        设置暂停位置的指针
         */
        int lem=0;
        byte[] bytes=new byte[1];
        while ((lem=read.read(bytes))!=-1){
            write.write(bytes,0,lem);
            //暂停后,获取文件的指针位置,把这个文件的指针位置,存到文本文件中,下次用户开始复制的时候,将指针设置到在这个位置开始复制
        }
        write.close();
        read.close();
    }
}
           

具体代码,有待完善。

二、序列化和反序列化

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的类型 和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中存储的数据 信息,都可以用来在内存中创建对象。

简单来说:就是序列化就是把对象通过流的方式存储到文件中。反之就是将文件中存储的对象以流的形式还原成对象

注意:对象 要重写Serializable 接口才能被序列化

序列化流:ObjectOutputStream

反序列化流:ObjectInputStream

当一个类没有实现Serializable接口时,序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常。通过实现接口启动序列化的功能,未实现接口的无法进行序列化功能S。erializable接口叫做标记型接口。会给类添加一个标记

public class MyTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        writeObj();
        //读取文件中的数据
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("student.txt"));
        //类型转换
        Student s1 = (Student) is.readObject();
        Student s2 = (Student) is.readObject();
        Student s3 = (Student) is.readObject();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        is.close();
    }
    private static void writeObj() throws IOException {
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("student.txt"));
        Student s1 = new Student("张三", 23);
        Student s2 = new Student("李四", 24);
        Student s3 = new Student("王五", 25);
        /*将对象写入到文本文件中保存起来。NotSerializableException 不能序列化的异常。*/
        os.writeObject(s1);
        os.writeObject(s2);
        os.writeObject(s3);
        os.close();
    }
}
class Student implements Serializable {
    private static final long serialVersionUID = -903491141342695531L;
    private String name;
    //transient private int age; //  transient 阻止这个属性被序列化
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

           

当我们在序列化之后,修改一些代码。然后进行反序列化时,会抛出InvalidClassException异常()

**原因:**完成序列化以后,序列化文件中还存在一个标记,然后在进行反序列化的时候,会验证这个标记和序列化前的标记是否一致,如果一致就正常进行反序列化,如果不一致就报错了. 而现在我们把这个类做了修改,将相当于更改了标记,而导致这两个标记不一致,就报错了.

编译完成之后形成的class文件,添加一个序列号。在修改代码之后,不进行序列化的情况下,直接进行反序列化会抛InvalidClassException异常()。我们可以自己加上一个序列号,保障不会发生改变 private static final long serialVersionUID=10l;。值可以任意给定。

另一种存取的方式:

将该类对象放入一个ArrayList集合中,然后对ArrayList集合进行序列化

public class MyTest2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        writeObj();
        //读取文件中的数据
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("student.txt"));
        //类型转换
        ArrayList<Student> list = (ArrayList<Student>) is.readObject();
        for (Student student : list) {
            System.out.println(student);
        }
        is.close();
    }
    private static void writeObj() throws IOException {
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("student.txt"));
        Student s1 = new Student("张三", 23);
        Student s2 = new Student("李四", 24);
        Student s3 = new Student("王五", 25);
        //创建一个集合
        ArrayList<Student> list= new ArrayList<>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        //ArrayList实现了Serializable,所以可以被序列化
        os.writeObject(list);
    }
}
           

transient关键字:阻止该这个属性被序列化。存储文件时不会发生改变

static修饰的不能够被序列化

三、Properties类

Properties 类继承Hashtable。表示了一个持久的属性集。可保存在流中或从流中加载。属性列表中每个键和值都是一个字符串

3.1 Properties作为Map集合的使用

public class Demo01 {
    public static void main(String[] args) {
        Properties properties = new Properties();
       /* 父类方法
        properties.put("username","张三");
        properties.put("password","123456");
        String username = (String) properties.get("username");
        System.out.println(username);*/
       //特有方法
        properties.setProperty("username", "张三");
        properties.setProperty("password", "123456");
        //get方法1 通过键获取值
        String username = properties.getProperty("username");
        System.out.println(username);
        //get方法2 一个默认值,如果通过键,没有获取到值,就返回默认值
        String username2 = properties.getProperty("username", "李四");
        System.out.println(username2);//默认值为张三
    }
}

           

3.2Properties的load()和store()功能

public class Demo02 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.setProperty("username", "张三");
        properties.setProperty("password", "123456");
        //使用集合的store方法,可以将集合中的键值存到文件中。键值之间用"="连接
        //第二个参数为提示
        properties.store(new FileWriter("user.properties"),null);
        //使用几个的load方法,可以将文件中的键值数据读取回集合
        //对于读取的文件有要求:1.键值之间使用"="连接。2.文件的后缀名是 .properties
        Properties properties2 = new Properties();
        properties2.load(new FileReader("user.properties"));
        //输出集合
        System.out.println(properties2);
    }
}
           

properties文件称为配置文件

3.3 Properties练习

判断Properties集合中是否存在键,如果存在,就修改该键的值

public class Test01 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileReader("user.properties"));
        //判断是否含有该键
        if (properties.containsKey("username")){
            properties.setProperty("username","李四");//键相同,值覆盖
            //重写回文件覆盖数据
            properties.store(new FileWriter("user.properties"), null);
            //打印回控制台
            properties.load(new FileReader("user.properties"));
            System.out.println(properties);
        }
    }
}
           

四、SequenceInputStream

SequenceInputStream :表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

4.1 构造方法

SequenceInputStream(InputStream s1, InputStream s2)  通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。
SequenceInputStream(Enumeration<? extends InputStream> e) 通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。

           

第一种构造,将两个输入流作为参数进行传递,合并两个文本文件

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //给定两个文件字节输入流FileInputStream
        FileInputStream fis1=new FileInputStream("C:\\Users\\asus\\Desktop\\许巍 - 蓝莲花.mp3");
        FileInputStream fis2=new FileInputStream("C:\\Users\\asus\\Desktop\\许巍歌曲合集.mp3");
        //使用SequenceInputStream(InputStream s1, InputStream s2)的构造 传递两个流
        SequenceInputStream sequenceInputStream = new SequenceInputStream(fis1, fis2);
        //给定一个文件字节输出流FileOutputStream
        FileOutputStream fos = new FileOutputStream("C:\\Users\\asus\\Desktop\\许巍歌曲大合集.mp3");
        //读写数据
        int len=0;
        byte[] bytes=new byte[1024];
        while ((len=sequenceInputStream.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        sequenceInputStream.close();
    }
}
           

第二种构造,有时不满足于两个文本文件的合并,那么可以使用Enumeration作为参数进行传递,合并多个文本文件

public class Demo02 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1=new FileInputStream("C:\\Users\\asus\\Desktop\\许巍 - 蓝莲花.mp3");
        FileInputStream fis2=new FileInputStream("C:\\Users\\asus\\Desktop\\许巍歌曲合集.mp3");
        FileInputStream fis3=new FileInputStream("C:\\Users\\asus\\Desktop\\许巍歌曲大合集.mp3");
        //创建一个Vector集合 泛型FileInputStream
        Vector<FileInputStream> vector = new Vector<>();
        //添加文件
        vector.add(fis1);
        vector.add(fis2);
        vector.add(fis3);
        //获取Enumeration类型的对象
        Enumeration<FileInputStream> elements = vector.elements();
        //构造方法
        SequenceInputStream sequenceInputStream = new SequenceInputStream(elements);
        FileOutputStream out = new FileOutputStream("C:\\Users\\asus\\Desktop\\许巍.mp3");
        int len = 0;
        byte[] bytes = new byte[1024 * 8];

        while ((len = sequenceInputStream.read(bytes)) != -1) {
            out.write(bytes, 0, len);
            out.flush();
        }
        out.close();
        sequenceInputStream.close();
    }
}
           

4.2 SequenceInputStream练习

把一个xxxx.mp3文件分割成多份 每份大小是 1M 然后再把分割后的小文件,再合并成一首歌。

public class Day4Test2 {
    public static void main(String[] args) throws IOException {
        /*
        把一个xxxx.mp3文件分割成多份 每份大小是 1M 然后再把分割后的小文件,再合并成一首歌。
         */
        FileInputStream fis = new FileInputStream(new File("C:\\Users\\asus\\Desktop\\许巍 - 曾经的你.mp3"));
        qiegeMp3(fis);
        hebing();
    }
    private static void hebing() throws IOException {
        File targetFile = new File("C:\\Users\\asus\\Desktop\\mp32");
        //遍历存放切割歌曲的文件夹
        File[] files = targetFile.listFiles();
        Vector<FileInputStream> vector=new Vector<>();
        for (File file : files) {
            FileInputStream fis = new FileInputStream(file);
            vector.add(fis);
        }
        //构造方法
        SequenceInputStream sequenceInputStream = new SequenceInputStream(vector.elements());
        FileOutputStream fos = new FileOutputStream("C:\\Users\\asus\\Desktop\\许巍 - 曾经的你hebing.mp3");
        //读写数据
        int len=0;
        byte[] bytes=new byte[1024];
        while ((len=sequenceInputStream.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        sequenceInputStream.close();
    }
		 private static void qiegeMp3(FileInputStream fis) throws IOException {
		   //目标文件夹
        File targetFile = new File("C:\\Users\\asus\\Desktop\\mp32");
        if (!targetFile.exists()){
            targetFile.mkdirs();
        }
        //定义切割的大小 一次为1M
        byte[] bytes=new byte[1024*1024];
        int len=0;
        int j=1;
        while ((len=fis.read(bytes))!=-1){
            FileOutputStream fos = new FileOutputStream(new File(targetFile,(j++)+"曾经的你.mp3"));
            fos.write(bytes,0,len);
            fos.close();
        }
        fis.close();
    }
}

           

继续阅读