基础知识复习 (九) IO流

一,File类

1.1 概述

  java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

  • 它是文件和目录路径名的抽象表示

  • 文件和目录是可以通过File封装成对象的

  • 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的

1.2 构造方法

  • public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。

  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

  • 构造举例,代码如下:

// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);

// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);

// 通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);

// 通过父级File对象和子路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);

小贴士:

  1. 一个File对象代表硬盘中实际存在的一个文件或者目录。

  2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。

1.3 常用方法

获取功能的方法

  • public String getAbsolutePath() :返回此File的绝对路径名字符串。

  • public String getPath() :将此File转换为路径名字符串。

  • public String getName() :返回由此File表示的文件或目录的名称。

  • public long length() :返回由此File表示的文件的长度。

    方法演示,代码如下:

    public class FileGet {
       public static void main(String[] args) {
           File f = new File("d:/aaa/bbb.java");    
           System.out.println("文件绝对路径:"+f.getAbsolutePath());
           System.out.println("文件构造路径:"+f.getPath());
           System.out.println("文件名称:"+f.getName());
           System.out.println("文件长度:"+f.length()+"字节");

           File f2 = new File("d:/aaa");    
           System.out.println("目录绝对路径:"+f2.getAbsolutePath());
           System.out.println("目录构造路径:"+f2.getPath());
           System.out.println("目录名称:"+f2.getName());
           System.out.println("目录长度:"+f2.length());
      }
    }
    输出结果:
    文件绝对路径:d:\aaa\bbb.java
    文件构造路径:d:\aaa\bbb.java
    文件名称:bbb.java
    文件长度:636字节

    目录绝对路径:d:\aaa
    目录构造路径:d:\aaa
    目录名称:aaa
    目录长度:4096

API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。

绝对路径和相对路径

  • 绝对路径:从盘符开始的路径,这是一个完整的路径。

  • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。

public class FilePath {
   public static void main(String[] args) {
    // D盘下的bbb.java文件
       File f = new File("D:\\bbb.java");
       System.out.println(f.getAbsolutePath());
   
// 项目下的bbb.java文件
       File f2 = new File("bbb.java");
       System.out.println(f2.getAbsolutePath());
  }
}
输出结果:
D:\bbb.java
D:\idea_project_test4\bbb.java

判断功能的方法

  • public boolean exists() :此File表示的文件或目录是否实际存在。

  • public boolean isDirectory() :此File表示的是否为目录。

  • public boolean isFile() :此File表示的是否为文件。

方法演示,代码如下:

public class FileIs {
   public static void main(String[] args) {
       File f = new File("d:\\aaa\\bbb.java");
       File f2 = new File("d:\\aaa");
    // 判断是否存在
       System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
       System.out.println("d:\\aaa 是否存在:"+f2.exists());
    // 判断是文件还是目录
       System.out.println("d:\\aaa 文件?:"+f2.isFile());
       System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
  }
}
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true

创建删除功能的方法

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

  • public boolean delete() :删除由此File表示的文件或目录。

  • public boolean mkdir() :创建由此File表示的目录。

  • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

方法演示,代码如下:

public class FileCreateDelete {
   public static void main(String[] args) throws IOException {
       // 文件的创建
       File f = new File("aaa.txt");
       System.out.println("是否存在:"+f.exists()); // false
       System.out.println("是否创建:"+f.createNewFile()); // true
       System.out.println("是否存在:"+f.exists()); // true

    // 目录的创建
    File f2= new File("newDir");
       System.out.println("是否存在:"+f2.exists());// false
       System.out.println("是否创建:"+f2.mkdir()); // true
       System.out.println("是否存在:"+f2.exists());// true

// 创建多级目录
    File f3= new File("newDira\\newDirb");
       System.out.println(f3.mkdir());// false
       File f4= new File("newDira\\newDirb");
       System.out.println(f4.mkdirs());// true
     
    // 文件的删除
      System.out.println(f.delete());// true
     
    // 目录的删除
       System.out.println(f2.delete());// true
       System.out.println(f4.delete());// false
  }
}

API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。

1.4 目录的遍历

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

