A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 惜言 初级黑马   /  2018-11-26 16:23  /  923 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

Day07-Day10四日份的笔记

Day07

线程间通信:

        多个线程在处理同一个资源, 但是多个线程的处理动作却不相同(线程的任务不同, 需要协调合作)
为什么要进行线程间通信:
        通常是竞争关系: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的.
        有时也"需要合作": 当我们需要多个线程来共同完成一件任务, 并且我们希望他们有规律的执行,
那么多线程之间需要一些协调通信, 以此来帮我们达到多线程共同操作一份数据
如何实现线程间通信:
        "等待唤醒机制"
等待唤醒机制:
        wait/notify, 就是"线程间的一种协作机制", 用于实现线程间通信
等待唤醒中的方法
java.lang.Object类:
        // 成员方法 (只能通过"锁对象"调用)
        void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
        void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
        void wait(): 让当前线程处于无限等待状态, 同时释放锁
wait和notify/notifyAll的执行原理:
        wait:线程不再活动, 不再参与调度, 进入 wait set 中, 因此不会浪费 CPU 资源, 也不会去竞争锁, 这时的线程状态即是"WAITING". 它还要等着别的线程执行"通知(notify)", 让在锁对象上等待的线程从 wait set 中释放出来, 重新进入到调度队列(ready queue)中
notify/notifyAll:
哪怕只通知了一个等待的线程, 被通知线程也不能立即恢复执行, 因为它当初中断的地方是在同步块内, 而
此刻它已经不持有锁, 所以它需要"再次尝试去获取锁"(很可能面临其它线程的竞争), 成功后才能在当初调用 wait() 之后的地方恢复执行
总结如下:
        如果能获取锁, 线程就从"WAITING"状态变成"RUNNABLE"状态
        否则, 从 wait set 出来, 又进入entry set, 线程就从"WAITING"状态又变成"BLOCKED"状态
调用 wait() 和 notify() 需要注意的细节:
        1. wait() 与 notify() 必须要由"同一个锁对象"调用
                因为对应的锁对象可以通过 notify() 唤醒使用同一个锁对象调用的 wait() 后的线程
        2. wait() 与 notify() 是属于Object类的方法
                因为锁对象可以是任意对象, 而任意对象的所属类都是继承了Object类的
        3. wait() 与 notify() 必须要在"同步代码块"或者是"同步方法"中使用
                因为必须要通过锁对象调用这2个方法
普通创建线程方式的缺点:
        "创建"线程和"销毁"线程都是比较占用内存和CPU的操作.
        对于一些数量多, 执行时间短的任务, 频繁的创建和销毁线程来执行, 会降低程序运行效率.
线程池:
        一个容纳多个线程的容器(集合)
线程池可以解决的问题:
        其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源
线程池的工作原理:
        提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行
java.util.concurrent.Executors类: 线程池工厂类, 用于创建和管理线程池
        // 静态方法:
        static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)
java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务
        // 成员方法:
        Future submit(Runnable task): 提交一个Runnable任务
        void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行完任务后仍在运行以便重复使用
线程池的创建和使用步骤:
        1. 使用Executors的静态方法 newFixedThreadPool(int nThreads) 创建线程池ExecutorService
        2. 创建一个任务类, 实现Runnable接口, 重写run()方法
        3. 调用ExecutorService对象的 submit(Runnable task) 方法, 传递任务给线程池, 执行任务
        4. 调用ExecutorService对象的 shutdown() 方法, 销毁线程池 (不建议执行)
// 创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
// 提交任务
RunnableImpl r = new RunnableImpl();  // 1个任务
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
// 销毁线程池
es.shutdown();
单独创建的线程 和 线程池 的适用场景:
单独创建的线程: 适合任务执行时间长, 但数量少的场景 (包子铺吃货)
    线程池: 适合任务执行时间短, 但数量多的场景 (网络请求, 10000个用户分别查看一个网页页面)
函数式:
        在数学中, 函数就是有输入量, 输出量的一套计算方案, 也就是"传入什么东西, 得到什么结果"
         y = f(x)
面向对象: 强调"用哪个对象的哪个方法"来做事 (注重语法形式: 继承 方法重写)
函数式: 强调"传入的参数 和 要执行的代码"
函数式编程的好处:
        简化代码编写 (使用 λ Lambda表达式, 简化匿名内部类的代码)
