JAVA笔记19-25

19.01_异常(异常的概述和分类)

  • A:异常的概述
    • 异常就是Java程序在运行过程中出现的错误。
  • B:异常的分类
    • 通过API查看Throwable
    • Error
      • 服务器宕机,数据库崩溃等,比较严重的,无法控制的。
    • Exception
      • 异常 只要是程序运行没有得到正常的结果,都属于异常。
      • int a = 10; int b = sc.nextInt(); int c = a / b; syso(c);
      • int[] array = new int[10000];//栈空间内存不够40000字节
        C:异常的继承体系
    • Throwable
      • Error
      • Exception
        • RuntimeException

19.02_异常(JVM默认是如何处理异常的)

  • A:JVM默认是如何处理异常的
    • main函数收到这个问题时,有两种处理方式:
    • a:自己将该问题处理,然后继续运行
    • b:自己没有针对的处理方式,只有交给调用main的jvm来处理
    • jvm有一个默认的异常处理机制,就将该异常进行处理.
    • 并将该异常的名称,异常的信息.异常出现的位置打印在了控制台上,同时将程序停止运行
  • B:案例演示
    • JVM默认如何处理异常

19.03_异常(try...catch的方式处理异常1)

  • A:异常处理的两种方式
    • a:try…catch…finally
      • try catch
      • try catch finally
      • try finally
      • 基本格式: try{
      •           可能出现问题的代码段【throw语句】;
        
      •       }catch(异常类型1 引用名){ 
        
      •           问题的解决方案; 
        
      •       }catch(异常类型n 引用名){ 
        
      •           问题的解决方案; 
        
      •       }finally{
        
      •           额外释放资源;
        
      •       }
        
    • b:throws
  • B:try...catch处理异常的基本格式
    • try…catch…finally
  • C:案例演示
    • try...catch的方式处理1个异常

19.04_异常(try...catch的方式处理异常2)

  • A:案例演示
    • try...catch的方式处理多个异常
    • JDK7以后处理多个异常的方式及注意事项

19.05_异常(编译期异常和运行期异常的区别)

  • A:编译期异常和运行期异常的区别
    • Java中的异常被分为两大类:编译时异常和运行时异常。

    • 所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常

    • 编译时异常

      • Java程序必须显示处理[try-catch,throws],否则程序就会发生错误,无法通过编译
      • 流相关的异常,就属于编译时异常
    • 运行时异常

      • 无需显示处理[try-catch,throws],也可以和编译时异常一样处理
      • 程序员所犯的错误,需要回来修改代码【越界、空指针】
  • B:案例演示
    • 编译期异常和运行期异常的区别

19.06_异常(Throwable的几个常见方法,了解)

  • A:Throwable的几个常见方法
    • a:getMessage()
      • 获取异常信息,返回字符串。
    • b:toString()
      • 获取异常类名和异常信息,返回字符串。
    • c:printStackTrace()
      • 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
      • JVM默认处理方式
  • B:案例演示
    • Throwable的几个常见方法的基本使用

19.07_异常(throws的方式处理异常)

  • A:throws的方式处理异常
    • 定义功能方法时,需要把出现的问题暴露出来让调用者去处理。
    • 那么就通过throws在方法上标识。
  • B:注意
    • 运行时异常,抛出后可以不显式处理【不写throws】
    • 编译时异常,抛出后必须显式处理【必须要写throws】
  • C:案例演示
    • 举例分别演示编译时异常和运行时异常的抛出
    • setAge(-17)

19.08_异常(throw的概述以及和throws的区别)

  • A:throw的概述
    • 程序员主动抛出异常
    • 在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。
  • B:案例演示
    • 分别演示编译时异常对象和运行时异常对象的抛出
  • C:throws和throw的区别
    • a:throws
      • 用在方法声明后面,跟的是异常类名
      • 可以跟多个异常类名,用逗号隔开
      • 表示抛出异常,由该方法的调用者来处理
    • b:throw
      • 用在方法体内,跟的是异常对象名
      • 只能抛出一个异常对象名
      • 表示抛出异常,由方法体内的语句处理

19.09_异常(finally关键字的特点及作用)

  • A:finally的特点
    • 被finally控制的语句体一定会执行
    • 唯一的特殊情况:在执行到finally之前jvm退出了(比如System.exit(0))
  • B:finally的作用
    • 用于释放资源,在IO流操作和数据库操作中会见到
  • C:案例演示
    • finally关键字的特点及作用

19.10_异常(finally关键字的面试题)

  • A:面试题1
    • final,finally和finalize的区别
    • final可以修饰类、方法、变量
    • finally是try语句中的一个语句体,用来释放资源,不能单独使用
    • finalize是Object中一个方法,当垃圾回收器回收类对象资源时,对象的垃圾回收器调用该方法。
  • B:面试题2
    • 如果catch里面有return语句,请问finally的代码还会执行吗? 如果会,请问是在return前还是return后?
    • return先执行建立返回路径,然后finally执行,最后return完全返回。

19.11_异常(自定义异常概述和基本使用)

  • A:为什么需要自定义异常
    • 让用户根据异常名称快速定位异常
    • 举例:人的年龄
  • B:自定义异常概述
    • 继承自Exception
    • 继承自RuntimeException
  • C:案例演示
    • 自定义异常的基本使用

19.12_异常(异常的注意事项及如何使用异常处理)

  • A:异常注意事项
    • a:子类【重写】父类方法时,子类的方法必须抛出【相同的异常】或父类异常的【子类】。(父亲坏了,儿子不能比父亲更坏)
    • b:如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常
    • c:如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws
  • B:如何使用异常处理
    • 原则:如果该功能内部可以将问题处理,用try,如果处理不了,交由调用者处理,这是用throws

    • 区别:

      • 后续程序需要继续运行就try
      • 后续程序不需要继续运行就throws
    • 如果JDK没有提供对应的异常,需要自定义异常。

19.13_异常(练习)

  • 键盘录入一个int类型的整数,对其求二进制表现形式

    • 如果录入的整数过大,给予提示,录入的整数过大请重新录入一个整数BigInteger
    • 如果录入的是小数,给予提示,录入的是小数,请重新录入一个整数
    • 如果录入的是其他字符,给予提示,录入的是非法字符,请重新录入一个整数
      alt + shift + z try-catch快捷键
      try-catch的嵌套使用

    Scanner sc = new Scanner(System.in);
    System.out.println("请输入一个整数: ");

    while(true) {
    String line = sc.nextLine();
    try {
    int num = Integer.parseInt(line);
    System.out.println(Integer.toBinaryString(num));
    break;
    }catch(Exception e) {
    try {
    new BigInteger(line);//11111122.33333
    System.out.println(录入有误,您录入的是一个过大的整数!);
    }catch(Exception e2){
    try{
    new BigDecimal(line);//11a1222.333
    System.out.println("录入错误,您录入的是一个小数,请重新输入一个整数: ");
    }catch (Exception e1) {
    System.out.println("录入错误,您录入的是非法字符,请重新输入一个整数: ");
    }
    }
    }
    };

