• 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的序列化与反序列化:实质是一个数组,但是这个数组并不一定放满了,因此我们不需要讲后面没有使用的地方进行序列化,可以根据自己的需要定制序列化,只是序列化数组中的有效元素,提高性能。
    • 序列化中子父类构造函数调用问题
      • 一个类实现了序列化接口,其子类都可以进行序列化
      • 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用;反之不会。
  • 相关阅读:
    正则表达式
    9.4、分布式进程
    Linux文件编辑工具——VIM
    14 Linux网络管理
    13.Linux系统服务
    12.Linux进程管理
    11.Linux磁盘管理——lvm,raid
    10.Linux磁盘管理
    09.Linux软件包管理——(YUM 、RPM)
    07.Linux 压缩打包
  • 原文地址:https://www.cnblogs.com/little-YTMM/p/5410400.html
Copyright © 2020-2023  润新知