Java基础—IO流(三)

来源:互联网 发布:linux查看svn账号密码 编辑:程序博客网 时间:2024/05/18 04:53

IO流(三)

File类

一、概述

    File类用于将文件或文件夹封装成对象,方便对文件和文件夹的属性信息进行操作。该类可以作为参数传递给IO流的构造函数,弥补流对象在操作文件和文件夹上的缺陷。

二、File类的使用

    1.构造方法
      1)File(String FileName)
        示例:File f1 = new File("C:\\abc\\a.txt");

      2)File(Strng, parent, String FileName)
        示例:File f2 = new File("C:\\abc", "b.txt");
        该构造方法的好处在于对文件的操作更加灵活,出现文件目录固定,文件名需要改变的时候,这个构造方法更好

      3)File(File parent, String FileName)
        示例:File d = new File("C:\\abc");
           File f3 = new File(d, "c.txt");

示例代码:

package com.heisejiuhuche.io;import java.io.File;public class FileDemo {    public static void main(String[] args) {        /* 使用File类的不同构造方法封装文件对象 */        File f1 = new File("Demo1.txt");        File f2 = new File("C:/Users/jeremy/Documents/javaTmp/", "Demo2.txt");        File d = new File("C:/Users/jeremy/Documents/javaTmp/");        File f3 = new File(d, "Demo3.txt");        /* 打印各个文件对象 */        System.out.println("f1:" + f1);        System.out.println("f2:" + f2);        System.out.println("f3:" + f3);    }}

程序输出结果:

f1:Demo1.txtf2:C:\Users\jeremy\Documents\javaTmp\Demo2.txtf3:C:\Users\jeremy\Documents\javaTmp\Demo3.txt

打印封装文件对象时的绝对路径或相对路径。

    2.成员方法
      1)创建操作
        -boolean createNewFile():文件名不存在的情况下创建新文件
        -boolean mkdir():创建一级目录
        -boolean mkdirs():创建多级目录

      2)删除操作
        -boolean delete():删除指定文件
        -void deleteOnExit():虚拟机退出的时候删除指定文件

      3)判断操作
        -boolean canExecute():判断文件对象是否可执行
        -boolean canRead():判断文件对象是否可读
        -boolean canWrite():判断文件对象是否可写
        -boolean exists():判断文件对象是否存在
        -boolean isDirectory():判断文件对象是否是文件夹,判断之前,必须先判断该文件对象是否存在
        -boolean isFile():判断文件对象是否是文件,判断之前,必须先判断该文件对象是否存在
        -boolean isHidden():判断文件对象是否是隐藏文件
        -boolean isAbsolute():判断文件对象路径是否是绝对路径
        -int compareTo(File pathname):比较两个文件对象,以自然顺序排序

      4)获取操作
        -String getName():获取文件对象名
        -String getParent():获取文件对象的父母路,必须明确指定文件路径
        -String getPath():获取文件对象相对路径
        -String getAbsolutePath():获取文件对象绝对路径,文件可以存在也可以不存在
        -long lastModified():获取文件对象最近一次被修改的时间
        -long length():获取文件对象大小

      5)修改操作
        -boolean renameTo(File dest):将文件修改为指定文件名并存入指定目录

      6)其他重要方法
        -static File[] listRoots():获取有效盘符
        -String[] list():获取指定目录中的文件和文件夹,包括隐藏文件;必须是存在目录调用,文件调用会空指针
        -String[] list(FilenameFileter filter):获取指定格式的文件
        -File[] listFiles()
        -File[] listFiles(FileFilter filter)
        -File[] listFiles(FilenameFilter filter)

    3.递归
      用递归遍历文件夹里的所有内容并以层级结构打印。

示例代码:

package com.heisejiuhuche.io;import java.io.File;public class RecursionDemo {    public static void main(String[] args) {        File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");        recur(dir, 0);    }    /* 目录层级结构 */    private static String getLevel(int level) {        StringBuilder sb = new StringBuilder();        /* 树形结构图形 */        sb.append("|--");        for(int x= 0; x < level; x++) {            /* 根据层级的不同在树形结构图形前补上制表符 */            sb.insert(0, "\t");        }        return sb.toString();    }    /* 递归遍历文件夹,并打印所有文件和文件夹 */    private static void recur(File dir, int level) {        /* 列出所有文件到文件数组 */        File[] files = dir.listFiles();        /* 打印文件夹的名称 */        System.out.println(getLevel(level) + dir.getName());        /* 打印完文件夹的名称,层级自增1 */        level++;        /* 遍历所有文件,如果是文件夹,则递归遍历里面的文件和文件夹 */        for(int x = 0; x < files.length; x++) {            if(files[x].isDirectory()) {                recur(files[x], level);            }            else                System.out.println(getLevel(level) + files[x].getName()); //如果不是文件夹,打印文件        }    }}

程序输出结果:

|--testfile    |--aaa        |--ccc            |--ddd                |--h.txt            |--ddd - Copy (2).txt            |--ddd - Copy - Copy.txt            |--ddd - Copy.txt            |--ddd.txt        |--ccc - Copy (2).txt        |--ccc - Copy - Copy.txt        |--ccc - Copy.txt        |--ccc.txt    |--aaa - Copy (2).txt    |--aaa - Copy - Copy.txt    |--aaa - Copy.txt    |--aaa.txt    |--bbb        |--eee            |--f.txt        |--eee - Copy (2).txt        |--eee - Copy - Copy.txt        |--eee - Copy.txt        |--eee.txt

注意:
递归的使用必须要明确控制条件,便面无限循环;递归要慎重使用,调用次数过多,会造成内存溢出。

    4.删除带内容目录

示例代码:

package com.heisejiuhuche.io;import java.io.File;public class DelDirWithContentDemo {    public static void main(String[] args) {        File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");        recur(dir);    }    /* 遍历所有文件夹,删除文件,并删除文件夹 */    private static void recur(File dir) {        File[] files = dir.listFiles();        for(int x = 0; x < files.length; x++) {            if(files[x].isDirectory())                recur(files[x]);            else                /* 打印文件名及文件删除结果 */                System.out.println(files[x].getName() + "--files--" + files[x].delete());        }        /* 打印文件夹名及文件夹删除结果 */        System.out.println(dir.getName() + "**dir**" + dir.delete());    }}

程序输出结果:

demo - Copy (2) - Copy.txt--files--trueddd**dir**truedemo - Copy (2) - Copy - Copy - Copy.txt--files--truedemo - Copy (2) - Copy - Copy.txt--files--trueccc**dir**truedemo - Copy (2) - Copy - Copy.txt--files--truedemo - Copy (2) - Copy.txt--files--truedemo - Copy (2).txt--files--truedemo - Copy (3) - Copy.txt--files--trueaaa**dir**truedemo - Copy - Copy - Copy.txt--files--truedemo - Copy - Copy.txt--files--truedemo - Copy - Copy - Copy.txt--files--trueeee**dir**truebbb**dir**truedemo - Copy (2) - Copy.txt--files--truedemo - Copy (2).txt--files--truedemo - Copy - Copy (2).txt--files--truedemo - Copy - Copy (3).txt--files--truedemo - Copy - Copy - Copy.txt--files--truedemo - Copy - Copy.txt--files--truedemo - Copy.txt--files--truedemo.txt--files--truetestfile**dir**true

注意:
Java无法访问windows中的隐藏目录,所以,最好在for循环中判断的时候加上:
if(!files[x].isHidden() && files[x].isDirectory())
忽略隐藏的文件夹

    5.练习
      FileWriter接收File的构造方法。

示例代码:

package com.heisejiuhuche.io;/** * 接收键盘录入,然后写入指定目录下的文件中 * 为了测试FileWriter接收File类型数据作为构造方法参数 */import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.InputStreamReader;public class FileWriterTest {    public static void main(String[] args) {        try {            writeToFile(createFile("C:\\Users\\jeremy\\Documents\\FileWriter.txt"));        } catch(IOException e) {            e.printStackTrace();        }    }    private static File createFile(String filepath) throws IOException {        File file = new File(filepath);        if(!file.exists())            file.createNewFile();        return file;    }    private static void writeToFile(File file) {        BufferedReader bufr = null;        BufferedWriter bufw = null;        try {            bufr = new BufferedReader(new InputStreamReader(System.in));            bufw = new BufferedWriter(new FileWriter(file));            String line = null;            while((line = bufr.readLine()) != null) {                if(line.equals("over"))                    break;                bufw.write(line);                bufw.newLine();                bufw.flush();            }        } catch(IOException e) {            e.printStackTrace();        } finally {            try {                if(bufr != null)                    bufr.close();            } catch(IOException e) {                e.printStackTrace();            }            try {                if(bufw != null)                    bufw.close();            } catch(IOException e) {                e.printStackTrace();            }        }     }}

其他类及功能性流对象

一、Properties类

    1.概述
      Properties类是HashTable的子类。该类具备Map集合的特点,储存字符串作为键值对。Properties类是集合中和IO技术相结合的容器。该类可以用于键值对形式的配置文件。配置文件用于保存软件运行时的各项参数。配置完成之后将会被持久化存储,每次运行软件都会加载该配置文件。

    2.应用
      1)设置并打印键值对

示例代码:

package com.heisejiuhuche.io;import java.util.Properties;import java.util.Set;public class PropertiesDemo2 {    public static void main(String[] args) {        /* 创建Property对象 */        Properties prop = new Properties();        /* 设置键值对 */        prop.setProperty("zhangsan", "18");        prop.setProperty("wangwu", "20");        /* 通过键获取值,并打印 */        System.out.println(prop.getProperty("zhangsan"));        /* 将键都返回并装进Set */        Set<String> value = prop.stringPropertyNames();        /* 遍历集合并打印键值 */        for(String str : value) {            System.out.println(str + "::" + prop.getProperty(str));        }    }}

程序输出结果:

18zhangsan::18wangwu::20

      2)从文件加载配置并修改

示例代码:

package com.heisejiuhuche.io;import java.io.BufferedReader;import java.io.FileOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.OutputStreamWriter;import java.util.Properties;public class PropertiesLoadDemo {    public static void main(String[] args) throws IOException {        Properties prop = new Properties();//      loadProp();        /* 创建缓冲输入流对象并关联配置文件 */        BufferedReader bufr = new BufferedReader(new FileReader(                "C:/Users/jeremy/Documents/javaTmp/info.txt"));        /* 调用prop独享的load方法加载配置文件 */        prop.load(bufr);        /* 在控制台列出所有键值对 */        prop.list(System.out);        /* 修改键值对 */        prop.setProperty("李四", "50");        /* 调用store方法修改配置文件并保存 */        prop.store(new OutputStreamWriter(new FileOutputStream(                "C:/Users/jeremy/Documents/javaTmp/info.txt"), "GBK"), "Test");        prop.list(System.out);    }    /* 方法一:读取文件,将每一行按=号分割,将键值对存入Properties对象 */    private static void loadProp() {         BufferedReader bufr = null;         Properties prop = null;         try {             bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/info.txt"));             prop = new Properties();             String line = null;             while((line = bufr.readLine()) != null) {                 String[] kv = line.split("=");                 prop.setProperty(kv[0], kv[1]);             }         } catch(IOException e) {             e.printStackTrace();         } finally {             try {                 if(bufr != null)                     bufr.close();             } catch(IOException e) {                 e.printStackTrace();             }         }         System.out.println(prop);    }}

程序输出结果:

-- listing properties --王五=19张三=20李四=90-- listing properties --王五=19张三=20李四=50

注意:
-#都是注释,不会被Properties加载
-Properties加载的配置文件必须有固定格式;通常为:键=值

      3)记录程序运行次数
        创建配置文件记录程序运行次数,到达5此,提示用户注册。

示例代码:

package com.heisejiuhuche.io;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.util.Properties;public class PropertiesTest {    public static void main(String[] args) throws IOException {        try {            checkAuth();        } catch(IOException e) {            throw new RuntimeException("配置文件加载异常...");        }    }    private static void checkAuth() throws IOException {        /* 创建配置文件对象 */        File file = new File("C:/Users/jeremy/Documents/javaTmp/auth.txt");        /* 如果文件不存在,创建文件,避免异常 */        if(!file.exists())            file.createNewFile();        /* 创建输出流对象 */        FileInputStream fis = new FileInputStream(file);        /* 创建Properties对象 */        Properties prop = new Properties();        /* 加载配置文件 */        prop.load(fis);        String val = null;        int count = 0;        /* 如果读取键值为空,说名是第一次运行,那么将计数器count+1,然后和键time一起存入prop对象         * 并写入配置文件         */        if((val = prop.getProperty("time")) != null) {            /* 如果取到了值,说明不是第一次运行,那么让计数器count等于time键所对应的值             * 再+1,然后和time键一起再次存入配置文件             */            count = Integer.parseInt(val);            /* 判断,如果count = 5,说明使用次数已到,结束程序,提示注册 */            if(count >= 5) {                System.out.println("请注册...");                return;            }        }        count++;        prop.setProperty("time", count + "");        /* 输出流不能在load语句前声明,否则将会覆盖配置文件,导致配置信息清空 */        FileOutputStream fos = new FileOutputStream(file);        /* 将配置文件更新 */        prop.store(fos, "CheckAuth");        fis.close();        fos.close();    }}

二、打印流

    1.PrintStream类
      1)概述
      PrintStream类是字节打印流,其为其他流添加了功能,使它们能够方便打印各种数据值形式。该类不会抛出IOException,同时内部有刷新机制,无须手动刷新缓冲区。PrintStream可以直接操作文件对象。

      2)常用方法
        -PrintStream(File file):接收File对象
        -PrintStream(OutputStream out):接收字节输出流
        -PrintStream(String filename):接收字符串路径
        -println():打印所有基本数据类型,保持数据原样

      3)PrintStream示例