Lambda表达式的3个部分:
    1. 一些参数 ()
                接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔
    2. 一个箭头 ->
                将参数传递给方法体
    3. 一段代码 {}
                重写接口抽象方法的方法体
格式:
        // 写成多行
        (数据类型 变量名, 数据类型 变量名) -> {
        一些重写方法的代码
        一些重写方法的代码
        ...
    }
        // 如果代码只有一行, 也可以合并写成一行
        (参数列表) -> {一些重写方法的代码}
Day08
java.io.File类: "文件"和"目录"的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
private String path;
"D:\itheima-teach\sjz-javaee-10\s2-进阶\08\avi\02_File类的概述.avi"
"D:\itheima-teach\sjz-javaee-10\s2-进阶\"
String -> File
Input输入 Output输出
我们可以对File进行的操作:
        创建文件/目录
        删除文件/目录
        获取文件/目录
        判断文件/目录是否存在
        对目录进行遍历
        获取文件的大小
重要英文单词的含义: (起变量名时会用到)
        file: 文件
        directory: 目录
        path: 路径
java.io.File类: 文件和目录的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
        // 静态成员变量
static String pathSeparator: 路径分隔符的字符串形式
static char pathSeparatorChar: 路径分隔符的char形式
            Windows系统是 分号;  
        Linux系统是 冒号:
static String separator: 文件名称分隔符的字符串形式
static char separatorChar: 文件名称分隔符的char形式
            Window系统是 反斜杠\
        Linux系统是 正斜杠/\
绝对路径:
        以盘符开始的路径
        如: "D:\\a\\hi.txt"
相对路径:
        不以盘符开始的简化路径. IDEA项目, 相对于项目的根目录
        如: "a\\1.mp3", "123.txt"
        "d:\\t"
注意事项:
        1. 路径不区分大小写 (在Windows系统中不区分大小写, Linux, Mac区分)
        2. 路径一般写成字符串, 而字符串中一个\是转义, 所以要写两个\\
public class Test {
    public static void main(String[] args) {
        File f1 = new File("z:\\z.txt");  // 不存在的路径也可以创建File对象
        System.out.println(f1);
        File f2 = new File("目录\\", "a.txt");  // 相对路径也可以
        System.out.println(f2);
        File f3 = new File(new File("d:\\"), "c.txt");
        System.out.println(f3);
        // 创建File对象后, 并不会在磁盘上创建文件或目录!
    }
}
long -> Date/Calendar 方法
String -> File 方法
java.io.File类
    // 常用获取方法
    String getAbsolutePath(): 返回此File的绝对路径名字符串
    String getPath(): 获取File对象的封装路径 (创建对象时传入的路径)
    String getName(): 获取File对象的文件名或目录名  d:\a\b\c\aaa.txt
    long length(): 获取File表示的"文件"大小的字节byte数 (不能获取目录的大小)
java.io.File类
    // 常用创建删除方法
        boolean createNewFile(): 当文件不存在时, 创建一个新的空文件
                false: 路径已经存在(无论文件还是目录)   
        抛IO异常: 写的路径不符合逻辑 (Y:\\a.txt\dsfsd)
        boolean delete(): 删除由此File表示的文件或目录.
        删除目录时: 必须是空目录
        boolean mkdir(): 创建File表示的目录  "d:\\a\\b\\c\\我的目录"
                false: 1. 路径已经存在(无论文件还是目录)   2. 写的路径不符合逻辑 (Y:\\a.txt\dsfsd)
        boolean mkdirs(): 创建File表示的多级目录   "d:\\a\\b\\c\\我的目录"
                false: 1. 路径已经存在(无论文件还是目录)   2. 写的路径不符合逻辑 (Y:\\a.txt\ds)
java.io.File类
    // 常用获取目录中内容的方法
        String[] list(): 获取当前File目录下的所有子文件或目录的名字数组
        File[] listFiles(): 获取当前File目录中的所有子文件或目录的File对象数组
注意:
        只能用表示目录的File对象调用
        用文件的File对象, 或者路径不存在, 调用会报错
