Java——再看IO

一。编码问题

  • utf-8编码中,一个中文占3个字节,一个英文占1个字节;gbk编码中,一个中文占2个字节,一个英文占1个字节。
  • Java是双字节编码,为utf-16be编码,是说一个字符(无论中文还是英文,都占用2个字节)。因此如果这么问:Java字符串中一个字符可以放一个中文吗?是可以的!
  • 如果一直某个字节序列的编码方式,当我们想将它还原成字符串时,应明确指定其编码格式,否则会出现乱码。
  • 文本文件就是字节序列,可以是任意编码的字节序列。如果在中文机器上,直接创建文本文件,该文本文件只认识ANSI编码
public static void main(String[] args) throws UnsupportedEncodingException {
        // TODO Auto-generated method stub
        
        /*
         * 在utf-8编码中中文占3个字节,而bgk编码中中文占2个字节
         */
        String s = "慕课ABC";
        byte[] byte1 = s.getBytes();
        for(byte b : byte1)  //byte 8bits, int 32 bits. xx vs xxxxxxxx
            System.out.print(Integer.toHexString(b & 0xff) + " "); //e6 85 95 e8 af be 41 42 43 (code: utf-8)
        System.out.println();
        
        byte[] byte2 = s.getBytes("gbk");
        for(byte b : byte2) 
            System.out.print(Integer.toHexString(b & 0xff) + " "); //c4 bd bf ce 41 42 43 
        
        /*
         * java是双字节编码,utf-16be编码。意思是Java里的字符串的一个字符占用2个字节 
         * 面试官会问:Java一个字符中可不可以放汉字呢?如果是gbk编码,是可以的。
         */
        System.out.println();
        byte[] byte3 = s.getBytes("utf-16be");
        for(byte b : byte3) 
            System.out.print(Integer.toHexString(b & 0xff) + " "); //61 55(慕) 8b fe(课) 0 41(A) 0 42(B) 0 43(C) 
        
        System.out.println();
        String s1 = new String(byte3);
        System.out.println(s1); //乱码
        String s2 = new String(byte3, "utf-16be");
        System.out.println(s2); //慕课ABC
        
    }
View Code

二。File类的使用

  • java.io.File类用于表示文件(目录);
  • File类值用于表示文件(目录 )的信息(名称、大小等),不能用于文件内容的访问。
  •  静态方法:File.seperator可以直接当成分隔符使用,无论在什么系统下都可以使用,避免\ or /的困扰。 
/**
     * 递归遍历一个目标及所有子目录下的文件,打印其文件名
     * @param dir
     */
    public void listDirectory(File dir) {
        if(!dir.exists())
            throw new IllegalArgumentException("目录" + dir + "不存在");
        if(!dir.isDirectory())
            throw new IllegalArgumentException(dir + "不是一个目录");
        //String[] fileNames = dir.list();
        File[] files = dir.listFiles();
        
        //如果要遍历子目录下的内容,就要构造File对象做递归操作
        if(files != null && files.length > 0) {
            for(File file : files) {
                if(file.isDirectory()) 
                    listDirectory(file);
                else System.out.println(file.getName());
            }
        }
            
    }
View Code

  

三。RandomAccessFile的使用

  • Java提供的对文件内容的访问,既可以读文件,也可以写文件。
  • 支持随机访问文件,可以访问文件的任意位置。
  • Java文件模型:
    • 在硬盘上的文件是byte byte byte存储的,是数据的集合。
  • 打开文件
    • 有两种模式“rw”和“r”
    • RandomAccessFile raf = new RandomAccessFile(file, "rw");
    • 文件指针,打开文件是指针在开头 pointer = 0;
  • 写文件
    • raf.write(int) —— 只写一个字节(后8位),同时指针指向下一个位置,准备再次写入
  • 读方法
    • int b = raf.read() —— 读一个字节
  • 文件读写完成后一定要关闭