public class FileFor {
   public static void main(String[] args) {
       File dir = new File("d:\\java_code");
     
    //获取当前目录下的文件以及文件夹的名称。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
       //获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
       File[] files = dir.listFiles();
       for (File file : files) {
           System.out.println(file);
      }
  }
}

小贴士:

调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。

二,递归

2.1 概述

  • 递归:指在当前方法内调用自己的这种现象。把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算

  • 递归的分类:

    • 递归分为两种,直接递归和间接递归。

    • 直接递归称为方法自身调用自己。

    • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

  • 注意事项

    • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。

    • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。

    • 构造方法,禁止递归

public class Demo01DiGui {
public static void main(String[] args) {
// a();
b(1);
}

/*
* 3.构造方法,禁止递归
* 编译报错:构造方法是创建对象使用的,不能让对象一直创建下去
*/
public Demo01DiGui() {
//Demo01DiGui();
}


/*
* 2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
* 4993
* Exception in thread "main" java.lang.StackOverflowError
*/
private static void b(int i) {
System.out.println(i);
//添加一个递归结束的条件,i==5000的时候结束
if(i==5000){
return;//结束方法
}
b(++i);
}

/*
* 1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。 Exception in thread "main"
* java.lang.StackOverflowError
*/
private static void a() {
System.out.println("a方法");
a();
}
}

2.2 递归累加求和

计算1 ~ n的和

分析:num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成一个方法,递归调用。

实现代码

public class DiGuiDemo {
public static void main(String[] args) {
//计算1~num的和,使用递归完成
int num = 5;
    // 调用求和的方法
int sum = getSum(num);
    // 输出结果
System.out.println(sum);

}
/*
 通过递归算法实现.
 参数列表:int
 返回值类型: int
*/
public static int getSum(int num) {
    /*
      num为1时,方法返回1,
      相当于是方法的出口,num总有是1的情况
    */
if(num == 1){
return 1;
}
    /*
         num不为1时,方法返回 num +(num-1)的累和
         递归调用getSum方法
       */
return num + getSum(num-1);
}
}

代码执行图解

小贴士:递归一定要有条件限定,保证递归能够停止下来,次数不要太多,否则会发生栈内存溢出。

2.3 递归求阶乘

  • 阶乘:所有小于及等于该数的正整数的积。

n的阶乘:n! = n * (n-1) *...* 3 * 2 * 1 

分析:这与累和类似,只不过换成了乘法运算,学员可以自己练习,需要注意阶乘值符合int类型的范围。

推理得出:n! = n * (n-1)!

代码实现

public class DiGuiDemo {
//计算n的阶乘,使用递归完成
   public static void main(String[] args) {
       int n = 3;
    // 调用求阶乘的方法
       int value = getValue(n);
    // 输出结果
       System.out.println("阶乘为:"+ value);
  }
/*
 通过递归算法实现.
 参数列表:int
 返回值类型: int
*/
   public static int getValue(int n) {
    // 1的阶乘为1
       if (n == 1) {
           return 1;
      }
    /*
     n不为1时,方法返回 n! = n*(n-1)!
         递归调用getValue方法
    */
       return n * getValue(n - 1);
  }
}

2.4 递归打印多级目录

分析:多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。

代码实现

public class DiGuiDemo2 {
   public static void main(String[] args) {
    // 创建File对象
       File dir  = new File("D:\\aaa");
    // 调用打印目录方法
       printDir(dir);
  }

   public static void  printDir(File dir) {
    // 获取子文件和目录
       File[] files = dir.listFiles();
    // 循环打印
    /*
     判断:
     当是文件时,打印绝对路径.
     当是目录时,继续调用打印目录的方法,形成递归调用.
    */
       for (File file : files) {
  // 判断
           if (file.isFile()) {
            // 是文件,输出文件绝对路径
               System.out.println("文件名:"+ file.getAbsolutePath());
          } else {
            // 是目录,输出目录绝对路径
               System.out.println("目录:"+file.getAbsolutePath());
            // 继续遍历,调用printDir,形成递归
               printDir(file);
          }
      }
  }
}

三,IO流