19.14_File类(File类的概述和构造方法)

  • A:File类的概述
    • File更应该叫做一个路径
      • 文件路径或者文件夹路径
      • 路径分为绝对路径和相对路径
      • 绝对路径是一个固定的路径,从盘符开始
      • 相对路径相对于某个位置,在eclipse下是指当前项目下,在dos下
    • 查看API指的是当前路径
    • 文件和目录路径名的抽象表示形式
  • B:构造方法
    • File(String pathname):根据一个路径得到File对象,注意分隔符F:\java
    • File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
    • File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
  • C:案例演示
    • File类的构造方法

19.15_File类(File类的创建功能)

  • A:创建功能
    • public boolean createNewFile():创建文件 如果存在这样的文件,就不创建了
    • public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
    • public boolean mkdirs():创建多级文件夹,如果父文件夹不存在,会帮你创建出来
  • B:案例演示
    • File类的创建功能

    • 注意事项:

      • 如果你创建文件或者文件夹忘了写盘符路径,那么,默认在项目路径下。
      • 创建文件时可以不指定后缀,创建文件夹可以指定后缀

19.16_File类(File类的重命名和删除功能)

  • A:重命名和删除功能
    • public boolean renameTo(File dest):把文件重命名为指定的文件路径或剪切
    • public boolean delete():删除文件或者文件夹
  • B:重命名注意事项
    • 如果路径名相同,就是改名。
    • 如果路径名不同,就是改名并剪切。
  • C:删除注意事项:
    • Java中的删除不走回收站。
    • 要删除一个文件夹,请注意该文件夹内不能包含文件或者文件夹

19.17_File类(File类的判断功能)

  • A:判断功能
    • public boolean isDirectory():判断是否是目录
    • public boolean isFile():判断是否是文件
    • public boolean exists():判断是否存在
    • public boolean canRead():判断是否可读,默认都是可读,setReadable无效
    • public boolean canWrite():判断是否可写,可以设置
    • public boolean isHidden():判断是否隐藏,windows中先设置隐藏
  • B:案例演示
    • File类的判断功能

19.18_File类(File类的获取功能)

  • A:获取功能
    • public String getAbsolutePath():获取绝对路径
    • public String getPath():获取路径,构造方法中传入的
    • public String getName():获取名称
    • public long length():获取长度。字节数
    • public long lastModified():获取最后一次的修改时间,毫秒值
    • public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
    • public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
  • B:案例演示
    • File类的获取功能

19.19_File类(输出指定目录下指定后缀的文件名)

  • A:案例演示
    • 需求:判断 E 目录下是否有后缀名为.txt的文件,如果有,就输出该文件名称

19.20_File类(文件名称过滤器的概述及使用)

  • A:文件名称过滤器的概述

    • public String[] list(FilenameFilter filter)
    • public File[] listFiles(FileFilter filter)
  • B:文件名称过滤器的使用

    • 需求:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
  • C:源码分析

    • 带文件名称过滤器的list()方法的源码

    file.list(new FilenameFilter() {
    @Override
    public boolean accept(File dir,String name) {
    File f = new File(dir,name);
    return f.isFile() && f.getName().endsWith(".jpg");
    }
    });

19.22_day19总结

把今天的知识点总结一遍。

20.01_IO流(IO流概述及其分类)

  • 1.概念
    • IO流用来处理设备之间的数据传输
    • Java对数据的操作是通过流的方式
    • Java用于操作流的类都在IO包中
    • 流按流向分为两种:输入流,输出流。
    • 流按操作类型【单位】分为两种:
      • 字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
      • 字符流 : 字符流只能操作纯字符数据,比较方便。
  • 2.IO流常用父类
    • 字节流的抽象父类:
      • InputStream
      • OutputStream
    • 字符流的抽象父类:
      • Reader
      • Writer
  • 3.IO程序书写
    • 使用前,导入IO包中的类
    • 使用时,进行IO异常处理
    • 使用后,释放资源

20.02_IO流(FileInputStream)

  • read()一次读取一个字节
  •   FileInputStream fis = new FileInputStream("aaa.txt");   //创建一个文件输入流对象,并关联aaa.txt
      int b;                                                  //定义变量,记录每次读到的字节
      while((b = fis.read()) != -1) {                         //将每次读到的字节赋值给b并判断是否是-1
          System.out.println(b);                              //打印每一个字节
      }
      
      fis.close();                                            //关闭流释放资源
    

20.03_IO流(read()方法返回值为什么是int)

  • read()方法读取的是一个字节,为什么返回是int,而不是byte
  •   因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到11111111
      那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上
      24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型
    

20.04_IO流(FileOutputStream)

  • write()一次写出一个字节
  •   FileOutputStream fos = new FileOutputStream("bbb.txt"); //如果没有bbb.txt,会创建出一个
      //fos.write(97);                        //虽然写出的是一个int数,但是在写出的时候会将前面的24个0去掉,所以写出的一个byte
      fos.write(98);
      fos.write(99);
      fos.close();
    

20.05_IO流(FileOutputStream追加)

  • A:案例演示
    • FileOutputStream的构造方法写出数据如何实现数据的追加写入
  •   FileOutputStream fos = new FileOutputStream("bbb.txt",true);    //如果没有bbb.txt,会创建出一个
      //fos.write(97);                        //虽然写出的是一个int数,但是在写出的时候会将前面的24个0去掉,所以写出的一个byte
      fos.write(98);
      fos.write(99);
      fos.close();
    

20.06_IO流(拷贝图片)

  • FileInputStream读取
  • FileOutputStream写出

20.07_IO流(拷贝音频文件画原理图)

  • A:案例演示

    • 字节流一次读写一个字节复制音频mp3文件
  • 弊端:效率太低

      FileInputStream fis = new FileInputStream("致青春.mp3");   //创建输入流对象,关联致青春.mp3
      FileOutputStream fos = new FileOutputStream("copy.mp3");//创建输出流对象,关联copy.mp3
      
      int b;
      while((b = fis.read()) != -1) {
          fos.write(b);
      }
      
      fos.close();
      fis.close();
    

20.08_IO流(字节数组拷贝之available()方法)

  • A:案例演示

    • int read(byte[] b):一次读取一个字节数组
    • write(byte[] b):一次写出一个字节数组
    • available()获取读的文件所有的字节个数
  • 弊端:有可能会内存溢出

      FileInputStream fis = new FileInputStream("致青春.mp3");
      FileOutputStream fos = new FileOutputStream("copy.mp3");
      byte[] arr = new byte[fis.available()];                 //根据文件大小做一个字节数组
      fis.read(arr);                                          //将文件上的所有字节读取到数组中
      fos.write(arr);                                         //将数组中的所有字节一次写到了文件上
      fis.close();
      fos.close();
    

20.09_IO流(定义小数组)

  • write(byte[] b)
  • write(byte[] b, int off, int len)写出有效的字节个数

20.10_IO流(定义小数组的标准格式)

  • A:案例演示
    • 字节流一次读写一个字节数组复制图片和视频
      FileInputStream fis = new FileInputStream("致青春.mp3");
      FileOutputStream fos = new FileOutputStream("copy.mp3");
      int len;
      byte[] arr = new byte[1024 * 8]; //自定义字节数组

      while((len = fis.read(arr)) != -1) {
      //fos.write(arr);
      fos.write(arr, 0, len); //写出字节数组写出有效个字节个数
      }

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