示例代码:

package com.heisejiuhuche.io;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;public class PrintStreamDemo {    public static void main(String[] args) throws IOException {        /* 创建输入流和PrintStream对象并关联文件 */        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));        PrintStream ps = new PrintStream("C:/Users/jeremy/Documents/javaTmp/PrintStream.txt");        String len = null;        /* 接收键盘输入并写入文件 */        while((len = bufr.readLine()) != null) {            if(len.equals("over"))                break;            ps.println(len);            ps.flush();        }        bufr.close();        ps.close();    }}

该类的使用方法和下面介绍的PrintWriter类基本相同。

    2.PrintWriter类
      1)概述
      字符打印流可以打印基本舒蕾型。其最强大的功能是println方法,可以实现自动换行并直接操作基本数据类型。

      2)常用方法
        -PrintWriter(File file):接收File对象
        -PrintWriter(File file, String csn):接收File对象,并制定字符集
        -PrintWriter(OutputStream out):接收字节输出流
        -PrintWriter(OutputStream out, boolean autoflush):接收字节输出流,设置自动刷新
        -PrintStream(Writer out):接收字符输出流
        -PrintWriter(String filename):接收字符串路径

      3)PrintWriter示例

示例代码:

package com.heisejiuhuche.io;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;public class PrintWriterDemo {    public static void main(String[] args) throws IOException {        /* 创建输入流对象和打印流对象 */        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));        PrintWriter pw = new PrintWriter(System.out, true);        String len = null;        while((len = bufr.readLine()) != null) {            /* 如果len等于over,就停止程序 */            if(len.equals("over"))                break;            /* 调用println方法,在打印的时候自动换行 */            pw.println(len.toUpperCase());        }        bufr.close();        pw.close();    }}