3.1 IO流概述和分类

  • IO流介绍

    • IO:输入/输出(Input/Output)

    • 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输

    • IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载

  • IO流的分类

    • 按照数据的流向

      • 输入流:读数据

      • 输出流:写数据

    • 按照数据类型来分

      • 字节流

        • 字节输入流

        • 字节输出流

      • 字符流

        • 字符输入流

        • 字符输出流

  • IO流的使用场景

    • 如果操作的是纯文本文件,优先使用字符流

    • 如果操作的是图片、视频、音频等二进制文件。优先使用字节流

    • 如果不确定文件类型,优先使用字节流。字节流是万能的流

3.2 字节流写数据

  • 字节流抽象基类

    • InputStream:这个抽象类是表示字节输入流的所有类的超类

    • OutputStream:这个抽象类是表示字节输出流的所有类的超类

    • 子类名特点:子类名称都是以其父类名作为子类名的后缀

  • 字节输出流

    • FileOutputStream(String name):创建文件输出流以指定的名称写入文件

  • 使用字节输出流写数据的步骤

    • 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)

    • 调用字节输出流对象的写数据方法

    • 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)

  • 示例代码

    public class FileOutputStreamDemo01 {
       public static void main(String[] args) throws IOException {
           //创建字节输出流对象
           //FileOutputStream(String name):创建文件输出流以指定的名称写入文件
           FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
           /*
               做了三件事情:
                   A:调用系统功能创建了文件
                   B:创建了字节输出流对象
                   C:让字节输出流对象指向创建好的文件
            */

           //void write(int b):将指定的字节写入此文件输出流
           fos.write(97);
    //       fos.write(57);
    //       fos.write(55);

           //最后都要释放资源
           //void close():关闭此文件输出流并释放与此流相关联的任何系统资源。
           fos.close();
      }
    }

3.3 字节流写数据的三种方式

  • 写数据的方法分类

    方法名说明
    void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据
    void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据
    void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据
  • 示例代码

    public class FileOutputStreamDemo02 {
       public static void main(String[] args) throws IOException {
           //FileOutputStream(String name):创建文件输出流以指定的名称写入文件
           FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
           //new File(name)
    //       FileOutputStream fos = new FileOutputStream(new File("myByteStream\\fos.txt"));

           //FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件
    //       File file = new File("myByteStream\\fos.txt");
    //       FileOutputStream fos2 = new FileOutputStream(file);
    //       FileOutputStream fos2 = new FileOutputStream(new File("myByteStream\\fos.txt"));

           //void write(int b):将指定的字节写入此文件输出流
    //       fos.write(97);
    //       fos.write(98);
    //       fos.write(99);
    //       fos.write(100);
    //       fos.write(101);

    //       void write(byte[] b):将 b.length字节从指定的字节数组写入此文件输出流
    //       byte[] bys = {97, 98, 99, 100, 101};
           //byte[] getBytes():返回字符串对应的字节数组
           byte[] bys = "abcde".getBytes();
    //       fos.write(bys);

           //void write(byte[] b, int off, int len):将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流
    //       fos.write(bys,0,bys.length);
           fos.write(bys,1,3);

           //释放资源
           fos.close();
      }
    }

3.4 字节流写数据的两个小问题

  • 字节流写数据如何实现换行

    • windows:\r\n

    • linux:\n

    • mac:\r

  • 字节流写数据如何实现追加写入

    • public FileOutputStream(String name,boolean append)

    • 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头

  • 示例代码

    public class FileOutputStreamDemo03 {
       public static void main(String[] args) throws IOException {
           //创建字节输出流对象
    //       FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
           FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt",true);

           //写数据
           for (int i = 0; i < 10; i++) {
               fos.write("hello".getBytes());
               fos.write("\r\n".getBytes());
          }

           //释放资源
           fos.close();
      }
    }

3.5 字节流写数据加异常处理

  • 异常处理格式

    • try-catch-finally

      try{
      可能出现异常的代码;
      }catch(异常类名 变量名){
      异常的处理代码;
      }finally{
      执行所有清除操作;
      }
    • finally特点

      • 被finally控制的语句一定会执行,除非JVM退出

  • 示例代码

    public class FileOutputStreamDemo04 {
       public static void main(String[] args) {
           //加入finally来实现释放资源
           FileOutputStream fos = null;
           try {
               fos = new FileOutputStream("myByteStream\\fos.txt");
               fos.write("hello".getBytes());
          } catch (IOException e) {
               e.printStackTrace();
          } finally {
               if(fos != null) {
                   try {
                       fos.close();
                  } catch (IOException e) {
                       e.printStackTrace();
                  }
              }
          }
      }
    }