20.11_IO流(BufferedInputStream和BufferOutputStream拷贝)

  • A:缓冲思想

    • 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,
    • 这是加入了数组这样的缓冲区效果,java本身在设计的时候,
    • 也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流
  • B.BufferedInputStream

    • BufferedInputStream内置了一个缓冲区(数组)
    • 从BufferedInputStream中读取一个字节时
    • BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 返回给程序一个
    • 程序再次读取时, 就不用找文件了, 直接从缓冲区中获取
    • 直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个
  • C.BufferedOutputStream

    • BufferedOutputStream也内置了一个缓冲区(数组)
    • 程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中
    • 直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
  • D.拷贝的代码

      FileInputStream fis = new FileInputStream("致青春.mp3");           //创建文件输入流对象,关联致青春.mp3
      BufferedInputStream bis = new BufferedInputStream(fis);         //创建缓冲区对fis装饰
      FileOutputStream fos = new FileOutputStream("copy.mp3");        //创建输出流对象,关联copy.mp3
      BufferedOutputStr·eam bos = new BufferedOutputStream(fos);      //创建缓冲区对fos装饰
      
      int b;
      while((b = bis.read()) != -1) {     
          bos.write(b);
      }
      
      bis.close();                        //只关装饰后的对象即可
      bos.close();
    
  • E.小数组的读写和带Buffered的读取哪个更快?

    • 定义小数组如果是8192个字节大小和Buffered比较的话
    • 定义小数组会略胜一筹,因为读和写操作的是同一个数组
    • 而Buffered操作的是两个数组

20.12_IO流(flush和close方法的区别)

  • flush()方法
    • 用来刷新缓冲区的,刷新后可以再次写出
  • close()方法
    • 用来关闭流释放资源的的,如果是带缓冲区的流对象的close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出

20.13_IO流(字节流读写中文)

  • 字节流读取中文的问题
    • 字节流在读中文的时候有可能会读到半个中文,造成乱码
  • 字节流写出中文的问题
    • 字节流直接操作的字节,所以写出中文必须将字符串转换成字节数组
    • 写出回车换行 write("\r\n".getBytes());

20.14_IO流(流的标准处理异常代码1.6版本及其以前)

  • try finally嵌套

      FileInputStream fis = null;
      FileOutputStream fos = null;
      try {
          fis = new FileInputStream("aaa.txt");
          fos = new FileOutputStream("bbb.txt");
          int b;
          while((b = fis.read()) != -1) {
              fos.write(b);
          }
      }catch (Exception e) {
          e.print...();
      }finally {
          try {
              if(fis != null)
                  fis.close();
          }finally {
              if(fos != null)
                  fos.close();
          }
      }
    

20.15_IO流(流的标准处理异常代码1.7版本)

  • try close 自动回收资源

      try(
          FileInputStream fis = new FileInputStream("aaa.txt");
          FileOutputStream fos = new FileOutputStream("bbb.txt");
          //MyClose mc = new MyClose();
      ){
          int b;
          while((b = fis.read()) != -1) {
              fos.write(b);
          }
      }catch(Exception e) {
          ...
      }
    
  • 原理

    • 在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉

20.16_IO流(图片加密)

  • 给图片加密

      BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.jpg"));
      BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.jpg"));
      
      int b;
      while((b = bis.read()) != -1) {
          bos.write(b ^ 123);
      }
      
      bis.close();
      bos.close();
    

20.17_IO流(拷贝文件) E://dir//a.txt

  • 在控制台录入文件的路径,将文件拷贝到当前项目下

      Scanner sc = new Scanner(System.in);
      System.out.println("请输入一个文件路径");
      String line = sc.nextLine();                //将键盘录入的文件路径存储在line中
      File file = new File(line);                 //封装成File对象
      FileInputStream fis = new FileInputStream(file);
      FileOutputStream fos = new FileOutputStream(file.getName());
      
      int len;
      byte[] arr = new byte[8192];                //定义缓冲区
      while((len = fis.read(arr)) != -1) {
          fos.write(arr,0,len);
      }
      
      fis.close();
      fos.close();
    

20.18_IO流(录入数据拷贝到文件)

  • 将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出

      Scanner sc = new Scanner(System.in);
      FileOutputStream fos = new FileOutputStream("text.txt");
      System.out.println("请输入:");
    
      while(true) {
          String line = sc.nextLine();
          if("quit".equals(line))
              break;
          fos.write(line.getBytes());
          fos.write("\r\n".getBytes());
      }
      
      fos.close();
    

21.01_IO流(字符流FileReader)

  • 1.字符流是什么
    • 字符流是可以直接读写字符的IO流
    • 字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.
    • 只有纯文本文件,我们才选择 字符流进行操作,提高我们效率,简化操作
  • 2.FileReader
    • FileReader类的read()方法可以按照字符大小读取
  •   FileReader fr = new FileReader("aaa.txt");              //创建输入流对象,关联aaa.txt
      int ch;
      while((ch = fr.read()) != -1) {                         //将读到的字符赋值给ch
          System.out.println((char)ch);                       //将读到的字符强转后打印
      }
      
      fr.close();                                             //关流 
    

21.02_IO流(字符流FileWriter)

  • FileWriter类的write()方法可以自动把字符转为字节写出

  • 注意: Writer中自带缓冲区,注意刷新或关闭流。

      FileWriter fw = new FileWriter("aaa.txt");
      fw.write("aaa");
      fw.close();
    

21.03_IO流(字符流的拷贝)

FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("b.txt");

int ch;
while((ch = fr.read()) != -1) {
    fw.write(ch);
}

fr.close();
fw.close();

21.04_IO流(什么情况下使用字符流)

  • 字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.
  • 程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流。
  • 读取的时候是按照字符的大小读取的,不会出现半个中文
  • 写出的时候可以直接将字符串写出,不用转换为字节数组

21.05_IO流(字符流是否可以拷贝非纯文本的文件)

  • 不可以拷贝非纯文本的文件
  • 因为在读的时候会将字节转换为字符,在转换过程中,可能找不到对应的字符,就会用?代替,写出的时候会将字符转换成字节写出去
  • 如果是?,直接写出,这样写出之后的文件就乱了,看不了了

21.06_IO流(自定义字符数组的拷贝)

  •   FileReader fr = new FileReader("aaa.txt");          //创建字符输入流,关联aaa.txt
      FileWriter fw = new FileWriter("bbb.txt");          //创建字符输出流,关联bbb.txt
      
      int len;
      char[] arr = new char[1024*8];                      //创建字符数组
      while((len = fr.read(arr)) != -1) {                 //将数据读到字符数组中
          fw.write(arr, 0, len);                          //从字符数组将数据写到文件上
      }
      
      fr.close();                                         //关流释放资源
      fw.close(); 
    

21.07_IO流(带缓冲的字符流)

  • BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率
  • BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率
  •   BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));  //创建字符输入流对象,关联aaa.txt
      BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt"));  //创建字符输出流对象,关联bbb.txt
      
      int ch;             
      while((ch = br.read()) != -1) {     //read一次,会先将缓冲区读满,从缓冲去中一个一个的返给临时变量ch
          bw.write(ch);                   //write一次,是将数据装到字符数组,装满后再一起写出去
      }
      
      br.close();                         //关流
      bw.close();  
    

21.08_IO流(readLine()和newLine()方法)

  • BufferedReader的readLine()方法可以读取一行字符(不包含换行符号)
  • BufferedWriter的newLine()可以输出一个跨平台的换行符号"\r\n"
  •   BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
      BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt"));
      String line;
      while((line = br.readLine()) != null) {
          bw.write(line);
          //bw.write("\r\n");                 //只支持windows系统
          bw.newLine();                       //跨平台的
      }
      
      br.close();
      bw.close(); 
    