注意:
在PrintWriter构造方法中设置true,就无须调用flush()刷新缓冲。

三、序列流

    1.SequenceInputStream类
      1)概述
      SequenceInputStream类标识其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到达到文件末尾;接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。

      2)应用
        将三个文件的内容使用SequenceInputStream写入到一个文件中。

示例代码:

package com.heisejiuhuche.io;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.SequenceInputStream;import java.util.ArrayList;import java.util.Enumeration;import java.util.Iterator;public class SequenceInputStreamDemo {    public static void main(String[] args) {        write();    }    private static void write() {        SequenceInputStream sis = null;        ArrayList<FileInputStream> list = null;        FileOutputStream fos = null;        try {            /* 创建集合对象 */            list = new ArrayList<FileInputStream>();            /* 将输入流对象存入集合 */            for(int x = 1; x <= 3; x++) {                list.add(new FileInputStream(                "C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".txt"));            }            Iterator<FileInputStream> it = list.iterator();            /* 得到装有输入流对象的Enumeration */            Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {                public boolean hasMoreElements() {                    return it.hasNext();                }                public FileInputStream nextElement() {                    return it.next();                }            };            /* 创建序列流对象和输出流对象 */            sis = new SequenceInputStream(en);            fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\4.txt");            byte[] buf = new byte[1024];            int len = 0;            /* 合并文件 */            while((len = sis.read(buf)) != -1) {                fos.write(buf, 0, len);                fos.flush();            }        } catch(IOException e) {            e.printStackTrace();        } finally {            try {                if(sis != null)                    sis.close();            } catch(IOException e) {                e.printStackTrace();            }            try {                if(fos != null)                    fos.close();            } catch(IOException e) {                e.printStackTrace();            }        }    }}

      3)切割文件后再合并
        将一个MP3文件按1M切割,最后合并,要求保证合并后的文件可以播放。

示例代码:

package com.heisejiuhuche.io;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.SequenceInputStream;import java.util.ArrayList;import java.util.Enumeration;import java.util.Iterator;public class SplitMp3Test {    public static void main(String[] args) {        split();        merge();    }    private static void merge() {        /* 创建集合,用于存放输入流对象 */        ArrayList<FileInputStream> list = new ArrayList<FileInputStream>();        SequenceInputStream sis = null;        FileOutputStream fos = null;        try {            /* 将四个文件输入流对象存入集合 */            for(int x = 1; x <= 4; x++) {                list.add(new FileInputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".part"));            }            Iterator<FileInputStream> it = list.iterator();            /* 拿到有输入流对象的Enumeration */            Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {                public boolean hasMoreElements() {                    return it.hasNext();                }                public FileInputStream nextElement() {                    return it.next();                }            };            /* 创建序列流对象 */            sis = new SequenceInputStream(en);            /* 创建输出流对象,并关联文件 */            fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\back.mp3");            byte[] buf = new byte[1024];            int len = 0;            /* 合并文件 */            while((len = sis.read(buf)) != -1) {                fos.write(buf, 0, len);                fos.flush();            }        } catch(FileNotFoundException e) {            throw new RuntimeException("文件不存在...");         } catch(IOException e) {            throw new RuntimeException("合并失败...");        } finally {            try {                if(sis != null)                    sis.close();            } catch(IOException e) {                throw new RuntimeException("资源关闭失败...");            }            try {                if(fos != null)                    fos.close();            } catch(IOException e) {                throw new RuntimeException("资源关闭失败...");            }        }    }    private static void split() {        File file = null;        FileInputStream fis = null;        FileOutputStream fos = null;        try {            /* 创建输入流对象 */            file = new File("C:\\Users\\jeremy\\Documents\\javaTmp\\Backseat.mp3");            fis = new FileInputStream(file);            byte[] buf = new byte[1024 * 1024];            int count = 1;            int len = 0;            /* 将文件按1M大小分割,分为count个文件 */            while((len = fis.read(buf)) != -1) {                fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + count++ + ".part");                fos.write(buf, 0, len);                fos.flush();            }        } catch(IOException e) {            throw new RuntimeException("分割失败...");        } finally {            try {                if(fis != null)                    fis.close();            } catch(IOException e) {                throw new RuntimeException("输入资源关闭失败");            }            try {                if(fos != null)                    fos.close();            } catch(IOException e) {                throw new RuntimeException("输出资源关闭失败");            }        }    }}

四、对象流

    1.ObjectOutputStream和ObjectInputStream类
      1)概述
      ObjectOutputStream类可以将Java对象的基本数据类型和图形写入OutputStream,再利用ObjectInputStream读取(重构)该对象。该类用于将对象持久化存储在硬盘上,以便程序下次启动的时候能再次加载该对象。

      2)常用方法
        ObjectOutputStream具备直接操作基本数据类型的一些列write()方法及操作对象的writeObject()方法。以一个示例来演示该类的使用方法及注意事项。

示例代码:

package com.heisejiuhuche.io;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class ObjectOutputInputStreamDemo {    static final File file = new File("C:/Users/jeremy/Documents/javaTmp/person.txt");    public static void main(String[] args) throws Exception {        writeObj();    }    private static void readObj() throws Exception {        /* 创建对象输入流对象,传入一个字节输入流,并关联文件 */        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));        /* 读取Person对象,向下转型为Person类型 */        Person p = (Person)ois.readObject();        /* 打印Person对象 */        System.out.println(p);        /* 关闭资源 */        ois.close();    }    private static void writeObj() throws Exception {        /* 创建对象输出流对象,传入一个字节输出流,并关联文件 */        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));        /* 调用writeObject方法将Person对象写入文件 */        oos.writeObject(new Person("zhangsan", 28));        /* 写入文件后读取并打印在控制台 */        readObj();        /* 关闭资源  */        oos.close();    }}class Person {    private String name;    private int age;    Person(String name, int age) {        this.name = name;        this.age = age;    }    public String toString() {        return this.name + "::" + this.age;    }}