3.6 字节流读数据(一次读一个字节数据)

  • 字节输入流

    • FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name命名

  • 字节输入流读取数据的步骤

    • 创建字节输入流对象

    • 调用字节输入流对象的读数据方法

    • 释放资源

  • 示例代码

    public class FileInputStreamDemo01 {
       public static void main(String[] args) throws IOException {
           //创建字节输入流对象
           //FileInputStream(String name)
           FileInputStream fis = new FileInputStream("myByteStream\\fos.txt");

           int by;
           /*
               fis.read():读数据
               by=fis.read():把读取到的数据赋值给by
               by != -1:判断读取到的数据是否是-1
            */
           while ((by=fis.read())!=-1) {
               System.out.print((char)by);
          }

           //释放资源
           fis.close();
      }
    }

3.7 字节流复制文本文件

  • 案例需求

    把“E:\itcast\窗里窗外.txt”复制到模块目录下的“窗里窗外.txt”

  • 实现步骤

    • 复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)

    • 数据源:

      E:\itcast\窗里窗外.txt --- 读数据 --- InputStream --- FileInputStream

    • 目的地:

      myByteStream\窗里窗外.txt --- 写数据 --- OutputStream --- FileOutputStream

  • 代码实现

    public class CopyTxtDemo {
       public static void main(String[] args) throws IOException {
           //根据数据源创建字节输入流对象
           FileInputStream fis = new FileInputStream("E:\\itcast\\窗里窗外.txt");
           //根据目的地创建字节输出流对象
           FileOutputStream fos = new FileOutputStream("myByteStream\\窗里窗外.txt");

           //读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
           int by;
           while ((by=fis.read())!=-1) {
               fos.write(by);
          }

           //释放资源
           fos.close();
           fis.close();
      }
    }

3.8 字节流读数据(一次读一个字节数组数据)

  • 一次读一个字节数组的方法

    • public int read(byte[] b):从输入流读取最多b.length个字节的数据

    • 返回的是读入缓冲区的总字节数,也就是实际的读取字节个数

  • 示例代码

    public class FileInputStreamDemo02 {
       public static void main(String[] args) throws IOException {
           //创建字节输入流对象
           FileInputStream fis = new FileInputStream("myByteStream\\fos.txt");

           /*
               hello\r\n
               world\r\n

               第一次:hello
               第二次:\r\nwor
               第三次:ld\r\nr

            */

           byte[] bys = new byte[1024]; //1024及其整数倍
           int len;
           while ((len=fis.read(bys))!=-1) {
               System.out.print(new String(bys,0,len));
          }

           //释放资源
           fis.close();
      }
    }

四,IO流扩展

字节缓冲流构造方法

  • 字节缓冲流介绍

    • lBufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用

    • lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节

  • 构造方法:

    方法名说明
    BufferedOutputStream(OutputStream out) 创建字节缓冲输出流对象
    BufferedInputStream(InputStream in) 创建字节缓冲输入流对象
  • 示例代码

    public class BufferStreamDemo {
       public static void main(String[] args) throws IOException {
           //字节缓冲输出流:BufferedOutputStream(OutputStream out)

           BufferedOutputStream bos = new BufferedOutputStream(new                                       FileOutputStream("myByteStream\\bos.txt"));
           //写数据
           bos.write("hello\r\n".getBytes());
           bos.write("world\r\n".getBytes());
           //释放资源
           bos.close();
       

           //字节缓冲输入流:BufferedInputStream(InputStream in)
           BufferedInputStream bis = new BufferedInputStream(new                                                          FileInputStream("myByteStream\\bos.txt"));

           //一次读取一个字节数据
    //       int by;
    //       while ((by=bis.read())!=-1) {
    //           System.out.print((char)by);
    //       }

           //一次读取一个字节数组数据
           byte[] bys = new byte[1024];
           int len;
           while ((len=bis.read(bys))!=-1) {
               System.out.print(new String(bys,0,len));
          }

           //释放资源
           bis.close();
      }
    }