21.09_IO流(将文本反转)

  • 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换

21.10_IO流(LineNumberReader) 了解

  • LineNumberReader是BufferedReader的子类, 具有相同的功能, 并且可以统计行号
    • 调用getLineNumber()方法可以获取当前行号
    • 调用setLineNumber()方法可以设置当前行号
  •   LineNumberReader lnr = new LineNumberReader(new FileReader("aaa.txt"));
      String line;
      lnr.setLineNumber(100);                                 //设置行号
      while((line = lnr.readLine()) != null) {
          System.out.println(lnr.getLineNumber() + ":" + line);//获取行号
      }
      
      lnr.close(); 
    

21.11_IO流(装饰设计模式)

  • 设计模式: 前人总结的,为了解决特定场景问题所提出的解决方案,常用的有23种,很巧妙,提高程序员工作效率【遇到类似问题,直接套用对应的设计模式即可】。
  • 可以对类功能进行增强,同时保证低耦合
  • IO流中类 就是装饰模式的具体应用。
  •   //公共接口
      interface Coder {
          public void code();
      }
      
      //具体类
      class Student implements Coder {
      
          @Override
          public void code() {
              System.out.println("javase");
              System.out.println("javaweb");
          }
          
      }
      
      //装饰器类
      class BriupStudent implements Coder {
          private Student s;                      //获取到被包装的类的引用
          public BriupStudent(Student s) {        //通过构造函数创建对象的时候,传入被包装的对象
              this.s = s;
          }
    
          @Override
          public void code() {                    //对其原有功能进行升级
              s.code();
              System.out.println("唱歌");
              System.out.println("跳舞");
              System.out.println("做饭");
              System.out.println("洗衣服");
          }
          
      } 
      
      //编码: 开闭原则【对修改关闭,对扩展开放】
      main() {
          //Coder stu = new Student();
          //stu.code();
    
          Coder stu = new BriupStudent(new Student());
          stu.code();
      }
    

21.12_IO流(使用指定的码表读写字符)

  • FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)
  • FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)
  •   BufferedReader br =                                     //高效的用指定的编码表读
              new BufferedReader(new InputStreamReader(new FileInputStream("UTF-8.txt"), "UTF-8"));
      BufferedWriter bw =                                     //高效的用指定的编码表写
              new BufferedWriter(new OutputStreamWriter(new FileOutputStream("GBK.txt"), "GBK"));
      int ch;
      while((ch = br.read()) != -1) {
          bw.write(ch);
      }
      
      br.close();
      bw.close();
    

21.13_IO流(转换流图解)

  • 画图分析转换流

21.14_IO流(获取文本上字符出现的次数)

  • 获取一个文本上每个字符出现的次数,将结果写在times.txt上
  • TreeMap 字符 次数 Test7Count.java
  • a: 10 缓冲流
  • b: 13
  • 中: 16

21.15_IO流(试用版软件)

  • 当我们下载一个试用版软件,没有购买正版的时候,每执行一次就会提醒我们还有多少次使用机会用学过的IO流知识,模拟试用版软件,试用10次机会,执行一次就提示一次您还有几次机会,如果次数到了提示请购买正版
  • 配置信息 软件安装的时间 配置文件config.txt
  • 一个月以内可以正常使用,一个月以后就无法使用,提示你购买正版.

21.16_File类(递归)

  • 函数自己调用自己,就是递归
  • 注意: 书写递归函数,一定要设置结束条件,不能让程序一直递归下去。
  • 递归的好处,在特定场景中很容易帮助理解;弊端是效率低,还可能造成栈内存溢出
  • 5的阶乘
  • 波非那切数 1 1 2 3 5 8 13 21 34 55
  • f(n){//5
  • if(n == 1 || n == 2)
  •   return 1; 
    
  • return f(n-1) + f(n-2);
  • }

21.17_File类(练习)

  • 需求:从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.java文件名

21.18_File类递归练习(统计该文件夹大小)

  • 需求:1,从键盘接收一个文件夹路径,统计该文件夹大小
  • 求一个目录的长度: 求出来所有子文件 的长度和
  • E:\a的大小: test.java + dir1大小 + dir2大小
  • E:\a\test.java
  • E:\a\dir1\a.txt
  • E:\a\dir2\b.txt c.txt

21.19_IO流(总结)

  • 1.会用BufferedReader读取GBK码表和UTF-8码表的字符
  • 2.会用BufferedWriter写出字符到GBK码表和UTF-8码表的文件中
  • 3.会使用BufferedReader从键盘读取一行

22.01_IO流(序列流)(了解)

  • 1.什么是序列流
    • 序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推.
  • 2.使用方式
    • 整合两个: SequenceInputStream(InputStream, InputStream)
    •   FileInputStream fis1 = new FileInputStream("a.txt");            //创建输入流对象,关联a.txt
        FileInputStream fis2 = new FileInputStream("b.txt");            //创建输入流对象,关联b.txt
        SequenceInputStream sis = new SequenceInputStream(fis1, fis2);  //将两个流整合成一个流
        FileOutputStream fos = new FileOutputStream("c.txt");           //创建输出流对象,关联c.txt
        
        int b;
        while((b = sis.read()) != -1) {                                 //用整合后的读
            fos.write(b);                                               //写到指定文件上
        }
        
        sis.close();
        fos.close(); 
      

22.02_IO流(序列流整合多个)(了解)

  • 整合多个: SequenceInputStream(Enumeration)
  •   FileInputStream fis1 = new FileInputStream("a.txt");    //创建输入流对象,关联a.txt
      FileInputStream fis2 = new FileInputStream("b.txt");    //创建输入流对象,关联b.txt
      FileInputStream fis3 = new FileInputStream("c.txt");    //创建输入流对象,关联c.txt
      Vector<InputStream> v = new Vector<>();                 //创建vector集合对象
      v.add(fis1);                                            //将流对象添加
      v.add(fis2);
      v.add(fis3);
      Enumeration<InputStream> en = v.elements();             //获取枚举引用
      SequenceInputStream sis = new SequenceInputStream(en);  //传递给SequenceInputStream构造
      FileOutputStream fos = new FileOutputStream("d.txt");
      int b;
      while((b = sis.read()) != -1) {
          fos.write(b);
      }
    
      sis.close();
      fos.close();
    

22.03_IO流(内存输出流*****)(掌握)

  • 1.什么是内存输出流
    • 该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据
  • 2.使用方式
    • 创建对象: new ByteArrayOutputStream()
    • 写出数据: write(int), write(byte[])
    • 获取数据: toByteArray() toString()
    •   FileInputStream fis = new FileInputStream("a.txt");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b;
        while((b = fis.read()) != -1) {
            baos.write(b);
        }
        
        //byte[] newArr = baos.toByteArray();               //将内存缓冲区中所有的字节存储在newArr中
        //System.out.println(new String(newArr));
        System.out.println(baos);
        fis.close();
      