注意:
上面的代码报出异常:
Exception in thread “main” java.io.NotSerializableException: com.heisejiuhuche.io.Person
在使用ObjectOutputStream写入对象的时候,被写入的对象必须实现Serializable接口

      3)Serializable接口
      Serializable接口是一个标记接口,它没有任何方法。该接口给对象定义一个固定的数字标识,将该对象类序列化。使用该标识作为对象存储和读取是否一致的判断标准。这个数字标识叫做SerialVersionUID,是根据每个对象的成员,由Java计算出来的一个固定值。如果读取的时候,该值发生了变化,会抛出异常。下面会用代码演示这一现象及序列化的其他特点。

修改上述代码,按要求使Person类实现Serializable接口。

示例代码:

class Person implements Serializable {    private String name;    private int age;    Person(String name, int age) {        this.name = name;        this.age = age;    }    public String toString() {        return this.name + "::" + this.age;    }}

程序运行结果:

zhangsan::28

此时,如果将Person类中name成员的private关键字去掉,在写入和读取时,就会发生UID不匹配的情况,抛出异常。

示例代码:

class Person implements Serializable {    /* 去掉了private关键字 */    String name;    private int age;    Person(String name, int age) {        this.name = name;        this.age = age;    }    public String toString() {        return this.name + "::" + this.age;    }}

程序运行结果:

Exception in thread "main" java.io.InvalidClassException这证明了UID是根据成员的数字标识算出来的一个固定值。

如果要实现更改成员之后还能读取该类,只需手动指定UID。下面的代码仍然去掉了private关键字,但是读取无误。

示例代码:

package com.heisejiuhuche.io;import java.io.Serializable;class Person implements Serializable {    /* 手动指定UID */    private static final long serialVersionUID = 37L;     String name;    private int age;    Person(String name, int age) {        this.name = name;        this.age = age;    }    public String toString() {        return this.name + "::" + this.age;    }}

程序运行结果:zhangsan::28

注意:
-已被赋值的静态成员,在序列化后,值不能修改;如,static String country = “cn”;之后再修改country的值,读取的时候,还是”cn”;
-不想序列化某非静态成员,可以加上transient关键字:transient private int age;

五、管道流

    1.概述
      管道流能通过结合线程技术,实现输入流和输出流的直接连接。管道输入和输出流必须连接在一起使用。某个线程从PipedInputStream读取数据,并由另一个线程写入到PipedOutputStream。这是涉及多线程技术的IO流

    2.PipedInputStream和PipedOutputStream类
      管道流可以通过构造方法连接,也可以通过调用connect()方法连接。