字节流复制视频

  • 案例需求

    把“E:\itcast\字节流复制图片.avi”复制到模块目录下的“字节流复制图片.avi”

  • 实现步骤

    • 根据数据源创建字节输入流对象

    • 根据目的地创建字节输出流对象

    • 读写数据,复制视频

    • 释放资源

  • 代码实现

    public class CopyAviDemo {
       public static void main(String[] args) throws IOException {
           //记录开始时间
           long startTime = System.currentTimeMillis();

           //复制视频
    //       method1();
    //       method2();
    //       method3();
           method4();

           //记录结束时间
           long endTime = System.currentTimeMillis();
           System.out.println("共耗时:" + (endTime - startTime) + "毫秒");
      }

       //字节缓冲流一次读写一个字节数组
       public static void method4() throws IOException {
           BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\itcast\\字节流复制图片.avi"));
           BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));

           byte[] bys = new byte[1024];
           int len;
           while ((len=bis.read(bys))!=-1) {
               bos.write(bys,0,len);
          }

           bos.close();
           bis.close();
      }

       //字节缓冲流一次读写一个字节
       public static void method3() throws IOException {
           BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\itcast\\字节流复制图片.avi"));
           BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));

           int by;
           while ((by=bis.read())!=-1) {
               bos.write(by);
          }

           bos.close();
           bis.close();
      }


       //基本字节流一次读写一个字节数组
       public static void method2() throws IOException {
           //E:\\itcast\\字节流复制图片.avi
           //模块目录下的 字节流复制图片.avi
           FileInputStream fis = new FileInputStream("E:\\itcast\\字节流复制图片.avi");
           FileOutputStream fos = new FileOutputStream("myByteStream\\字节流复制图片.avi");

           byte[] bys = new byte[1024];
           int len;
           while ((len=fis.read(bys))!=-1) {
               fos.write(bys,0,len);
          }

           fos.close();
           fis.close();
      }

       //基本字节流一次读写一个字节
       public static void method1() throws IOException {
           //E:\\itcast\\字节流复制图片.avi
           //模块目录下的 字节流复制图片.avi
           FileInputStream fis = new FileInputStream("E:\\itcast\\字节流复制图片.avi");
           FileOutputStream fos = new FileOutputStream("myByteStream\\字节流复制图片.avi");

           int by;
           while ((by=fis.read())!=-1) {
               fos.write(by);
          }

           fos.close();
           fis.close();
      }
    }

字符流

4.1 为什么会出现字符流

  • 字符流的介绍

    由于字节流操作中文不是特别的方便,所以Java就提供字符流

    字符流 = 字节流 + 编码表

  • 中文的字节存储方式

    用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?

    汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

4.2 编码表

  • 什么是字符集

    是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

  • 常见的字符集

    • ASCII字符集:

      lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

      基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    • GBXXX字符集:

      GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

    • Unicode字符集:

      UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码

      编码规则:

      128个US-ASCII字符,只需一个字节编码

      拉丁文等字符,需要二个字节编码

      大部分常用字(含中文),使用三个字节编码

      其他极少使用的Unicode辅助字符,使用四字节编码

4.3 字符串中的编码解码问题

  • 相关方法

    方法名说明
    byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节
    byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节
    String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串
    String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串
  • 代码演示

    public class StringDemo {
       public static void main(String[] args) throws UnsupportedEncodingException {
           //定义一个字符串
           String s = "中国";

           //byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67]
           //byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67]
           byte[] bys = s.getBytes("GBK"); //[-42, -48, -71, -6]
           System.out.println(Arrays.toString(bys));

           //String ss = new String(bys);
           //String ss = new String(bys,"UTF-8");
           String ss = new String(bys,"GBK");
           System.out.println(ss);
      }
    }