public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        File demo = new File("demo");
        if(!demo.exists())
            demo.mkdir();
        File file = new File(demo, "raf.dat");
        if(!file.exists())
            file.createNewFile();
        
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        //指针的位置,输出为0,随机读取文件好处:文件下载时,文件很大,分成程序同时下载,灭个下载
        //然后在拼接在一起,迅雷每个线程下载文件的一部分,需要知道拼接的位置在哪里,所以需要随机读取
        System.out.println(raf.getFilePointer());
        
        raf.write('A'); //只写了一个字节(后8位)
        System.out.println(raf.getFilePointer());
        
        int i = 0x7fffffff;
        //用write方法,每次只能写一个字节,所以得写4次
        raf.write(i >>> 24);
        raf.write(i >>> 16);
        raf.write(i >>> 8);
        raf.write(i);
        System.out.println(raf.getFilePointer());
        
        //可以直接写一个int
        raf.writeInt(i);
        String s = "中";
        byte[] utf = s.getBytes("gbk");
        raf.write(utf);
        System.out.println(raf.getFilePointer());
        
        //读文件,必须把指针移到头部
        raf.seek(0);
        byte[] buf = new byte[(int)raf.length()];
        raf.read(buf);
        System.out.println(Arrays.toString(buf));
        System.out.println(new String(buf, "utf-8"));
        for(byte b : buf) {
            System.out.print(Integer.toHexString(b & 0xff) + " ");
        }
        
    }
View Code

四。字节流的使用

  • IO流(输入流、输出流; 字节流、字符流)
  • InputStream  
    • 抽象了应用程序读取数据的方式;
    • int b = in.read(); //读取一个字节,无符号填充到int的低八位。-1是EOF
    • in.read(byte[] buf) //读取数据填充到字符数组buf中
  • OutputStream
    • 抽象了应用程序写出数据的方式;
    • out.write(int b) //只写出一个byte到流,b的低八位
    • out.write(byte[] buf) //将buf字节数组都写入到流
  • EOF = End 读到-1就读到结尾了
  • FileInputStream —— 具体实现了在文件上读取数据
  • byte类型为8位,int类型为32位,为了避免数据转换错误,通过&0xff 将高位24位清零!
    批量读取与单独读取有什么区别?用批量读取会节省很多时间。
//批量读取,适合大文件
public static void printHexByByteArray(String fileName) throws IOException {
        FileInputStream in = new FileInputStream(fileName);
        byte[] buf = new byte[20 * 1024];
        int bytes = 0;
        int j = 1;
        while((bytes = in.read(buf, 0, buf.length)) != -1) {
            for(int i=0; i<bytes; i++) {
                System.out.print(Integer.toHexString(buf[i] & 0xff) + " ");
                if(j++ % 10 == 0)
                    System.out.println();
            }
        }
}

//单字节读取
/**
     * 读取指定文件内容,按照16进制输出到控制台
     * 每输出10个byte就换行
     * @param fileName
     * @throws IOException 
     */
    public static void printHex(String fileName) throws IOException {
        //把文件作为字节流进行读操作
        FileInputStream in = new FileInputStream(fileName);
        int b;
        int i = 0;
        while((b = in.read()) != -1) {
            if(b <= 0xf)
                System.out.print("0");
            System.out.print(Integer.toHexString(b) + " ");
            if(i++ % 10 == 0)
                System.out.println();
        }
        in.close();
    }
View Code
  • DataInputDtream 和 DataOutputStream
    • 对流功能的扩展,可以更加方便的读取int,long,字符等类型数据,如writeInt()/writeDouble()/writeUTF()
  • BufferedInoutStream 和 BufferedOutputStream
    • 这两个类为IO提供了带缓冲区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能
    • 从应用程序中把输入放入文件,相当于将一桶水倒入另一个桶中:
      • FileOutputStream的write()方法相当于一滴一滴地把水转移过去;
      • DataOutputStream的writexxx()方法相当于用一个非常小的容器转移;
      • BufferedOutputStream的write()方法相当于用一个更大的容器做缓存,提高性能。