示例代码:

package com.heisejiuhuche.io;import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutputStream;public class PipedStreamDemo {    public static void main(String[] args) throws IOException {        /* 创建管道流对象并连接 */        PipedInputStream pis = new PipedInputStream();        PipedOutputStream pos = new PipedOutputStream();        pis.connect(pos);        /* 启动线程 */        new Thread(new Read(pis)).start();        new Thread(new Write(pos)).start();    }}class Read implements Runnable {    private PipedInputStream pis;    Read(PipedInputStream pis) {        this.pis = pis;    }    public void run() {        /* 等待管道输出流写入数据,读到输出流数据前,处于阻塞状态 */        try {            byte[] buf = new byte[1024];            System.out.println("等待读取...");            int len = pis.read(buf);            System.out.println("读取结束...");            System.out.println(new String(buf, 0, len));        } catch(IOException e) {            throw new RuntimeException("读取失败");        }    }}class Write implements Runnable {    private PipedOutputStream pos;    Write(PipedOutputStream pos) {        this.pos = pos;    }    public void run() {        /* 往管道输入流中写入数据,写入先等待6秒,模拟写入过程 */        try {            System.out.println("数据写入中...");            Thread.sleep(6000);            pos.write("数据来啦!!!!!".getBytes());            pos.close();        } catch(Exception e) {            throw new RuntimeException("写入失败");        }    }}

程序运行结果:

等待读取...数据写入中...读取结束...数据来啦!!!!!

六、RandomAccessFile类

    1.概述
      RandomAccessFile类不是IO体系中的子类,而是直接继承自Object。但是它仍然是IO包中的成员,因为它具备读写功能。该类支持对随机访问文件的读取和写入。该类的内部封装了数组,通过文件指针对数据进行操作,可以通过getFilePointer()方法获取指针位置;通过seek()方法改变指针的位置。RandomAccessFile类只能操作文件,并必须设定操作模式。

    2.应用
      演示RandomAccessFile的基本方法及特点。

示例代码:

package com.heisejiuhuche.io;import java.io.IOException;import java.io.RandomAccessFile;public class RandomAccessFileDemo {    public static void main(String[] args) throws IOException {        write();        read();    }    private static void write() throws IOException {        /* 创建RnadomAccessFile对象 */        RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");        raf.write("李四".getBytes());        /* write()方法的特点是,只写出数据的最低8位,会造成数据丢失,出现乱码 *///      raf.write(258);        /* 保证数据不丢失,要调用writeInt()方法,写入4个8位 */        raf.writeInt(97);        raf.write("王五".getBytes());        raf.writeInt(99);        /* 让指针回到文件起始位置 */        raf.seek(0);        /* 写入数据,那么李四的数据将被修改 */        raf.write("赵六".getBytes());        raf.writeInt(103);        raf.close();    }    private static void read() throws IOException {        RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");        /* 创建4个字节的数组 */        byte[] buf = new byte[4];        /* 如果想要直接取王五的数据,调用seek方法让指针指向第二个8位即可 */        raf.seek(8);        /* 如果想要直接取王五的数据,调用skepBytes方法跳过前8位 */        raf.skipBytes(8);        /* 读取4个字节到数组中 */        raf.read(buf);        /* 用readInt读取下4个字节 */        int age = raf.readInt();        System.out.println(new String(buf) + age);    }}

注意:
-write()方法只写数据的最低8位,会造成数据丢失;在写入int类型数据时,调用writeInt()方法;
-skipBytes()方法只能往前跳过指定字节数,不能往回跳转;
-RandomAccessFile对象不仅能写如数据,还能修改该数据;
-RandomAccessFile对象在创建时,如果模式为”r”,就不会创建文件;如果该被读取文件不存在,会抛出异常;模式为”rw”,会自动创建该文件;如果文件已存在,则不会覆盖,继续添加内容;
-RandomAccessFile类可以结合多线程,实现数据的分段写入,提高写入效率

七、其他流对象

    1.DataInputStream和DataOutputStream类
      用于操作基本数据类型的流对象。

示例代码:

package com.heisejiuhuche.io;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class DataStreamDemo {    public static void main(String[] args) throws IOException {        write();        read();        writeUTF();        readUTF();    }    private static void readUTF() throws IOException {        DataInputStream dos = new DataInputStream(new FileInputStream(                "C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));        System.out.println(dos.readUTF());    }    private static void writeUTF() throws IOException {        DataOutputStream dos = new DataOutputStream(new FileOutputStream(                "C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));        /* 使用UTF-8修改版字符集写入数据 */        dos.writeUTF("你好");        dos.close();    }    private static void read() throws IOException {        /* 创建DataInputStream对象并关联文件  */        DataInputStream dos = new DataInputStream(new FileInputStream(                "C:/Users/jeremy/Documents/javaTmp/data.txt"));        /* 读取基本数据类型 */        int num = dos.readInt();        boolean b = dos.readBoolean();        double d = dos.readDouble();        System.out.println("num = " + num);        System.out.println("b = " + b);        System.out.println("d = " + d);    }    private static void write() throws IOException {        /* 创建DataOutputStream对象并关联文件  */        DataOutputStream dos = new DataOutputStream(new FileOutputStream(                "C:/Users/jeremy/Documents/javaTmp/data.txt"));        /* 写入基本数据类型 */        dos.writeInt(234);        dos.writeBoolean(false);        dos.writeDouble(124.135252);        dos.close();    }}

程序运行结果:

num = 234b = falsed = 124.135252你好

    2.ByteArrayInputStream和ByteArrayOutputStream类
      用于操作字节数组的流对象。该类的对象无须关闭,因为没有调用任何系统底层资源;如果关闭,仍可以调用其方法,同时没有任何IO异常ByteArrayInputStream在初始化的时候需要接收一个字节数组;ByteArrayOutputStream内部的缓冲区,会随着数据的不断写入而自动增长。这两个类用流的读写思想来操作数组,用于往内存中暂时写入数据。

示例代码:

package com.heisejiuhuche.io;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;public class ByteArrayStreamDemo {    public static void main(String[] args) {        readWrite();    }    private static void readWrite() {        /* 创建字节数组流对象并关联文件 */        ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());        ByteArrayOutputStream bos = new ByteArrayOutputStream();        int ch = 0;        /* 循环读取所有字节并写去字节数组输出流对象 */        while((ch = bis.read()) != -1) {            bos.write(ch);        }        /* 打印数组大小,并打印内容 */        System.out.println(bos.size());        System.out.println(new String(bos.toByteArray()));        System.out.println(bos.toString());    }}

程序运行结果:

7abcdefgabcdefg

注意:
和字节数组输入输出流类似的流对象还有:

分类 输入 输出 字符数组流 CharArrayReader CharArrayWriter 字符串流 StringReader StringWriter


这些类的使用方式和字节数组流相似。


字符编码

一、概述

    字符编码表是由计算机二进制数1010的排列组合和文字的对应关系形成的。它的出现可以让计算机识别各个国家不同的文字和符号。

二、常见字符编码表

    1.ASCII码表
      美国标准信息交换码,用一个字节的7位标识一个字符,最高位为0

    2.ISO8859-1
      拉丁码表(欧洲码表),用一个字节的8位标识一个字符。

    3.GB2312和GBK
      GB2312GBK都是中文字符编码表。后者是前者的升级版,囊括更多的中文文字和符号。

    4.Unicode
      国际标准码,融合了全世界多种文字和符号。

    5.UTF-8
      Unicode编码表的优化版,最多用3个字节标识一个字符。

三、乱码问题

    乱码问题的产生,原因在于解码的时候没有使用编码时的编码表。比如,在写入中文数据的时候,指定了GBK编码表,但是在读取数据的时候却用了UTF-8编码表。GBK是两个字节表示一个字符,UTF-8是三个字节表示一个字符。如果输入的字符是“你好”,对应的GBK编码表上的数字是【-60,-29,-70,-61】;读取的时候误用了UTF-8编码表,那么会读取【-60,-29,-70】这三个字节,到UTF-8码表里面寻找有没有对应的字符,如果没有,返回?;接着拿【-61】去找,如果没有匹配的字符,返回?。反过来的过程相同。“你好”对应的UTF-8的数字是【-28,-67,-96,-27,-91,-67】,如果在读取时误用了GBK码表,那么就拿每两个数字去GBK表中查找对应字符,因此出现乱码。

四、编码解码

    1.定义
      编码就是将字符串转换为字节数组的过程;解码就是将字节数组转换为字符串的过程。

    2.解决乱码问题
      实际开发过程中,web服务器端通常默认使用ISO8859-1的编码表。如果出现使用GBK编码,而数据传到服务器端之后出现乱码的问题,只需要将乱码的字符串再次用ISO8859-1进行编码,在用GBK解码即可。

示例代码:

package com.heisejiuhuche.io;public class EncodeDemo {    public static void main(String[] args) throws Exception {        String s1 = "你好";        /* 使用gbk编码 */        byte[] b1 = s1.getBytes("GBK");        /* 使用iso8859-1解码 */        String s2 = new String(b1, "ISO8859-1");        /* 出现乱码???? */        System.out.println(s2);        /* 是同iso8859-1再次编码 */        byte[] b2 = s2.getBytes("ISO8859-1");        /* 使用gbk解码 */        String s3 = new String(b2, "GBK");        /* 还原字符串成功 */        System.out.println(s3);    }}

