目录

1.泛型

1.1 定义

允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
可以在类、接口或方法中预支地使用未知的类型。

1.2 作用

灵活地将数据类型应用到不同的类、方法、接口中,将数据类型作为参数进行传递

1.3 意义

在编译期间进行语法检查,将运行期异常提前到编译器,同时也避免了强制类型转换的麻烦。

1.4 自定义泛型类/接口

1.4.1 细节

  1. T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。
  2. 泛型类可以有多个参数。
  3. T只能是类,不能用基本数据类型填充,但可以使用包装类填充。
  4. jdk1.7后,泛型的简化操作:ArrayList flist = new ArrayList<>()。
  5. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
  6. 泛型擦除:如果定义了泛型类,但是在实例化时没有指定泛型类型,那么其类型等价于Object,但不是Object。
  7. 静态方法中不能使用类的泛型,因为类的泛型是在对象实例化时确定的,静态方法的加载时间要早于对象实例化,如果在对象实例化之前或者是使用类名来调用静态方法,会出现异常。
  8. 异常类不能使用泛型。

1.4.2 格式

[public] class Test<T>{}
[public] class Test<K,V>{}
[public] interface Test<T>{}

1.4.3 使用

Test<String> t = new Test<>();
Test<String,Integer> t = new Test<>();

1.4.4 继承关系中泛型的使用

  1. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型;
  2. 子类除了可以保留父类的泛型还可以拥有自己的泛型;
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}

1.5 泛型方法

1.5.1 细节

  1. 泛型方法和泛型类无关,非泛型类中也可以有泛型方法。
  2. 泛型方法可以是静态的,因为在方法调用时可以确定泛型。

1.5.2 格式

访问修饰符 [static] <泛型标识符> 返回值类型 方法名(参数列表) 抛出的异常{方法体}
public <E> E get(int id, E e) {
	E result = null;
	return result; 
}

1.6 泛型在继承中的体现

  1. 如果类A是类B的父类,而G是具有泛型声明的类或接口,G和G不存在父子类关系。
List<Object>不是List<String> 的父类,两者之间也不可以互相赋值。
  1. 如果类A是类B的父类,G是泛型标识,那么A

    是B

    的父类。

1.7 泛型通配符?

1.7.1 定义

不确定使用什么类型时,可以使用通配符?,表示任意类型,可用于接受任意类型的对象或是充当方法的参数。

List<?>,Map<?,?>

1.7.2 细节

  1. .读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
  2. 不能向List<?> 写入数据时,因为不知道具体的数据类型;唯一能写入的就是null,因为每个类型的对象都可以取到null。
  3. 编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?public static <?> void test(ArrayList<?> list){}
  4. 编译错误:不能用在泛型类的声明上class GenericTypeClass<?>{}
  5. 编译错误:不能用在创建对象上,右边属于创建集合对象ArrayList<?> list2 = new ArrayList<?>();

1.7.3 实例

        List<?> list = new ArrayList<>();
        list = new ArrayList<String>();
        list = new ArrayList<Integer>();
        list = new ArrayList<Person>();

1.7.4 有上限的泛型通配符

可以读取集合中的数据,但是不能向集合中添加除了null以外的对象

list1集合只能接受Person以及Person的子类
        List<? extends Person> list1 = new ArrayList<>();
        list1 = new ArrayList<String>(); // 编译错误
        list1 = new ArrayList<Person>(); // 正确
        list1 = new ArrayList<Student1>();// 正确
        list1 = new ArrayList<Teacher>();// 正确

1.7.4 有下限的泛型通配符

  1. 可以读取集合中的数据。
  2. 可以向集合中添加下限类及其子类对象。
list1集合只能接受Teacher以及Teacher的父类
        List<? super Teacher> list1 = new ArrayList<>();
        list1 = new ArrayList<Student1>();// 编译错误
        list1 = new ArrayList<Person>();// 正确
        list1 = new ArrayList<Teacher>();// 正确
        list1 = new ArrayList<Object>();// 正确
        list1.add(new Teacher("123",21,2320));// 正确

2.File类

2.1 概述

  1. File是文件和文件目录路径的抽象表示形式,File 能新建、删除、重命名文件和目录,也可以获取文件或目录的名称、路径、修改时间、长度等信息,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
  2. 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
  3. File后续常用于充当IO流构造器的参数,指定读取写入的文件。
  4. 同一目录下,文件名和文件夹名称不能重复。