4.4 字符流中的编码解码问题

  • 字符流中和编码解码问题相关的两个类

    • InputStreamReader:是从字节流到字符流的桥梁

      它读取字节,并使用指定的编码将其解码为字符

      它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

    • OutputStreamWriter:是从字符流到字节流的桥梁

      是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节

      它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

  • 构造方法

    方法名说明
    InputStreamReader(InputStream in) 使用默认字符编码创建InputStreamReader对象
    InputStreamReader(InputStream in,String chatset) 使用指定的字符编码创建InputStreamReader对象
    OutputStreamWriter(OutputStream out) 使用默认字符编码创建OutputStreamWriter对象
    OutputStreamWriter(OutputStream out,String charset) 使用指定的字符编码创建OutputStreamWriter对象
  • 代码演示

    public class ConversionStreamDemo {
       public static void main(String[] args) throws IOException {
           //OutputStreamWriter osw = new OutputStreamWriter(new                                             FileOutputStream("myCharStream\\osw.txt"));
           OutputStreamWriter osw = new OutputStreamWriter(new                                              FileOutputStream("myCharStream\\osw.txt"),"GBK");
           osw.write("中国");
           osw.close();

           //InputStreamReader isr = new InputStreamReader(new                                         FileInputStream("myCharStream\\osw.txt"));
           InputStreamReader isr = new InputStreamReader(new                                                 FileInputStream("myCharStream\\osw.txt"),"GBK");
           //一次读取一个字符数据
           int ch;
           while ((ch=isr.read())!=-1) {
               System.out.print((char)ch);
          }
           isr.close();
      }
    }

4.5 字符流写数据的5种方式

  • 方法介绍

    方法名说明
    void write(int c) 写一个字符
    void write(char[] cbuf) 写入一个字符数组
    void write(char[] cbuf, int off, int len) 写入字符数组的一部分
    void write(String str) 写一个字符串
    void write(String str, int off, int len) 写一个字符串的一部分
  • 刷新和关闭的方法

    方法名说明
    flush() 刷新流,之后还可以继续写数据
    close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
  • 代码演示

    public class OutputStreamWriterDemo {
       public static void main(String[] args) throws IOException {
           OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"));

           //void write(int c):写一个字符
    //       osw.write(97);
    //       osw.write(98);
    //       osw.write(99);

           //void writ(char[] cbuf):写入一个字符数组
           char[] chs = {'a', 'b', 'c', 'd', 'e'};
    //       osw.write(chs);

           //void write(char[] cbuf, int off, int len):写入字符数组的一部分
    //       osw.write(chs, 0, chs.length);
    //       osw.write(chs, 1, 3);

           //void write(String str):写一个字符串
    //       osw.write("abcde");

           //void write(String str, int off, int len):写一个字符串的一部分
    //       osw.write("abcde", 0, "abcde".length());
           osw.write("abcde", 1, 3);

           //释放资源
           osw.close();
      }
    }

4.6 字符流读数据的2种方式

  • 方法介绍

    方法名说明
    int read() 一次读一个字符数据
    int read(char[] cbuf) 一次读一个字符数组数据
  • 代码演示

    public class InputStreamReaderDemo {
       public static void main(String[] args) throws IOException {
     
           InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\ConversionStreamDemo.java"));

           //int read():一次读一个字符数据
    //       int ch;
    //       while ((ch=isr.read())!=-1) {
    //           System.out.print((char)ch);
    //       }

           //int read(char[] cbuf):一次读一个字符数组数据
           char[] chs = new char[1024];
           int len;
           while ((len = isr.read(chs)) != -1) {
               System.out.print(new String(chs, 0, len));
          }

           //释放资源
           isr.close();
      }
    }

4.7 字符流复制Java文件

  • 案例需求

    把模块目录下的“ConversionStreamDemo.java” 复制到模块目录下的“Copy.java”

  • 实现步骤

    • 根据数据源创建字符输入流对象

    • 根据目的地创建字符输出流对象

    • 读写数据,复制文件

    • 释放资源

  • 代码实现

    public class CopyJavaDemo01 {
       public static void main(String[] args) throws IOException {
           //根据数据源创建字符输入流对象
           InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\ConversionStreamDemo.java"));
           //根据目的地创建字符输出流对象
           OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\Copy.java"));

           //读写数据,复制文件
           //一次读写一个字符数据
    //       int ch;
    //       while ((ch=isr.read())!=-1) {
    //           osw.write(ch);
    //       }

           //一次读写一个字符数组数据
           char[] chs = new char[1024];
           int len;
           while ((len=isr.read(chs))!=-1) {
               osw.write(chs,0,len);
          }

           //释放资源
           osw.close();
           isr.close();
      }
    }