程序运行结果:

????你好

五、联通的问题

    在文本文件中输入“联通”二字,保存退出;第二次打开时会出现乱码。

这里写图片描述

图中可见“联通”二字的二进制储存形式,符合UTF-8两个字节存储的格式要求;最高位分别为11010。所以当“联通”二字以GBK编码表形式编码存储后,记事本在读取的时候误认为是UTF-8编码表编码,会到UTF-8码表中查找字符,形成乱码。

六、练习

    5个学生,每个学生有3们课程的成绩。从键盘录入以上数据,格式为:姓名,数学成绩,语文成绩,英语成绩;并计算出总成绩。将最后的学生信息和计算出的总分按从高到低的顺序存入文件“stud.txt”中。

示例代码:

package com.heisejiuhuche.io;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.InputStreamReader;import java.util.Collections;import java.util.Comparator;import java.util.TreeSet;public class StudentInfoToFileTest {    public static void main(String[] args) {        /* 反转比较器 */        Comparator<Student> student = Collections.reverseOrder();        /* 获得学生对象并写入文件 */        StudentTools.infoToFile(StudentTools.getStudentInfo(student));    }}class StudentTools {    public static TreeSet<Student> getStudentInfo(Comparator<Student> comp) {        BufferedReader bufr = null;        try {            /* 创建TreeSet对象,用于储存学生对象并排序  */            TreeSet<Student> stuSet = null;            /* 接收键盘录入 */            bufr = new BufferedReader(new InputStreamReader(System.in));            /* 判断有无比较器参数,分别创建有比较器和没有比较器的两个TreeSet对象 */            if(comp == null)                stuSet = new TreeSet<Student>();            else                stuSet = new TreeSet<Student>(comp);            String tmp = null;            /* 不断接收键盘按固定格式的录入,如果收到over,结束程序 */            while((tmp = bufr.readLine()) != null) {                if(tmp.equals("over"))                    break;                /* 将字符串按指定格式切割 */                String[] info = tmp.split(",");                /* 将每段信息传入学生对象封装 */                Student stu = new Student(info[0], Integer.parseInt(info[1]),                                                     Integer.parseInt(info[2]),                                                     Integer.parseInt(info[3]));                /* 将封装号的学生对象存入集合以便排序 */                stuSet.add(stu);            }            return stuSet;        } catch(IOException e) {            throw new RuntimeException("文件读入失败...");        } finally {            try {                if(bufr != null)                    bufr.close();            } catch(IOException e) {                throw new RuntimeException("输入流关闭失败...");            }        }    }    public static void infoToFile(TreeSet<Student> stuSet) {        File file = new File("C:/Users/jeremy/Documents/javaTmp/StudentInfo.txt");        BufferedWriter bufw = null;        try {            bufw = new BufferedWriter(new FileWriter(file));            /* 将集合中的数据写入文件 */            for(Student stus : stuSet) {                bufw.write(stus.toString() + "\t");                bufw.write(stus.getSum() + "");                bufw.newLine();                bufw.flush();            }        } catch(IOException e) {            throw new RuntimeException("文件写入失败...");        } finally {            try {                if(bufw != null)                    bufw.close();            } catch(IOException e) {                throw new RuntimeException("输入流关闭失败...");            }        }    }}/* 创建学生类 */class Student implements Comparable<Student> {    private String name;    private int math, cn, en, sum;    Student(String name, int math, int cn, int en) {        this.name = name;        this.math = math;        this.cn = cn;        this.en = en;        sum = math + cn + en;    }    public void setName(String name) { this.name = name; }    public void setMath(int math) { this.math = math; }    public void setCn(int cn) { this.cn = cn; }    public void setEn(int en) { this.en = en; }    public String getName() { return name; }    public int getMath() { return math; }    public int getCn() { return cn; }    public int getEn() { return en; }    public int getSum() { return sum; }    /* 复写hashCode方法 */    public int hashCode() {        return this.name.hashCode() + this.sum * 37;    }    /* 复写equals方法 */    public boolean equals(Object obj) {        if(!(obj instanceof Student))            throw new ClassCastException("类型不匹配");        Student stu = (Student)obj;        return this.name.equals(stu.name) && this.sum == stu.sum;    }    /* 复写compareTo方法 */    public int compareTo(Student stu) {        int flag = new Integer(this.sum).compareTo(new Integer(stu.sum));        if(flag == 0)            return this.name.compareTo(stu.name);        return flag;    }    /* 复写toString方法 */    public String toString() {        return "Student[" + this.name + "," + math + "," + cn + "," + en + "]";    }}

程序运行结果:

Student[zhouqi,70,70,70]    210Student[zhaoliu,60,60,60]   180Student[wangwu,50,50,50]    150Student[lisi,40,40,40]      120Student[zhangsan,30,30,30]  90
0 0
原创粉丝点击