22.04_IO流(内存输出流之面试题)(掌握)

  • 定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)
  •       FileInputStream fis = new FileInputStream("a.txt");             //创建字节输入流,关联a.txt
          ByteArrayOutputStream baos = new ByteArrayOutputStream();       //创建内存输出流
          byte[] arr = new byte[5];                                       //创建字节数组,大小为5
          int len;
          while((len = fis.read(arr)) != -1) {                            //将文件上的数据读到字节数组中
              baos.write(arr, 0, len);                                    //将字节数组的数据写到内存缓冲区中
          }
          System.out.println(baos);                                       //将内存缓冲区的内容转换为字符串打印
          fis.close();
    

22.05_IO流(对象操作流ObjectOutputStream)(重点掌握)

  • 1.什么是对象操作流
    • 该流可以将一个对象写出, 或者读取一个对象到程序中. 也就是执行了序列化和反序列化的操作.
  • 2.使用方式
    • 写出: new ObjectOutputStream(OutputStream), writeObject()

        public class Demo3_ObjectOutputStream {
      
            /**
             * @param args
             * @throws IOException 
             * 将对象写出,序列化
             */
            public static void main(String[] args) throws IOException {
                Person p1 = new Person("张三", 23);
                Person p2 = new Person("李四", 24);
        //      FileOutputStream fos = new FileOutputStream("e.txt");
        //      fos.write(p1);
        //      FileWriter fw = new FileWriter("e.txt");
        //      fw.write(p1);
                //无论是字节输出流,还是字符输出流都不能直接写出对象
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("e.txt"));//创建对象输出流
                oos.writeObject(p1);
                oos.writeObject(p2);
                oos.close();
            }
        
        }
      

22.06_IO流(对象操作流ObjectInputStream)(了解)

  • 读取: new ObjectInputStream(InputStream), readObject()
    •   public class Demo3_ObjectInputStream {
      
            /**
             * @param args
             * @throws IOException 
             * @throws ClassNotFoundException 
             * @throws FileNotFoundException 
             * 读取对象,反序列化
             */
            public static void main(String[] args) throws IOException, ClassNotFoundException {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e.txt"));
                Person p1 = (Person) ois.readObject();
                Person p2 = (Person) ois.readObject();
                System.out.println(p1);
                System.out.println(p2);
                ois.close();
            }
        
        }
      

22.07_IO流(对象操作流优化)(了解)

* 将对象存储在集合中写出

Person p1 = new Person("张三", 23);
Person p2 = new Person("李四", 24);
Person p3 = new Person("马哥", 18);
Person p4 = new Person("辉哥", 20);

ArrayList<Person> list = new ArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("f.txt"));
oos.writeObject(list);                                  //写出集合对象

oos.close();
  • 读取到的是一个集合对象

      ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f.txt"));
          ArrayList<Person> list = (ArrayList<Person>)ois.readObject();   //泛型在运行期会被擦除,索引运行期相当于没有泛型
                                                                          //想去掉黄色可以加注解                    @SuppressWarnings("unchecked")
          for (Person person : list) {
              System.out.println(person);
          }
      
      ois.close();
    

22.08_IO流(加上id号)(了解)

  • 注意
    • 要写出的对象必须实现Serializable接口才能被序列化
    • 不用必须加id号

22.09_IO流(打印流的概述和特点)(掌握)

  • 1.什么是打印流
    • 该流可以很方便的将对象的toString()结果输出, 并且自动加上换行, 而且可以使用【自动刷出】的模式

    • System.out就是一个PrintStream, 其默认向控制台输出信息

        PrintStream ps = System.out;
        ps.println(97);                 //其实底层用的是Integer.toString(x),将x转换为数字字符串打印
        ps.println("xxx");
        ps.println(new Person("张三", 23));
        Person p = null;
        ps.println(p);                  //如果是null,就返回null,如果不是null,就调用对象的toString()
      
  • 2.使用方式
    • 打印: print(), println()

    • 自动刷出: PrintWriter(OutputStream out, boolean autoFlush, String encoding) ,只针对println方法,没什么大用处。

    • 打印流只操作【数据目的】

        PrintWriter pw = new PrintWriter(new FileOutputStream("g.txt"), true);
        pw.write(97);
        pw.print("大家好");
        pw.println("你好");               //自动刷出,只针对的是println方法
        pw.close();
      

22.10_IO流(标准输入输出流概述和输出语句)

  • 1.什么是标准输入输出流(掌握)
    • System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据
    • System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据
  • 2.修改标准输入输出流(了解)
    • 修改输入流: System.setIn(InputStream)
    • 修改输出流: System.setOut(PrintStream)
    •   System.setIn(new FileInputStream("a.txt"));             //修改标准输入流
        System.setOut(new PrintStream("b.txt"));                //修改标准输出流
        
        InputStream in = System.in;                             //获取标准输入流
        PrintStream ps = System.out;                            //获取标准输出流
        int b;
        while((b = in.read()) != -1) {                          //从a.txt上读取数据
            ps.write(b);                                        //将数据写到b.txt上
        }
        
        in.close();
        ps.close();
      

22.11_IO流(修改标准输入输出流拷贝图片)(了解)

    System.setIn(new FileInputStream("IO图片.png"));      //改变标准输入流
    System.setOut(new PrintStream("copy.png"));         //改变标准输出流
    
    InputStream is = System.in;                         //获取标准输入流
    PrintStream ps = System.out;                        //获取标准输出流
    
    int len;
    byte[] arr = new byte[1024 * 8];
    
    while((len = is.read(arr)) != -1) {
        ps.write(arr, 0, len);
    }
    
    is.close();
    ps.close();

22.11_IO流(两种方式实现键盘录入)(了解)

  • A:BufferedReader的readLine方法
    • BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  • B:Scanner方式

22.12_IO流(随机访问流概述和读写数据)(了解)

  • A:随机访问流概述

    • RandomAccessFile概述
    • RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。
    • 支持对随机访问文件的读取和写入。
  • B:read(),write(),seek()

    • seek从0开始

22.13_IO流(数据输入输出流)(了解)

  • 1.什么是数据输入输出流
    • DataInputStream, DataOutputStream可以按照基本数据类型大小读写数据
    • 例如按Long大小写出一个数字, 写出时该数据占8字节. 读取的时候也可以按照Long类型读取, 一次读取8个字节.
  • 2.使用方式
    • DataOutputStream(OutputStream), writeInt(), writeLong()

        DataOutputStream dos = new DataOutputStream(new FileOutputStream("b.txt"));
        dos.writeInt(997);
        dos.writeInt(998);
        dos.writeInt(999);
        
        dos.close();
      
    • DataInputStream(InputStream), readInt(), readLong()

        DataInputStream dis = new DataInputStream(new FileInputStream("b.txt"));
        int x = dis.readInt();
        int y = dis.readInt();
        int z = dis.readInt();
        System.out.println(x);
        System.out.println(y);
        System.out.println(z);
        dis.close();
      

22.14_IO流(Properties的概述和作为Map集合的使用)(掌握)

  • A:Properties的概述
    • Properties 类表示了一个持久的属性集。
    • Properties 可保存在流中或从流中加载。
    • 属性列表中每个键及其对应值都是一个字符串。
  • B:案例演示
    • Properties作为Map集合的使用

22.15_IO流(Properties的特殊功能使用)(掌握)

  • A:Properties的特殊功能
    • public Object setProperty(String key,String value)
    • public String getProperty(String key)
    • public Set<String> stringPropertyNames()
  • B:案例演示
    • Properties的特殊功能