4.8 字符流复制Java文件改进版

  • 案例需求

    使用便捷流对象,把模块目录下的“ConversionStreamDemo.java” 复制到模块目录下的“Copy.java”

  • 实现步骤

    • 根据数据源创建字符输入流对象

    • 根据目的地创建字符输出流对象

    • 读写数据,复制文件

    • 释放资源

  • 代码实现

    public class CopyJavaDemo02 {
       public static void main(String[] args) throws IOException {
           //根据数据源创建字符输入流对象
           FileReader fr = new FileReader("myCharStream\\ConversionStreamDemo.java");
           //根据目的地创建字符输出流对象
           FileWriter fw = new FileWriter("myCharStream\\Copy.java");

           //读写数据,复制文件
    //       int ch;
    //       while ((ch=fr.read())!=-1) {
    //           fw.write(ch);
    //       }

           char[] chs = new char[1024];
           int len;
           while ((len=fr.read(chs))!=-1) {
               fw.write(chs,0,len);
          }

           //释放资源
           fw.close();
           fr.close();
      }
    }

4.9 字符缓冲流

  • 字符缓冲流介绍

    • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途

    • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途

  • 构造方法

    方法名说明
    BufferedWriter(Writer out) 创建字符缓冲输出流对象
    BufferedReader(Reader in) 创建字符缓冲输入流对象
  • 代码演示

    public class BufferedStreamDemo01 {
        public static void main(String[] args) throws IOException {
            //BufferedWriter(Writer out)
            BufferedWriter bw = new BufferedWriter(new                                                            FileWriter("myCharStream\\bw.txt"));
            bw.write("hello\r\n");
            bw.write("world\r\n");
            bw.close();
    
            //BufferedReader(Reader in)
            BufferedReader br = new BufferedReader(new                                                           FileReader("myCharStream\\bw.txt"));
    
            //一次读取一个字符数据
    //        int ch;
    //        while ((ch=br.read())!=-1) {
    //            System.out.print((char)ch);
    //        }
    
            //一次读取一个字符数组数据
            char[] chs = new char[1024];
            int len;
            while ((len=br.read(chs))!=-1) {
                System.out.print(new String(chs,0,len));
            }
    
            br.close();
        }
    }

4.10 字符缓冲流复制Java文件

  • 案例需求

    把模块目录下的ConversionStreamDemo.java 复制到模块目录下的 Copy.java

  • 实现步骤

    • 根据数据源创建字符缓冲输入流对象

    • 根据目的地创建字符缓冲输出流对象

    • 读写数据,复制文件,使用字符缓冲流特有功能实现

    • 释放资源

  • 代码实现

    public class CopyJavaDemo01 {
        public static void main(String[] args) throws IOException {
            //根据数据源创建字符缓冲输入流对象
            BufferedReader br = new BufferedReader(new FileReader("myCharStream\\ConversionStreamDemo.java"));
            //根据目的地创建字符缓冲输出流对象
            BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\Copy.java"));
    
            //读写数据,复制文件
            //一次读写一个字符数据
    //        int ch;
    //        while ((ch=br.read())!=-1) {
    //            bw.write(ch);
    //        }
    
            //一次读写一个字符数组数据
            char[] chs = new char[1024];
            int len;
            while ((len=br.read(chs))!=-1) {
                bw.write(chs,0,len);
            }
    
            //释放资源
            bw.close();
            br.close();
        }
    }

4.11 字符缓冲流特有功能

  • 方法介绍

    BufferedWriter:
    方法名说明
    void newLine() 写一行行分隔符,行分隔符字符串由系统属性定义
    BufferedReader:
    方法名说明
    String readLine() 读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null
  • 代码演示

    public class BufferedStreamDemo02 {
        public static void main(String[] args) throws IOException {
    
            //创建字符缓冲输出流
            BufferedWriter bw = new BufferedWriter(new                                                          FileWriter("myCharStream\\bw.txt"));
    
            //写数据
            for (int i = 0; i < 10; i++) {
                bw.write("hello" + i);
                //bw.write("\r\n");
                bw.newLine();
                bw.flush();
            }
    
            //释放资源
            bw.close();
    
            //创建字符缓冲输入流
            BufferedReader br = new BufferedReader(new                                                          FileReader("myCharStream\\bw.txt"));
    
            String line;
            while ((line=br.readLine())!=null) {
                System.out.println(line);
            }
    
            br.close();
        }
    }

