1、文件编码

我们经常使用的txt文本文件本质上将文本char按照GBK、utf-8等多种编码方式序列化为byte字节存储起来的,当我们要读取某个txt文件的内容将其转换为字符串时,也需要使用该字节序列所使用的编码方式进行解码,否则会出现乱码。例如我们在中文Windows系统下创建一个txt文件,默认采用ANSI(GBK)编码方式,当我们在编译器中以utf-8的方式打开该文件就会出现中文乱码。这是由于GBK对于中文一个汉字的编码是两个字节,而utf8一个汉字是三个字节。两者对于英文的编码一样,所以英文不会乱码。 java是用utf-16be编码方式,中文、数字和英文都是两个字节。

String s="中文ABC";

        // GBK输出:d6 d0 ce c4 41 42 43,前四个代表两个汉字,后面三个为英文
        byte[] bytes2=s.getBytes("gbk");
        for (byte b:bytes2){    // 循环以十六进制打印每个字节
            System.out.print(Integer.toHexString(b & 0xff)+' ');
        }
        System.out.println();
        // utf-8输出:e4 b8 ad e6 96 87 41 42 43,前六个字节为两个汉字,后面三个英文
        byte[] bytes3=s.getBytes("utf8");
        for (byte b:bytes3){
            System.out.print(Integer.toHexString(b & 0xff)+' ');
        }

getBytes()方法默认采用项目的默认编码方式,可以对IDEA设置项目的默认编码

java语音输出中文播报 java输入英文输出中文_System

 2、文件目录

Java通过File类来实现对文件的操作,包括文件的初始化、文件的创建删除、获取文件信息等操作如下:

// 按文件相对路径初始化一个文件目录对象
        File dir=new File("src/com/file/output");
        if (!dir.exists())       // 如果目录不存在
            dir.mkdir();         // 则创建文件目录
        /*else
            dir.delete();       // 删除文件
         */

        File f1=new File(dir,"1.txt");    // 在目录dir下创建文件
        if (!f1.isFile()) {     // 如果f1不是一个文件
            try {
                f1.createNewFile();     // 则创建一个具体文件
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        String[] fileNames=dir.list();       // 获取目录下的子文件名字符串
        for(String file:fileNames)
            System.out.println(file);
        System.out.println(f1);     // 调用File的toString方法,输出文件绝对路径
        System.out.println(f1.getName());       // 输出文件名
        System.out.println(f1.getParent());     // 输出文件根目录路径
        File parent=f1.getParentFile();         // 获取根目录对象

通过递归遍历一个文件目录下所有文件:

public static void traverseFile(File file){
        if (file.isDirectory()){                // 如果文件是一个目录
            File[] files=file.listFiles();      // 则获取目录下的所有子文件,进行递归调用
            for (File f:files)
                traverseFile(f);
        }else                                   // 否则打印文件,输出文件路径
            System.out.println(file);
    }

3、读写文件

Java通过类RandomAccessFile打开文件有两种模式:“rw”(读写)、“r”(只读)。打开文件后会有一个文件指针位于文件开头pointer=0。

写方法write()一次写入一个字节8位数据,并将指针后移一个字节,pointer+1。Java中一个字符占用2个字节,因此调用write()只写入低8位,但是由于字符'A'的高8位为0,因此只写入低8位不会出错。但若要写入一个4字节的int,则需要分四次写入,或者可以调用函数writeInt()一次写入一个Int。类似地writeBytes()写入字符串等。

读方法read()一次读取一个字节。

文件读写结束之后要关闭文件流。

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

public class RAccessFile {
    public static void main(String[] args) throws IOException {
        File file=new File("src/com/file/output/1.txt");
        RandomAccessFile raf=new RandomAccessFile(file,"rw");

        raf.write('A');
        System.out.println(raf.getFilePointer());       // 写入一个字节后指针变为1

        int i=0x77777777;
        raf.write(i>>>24);      
        raf.write(i>>>16);
        raf.write(i>>>8);          // 右移8位,写入i的高8位
        raf.write(i);              // 自动写入低8位
        raf.writeInt(i);           // 调用方法写入Int

        String s="汉字";
        byte[] bytes=s.getBytes();
        raf.write(bytes);       //写入字节数组

        raf.seek(0);        // 将指针移动到文件头0的位置
        byte[] br=new byte[(int)raf.length()];      // 新建和文件长度相等的字节数组
        raf.read(br);                       // 将文件读到数组中
        System.out.println(Arrays.toString(br));

        raf.close();        // 关闭文件
    }
}

4、输入输出流

Java定义了两个抽象类InputStream和OutputStream用于实现从流中读取/输出数据,子类FileInputStream用于实现从文件流中读取数据。相应地子类FileOutputStream 用于向文件流中输出数据,其构造方法(filename,boolean)第一个参数为文件位置,第二个可选布尔型参数,如果为true,代表以追加的方式写入文件,否则将覆盖或新建一个文件。

输入流的基本方法int b=in.read()默认读取一个字节填充到b的低8位。in.read(byte[] buf , int start, int size)可以从输入流读取字节数组存放到buf中,存放位置从start开始,长度为size,后两个参数可选。

对应地输出流方法out.write(int b)将int的低8位写入输出流。out.write(byte[] buf , int start, int size),字节数组buf从start位置开始写size长度的数据到输出流。

如下为从文件读取输入流数据并显示、输出流向文件写入数据

public static void main(String[] args) throws IOException {
        FileInputStream in1=new FileInputStream("src/com/file/output/2.txt");
        // 以字节为单位进行读取
        int b;
        while ((b=in1.read())!=-1){      // 文件的结尾标识符EOF=-1
            System.out.print(Integer.toHexString(b)+" ");
        }

        FileInputStream in=new FileInputStream("src/com/file/output/2.txt");
        // 以1024个字节的buf缓冲块为单位进行读取
        byte[] buf=new byte[1024];
        // 从in读取数据到buf,从位置0开始最多读取buf.length长度的数据,返回实际读取到的字节数
        int nums;
        while((nums=in.read(buf,0,buf.length))!=-1){//返回-1代表没有读取到字节,到达文件尾
            for (int i = 0; i < nums; i++) {
                System.out.print(Integer.toHexString(buf[i] & 0xff)+" ");
            }
        }

        FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt",true);
        out.write('A');         // 写入字符
        byte[] bytes="汉字".getBytes();       // 写入字符缓存数组
        out.write(bytes);

        in.close();
        out.close();
    }

为了便于常用数据类型的读写,Java提供了装饰者类用于对输入输出流装饰。其中FilterInputStream为抽象类,其实现子类DataInputStream提供了对Int、Long、Char等多种类型数据的直接写入的API;装饰器的使用类似与管道,例如我们将DataInputStream套接到FileInputStream后面,这样从FileInputStream获取到的文件输入流就会进入DataInputStream,接着我们就可以利用DataInputStream对象完成对多种类型数据的操作。

java语音输出中文播报 java输入英文输出中文_开发语言_02

值得注意的是writeChars()是以Java默认编码utf-16be的方式写入字符串,如果希望以utf-8写入则需使用writeUTF()

FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt",true);
        DataOutputStream dos=new DataOutputStream(out);    //将FileOutputStream对象作为参数传入DataOutputStream
        dos.writeChars("字符串");
        dos.writeUTF("utf8字符串");

java语音输出中文播报 java输入英文输出中文_intellij-idea_03

   

java语音输出中文播报 java输入英文输出中文_intellij-idea_04

子类BufferedInputStream提供了通过缓存流来读写文件的方法,与之前的以字节为单位进行读写相比,先将字节先放入缓存中,然后再成批量的读写,速度上有明显的提升。例如通过buffer来拷贝一个文件如下:

FileInputStream in=new FileInputStream("src/com/file/output/1.txt");
        BufferedInputStream bis=new BufferedInputStream(in);
        FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt");
        BufferedOutputStream bos=new BufferedOutputStream(out);

        int c;
        while ((c=bis.read())!=-1){
            bos.write(c);               // 将数据写入缓冲区
            bos.flush();                // 刷新缓冲区,将缓冲区数据写入文件
        }

        bos.close();
        bis.close();                    // 关闭文件流,先打开的后关闭

使用Scanner类的nextXx()可以方便地读入指定类型的数据。需要注意的是在输入完age之后我们会敲一个换行符,不会被nextInt()读取而是留在输入流中,这时如果直接读取name会读到这个换行符,因此需要先用一个nextLine()吸收掉这个换行符。也可以使用next()来代替nextLine(),它不会读取字符流中的回车符和tab,或者空格键。

public static void main(String [] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你的年龄:");
        int age = sc.nextInt();
        sc.nextLine();  //吸收输入流中的换行符
        System.out.println("请输入你的姓名:");
        String name = sc.nextLine();
        System.out.println("请输入你的工资:");
        float salary = sc.nextFloat();
        System.out.println("你的信息如下:");
        System.out.println("姓名:"+name+"\n"+"年龄:"+age+"\n"+"工资:"+salary);
    }

5、字符流

Java在字节流的基础上封装了字符流类用于实现以字符为单位读取文件,通过InputStreamReader/OutputStreamWriter完成在字节流byte与字符流char之间的相互转换,转换时要注意选择合适的编码方式,否则会出现乱码。如下分别是以单个字符和字符数组为单位进行字符读取

FileInputStream in=new FileInputStream("src/com/file/output/1.txt");
        // 新建utf-8解码的字符流对象
        InputStreamReader reader=new InputStreamReader(in,"utf8");
        int c;
        while ((c=reader.read())!=-1){      // 以字符为单位读取和显示
            System.out.print((char)c);
        }
        
        // 以数组为单位进行读取,将字符读取到缓冲数组中再转换为字符串显示
        char[] buffer=new char[1024];
        int nums;
        while ((nums=reader.read(buffer,0,buffer.length))!=-1){
            String s=new String(buffer,0,nums);
            System.out.println(s);
        }

为了便于文件的读写,Java提供了文件流读写类FileReader和FileWriter用于实现直接读写文件,但该类无法选择编码方式,只能按照项目的默认编码方式进行读写。

FileReader fr=new FileReader("src/com/file/output/1.txt");
        // 以追加的方式写入文件
        FileWriter fw=new FileWriter("src/com/file/output/2.txt",true);
        char[] buffer=new char[1024];
        int nums;
        while ((nums=fr.read(buffer,0,buffer.length))!=-1){
            fw.write(buffer,0,nums);
            fw.flush();
        }

        fw.close();
        fr.close();

字符流过滤器可以更方便地对文件流进行操作,例如实现按行读写操作。在字符流InputStreamReader的基础上创建BufferedReader类,而字符流又是在字节流FileInputStream的基础上创建的,因此可以嵌套构造字符流过滤器。注意BufferedWriter不能识别换行符,需要手动通过newLine()换行。也可以使用PrintWriter来实现文件的写入,它可以直接从文件名创建、输入一行以及自动刷新等操作,十分方便。

// 嵌套创建字符流过滤器
        BufferedReader br=new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("src/com/file/output/1.txt")
                )
        );
        BufferedWriter bw=new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream("src/com/file/output/2.txt",true)
                )
        );
        PrintWriter pw=new PrintWriter("src/com/file/output/3.txt");
        
        String line;
        while ((line=br.readLine())!=null){     // 按行读取文件内容
            bw.write(line);
            bw.newLine();           // bw写入时手动添加换行
            bw.flush();
            
            pw.println(line);       // pw可以写入一行
            pw.flush();
        }

6、对象序列化

Java对象的序列化就是通过序列化流ObjectOutputStream将对象转化为byte序列,反之称为反序列化ObjectInputStream。

对象必须实现了序列化接口Serializable才能进行序列化,否则会报错。如果父类实现了序列化接口,则其子类不需要再实现。在子类对象进行反序列化操作时,如果其父类没有实现接口,父类的构造函数将会被调用。

例如实现一个Student类并将其对象序列化为obj.dat文件以及从该文件中读取对象

public class Student implements Serializable {  // Student类实现接口
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +"name='" + name + '\'' + ", age=" + age +'}';
    }
}

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos=new ObjectOutputStream(
                new FileOutputStream("src/com/file/output/obj.dat")
        );
        Student stu=new Student("小明",23);
        oos.writeObject(stu);       // 将对象写入序列化流
        oos.flush();
        oos.close();

        ObjectInputStream ois=new ObjectInputStream(
                new FileInputStream("src/com/file/output/obj.dat")
        );
        Student s2=(Student)ois.readObject();       // 从反序列化流中取出对象
        System.out.println(s2);
    }