22.16_IO流(Properties的load()和store()功能)(了解)

  • A:Properties的load()和store()功能
  • B:案例演示
    • Properties的load()和store(OutputStream out,String comments)功能;
    • String comments是对列表参数的描述,null与非null值都可以。

24.01_多线程(多线程的引入)(了解)

  • 1.什么是线程
    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • QQ聊天 进程 你在给同学发信息的同时,能否接收其他同学的消息?提高效率
    • 进程,是一个应用程序的执行流程,进程是资源分配的最小单位
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
  • 2.多线程的应用场景
    • 红蜘蛛同时共享屏幕给多个电脑
    • 迅雷开启多条线程一起下载
    • QQ同时和多个人一起视频
    • 服务器同时处理多个客户端请求

24.02_多线程(多线程并行和并发的区别)(了解)

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
  • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

24.03_多线程(Java程序运行原理和JVM的启动是多线程的吗)(了解)

  • A:Java程序运行原理

    • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
  • B:JVM的启动是多线程的吗

    • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

24.04_多线程(多线程程序实现的方式1)(掌握)

  • 1.继承Thread
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
    •   public class Demo2_Thread {
      
            /**
             * @param args
             */
            public static void main(String[] args) {
                MyThread mt = new MyThread();                           //4,创建自定义类的对象
                mt.start();                                             //5,开启线程
                
                for(int i = 0; i < 3000; i++) {
                    System.out.println("bb");
                }
            }
        
        }
        class MyThread extends Thread {                                 //1,定义类继承Thread
            public void run() {                                         //2,重写run方法
                for(int i = 0; i < 3000; i++) {                         //3,将要执行的代码,写在run方法中
                    System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                }
            }
        }
      

24.05_多线程(多线程程序实现的方式2)(掌握)

  • 2.实现Runnable
    • 定义类实现Runnable接口

    • 实现run方法

    • 把新线程要做的事写在run方法中

    • 创建自定义的Runnable的子类对象

    • 创建Thread对象, 传入Runnable

    • 调用start()开启新线程, 内部会自动调用Runnable的run()方法

        public class Demo3_Runnable {
            /**
             * @param args
             */
            public static void main(String[] args) {
                MyRunnable mr = new MyRunnable();                       //4,创建自定义类对象
                //Runnable target = new MyRunnable();
                Thread t = new Thread(mr);                              //5,将其当作参数传递给Thread的构造函数
                t.start();                                              //6,开启线程
                
                for(int i = 0; i < 3000; i++) {
                    System.out.println("bb");
                }
            }
        }
        
        class MyRunnable implements Runnable {                          //1,自定义类实现Runnable接口
            @Override
            public void run() {                                         //2,重写run方法
                for(int i = 0; i < 3000; i++) {                         //3,将要执行的代码,写在run方法中
                    System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                }
            }
            
        }
      

24.06_多线程(实现Runnable的原理)(了解)

  • 查看源码
    • 1,看Thread类的构造函数,传递了Runnable接口的引用
    • 2,通过init()方法找到传递的target给成员变量的target赋值
    • 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

24.07_多线程(两种方式的区别)(掌握)

  • 查看源码的区别:

    • a.继承Thread: 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
    • b.实现Runnable: 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
    • 好处是:可以直接使用Thread类中的方法,代码简单继承Thread
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现Runnable接口

    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

24.08_多线程(匿名内部类实现线程的两种方式)(掌握)

  • 继承Thread类

      new Thread() {                                                  //1,new 类(){}继承这个类
          public void run() {                                         //2,重写run方法
              for(int i = 0; i < 3000; i++) {                         //3,将要执行的代码,写在run方法中
                  System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
              }
          }
      }.start();
    
  • 实现Runnable接口

      new Thread(new Runnable(){                                      //1,new 接口(){}实现这个接口
          public void run() {                                         //2,重写run方法
              for(int i = 0; i < 3000; i++) {                         //3,将要执行的代码,写在run方法中
                  System.out.println("bb");
              }
          }
      }).start(); 
    

24.09_多线程(获取名字和设置名字)(掌握)

  • 1.获取名字
    • 通过getName()方法获取线程对象的名字
    • 线程对象 有默认的名称,命名方式: Thread-n 或 main
  • 2.设置名字
    • 通过构造函数可以传入String类型的名字
    •   new Thread("xxx") {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                }
            }
        }.start();
        
        new Thread("yyy") {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(this.getName() + "....bb");
                }
            }
        }.start(); 
      
    • 通过setName(String)方法可以设置线程对象的名字
    •   Thread t1 = new Thread() {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                }
            }
        };
        
        Thread t2 = new Thread() {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(this.getName() + "....bb");
                }
            }
        };
        t1.setName("芙蓉姐姐");
        t2.setName("凤姐");
        
        t1.start();
        t2.start();
      

24.10_多线程(获取当前线程的对象)(掌握)

  • Thread.currentThread(), 主线程也可以获取
    •   new Thread(new Runnable() {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa");
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(Thread.currentThread().getName() + "...bb");
                }
            }
        }).start();
        Thread.currentThread().setName("我是主线程");                    //获取主函数线程的引用,并改名字
        System.out.println(Thread.currentThread().getName());       //获取主函数线程的引用,并获取名字
      