4.12 字符缓冲流特有功能复制Java文件

  • 案例需求

    使用特有功能把模块目录下的ConversionStreamDemo.java 复制到模块目录下的 Copy.java

  • 实现步骤

    • 根据数据源创建字符缓冲输入流对象

    • 根据目的地创建字符缓冲输出流对象

    • 读写数据,复制文件,使用字符缓冲流特有功能实现

    • 释放资源

  • 代码实现

    public class CopyJavaDemo02 {
        public static void main(String[] args) throws IOException {
            //根据数据源创建字符缓冲输入流对象
            BufferedReader br = new BufferedReader(new FileReader("myCharStream\\ConversionStreamDemo.java"));
            //根据目的地创建字符缓冲输出流对象
            BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\Copy.java"));
    
            //读写数据,复制文件
            //使用字符缓冲流特有功能实现
            String line;
            while ((line=br.readLine())!=null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
    
            //释放资源
            bw.close();
            br.close();
        }
    }

 五,打印流和序列

打印流

  控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

PrintStream类

构造方法

  • public PrintStream(String fileName): 使用指定的文件名创建一个新的打印流。

构造举例,代码如下:

PrintStream ps = new PrintStream("ps.txt")

改变打印流向

System.out就是PrintStream类型的,改变它的流向。

public class PrintDemo {
    public static void main(String[] args) throws IOException {
        // 调用系统的打印流,控制台直接输出97
        System.out.println(97);
      
        // 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("ps.txt");
          
          // 设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
          // 调用系统的打印流,ps.txt中输出97
        System.out.println(97);
    }
}

序列化

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:

ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法

  • public ObjectOutputStream(OutputStream out): 创建一个指定OutputStream的ObjectOutputStream。

构造举例,代码如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

序列化操作

  1. 一个对象要想序列化,必须满足两个条件:

  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

public class Employee implements java.io.Serializable {
   public String name;
   public String address;
   public transient int age; // transient瞬态修饰成员,不会被序列化
   public void addressCheck() {
    System.out.println("Address check : " + name + " -- " + address);
  }
}

2.写出对象方法

  • public final void writeObject (Object obj) : 将指定的对象写出。

public class SerializeDemo{
  public static void main(String [] args)   {
  Employee e = new Employee();
  e.name = "zhangsan";
  e.address = "beiqinglu";
  e.age = 20;
  try {
    // 创建序列化流对象
         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
      // 写出对象
      out.writeObject(e);
      // 释放资源
      out.close();
      fileOut.close();
      System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
      } catch(IOException i)   {
           i.printStackTrace();
      }
  }
}
输出结果:
Serialized data is saved

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。

反序列化操作1

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。

public class DeserializeDemo {
  public static void main(String [] args)   {
       Employee e = null;
       try {
            // 创建反序列化流
            FileInputStream fileIn = new FileInputStream("employee.txt");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            // 读取一个对象
            e = (Employee) in.readObject();
            // 释放资源
            in.close();
            fileIn.close();
      }catch(IOException i) {
            // 捕获其他异常
            i.printStackTrace();
            return;
      }catch(ClassNotFoundException c) {
      // 捕获类找不到异常
            System.out.println("Employee class not found");
            c.printStackTrace();
            return;
      }
       // 无异常,直接打印输出
       System.out.println("Name: " + e.name); // zhangsan
       System.out.println("Address: " + e.address); // beiqinglu
       System.out.println("age: " + e.age); // 0
  }
}

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

反序列化操作2

另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

  • 该类包含未知数据类型

  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Employee implements java.io.Serializable {
    // 加入序列版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public String address;
    // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
    public int eid;

    public void addressCheck() {
        System.out.println("Address check : " + name + " -- " + address);
    }
}
posted @ 2021-03-07 16:53  枯树老鸭  阅读(177)  评论(0)    收藏  举报