递归思想:
        遇到一个问题时, 将该问题拆解成可以解决的小问题, 如果解决不了, 继续拆解为更小的问题. 如果小问题解决了, 大问题也就能够解决
Java中实现递归的方式:
        方法内部调用方法自己 (所以必须定义方法)
递归的分类:
        直接递归: 方法自己调用方法
        间接递归: A方法调用B方法, B方法调用C方法, C方法调用A方法
递归时的注意事项:
    1. 递归要有限定条件(出口), 保证递归能够停止(就是在某种情况下方法不再调用自己), 否则会栈内存溢出
    2. 递归次数不能太多, 否则会栈内存溢出
    3. 构造方法不能递归
递归的使用前提:
        调用方法时, 方法的主体不变, 但每次传递的参数值不同, 可以使用递归
递归实现思路
        将要解决的问题, 封装为方法
                求1~n的和: 定义方法
        private static int getSum(int n) { // 解决问题: 求n~1的和
                // 出口(方法自己不再调用自己)
                if (n == 1) {
                                return 1;
                }
                // n~1的和 = n + ((n-1)~1的和)
            return n + getSum(n-1);
        }
在方法内部, 拆解问题, 不能解决的小问题, 继续递归调用方法
java.io.File类:  Filter过滤器
        File[] listFiles(FileFilter filter): 返回文件过滤器过滤后的File对象数组
        File[] listFiles(FilenameFilter filter): 返回文件过滤器过滤后的File对象数组
java.io.FileFilter接口: 用于File对象的过滤器
        boolean accept(File pathName): true则会将参数的File对象加入返回的File[], false则不加入
java.io.FilenameFilter接口: 将File对象拆分为父路径和子路径来判断的过滤器
        boolean accept(File dir, String name): true则会将参数的File对象加入返回的File[], false则不加入
        dir: 被找到的文件所在的目录 (父路径)
        name: 文件的名称          (子路径)
Day09
IO流: 输入(Input)输出(Output)流
流: 数据流
输入: 从硬盘(文件)读取到内存(Java程序)
输出: 从内存(Java程序)写入到硬盘(文件)(入和出都是相对于内存来说的)
IO流的分类:
    字节流 (读写字节: byte, 可以读写所有类型的文件, 包括视频, 图片, 音频, 文本等)
        字节输入流: java.io.InputStream 抽象类
        字节输出流: java.io.OutputStream 抽象类
    字符流 (读写字符: char, String, 只能读写文本文件)
        字符输入流: java.io.Reader 抽象类
        字符输出流: java.io.Writer 抽象类
计算机中, 无论文本, 视频, 音频, 图片... 一切都是以"字节byte"形式存储的
也就是以数字形式存储的, 而数字可以用不同进制表示, 计算机能看懂的是二进制数字
java.io.OutputStream抽象类: 字节输出流 (顶层类)
        // 成员方法
        void close() :释放资源
        void flush() :刷新缓冲区(对于字节流来说没有作用)
        // 写字节的成员方法
        abstract void write(int b): 一次写一个字节 (参数int便于传递byte的整数不用强转)
        void write(byte[] b): 一次写一个字节数组
        void write(byte[] b, int offset, int len): 一次写一个字节数组的一部分
java.io.FileOutputStream类: 文件字节输出流 (向文件写数据)
        // 构造方法
        FileOutputStream(String name): 通过文件路径创建文件字节输出流
        FileOutputStream(File file): 通过File对象创建文件字节输出流
构造方法的作用:
        1. 创建一个FileOutputStream对象
        2. 根据构造方法传递的路径, 在磁盘上创建一个空文件 ("如果文件存在则会清空数据")
        3. 将创建的FileOutputStream对象指向这个磁盘上的文件
写数据的原理:
        Java程序 -> JVM虚拟机 -> OS操作系统 -> OS调用写数据的方法 -> 将数据写入文件
使用字节输出流写数据到文件的步骤:
        1. 创建对象: 创建FileOutputStream对象, 构造方法中传递文件路径
        2. 写入数据: 使用FileOutputStream对象调用 write(int b) 方法, 将字节写入文件
        3. 释放资源: 使用FileOutputStream对象调用 close() 方法, 关闭流对象释放资源
        FileOutputStream fos = new FileOutputStream("模块名\\文件名.txt");
        fos.write(97);
        fos.close();