/**
     * 通过缓冲区的方法拷贝文件
     * @param srcFile
     * @param dstFile
     * @throws IOException
     */
    public static void copyFileByBuffer(File srcFile, File dstFile) throws IOException {
        if(!srcFile.exists())
            throw new IllegalArgumentException("File: " + srcFile + " not exist.");
        if(!srcFile.isFile())
            throw new IllegalArgumentException(srcFile + "not a file.");
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream(srcFile));
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(dstFile));
        int c;
        while((c = bis.read()) != -1) {
            bos.write(c);
            bos.flush();
        }
        bis.close();
        bos.close();
    }
    
View Code

五。字符流的使用

  • 文本和文本文件
    • Java中的文本(char)是16位无符号整数,是字符的Unicode编码(双字节编码);
    • 文件是byte byte byte的数据序列;
    • 文本文件是文本(char)序列按照某种编码方案(utf-8, utf-16be,gbk)序列化为byte的存储。
  • 字符流(Reader,Writer)—— 操作的是文本文件
    • 字符的处理,一次处理一个字符;
    • 字符的底层仍然是基本的字节序列。
    • 字符流的基本实现:
      • InputStreamReader 完成byte流解析为char流,按照编码解析
      • OutputStreamReader 提供char流解析为byte流,按照编码处理
    • 文件读写流:
      • FileReader
      • FileWriter
    • 字符流的过滤
      • BufferedReader —— 可以readLine,一次读一行
      • BufferedWriter/Printer —— 写一行
public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("demo/out.data")));
        BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream("demo/out1.data")));
        PrintWriter pw = new PrintWriter("demo/out2.data");
        String line;
        while((line = br.readLine()) != null) {
            System.out.println(line);
            pw.println(line);
            pw.flush();
//            bw.write(line);
//            bw.newLine();
//            bw.flush();
        }
        pw.close();
        bw.close();
        br.close();
        
    }
View Code

六。序列化与反序列化

  • 对象的序列化和反序列化
    • 对象序列化是指将Object对象转化成byte序列,反之叫做对象的反序列化;
    • 序列化流(ObjectOutputStream)是过滤流——writeObject();
    • 反序列化流(ObjectInputStream)—— readObject();
    • 序列化接口(Serializable)
      • 对象必须实现序列化接口才能实现序列化,否则将出现异常,这个接口没有任何方法,只是一个标准。
    • 对象的序列化和反序列化将那些实现了Serialization接口的对象转换成一个字节序列,并能够在以后将这个字节完全恢复为原来的对象;
      • 这样做的一个好处是:能够自动弥补不同操作系统之间的差异。可以在运行Windows操作系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里能够准确的重新组装,而不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节;
      • 将序列化的概念加入java中主要是为了支持两种特性:
        • 一是java的远程方法调用(Remote Method Invocation,RMI),它使存活在其他计算机上的对象使用起来就像存活于本机上一样,当向远程对象大宋消息时,需要通过对象序列化来传输参数和返回值;
        • 二是对于java Bean来说,序列化也是必须的。使用一个Bean时,一般情况是在设计阶段对他的状态信息进行配置。这种状态信息必须保存下来,并在程序启东市进行后期恢复。
public static void main(String[] args) throws IOException, IOException {
        String file = "demo/obj.txt";
        //1. 对象序列化
        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file));
        StudentDemo st = new StudentDemo("aaa", "12", 15);
        oos.writeObject(st);
        oos.flush();
        oos.close();
        
        //2.反序列化
        ObjectInputStream pis = new ObjectInputStream(
                new FileInputStream(file));
        try {
            StudentDemo stu = (StudentDemo) pis.readObject();
            System.out.println(stu);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        pis.close();
    }
View Code
  • transient 修饰后,不会进行虚拟机默认的序列化;也可以自己完成这个元素的序列化
    • 为什么要使用transient关键字呢?
    • 分析ArrayList的序列化与反序列化:实质是一个数组,但是这个数组并不一定放满了,因此我们不需要讲后面没有使用的地方进行序列化,可以根据自己的需要定制序列化,只是序列化数组中的有效元素,提高性能。
  • 序列化中子父类构造函数调用问题
    • 一个类实现了序列化接口,其子类都可以进行序列化
    • 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用;反之不会。
原文地址:https://www.cnblogs.com/little-YTMM/p/5410400.html