2.2 构造器

  1. public File(String pathname)绝对路径、相对路径都可以
  2. public File(String parent,String child)
  3. public File(File parent,String child)

2.2.1 注意事项

  1. 绝对路径:一个固定的路径,从盘符开始;相对路径:相对于当前位置开始。一个文件绝对路径只有一个,但是相对路径可以有很多个。
  2. 路径中的分隔符与系统有关,unix是‘/’,windows是‘\’。由于跨平台性,所以java提供了静态常量public static final String separator,根据操作系统,动态的提供分隔符。File f1 = new File("D:" + File.separator + "谷歌文件下载");
  3. 创建的是文件还是目录只与File对象调用的方法有关,与构造器传入的参数无关。文件名可以没有后缀,文件夹可以有"."这个符号。

2.3 常用方法

2.3.1 获取

  1. public String getAbsolutePath():获取绝对路径
  2. public String getPath() :获取路径(创建File对象时传入的路径参数)
  3. public String getName() :获取路径最末尾的文件或者目录名称
  4. public String getParent():获取上层文件目录路径。若无,返回null
  5. public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
  6. public long lastModified() :获取最后一次的修改时间,毫秒值
  7. public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
  8. public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组

2.3.2 重命名

public boolean renameTo(File dest):把文件(文件夹)重命名为指定的文件路径

  1. 这里的重命名不仅可以修改文件(文件夹)名,也可以修改文件(文件夹)的路径
  2. 如果修改了文件夹的路径,那么其内部的文件以及文件夹也会一同移动。
  3. 形参dest对象所指向的文件不能存在,否则重命名失败。

2.3.3 判断

  1. public boolean isDirectory():判断是否是文件目录
  2. public boolean isFile() :判断是否是文件
  3. public boolean exists() :判断是否存在
  4. public boolean canRead() :判断是否可读
  5. public boolean canWrite() :判断是否可写
  6. public boolean isHidden() :判断是否隐藏

2.3.4 创建

  1. public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
  2. public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  3. public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
    注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
    createNewFile()是创建文件,mkdir和mkdirs是创建目录
// 创建以dir1为父目录,名为"dir2"的File对象
File dir2 = new File(dir1, "dir2");
if (!dir2.exists()) { // 如果还不存在,就创建为目录
dir2.mkdirs();
}
// 创建以dir2为父目录,名为"test.txt"的File对象
File file = new File(dir2, "test.txt");
if (!file.exists()) { // 如果还不存在,就创建为文件
file.createNewFile();
}

2.3.5 删除

public boolean delete():删除文件或者目录
注意事项:Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

3.IO流

3.1 概述

3.1.1 细节

  1. 处理文本文件(.txt,.java,.c)一般使用字符流;
  2. 处理非文本文件(.jpg,.mp3,.mp4,.doc,.ppt),一般使用字节流处理;
  3. 字符流只能处理文本文件,字节流既可以处理文本文件,也可以处理非文本文件。
  4. 如果想要将文本文件显示到控制台,一般使用字符流,因为字节流可能会乱码;但是如果进行复制,字节流处理文本文件不会乱码。
  5. 对于字符流和字节流来说,构造器的参数可以是String型的文件名,不一定要传入File对象。
  6. 文件流是直接作用在文件上,把文件对象作为构造器的参数;处理流是作用在已有的流上面(不一定是文件流,也可以是转换流),把已有的流作为构造器的参数。

3.1.2 分类(开发时使用缓冲流)

  1. 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
  2. 按数据流的流向不同分为:输入流,输出流
  3. 按流的角色的不同分为:节点流(文件流),处理流
抽象基类 字节流 字符流(只能操作文本文件)
输入流 InputStream Reader
输出流 OutputStream Writer

字节流使用byte[],字符流使用char[]