向文件中写入字节数据时, 十进制的数字会被转换为"二进制"的数字写入文件
文本编辑器打开文本文件时, 会先查询编码表, 将二进制数字转换为对应的字符进行显示
        0-127: 查询ASCII码表  -128~127
        其他: 查询系统默认码表
                Windows简体中文系统的程序打开是按 GBK 码表
                IDEA中使用的是 UTF-8 码表   
ASCII编码表: 1个byte就是一个字符  97 a
GBK编码表:   2个byte数字组成一个汉字. "你": -60, -29
UTF-8编码表: 3个byte数字组成一个汉字. "你": -28, -67, -96
java.lang.String类:
        byte[] getBytes(): 使用平台的默认字符集将此String编码为byte数组
java.io.FileOutputStream类: 文件字节输出流 (向文件写数据)
        void write(byte[] b): 一次写一个字节数组
        void write(byte[] b, int off, int len): 一次写一个字节数组的一部分
Java中, byte的范围是 -128 ~ 127 共256个数值
编码表中字符范围是       0 ~ 255 共256个数值
写多个字节时:
        如果第一个字节是正数中的 0~127, 则记事本会查询 ASCII码表 显示字符
        如果第一个字节是负数中的:-128~-1, 则记事本会查询 GBK码表 显示字符.(将两个连续的byte组合为一个中文)
java.io.FileOutputStream类: 文件字节输出流
        // 带有 续写 功能的构造方法, 不会清空文件
        FileOutputStream(String name, boolean append): 通过文件路径创建流, true可以续写
        FileOutputStream(File file, boolean append): 通过File对象创建流, true可以续写
换行符:
        Windows系统: "\r\n"
        Linux系统: "\n"
        MacOS系统: "\r"
java.io.InputStream抽象类: 字节输入流 (顶层类)
        // 常用成员方法
        void close(): 释放资源
        // 读数据的方法
        int read(): 一次读一个字节
        读到文件末尾返回-1 (返回int也是为了代码编写方便)
        int read(byte[] b): 一次读一个字节数组
        读到的字节存放在参数中的字节数组中, 返回int值是本次读到的字节的个数. 读到文件末尾返回-1
java.io.FileInputStream类: 文件字节输入流
        // 构造方法
        FileInputStream(String name): 使用文件路径创建文件字节输入流
        FileInputStream(File file): 使用File对象创建文件字节输入流
构造方法的作用:
        1. 创建FileInputStream对象
        2. 将FileInputStream对象指向磁盘上的文件
Java程序从文件读取数据的原理:
        Java程序 -> JVM虚拟机 -> OS(操作系统) -> OS调用读取的方法 -> 读取磁盘文件数据
java.io.InputStream抽象类: 字节输入流 (顶层类)
        int read(): 一次读一个字节
        读到文件末尾返回-1 (返回int也是为了代码编写方便)
FileInputStream读数据步骤:
        1. 创建FileInputStream对象
        2. 使用FileInputStream对象, 调用 int read() 方法, 一次读取一个byte
        3. 释放资源
        FileInputStream fis = new FileInputStream("模块名\\文件名");
        // 一次读一个字节:
        int by;   // int变量用来存储每次读到的数据
        while (   (  by = fis.read()  ) != -1    ) {
                System.out.print((char)by);  // 不要换行, 文件中自带换行的byte
        }
        fis.close();
注意: 字符流只能读写"文本文件"
java.io.Reader抽象类: 字符输入流 (顶层)
        // 常用成员方法
        void close() :关闭此流并释放与此流相关联的任何系统资源
        // 读
        int read(): 一次读一个字符char, 返回读到的字符.
            读到文件末尾返回-1  (返回int为了代码编写方便)
        int read(char[] cbuf): 一次读取一个字符数组char[]
                返回读取的字符个数. 读到文件末尾返回-1
java.io.FileReader类: 文件字符输入流
        // 构造方法
        FileReader(File file): 根据File对象创建文件字符输入流
        FileReader(String fileName): 根据File对象创建文件字符输入流
        构造方法的作用:
                1. 创建FileReader对象
                2. 将FileReader对象指向磁盘上的文件