24.11_多线程(休眠线程)(掌握)

  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000

          new Thread() {
              public void run() {
                  for(int i = 0; i < 10; i++) {
                      System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }.start();
          
          new Thread() {
              public void run() {
                  for(int i = 0; i < 10; i++) {
                      System.out.println(getName() + "...bb");
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }.start();
    

24.12_多线程(守护线程)(掌握)

  • setDaemon(boolean), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出【守护线程 也可以守护 main线程】
    •   Thread t1 = new Thread() {
            public void run() {
                for(int i = 0; i < 50; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        
        Thread t2 = new Thread() {
            public void run() {
                for(int i = 0; i < 5; i++) {
                    System.out.println(getName() + "...bb");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        
        t1.setDaemon(true);                     //将t1设置为守护线程
        
        t1.start();
        t2.start();
      

24.13_多线程(加入线程)(掌握)

  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续
    •   final Thread t1 = new Thread() {
            public void run() {
                for(int i = 0; i < 50; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        
        Thread t2 = new Thread() {
            public void run() {
                for(int i = 0; i < 50; i++) {
                    if(i == 2) {
                        try {
                            t1.join();  //插队,加入
                            //t1.join(30);  //加入,有固定的时间,过了固定时间,继续交替执行
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(getName() + "...bb");
                }
            }
        };
        
        t1.start();
        t2.start();
      

24.14_多线程(礼让线程)(了解)

  • yield()让出cpu
  • 建议性的

24.15_多线程(设置线程的优先级)(了解)

  • setPriority(int)设置线程的优先级,参数越大优先级越高。
  • 优先级取值 1 到 10 ,数值越大,优先级越高
  • 建议性的

24.16_多线程(同步代码块)(掌握)

  • 1.什么情况下需要同步
    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  • 2.同步代码块
    • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

        class Printer {
            Demo d = new Demo();
            public static void print1() {
                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                synchronized(d){                
                    System.out.print("我");
                    System.out.print("要");
                    System.out.print("赚");
                    System.out.print("大");
                    System.out.print("钱");
                    System.out.print("\r\n");
                }
            }
      
            public static void print2() {   
                synchronized(d){    
                    System.out.print("努");
                    System.out.print("力");
                    System.out.print("学");
                    System.out.print("习");
                    System.out.print("\r\n");
                }
            }
        }
      

24.17_多线程(同步方法)(掌握)

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

      class Printer {
          public static void print1() {
              //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
              synchronized(Printer.class){        
                  System.out.print("I");
                  System.out.print("T");
                  System.out.print("程");
                  System.out.print("序");
                  System.out.print("员");
                  System.out.print("\r\n");
              }
          }
          /*
           * 非静态同步函数的锁是:this
           * 静态的同步函数的锁是:字节码对象
           */
          public static synchronized void print2() {  
              System.out.print("杰");
              System.out.print("普");
              System.out.print("软");
              System.out.print("件");
              System.out.print("\r\n");
          }
      }
    

24.18_多线程(线程安全问题)(掌握)

  • 多线程并发操作同一数据时, 就有可能出现线程安全问题

  • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

          public class Demo2_Synchronized {
    
              /**
               * @param args
               * 需求:铁路售票,一共100张,通过四个窗口卖完.
               */
              public static void main(String[] args) {
                  TicketsSeller t1 = new TicketsSeller();
                  TicketsSeller t2 = new TicketsSeller();
                  TicketsSeller t3 = new TicketsSeller();
                  TicketsSeller t4 = new TicketsSeller();
                  
                  t1.setName("窗口1");
                  t2.setName("窗口2");
                  t3.setName("窗口3");
                  t4.setName("窗口4");
                  t1.start();
                  t2.start();
                  t3.start();
                  t4.start();
              }
          
          }
          
          class TicketsSeller extends Thread {
              private static int tickets = 100;
              static Object obj = new Object();
              public TicketsSeller() {
                  super();
              }
              public TicketsSeller(String name) {
                  super(name);
              }
              public void run() {
                  while(true) {
                      synchronized(obj) {
                          if(tickets <= 0) 
                              break;
                          try {
                              Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(getName() + "...这是第" + tickets-- + "号票");
                      }
                  }
              }
          }
    

24.19_多线程(火车站卖票的例子用实现Runnable接口)(掌握)

  • 作业

24.20_多线程(死锁)(了解)

  • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    • 尽量不要嵌套使用

        private static String s1 = "筷子左";
        private static String s2 = "筷子右";
        public static void main(String[] args) {
            new Thread() {
                public void run() {
                    while(true) {
                        synchronized(s1) {
                            System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                            synchronized(s2) {
                                System.out.println(getName() + "...拿到" + s2 + "开吃");
                            }
                        }
                    }
                }
            }.start();
            
            new Thread() {
                public void run() {
                    while(true) {
                        synchronized(s2) {
                            System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                            synchronized(s1) {
                                System.out.println(getName() + "...拿到" + s1 + "开吃");
                            }
                        }
                    }
                }
            }.start();
        }
      

24.21_多线程(以前的线程安全的类回顾)(掌握)

  • A:回顾以前说过的线程安全问题
    • 看源码:Vector,StringBuffer,Hashtable Collections.synchroinzedXxx(xxx)
    • Vector是线程安全的,ArrayList是线程不安全的
    • StringBuffer是线程安全的,StringBuilder是线程不安全的
    • Hashtable是线程安全的,HashMap是线程不安全的

25.01_多线程(两个线程间的通信)(掌握)

  • 1.什么时候需要通信
    • 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    • 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
  • 2.怎么通信
    • 如果希望线程等待, 就调用wait()【先释放锁,再阻塞】
    • 如果希望唤醒等待的线程, 就调用notify();【被唤醒,先转入运行,再竞争锁,获取锁以后,才可以往下运行】
    • 这两个方法必须在同步代码中执行, 并且使用【同步锁对象】来调用

25.02_多线程(三个或三个以上间的线程通信)

  • 多个线程通信的问题
    • notify()方法是随机唤醒一个线程
    • notifyAll()方法是唤醒所有线程
    • JDK5之前无法唤醒指定的一个线程
    • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
  • 在同步代码块中,使用哪个对象锁,就使用该对象调用wait方法
  • 为什么wait方法和notify方法定义在Object类中?
    • 我们使用锁对象调用上述方法,而锁对象可以是任意对象,所以定义到Object类中
  • sleep方法和wait方法有什么区别?
    • sleep方法必须传入参数值,即一个时间值,时间到了自动醒来
    • wait方法中参数可有可无,有参表示过了参数时间后进入等待,否则直接等待
    • sleep方法在同步函数或同步代码块中,不释放锁
    • wait方法在同步函数或同步代码块中,释放锁

25.03_多线程(JDK1.5的新特性互斥锁)(掌握)

  • 1.同步
    • 使用ReentrantLock类的lock()和unlock()方法进行同步
    • 类似synchronized
  • 2.通信
    • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
    • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
    • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

25.04_多线程(线程组的概述和使用)(了解)

  • A:线程组概述

    • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    • 默认情况下,所有的线程都属于【主线程组】
      • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
      • public final String getName()//通过线程组对象获取他组的名字
    • 我们也可以给线程设置分组
      • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
      • 2,创建线程对象
      • 3,Thread(ThreadGroup group, Runnable target, String name)
      • 4,设置整组的优先级或者守护线程
    • B:案例演示
      • 线程组的使用,默认是主线程组
  •   MyRunnable mr = new MyRunnable();
      Thread t1 = new Thread(mr, "张三");
      Thread t2 = new Thread(mr, "李四");
      //获取线程组
      // 线程类里面的方法:public final ThreadGroup getThreadGroup()
      ThreadGroup tg1 = t1.getThreadGroup();
      ThreadGroup tg2 = t2.getThreadGroup();
      // 线程组里面的方法:public final String getName()
      String name1 = tg1.getName();
      String name2 = tg2.getName();
      System.out.println(name1);
      System.out.println(name2);
      // 通过结果我们知道了:线程默认情况下属于main线程组
      // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
      System.out.println(Thread.currentThread().getThreadGroup().getName());
    
    • 自己设定线程组
  •   // ThreadGroup(String name)
      ThreadGroup tg = new ThreadGroup("这是一个新的组");
    
      MyRunnable mr = new MyRunnable();
      // Thread(ThreadGroup group, Runnable target, String name)
      Thread t1 = new Thread(tg, mr, "张三");
      Thread t2 = new Thread(tg, mr, "李四");
      
      System.out.println(t1.getThreadGroup().getName());
      System.out.println(t2.getThreadGroup().getName());
      
      //通过组名称设置后台线程,表示该组的线程都是后台线程
      tg.setDaemon(true);
    

25.05_多线程(线程的五种状态)(掌握)

  • 看图说话
  • 新建,就绪,运行,阻塞,死亡

25.06_多线程(线程池的概述和使用)(了解)

  • A:线程池概述
    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • B:内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future<?> submit(Runnable task)
      • <T> Future<T> submit(Callable<T> task)
    • 使用步骤:
      • 创建线程池对象
      • 创建Runnable实例
      • 提交Runnable实例
      • 关闭线程池
    • C:案例演示
      • 提交的是Runnable
  •   // public static ExecutorService newFixedThreadPool(int nThreads)
      ExecutorService pool = Executors.newFixedThreadPool(2);
    
      // 可以执行Runnable对象或者Callable对象代表的线程
      pool.submit(new MyRunnable());
      pool.submit(new MyRunnable());
    
      //结束线程池
      pool.shutdown();
    

25.07_多线程(多线程程序实现的方式3)(了解)

  • 提交的是Callable
  •   // 创建线程池对象
      ExecutorService pool = Executors.newFixedThreadPool(2);
    
      // 可以执行Runnable对象或者Callable对象代表的线程
      Future<Integer> f1 = pool.submit(new MyCallable(100));
      Future<Integer> f2 = pool.submit(new MyCallable(200));
    
      // V get()
      Integer i1 = f1.get();
      Integer i2 = f2.get();
    
      System.out.println(i1);
      System.out.println(i2);
    
      // 结束
      pool.shutdown();
    
      public class MyCallable implements Callable<Integer> {
    
          private int number;
      
          public MyCallable(int number) {
              this.number = number;
          }
      
          @Override
          public Integer call() throws Exception {
              int sum = 0;
              for (int x = 1; x <= number; x++) {
                  sum += x;
              }
              return sum;
          }
      
      }
    
  • 多线程程序实现的方式3的好处和弊端
    • 好处:

      • 可以有返回值
      • 可以抛出异常
    • 弊端:

      • 代码比较复杂,所以一般不用

25.08_设计模式(简单工厂模式概述和使用)(了解)

  • A:简单工厂模式概述
    • 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责
  • C:缺点
    • 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
  • D:案例演示
    • 动物抽象类:public abstract Animal { public abstract void eat(); }
    • 具体狗类:public class Dog extends Animal {}
    • 具体猫类:public class Cat extends Animal {}
    • 开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
  •   public class AnimalFactory {
          private AnimalFactory(){}
      
          //public static Dog createDog() {return new Dog();}
          //public static Cat createCat() {return new Cat();}
      
          //改进
          public static Animal createAnimal(String animalName) {
              if(“dog”.equals(animalName)) {}
              else if(“cat”.equals(animale)) {
      
              }else {
                  return null;
              }
          }
      } 
    

25.09_设计模式(工厂方法模式的概述和使用)(了解)

  • A:工厂方法模式概述
    • 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
    • 普通工厂模式【一个方法生产多种对象】
    • 多个工厂模式【多个方法各自生产对象】
    • 静态工厂模式【方法设置static】
    • 抽象工厂模式【将工厂抽象得到 抽象工厂基类】
  • B:抽象工厂模式优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
  • C:缺点
    • 需要额外的编写代码,增加了工作量
  • D:案例演示
  •   动物抽象类:public abstract Animal { public abstract void eat(); }
      工厂接口:public interface Factory {public abstract Animal createAnimal();}
      具体狗类:public class Dog extends Animal {}
      具体猫类:public class Cat extends Animal {}
      开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
      狗工厂:public class DogFactory implements Factory {
          public Animal createAnimal() {…}
              }
      猫工厂:public class CatFactory implements Factory {
          public Animal createAnimal() {…}
              }  
    

25.10_多线程(单例设计模式)(掌握)

  • 单例设计模式:保证类在内存中只有一个对象。

  • 如何保证类在内存中只有一个对象呢?

    • (1)控制类的创建,不让其他类来创建本类的对象。private
    • (2)在本类中定义一个本类的对象。Singleton s;
    • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
  • 单例写法两种:

    • (1)饿汉式 开发用这种方式。
    •   //饿汉式
        class Singleton {
            //1,私有构造函数
            private Singleton(){}
            //2,创建本类对象
            private static Singleton s = new Singleton();
            //3,对外提供公共的访问方法
            public static Singleton getInstance() {
                return s;
            }
            
            public static void print() {
                System.out.println("11111111111");
            }
        }
      
    • (2)懒汉式 面试写这种方式。多线程的问题?
    •   //懒汉式,单例的延迟加载模式
        class Singleton {
            //1,私有构造函数
            private Singleton(){}
            //2,声明一个本类的引用
            private static Singleton s;
            //3,对外提供公共的访问方法
            public static Singleton getInstance() {
                if(s == null)
                    //线程1,线程2
                    s = new Singleton();
                return s;
            }
            
            public static void print() {
                System.out.println("11111111111");
            }
        }
      
    • (3)第三种格式
    •   class Singleton {
            private Singleton() {}
        
            public static final Singleton s = new Singleton();//final是最终的意思,被final修饰的变量不可以被更改
        }
      

25.11_多线程(Runtime类)

  • Runtime类是一个单例类
    •   Runtime r = Runtime.getRuntime();
        //r.exec("shutdown -s -t 300");     //300秒后关机
        r.exec("shutdown -a");              //取消关机
      

25.12_多线程(Timer)(掌握)

  • Timer类:计时器

          public class Demo5_Timer {
              /**
               * @param args
               * 计时器
               * @throws InterruptedException 
               */
              public static void main(String[] args) throws InterruptedException {
                  Timer t = new Timer();
                  t.schedule(new MyTimerTask(), new Date(114,9,15,10,54,20),3000);
                  
                  while(true) {
                      System.out.println(new Date());
                      Thread.sleep(1000);
                  }
              }
          }
          class MyTimerTask extends TimerTask {
              @Override
              public void run() {
                  System.out.println("起床背英语单词");
              }
              
          }
    

25.13_GUI(如何创建一个窗口并显示)

  • Graphical User Interface(图形用户接口)。
  •   Frame  f = new Frame(“my window”);
      f.setLayout(new FlowLayout());//设置布局管理器
      f.setSize(500,400);//设置窗体大小
      f.setLocation(300,200);//设置窗体出现在屏幕的位置
      f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
      f.setVisible(true);
    

25.14_GUI(布局管理器)

  • FlowLayout(流式布局管理器)
    • 从左到右的顺序排列。
    • Panel默认的布局管理器。
  • BorderLayout(边界布局管理器)
    • 东,南,西,北,中(填充所有)
    • Frame默认的布局管理器。
  • GridLayout(网格布局管理器)
    • 规则的矩阵
  • CardLayout(卡片布局管理器)
    • 选项卡eclipse编辑区
  • GridBagLayout(网格包布局管理器)
    • 非规则的矩阵(计算器应用)

25.15_GUI(窗体监听)

Frame f = new Frame("我的窗体");
//事件源是窗体,把监听器注册到事件源上
//事件对象传递给监听器
f.addWindowListener(new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
                     //退出虚拟机,关闭窗口
        System.exit(0);
    }
});

25.16_GUI(鼠标监听)

  • 组件.addMouseListener(new MouseAdapter(){ 重写方法; });

25.17_GUI(键盘监听和键盘事件)

  • 组件.addKeyListener();
  • KeyEvent: getKeyCode() VK_SPACE

25.18_GUI(动作监听)

  • 默认就是空格键 鼠标左键
  • 应用场景: 点击左键、空格 视频暂停播放
  • 组件.addActionListener(new ActionListener(){ ... });

25.19_设计模式(适配器设计模式)(掌握)

  • a.什么是适配器
    • 在使用监听器的时候, 需要定义一个类事件监听器接口.
    • 通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.
    • 适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.
  • b.适配器原理
    • 适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
    • 适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的
    • 目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.

25.20_GUI(需要知道的)

  • 事件处理
    • 事件: 用户的一个操作
    • 事件源: 被操作的组件
    • 监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法

25.21_day25总结

把今天的知识点总结一遍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容