* 抽象基类         节点流(或文件流)                               缓冲流(处理流的一种)
 * InputStream     FileInputStream   (read(byte[] buffer))        BufferedInputStream (read(byte[] buffer))
 * OutputStream    FileOutputStream  (write(byte[] buffer,0,len)  BufferedOutputStream (write(byte[] buffer,0,len) / flush()
 * Reader          FileReader (read(char[] cbuf))                 BufferedReader (read(char[] cbuf) / readLine())
 * Writer          FileWriter (write(char[] cbuf,0,len)           BufferedWriter (write(char[] cbuf,0,len) / flush()

3.1.3 图示

在这里插入图片描述

3.1.4 IO 流体系

在这里插入图片描述

3.1.5 使用字节流读取中文为什么会乱码

在utf-8中一个中文字符占用三个字节,字节流以字节为单位读取,在读中文字符的时候,三个字节可能会分多次进行读取,就会导致乱码。

3.1.6 输入过程

  1. 创建File对象,指定读入文件路径。(文件一定要存在,不然会抛异常)
  2. 创建相应的输入流,将File对象作为参数传入构造器中;
  3. 创建byte[]或char[],调用read(byte[] b/char[] c)方法读取文件;
  4. 关闭输入流,使用try-catch-finally;

3.1.7 输出过程

  1. 创建File对象,指定写入文件路径。(文件不一定要存在)
  2. 创建相应的输出流,将File对象作为参数传入构造器中;
  3. 调用write(byte[] b/char[] c,0,len)方法写入文件;(注意一定要指定长度
  4. 关闭输出流,使用try-catch-finally;

3.2 FileReader

3.2.1 细节

  1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
  2. 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源,需要使用try-catch-finally处理。
  3. 读入的文件一定要存在,否则就会报FileNotFoundException。

3.2.2 方法

  1. int read()
    读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1
  2. int read(char[] cbuf)
    将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  3. int read(char[] cbuf,int off,int len)
    将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  4. public void close() throws IOException
    关闭此输入流并释放与该流关联的所有系统资源。

3.2.3 无参构造read()读取数据

int read():读取单个字符,返回值是int型字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1。
缺点:每次只能读取一个字符,效率低。

    public void test5() {
        FileReader fr = null;
        try {
//            1. 创建File对象,获取文件
            File file = new File("123.txt");
//            2. 创建字符输入流对象
            fr = new FileReader(file);
            fr = new FileReader("123.txt"); // 和上面效果相同
            int data;
//            3. 读取数据read(),返回int,可以转为char,到达文件末尾返回-1
            while ((data = fr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//             4. 关闭流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.2.4 带参构造read(char[] cbuf)读取数据

int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

 public void test6(){
        FileReader fr = null;
        try {
//            1. 创建File对象,获取文件
            File file = new File("123.txt");
//            2. 创建字符输入流对象
            fr = new FileReader(file);
            int len;
            char[] c = new char[5];
//            3. 读取数据read(char[] c),返回int,表示读取的字符个数,如果已到达流的末尾,则返回 -1
//            读取的数据存放在c中,读取的字符个数len <= c.length
            while ((len = fr.read(c)) != -1) {
//                遍历方式1:
//                for (int i = 0; i < len; i++) {
//                    System.out.print(c[i]);
//                }
//                遍历方式2:
                String s = new String(c,0,len);
                System.out.print(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//             4. 关闭流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.3 FileWriter

3.3.1 细节

  1. 如果想要写入的文件不存在,不会抛异常,会创建一个文件,但是文件的上级目录一定要存在。
  2. 写入的文件存在,如果流使用的构造器是new FileWriter(file,false)或者new FileWriter(file),则会覆盖之前的文件;如果流使用的构造器是new FileWriter(file,true),则会在原来的基础上追加内容。
  3. 如果同一个FileWriter对象多次调用write,那么只有第一个write决定是覆盖还是追加之前的文件,其余write都是在第一个write基础上追加内容。

3.3.2 方法

  1. void write(int c)
    写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。
  2. void write(char[] cbuf)
    写入字符数组。
  3. void write(char[] cbuf,int off,int len)
    写入字符数组的某一部分。从off开始,写入len个字符
  4. void write(String str)
    写入字符串。
  5. void write(String str,int off,int len)
    写入字符串的某一部分。
  6. void flush()
    刷新该流的缓冲,则立即将它们写入预期目标。
  7. public void close() throws IOException
    关闭此输出流并释放与该流关联的所有系统资源。

3.3.3 实例

    public void test7(){
        File file = new File("hello.text");
        FileWriter fw = null;
        try {
            fw = new FileWriter(file,false);
            fw.write("hello world\n");
            fw.write("hi world\n");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fw!=null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.3.4 字符文件流实现文件复制

try-catch后面的代码会执行。

    public void test8() {
//        文件复制
        File file = new File("hello.text");
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(file);
            file = new File("hello1.text");
            fw = new FileWriter(file);
            char[] c = new char[5];
            int len;
            while ((len = fr.read(c)) != -1) {
                fw.write(c, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.4 FileInputStream

3.4.1 细节

  1. 跟FileReader类似,FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,为了防止乱码,一般需要使用FileReader。

3.4.2 方法

  1. int read()
    从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
  2. int read(byte[] b)
    从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。
  3. int read(byte[] b, int off,int len)
    将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。
  4. public void close() throws IOException
    关闭此输入流并释放与该流关联的所有系统资源。

3.5 FileOutputStream

3.5.1 细节

  1. 跟FileWriter类似。

3.5.2 方法

  1. void write(int b)
    将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
  2. void write(byte[] b)
    将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。
  3. void write(byte[] b,int off,int len)
    将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
  4. public void flush()throws IOException
    刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立
    即写入它们预期的目标。
  5. public void close() throws IOException
    关闭此输出流并释放与该流关联的所有系统资源。

3.5.3 字节文件流实习文件复制

对于复制来说,文本文件、非文本文件都可以。

 public void copyFile(String srcPath, String destPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File src = new File(srcPath);
            File dest = new File(destPath);
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            byte[] b = new byte[1024]; // 1kB
            int len;
            while ((len = fis.read(b)) != -1) {
                fos.write(b, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.6 缓冲流

缓冲流是处理流的一种,作用在相应的文件流之上,主要目的是提升文件的读写速度。

3.6.1 细节

  1. 缓冲流的构造器参数是文件流。
  2. 缓存流关闭的同时会自动关闭内部的文件流。
  3. 缓冲流提高了文件读写的速度,其内部提供了一个缓冲区。
  4. 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区数据被取完后,才重新从文件中读取下一个8192个字节数组。(见下图)
  5. 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流。
    在这里插入图片描述

3.6.2 分类

  1. BufferedInputStream(InputStream in) 字节输入缓冲流
  2. BufferedOutputStream(OutputStream out) 字节输出缓冲流
  3. BufferedReader(Reader in) 字符输入缓冲流
  4. BufferedWriter(Writer out) 字符输出缓冲流

3.6.3 字节缓冲流实现文件复制

字节缓冲流无新增方法。

public void test10() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
//            1. 创建文件对象
            File f = new File("img.png");
            File f1 = new File("img1.png");
//            2. 创建文件流
            FileInputStream fis = new FileInputStream(f);
            FileOutputStream fos = new FileOutputStream(f1);
//            3. 创建缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            byte[] buffer = new byte[1024];
            int len;
//            4. 读写文件
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            5. 关闭文件
//            关闭外层流的同时,内层流也会自动关闭
            if(bis!=null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bos!=null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.6.4 字符缓冲流实现文本文件复制

字符缓冲流新增方法
readLine():读取一行数据但不包括换行符,如果到达文件末尾则返回null;输入时使用
newLine():换行;输出时使用

public void test11() {
        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
            br = new BufferedReader(new FileReader(new File("hello.text")));
            br = new BufferedReader(new FileReader("hello.text")); // 可以不创建File对象,直接传入文件名
            bw = new BufferedWriter(new FileWriter(new File("hello2.text")));

//            读写操作:
//              方式1:使用char[]数组
            char[] c = new char[1024];
            int len;
            while ((len = br.read(c)) != -1) {
                bw.write(c, 0, len);
            }
//            方式2:使用String
            String data;
            while ((data = br.readLine()) != null) {
                bw.write(data);
                bw.newLine();// 换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.7 练习–统计文本中的字符出现次数

public void testWordCount() {
        FileReader fr = null;
        BufferedWriter bw = null;
        try {
            //1.创建Map集合
            Map<Character, Integer> map = new HashMap<Character, Integer>();

            //2.遍历每一个字符,每一个字符出现的次数放到map中
            fr = new FileReader("dbcp.txt");
            int c = 0;
            while ((c = fr.read()) != -1) {
                //int 还原 char
                char ch = (char) c;
                // 判断char是否在map中第一次出现
                if (map.get(ch) == null) {
                    map.put(ch, 1);
                } else {
                    map.put(ch, map.get(ch) + 1);
                }
            }

            //3.把map中数据存在文件count.txt
            //3.1 创建Writer
            bw = new BufferedWriter(new FileWriter("wordcount.txt"));

            //3.2 遍历map,再写入数据
            Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
            for (Map.Entry<Character, Integer> entry : entrySet) {
                switch (entry.getKey()) {
                    case ' ':
                        bw.write("空格=" + entry.getValue());
                        break;
                    case '\t'://\t表示tab 键字符
                        bw.write("tab键=" + entry.getValue());
                        break;
                    case '\r'://
                        bw.write("回车=" + entry.getValue());
                        break;
                    case '\n'://
                        bw.write("换行=" + entry.getValue());
                        break;
                    default:
                        bw.write(entry.getKey() + "=" + entry.getValue());
                        break;
                }
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

3.8 转换流

3.8.1 细节

  1. 主要作用:使用转换流来处理文件乱码问题,实现编码和解码的功能。
  2. 转换流实质上是字符流,提供了在字节流和字符流之间的转换。
  3. 转换流是处理流,是作用在文件流上的。
  4. 编码:字符串 -> 字节数组;解码:字节数组 -> 字符串
  5. 转换流可以直接进行数据的读写,也可以装载到缓冲流中进行读写。
  6. InputStreamReader是Reader的子类, OutputStreamWrite是Writer的子类。

3.8.2 分类

InputStreamReader(解码):将InputStream转换为Reader,将字节输入流以指定的字符集转换成字符输入流
OutputStreamWrite(编码):将Writer转换为OutputStream,将字符输出流以指定的字符集转换成字节输出流

构造器

 public InputStreamReader(InputStream in)
 public InputSreamReader(InputStream in,String charsetName)
如: Reader isr = new InputStreamReader(System.in,”gbk”);
 public OutputStreamWriter(OutputStream out)
 public OutputSreamWriter(OutputStream out,String charsetName)

3.8.3 转换流实现文件复制

public void test14() {
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        try {
            FileInputStream fis = new FileInputStream("hello.text");
            FileOutputStream fos = new FileOutputStream("hello4.text");
//       使用参数2指定的字符集解码:参数2指明了字符集,使用文件hello.text保存时使用的字符集,不会乱码。
            isr = new InputStreamReader(fis, "utf-8"); // 使用utf-8解码
//        参数2指明了字符集,使用参数2指定的字符集进行编码
            osw = new OutputStreamWriter(fos, "gbk"); // 使用gbk编码
            char[] cbuf = new char[1024];
            int len;
//            读写操作跟文件流一样
            while ((len = isr.read(cbuf)) != -1) {
                System.out.println(new String(cbuf, 0, len));
                osw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (osw != null)
                    osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (isr != null)
                    isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.9 字符编码集

3.9.1 概述

Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。

  1. 在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的(即字符集≈编码方式),都是直接将字符和最终字节流绑定死了。但是Unicode只定义了一个庞大的、全球通用的字符集并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案,推荐的Unicode编码是UTF-8和UTF-16。
  2. Unicode编码,是对UTF-8、UCS-2/UTF-16等具体编码方案的统称。

3.9.2 实例

中文在utf-8中使用三个字节存储。1110xxxx 10xxxxxx 10xxxxxx 其中xxx是存放中文字符的Unicode编码值
在这里插入图片描述

3.10 标准输入、输出流

3.10.1 概述

System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类,FilterOutputStream的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStreamin)
public static void setOut(PrintStreamout)

3.10.2 实例

从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续 进行输入操作,直至当输入“e”或者“exit”时,退出程序。

    public void test16(){

        BufferedReader br = null;
        try {
//        System.in:类型是InputStream,标准输入流,默认从键盘输入
            InputStreamReader isr = new InputStreamReader(System.in);
            br = new BufferedReader(isr);
            String buffer;
            while (true) {
                System.out.print("please input:");
                if ("e".equals(buffer = br.readLine()) || "exit".equals(buffer))
                    break;
                System.out.println(buffer.toUpperCase());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br!=null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.11 打印流

3.11.1 概述

  1. 默认情况下System.out.println()会将数据输出在控制台上,如果想要改变数据的流向,可以使用System中的setOut(PrintStreamout)
  2. System.out返回的是PrintStream的实例

3.11.2 实例

    public void test17(){
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("hello.text"),true);
        // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
            ps = new PrintStream(fos, true);
            if (ps != null) {// 把标准输出流(控制台输出)改成文件
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) { // 输出ASCII字符
                System.out.print((char) i);
                if (i % 50 == 0) { // 每50个数据一行
                    System.out.println(); // 换行
                } }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            } }
    }

3.12 数据流

3.13 对象流

3.13.1 概述

  1. 对象流包括ObjectOutputStream和ObjectInputStream,ObjectOutputStream可以将基本数据类型或者对象序列化,ObjectInputStream可以实现反序列化。
  2. 序列化机制:将内存中的java对象转换成与平台无关的字节流,将这些字节流持久存储在硬盘中或者通过网络传输给其它网络节点;其它节点接收到字节流后,可以将其还原成Java对象。

3.13.2 序列化的要求

  1. 类必须实现接口Serializable或Externalizable,表示是可序列化的。
  2. 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量private static final long serialVersionUID;,如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化,导致无法反序列化。
  3. java的序列化机制是在运行时通过判断类的serialVersionUID来验证版本的一致性的。在进行反序列化时,jvm会将传入字节流的serialVersionUID 与对应实体类的serialVersionUID 进行比较,如果相同则可以进行序列化,如果不同,就会抛出InvalidCastException
  4. 每一个成员变量(特别是自定义类属性)必须是可序列化的,如果不想序列化,可以使用transient进行修饰,另外static修饰的成员变量也不可序列化,因为不属于某个对象。

3.13.3 序列化

  1. 创建一个 ObjectOutputStream
  2. 调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
  3. 注意写出一次,操作flush()一次
    public void test18() {
//        序列化:
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("hello1.text"));
            oos.writeObject(new Person("123", 12));
            oos.flush(); //写出一次,操作flush()一次
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos!=null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.13.4 反序列化

  1. 创建一个 ObjectInputStream
  2. 调用 readObject() 方法读取流中的对象
    public void test19()  {
//        反序列化
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("hello1.text"));
            Person p = (Person) ois.readObject();
            System.out.println(p.getName() + "," + p.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.14 随机存取文件流

3.14.1 概述

  1. RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既是输入流也是输出流
  2. RandomAccessFile 类支持 “随机访问” 的方式,程序可以通过记录指针直接跳到文件的任意地方来读、写文件。
  3. RandomAccessFile 的读写操作都会使记录指针移动

3.14.2 记录指针

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:

  1. long getFilePointer():获取文件记录指针的当前位置
  2. void seek(long pos):将文件记录指针定位到 pos 位置

3.14.3 构造器

可以使用File文件做参数,也可以直接使用文件路径做参数

public RandomAccessFile(File file, String mode) 
public RandomAccessFile(String name, String mode)

3.14.4 访问模式

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:

r: 以只读方式打开
rw:打开以便读取和写入,如果出现异常,之前写入的内容会丢失。
rwd:打开以便读取和写入;同步文件内容的更新,如果出现异常,已被写入的文件会保存在硬盘
rws:打开以便读取和写入;同步文件内容和元数据的更新

如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。

3.14.5 在指定位置插入内容

    /**
     * 在文件任意位置插入元素
     * @param index 插入的位置
     * @param str 插入的元素
     */
    public void insert(int index, String str) {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("hello.text", "rw");
            raf.seek(index); // 将文件记录指针定位到 index 位置
            byte[] buffer = new byte[1024];
            int len;
            StringBuffer sb = new StringBuffer();
//            1. 读取index后边的数据,存放到sb中
            while ((len = raf.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));//一定要指定数组元素的范围
            }
//            2. 将元素插入文件index位置
            raf.seek(index); // 将文件记录指针定位到 index 位置,文件读取过程中指针也会移动
            raf.write(str.getBytes()); // 写入的时候指针也会移动,所以接下来的写入不用调整指针
//            3.将sb中的元素插入到新插入的元素之后
            raf.write(sb.toString().getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (raf != null)
                    raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.14.6 与字符、字节流的区别

  1. RandomAccessFile类支持 “随机访问” 的方式,程序可以通过记录指针直接跳到文件的任意地方来读、写文件;而字符字节流不支持随机访问,只能覆盖文件或者在文件末尾追加内容(详见FileWriter)。
  2. 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建(此点和字符字节流相同)。 如果写出到的文件存在,则会对原有文件内容进行从头逐个覆盖
  3. RandomAccessFile既是输入流又是输出流。

3.15 NIO