void write(int c): 写一个字符 (int类型为了代码编写方便)
FileWriter使用步骤:
        1.创建FileWriter对象, 构造方法中绑定要写入数据的目的地
        2.使用FileWriter中的方法 write(), 把数据写入到"内存缓冲区"中(字符转换为字节的过程)
        3.使用FileWriter中的方法 flush(), 把内存缓冲区中的数据,"刷新到文件中"
        4.释放资源 close() (会先把内存缓冲区中的数据刷新到文件中)
        FileWriter fw = new FileWriter("09_IOAndProperties\\d.txt");
        fw.write(97);  // int ch 用int值代表char
        //fw.flush();
        fw.close();
区别:
        flush(): 刷新缓冲区 (将数据从内存中写入到磁盘)
        close(): 刷新缓冲区, 并释放资源. 关闭流后不能再用同一个流对象操作
flush() 可以省略, 只用 close()来 刷新并释放资源
写的其他方法:
        void write(char[] cbuf): 写一个字符数组
        abstract void write(char[] b, int off, int len): 写一个字符数组的一部分
        void write(String str): 写一个字符串
        void write(String str, int off, int len): 写一个字符串的一部分
FileWriter中带有续写功能的构造:
        FileWriter(File file, boolean append): 通过File对象创建流. 第二个参数为true可以续写
        FileWriter(String fileName, boolean append): 通过文件路径创建流. 第二个参数为true可以续写
换行:
        windows: "\r\n"  fw.write("\r\n");
字节流 和 字符流 如何选择:
        1. 如果不确定读写的是什么类型的数据, 用字节流最通用
        2. 如果确定读写的就是文本, 用字符流最方便
Properties双列集合:
        键和值都是 String 类型
java.util.Properties类: 属性集, 属于Map的双列集合, 继承自Hashtable
        // 构造方法
        Properties(): 创建一个Properties集合
        // 可以使用Map接口中的方法
        // 特有成员方法
        Object setProperty(String key, String value): 保存/替换键值对
        String getProperty(String key): 通过键获取值. 键不存在返回null
        Set<String> stringPropertyNames(): 返回键的集合
        void store(OutputStream out, String comments): 将集合用字节流写入文件(不能中文),并写入注释
        void store(Writer writer, String comments): 将集合用字符流写入文件(可以中文),并写入注释
        void load(InputStream inStream): 从配置文件中通过字节流加载数据到Properties集合(不能中文)
        void load(Reader reader): 从配置文件中通过字符流加载数据到Properties集合(可以中文)
Properties将数据写入到文件的方法:
        void store(OutputStream out, String comments): 将集合用字节流写入文件(不能中文),并写入注释
        void store(Writer writer, String comments): 将集合用字符流写入文件(可以中文), 并写入注释
使用步骤:
        1.创建Properties集合对象,添加数据
        2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
        3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        4.释放资源
属性集(配置文件), 标准的后缀是 .properties
配置文件格式要求:
        一行一个键值对
        键和值中间用=分隔 (标准格式, 但空格分开也行)
        #表示单行注释
Properties将数据从文件加载到集合中的方法:
        void load(InputStream inStream): 从配置文件中通过字节流加载数据到Properties集合(不能读中文)
        void load(Reader reader): 从配置文件中通过字符流加载数据到Properties集合(可以读中文)
使用步骤:
        1.创建Properties集合对象
        2.使用Properties集合对象中的方法load读取保存键值对的文件
        3.遍历Properties集合

Day10
IO流:
    缓冲流: BufferedInputStream / BufferedOutputStream, BufferedReader / BufferedWriter
        在基本流上增加缓冲区 char[] byte[], 提高读写效率
    转换流: InputStreamReader / OutputStreamWriter
            字节转字符: FileReader 读 char <- byte 硬盘
                字符转字节: FileWriter 写 char -> byte 硬盘
    序列化流: ObjectInputStream / ObjectOutputStream
            序列化:   内存中的对象 写-> 硬盘上的文件中
        反序列化: 内存中的对象 <-读 硬盘上的文件中
    打印流: PrintStream
            可以自动换行, 原样输出  System.out.println();
基本流: FileXxx
包装流:
        包装流只是在基本流的外面增加了一些便捷的功能, 最终进行读写的还是基本流
        但是包装流增强了基本流的功能
        BufferedReader br = new BufferedReader(new FileReader());
缓冲流的原理:
        底层也是使用基本流(FileXxx)来读写
        但缓冲流内部定义了一个缓冲数组, 在读的时候类似于我们一次读一个数组的方式,
减少了磁盘操作次数, 提高了程序效率
字节缓冲流
        |_ BufferedInputStream     # 缓冲字节输入流
        |_ BufferedOutputStream    # 缓冲字节输出流
字符缓冲流
        |_ BufferedReader          # 缓冲字符输入流
        |_ BufferedWriter          # 缓冲字符输出流
java.io.BufferedOutputStream类: 缓冲字节输出流
        // 构造方法
        BufferedOutputStream(OutputStream out): 使用基本流创建一个缓冲字节输出流
        BufferedOutputStream(OutputStream out, int size): 使用基本流创建一个缓冲字节输出流, 设置缓冲区大小
BufferedOutputStream使用步骤:
        1.创建FileOutputStream对象, 构造方法中绑定要输出的目的地
        2.创建BufferedOutputStream对象, 构造方法中传递FileOutputStream对象
        3.使用BufferedOutputStream对象中的方法 write(), 把数据写入到内部缓冲区中
        4.使用BufferedOutputStream对象中的方法 flush(), 把内部缓冲区中的数据,刷新到文件中
        5.释放资源(会先调用flush方法刷新数据, 第4步可以省略)
        FileOutputStream fos = new FileOutputStream("文件路径");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write("你好".getBytes());
        // bos.flush();  // 可以省略
        bos.close();
java.io.BufferedInputStream类: 缓冲字节输入流
        // 构造方法
        BufferedInputStream(InputStream in): 使用基本流创建一个缓冲字节输入流
        BufferedInputStream(InputStream in, int size): 使用基本流创建一个缓冲字节输入流, 设置缓冲区大小
使用步骤:
        1.创建FileInputStream对象, 构造方法中绑定要读取的数据源
        2.创建BufferedInputStream对象, 构造方法中传递FileInputStream对象
        3.使用BufferedInputStream对象中的方法 read(), 读取文件
        4.释放资源 close()
        FileInputStream fis = new FileInputStream("文件路径");
        BufferedInputStream bis = new BufferedInputStream(fis);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
        System.out.println(new String(bytes, 0, len));
    }
        bis.close();
缓冲流 + 一次读写一个字节数组 效率最高
java.io.BufferedWriter类:
        // 构造方法
        BufferedWriter(Writer out): 使用基本流创建一个缓冲字符输出流
        BufferedWriter(Writer out, int size): 使用基本流创建一个缓冲字符输出流, 设置缓冲区大小
        // 特有方法
        void newLine(): 写入一个换行符, 换行符自动根据当前系统确定
java.io.BufferedReader类: 缓冲字符输入流
        // 构造方法
        BufferedReader(Reader in): 使用基本流创建一个缓冲字符输入流
        BufferedReader(Reader in, int size): 使用基本流创建一个缓冲字符输入流, 设置缓冲区大小
        // 特有方法
        String readLine(): 一次读一行字符串, "不包含换行符". 读到文件末尾返回null
编码: 字符 -> 字节  'a' -> 97
解码: 字节 -> 字符  97 -> 'a'
编码表: 字符和二进制数字的对应规则
字符集和编码表: 字符集包含编码表
        ASCII字符集
                ASCII编码表
                ASCII扩展编码表
        ISO-8859-1字符集: Tomcat Web服务器程序
                Latin-1: 拉丁字符. 没有中文. 每个字符由1个byte组成
        GB字符集
                GB2312编码表: 每个字符由2个byte组成
                GBK编码表: 每个字符由2个byte组成
                GB18030编码表: 每个字符由1, 2, 4个byte组成
        Unicode字符集
                UTF-8: ASCII字符占1个byte, 拉丁字符占2个byte, 中文占3个byte, Unicode辅助字符占4个byte
                UTF-16
                UTF-32
        ANSI: 表示使用系统默认编码表
乱码原因: 读写编码不一致
        GBK文件中存储的是"你好"在GBK中对应的byte
        而IDEA中使用FileReader读取文件时, 是将byte按照UTF-8编码表查找字符, 结果找不到, 就显示了问号
java.io.OutputStreamWriter类: 输出转换流. 字符流通往字节流的桥梁
        // 构造方法
        OutputStreamWriter(OutputStream out): 使用默认编码表创建转换流
        OutputStreamWriter(OutputStream out, String charsetName): 使用指定编码表创建转换流
// 使用默认UTF-8
OutputStreamWriter o = new OutputStreamWriter(new FileOutputStream("a.txt"));
o.write("dsfdsfdsaf")
// 使用指定的GBK
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK");
        osw.write("你") -> 查"GBK"码表 -> -1,-2 -> FileOutputStream -> a.txt文件
         写数据: 字符流 --------------------------> 字节流
java.io.InputStreamReader类: 输入转换流. 字节流通往字符流的桥梁
// 构造方法
InputStreamReader(InputStream in): 使用默认编码表创建转换流
InputStreamReader(InputStream in, String charsetName): 使用指定编码表创建转换流
// 使用默认UTF-8
InputStreamReader r = new InputStreamReader(new FileInputStream("a.txt"));
// 使用指定的GBK
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "GBK");
     '你' <- 查"GBK"码表 <- isr.read() <- -1,-2 <- FileInputStream <- a.txt文件
                         读数据: 字符流 <---------- 字节流       
序列化: 内存中的对象转换为字节序列, 以流的方式写入到磁盘的文件中
        对象 -> 字节
反序列化: 磁盘文件中的字节序列, 以流的方式读取到内存中变成对象
        字节 -> 对象
通过序列化流, 我们可以将内存中的数据方便的存储到磁盘上, 下次程序启动后也能从磁盘读取恢复之前的对象状态
OutputStream
        |_ ObjectOutputStream类: 对象字节输出流
InputStream
        |_ ObjectInputStream类: 对象字节输入流
java.io.ObjectOutputStream类: 对象字节输出流
        // 构造方法
        ObjectOutputStream(OutputStream out)
        // 特有成员方法
        void writeObject(Object obj): 将对象写出
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
// 写对象
Student s = new Student("小美女", 18);
oos.writeObject(s);
// 释放资源
oos.close();
注意:
        被读写的对象的类必须实现"java.io.Serializable"接口, 否则会抛出"NotSerializableException"
java.io.ObjectInputStream类: 对象字节输入流
        // 构造方法
        ObjectInputStream(InputStream in)
        // 特有成员方法
        Object readObject(): 读取对象
// 创建对象输入流
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("student.txt"));
// 读对象
Object o = oos.readObject();
Student s = (Student)o;
System.out.println(s);
// 释放资源
oos.close();
static 修饰的成员变量属于类不属于对象, 所以不能序列化
transient 修饰的成员变量, 不能被序列化
transient 应用场景:
        如果对象的某个属性不希望被序列化, 可以使用 transient 修饰, 这样就不会被对象流写到文件中
serialVersionUID序列号的作用:
        序列化操作时, 会根据类生成一个默认的 serialVersionUID 属性, 写入到文件中.
        该版本号是根据类中成员计算出来的一个数值. 当类的成员发生改变时, 序列版本号也会发生变化
        当代码中的类和读取的对象序列版本号不一致时, 就会抛出InvalidClassException
为了避免这种问题, 我们可以手动写死这个序列号, 这样无论成员如何变化, 序列版本号都是一样的
PrintStream特点:
        1. 只有输出流, 没有输入流
        2. PrintStream不会抛出IOException
        3. 有特殊方法 print(), println(), 可以输出任意类型的值, 原样输出
java.io.PrintStream类: 字节打印流
        // 构造方法
        PrintStream(File file): 创建字节打印流, 输出到一个文件
        PrintStream(OutputStream out): 创建字节打印流, 输出到一个字节输出流
        PrintStream(String fileName): 创建字节打印流, 输出到一个文件路径
注意事项:
        如果用 write(97) 方法, 会查编码表   97 -> a
        如果用 print(97), println(97), 则原样输出  97 int -> '9''7' -> byte -> 文件 97
        System.out.println();
        PrintStream out;
java.lang.System类:
        // 静态方法
        static void setOut(PrintStream out): 设置System.out的输出目的地为参数的打印流


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马