JAVASE高级部分

javaSE高级部分:

1 多线程:

创建多线程的方式:
1 继承Thread类

​ 步骤:

​ 1 创建一个继承于Thread类的子类

​ 2 重写Thread类的run(),将线程需要执行的操作声明在此方法中

​ 3 创建Thread类的子类的对象,并通过此对象调用start()

​ 问题一:不能直接调用run()启动线程,需要调用start()

​ 问题二:若要启动一个新的线程,不能让已经start的线程去执行,可以创建一个新的线程对象去调用start()

2 实现Runnable接口

​ 步骤:

​ 1 创建一个实现Runnable接口的类

​ 2 实现类去实现Runnable接口的抽象方法run()

​ 3 创建实现类的对象,并将此对象作为形参传到Thread类的构造器中创建Thread类的对象

​ 4 通过Thread类的对象调用start()

​ 1、2两种创建方式的对比:

​ 1 开发中优先选用实现Runnable接口的方式创建多线程

​ 2 原因:实现Runnable接口的方式没有单继承的局限性,并且更适合处理多个线程有共享数据的情况

​ 3 二者关系:Thread类也实现了Runnable接口,两种方式都需要重写run(),将线程需要执行的操作声明在其中

3 实现Callable接口 —jdk5.0新增
步骤:
*     1  创建一个实现Callable的实现类
*     2  实现call()方法,将此线程需要执行的操作声明在call()*     3  创建Callable接口实现类的对象
*     4Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
*     5FutureTask对象作为参数传递到Thread类的构造器中创建Thread类的对象并调用start()
*     6  可以获取Callable中call方法的返回值
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程强大?
*     1  call()方法可以有返回值
*     2  call()可以抛出异常被外面捕获
*     3  Callable支持泛型
*     4  需要借助FutureTask,   例如获取返回结果 ---> futureTask.get()
* Future接口:
*     1  可以对RunnableCallable任务的执行结果进行取消、查询是否完成、获取结果等。
*     2  FutureTaskFuture接口的唯一实现类
*     3  FutureTask同时实现了RunnableFuture接口。它既可以作为Runnable接口被线程执行,
*      也可以作为Future得到Callable的返回值
  代码演示:
        public static void main(String[] args) {
        MyThread1 m = new MyThread1();
        FutureTask<Integer> futureTask = new FutureTask(m);
        new Thread(futureTask).start();
        try {
            // 如果对call()方法的返回值不敢兴趣就不需要调用get(),  get()方法的目的只是为了获取call()方法的返回值
            // get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
4 使用线程池
* 创建多线程的方式四:  使用线程池
*
* 使用线程池的优点:
*    1 提高响应速度 (减少了创建新线程的时间)
*    2 降低资源消耗 (重复利用线程池中的线程,不需要每次都创建)
*    3 便于线程的管理
*       corePoorSize:核心池的大小
*       maximumPoolSize:最大线程数
*       keepAliveTime:线程没有任务时最多保持多长时间后会终止
* 步骤:
     // 1 提供指定线程数量的线程池
      ThreadPoolExecutor service = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        // 2 设置线程池的属性
//        service.setCorePoolSize(15);

        // 3 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new MyThread2());   // 适合用于Runnable
//        service.submit();  适合用于Callable
        service.execute(new MyThread3());

        service.shutdown();  // 4  关闭线程池
Thread类中的常用方法:

​ 1 start():启动当前线程,并调用当前线程的run()

​ 2 run():通常需要重写Thread类中的此方法,将当前线程需要执行的操作声明在此方法中

​ 3 currentThread():Thread类中的静态方法,用来获取执行当前代码的线程

​ 4 getName()/setName():获取/设置当前线程名称

​ 5 yield():Thread类的静态方法,释放当前cpu执行权

​ 6 join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完之后线程a才开始执行

​ 7 stop():不推荐使用,已经过时。当执行此方法时,强制结束当前线程

​ 8 sleep():Thread类的静态方法,使当前线程睡眠指定的毫秒数,在睡眠中,当前线程处于阻塞状态

​ 9 isAlive():判断当前线程是否存活

​ 10 线程优先级的设置:

​ 1> Thread类的静态属性:

​ MAX_PRIORITY:10

​ MIN_PRIORITY:1

​ NORM_PRIORITY:5 (默认的优先级)

​ 2> 获取和设置当前线程的优先级:

​ getPriority():获取当前线程的优先级

​ setPriority():设置当前线程的优先级

解决线程安全问题的方式一:同步代码块

​ 1 synchronized(同步监视器){

​ // 需要被同步的代码(即多个线程共同操作共享数据的代码)--------------->不能包含多了,也不能包含少了

​ }

​ 2 说明:

​ 1 操作共享数据的代码即为需要被同步的代码

​ 2 共享数据:多个线程共同操作的变量,例如例题的ticket

​ 3 同步监视器(锁):任何一个类的对象都可以充当锁,但多个线程必须要共用一把锁

​ 3 同步的利弊:

​ 1 使用同步的方式解决了线程的安全问题 ----------------->优点

​ 2 操作同步的代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程 ----------------->局限性

解决线程安全问题的方式二: 同步方法

​ 1 使用方法: 在需要被同步的方法的返回值前加synchronized

​ 2 同步方法:

​ 1 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明

​ 2 非静态的同步方法,同步监视器是this

​ 3 静态的同步方法,同步监视器是当前类本身

解决线程安全问题的方式三: Lock锁

​ synchronized和Lock的对比

​ 1 Lock是显式锁(需要手动的开启和关闭锁,不要忘记关闭锁),synchronized是隐式锁,出了作用域自动释放锁

​ 2 Lock只有代码块锁,synchronized有代码块锁和方法锁

​ 3 使用Lock锁,JVM花费更少的时间调度线程,性能更好,有更好的扩展性(提供了更多的子类)

​ 4 使用的优先顺序: Lock > 同步代码块 > 同步方法

线程通信:

​ 1 线程通信中涉及到的三个方法:

​ 1 wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放锁

​ 2 notify():一旦执行此方法,就会唤醒正在wait的一个线程(注意是一个),如果有多个线程被wait,则唤醒优先级最高的

​ 3 notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

​ 2 补充说明:

​ 1 wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中

​ 2 wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的锁,否则会出现异常

​ 3 wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的

sleep()和wait()的异同?

​ 1 相同点:一旦执行,都会使当前线程进入阻塞状态

​ 2 不同点:

​ 1 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()

​ 2 调用的要求不同:sleep()可以在任何场景下使用,wait()方法必须使用在同步代码块或同步方法中

​ 3 关于是否释放锁:如果两个方法都声明在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

单例模式懒汉式写法(并解决了线程的安全问题)
public class Bank {

    private static Bank instance = null;

    private Bank(){

    }

    public static Bank getInstance(){
        if(instance == null){
            synchronized (Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

2 java常用类

String类的使用

​ 1 String:字符串,使用 " " 引起来表示

​ 2 String类声明为final的,不可以被继承

​ 3 String类实现了Serializable接口,表示字符串是支持序列化的

​ 4 String类实现了Comparable接口,表示String类的对象可以比较大小

​ 5 String内部定义了final修饰的 char[] value ,用于存储字符串数据

​ 6 String代表不可变的字符序列, 简称:不可变性

​ 体现:1 当对字符串重新赋值时,需要重新指定内存区域复制,不能使用原有的value赋值

​ 2 当对现有的字符串进行连接操作时,也需要重新制定内存区域赋值,不能使用原有value赋值

​ 3 当调用String类的replace()方法修改指定字符或字符串时,也需要重新指定内存区域,不能使用原有value

​ 理解:只要对一个字符串对象进行任何的操作都需要重新创建,原有的字符串不能变!!!

​ 7 通过字面量的(区别于new的方式)给一个字符串赋值,此时的字符串声明在字符串常量池中

​ 8 字符串常量池中是不会存储相同内容(用重写过的equals()方法比较的)的字符串

String类的实例化方式:

​ 1 通过字面量的方式直接定义

​ 2 通过 new + 构造器 的方式

​ 3 面试题:String s = new String(“abc”),用此方法创建String类的对象,在内存中共创建了几个对象呢?

​ 两个,一个是new 的String对象保存在堆空间中,另一个是char[]类型的对象,对应着常量池中的数据:“abc”,如果字符

​ 串常量池中已经有"abc",则只需要创建一个String类型的对象,因为字符串常量池中不会存储相同内容的字符串,如果有就直

​ 接用现成的

@Test
// String不同拼接操作的对比
/**
 * 总结:
 *    1 常量与常量的拼接结果依然在常量池,并且常量池中不会存在相同内容的常量
 *    2 字符串拼接过程中只要有一个是变量,那么拼接的结果就在堆中,相当于new了一个字符串对象在堆空间中
 *    3 如果拼接的结果调用intern()方法,其返回值就在字符串常量池中找。
 *
 */
public void test3(){

    String s1 = "Cookie";
    String s2 = "Rookie";
    String s3 = "Cookie" + "Rookie";
    String s4 = s1 + "Rookie";
    String s5 = s1 + s2;
    String s6 = "Cookie" + s2;
    String s7 = s6.intern();
    String s8 = "CookieRookie";

    System.out.println(s3 == s8); //ture
    System.out.println(s3 == s4); //false
    System.out.println(s3 == s5); //false
    System.out.println(s4 == s5); //false
    System.out.println(s4 == s6); //false
    System.out.println(s5 == s6); //false
    System.out.println(s5 == s7); //false
    System.out.println(s6 == s7); //false
    System.out.println(s3 == s7); //ture
    System.out.println(s8 == s7); //ture
    

}
String类和其他结构之间的转换:

​ 1 String与基本数据类型、包装类之间的转换:

​ String----->基本数据类型、包装类:调用包装类的静态方法:parse xxx(str)

​ 基本数据类型、包装类----->String:调用String重载的静态方法valueOf(xxx)

​ 2 String与char[]之间的转换:

​ String------->char[]:调用String类的toCharArray()方法

​ char[]------->String:调用String类的构造器

​ 3 String与字节数组byte[]之间的替换:

​ String------->byte[]:调用String类的getBytes(),也就是编码的过程

​ byte[]------->String:调用String类的构造器,也就是解码的过程

​ 4 对于编码和解码的理解(编码集了解部分请看下文)

​ 编码:字符串(我们能看懂的那些字符)---->字节(底层的那些数字):看得懂的---->看不懂的二进制数

​ 解码:编码的逆过程 字节----->字符串:看不懂的二进制数------>看得懂的

​ 说明:解码时要求解码使用的字符集必须和编码使用的字符集一致,否则会出现乱码问题。

String类的常用方法:

​ 1 System.out.println(s1.length()); // 获取s1字符串的长度,底层为char[] 的长度

​ 2 System.out.println(s1.charAt(0)); // 获取指定索引处的字符

​ 3 System.out.println(s1.isEmpty()); // 判断s1是否为空

​ 4 System.out.println(s1.toLowerCase()); // 将s1字符串对象的内容全部转换为小写

​ 5 System.out.println(s1.toUpperCase()); // 将s1字符串对象的内容全部转换为大写

​ 6 String s3 = s2.trim(); // 去掉字符串开头和结尾的空格,中间的空格不变。

​ 7 System.out.println(s1.equalsIgnoreCase(s2)); // 比较s1和s2字符串的内容,忽略大小写

​ 8 String s4 = s3.concat(“def”); // 将指定字符串连接到此字符串的结尾,相当于"+"

​ 9 System.out.println(s5.compareTo(s6)); // 比较两个字符串的大小 a为97,b为98,c为99,此方法涉及到字符串排序

​ 10 String s8 = s7.substring(2); // 获取指定区间内的字符串内容,一个参数时终止索引到结尾

​ 11 String s9 = s7.substring(2, 4); // 获取指定区间内的字符串内容,两个参数时左闭右开。

​ 12 boolean b1 = s1.endsWith(“rld”); // 判断此字符串是否以指定的后缀结束

​ 13 boolean b2 = s1.startsWith(“hel”);// 判断此字符串是否以指定的前缀开始

​ 14 boolean b3 = s1.startsWith(“ll”, 2);// 判断此字符串从指定索引开始的子字符串是否以指定的前缀开始的

​ 15 System.out.println(s1.contains(s2)); // 判断此字符串中是否包含指定的字符串

​ 16 System.out.println(s1.indexOf(“Wo”)); // 返回指定字符串在此字符串中第一此出现处的索引值,如果没有出现返回-1

​ 17 System.out.println(s1.indexOf(“lo”,5)); // 返回指定字符串在此字符串中第一此出现处的索引值,从指定索引位置开始查

​ 找,如果没有出现返回-1

​ 18 System.out.println(s3.lastIndexOf(“or”)); // 返回指定字符串在此字符串中第一此出现处的索引值,从结尾反向查找,

​ 如果没有出现返回-1,索引值依然是从前往后数。

/**
 * 什么情况下 indexOf(str) 和 lastIndexOf(str)返回值相同?
 * 1 指定字符串中只存在唯一的一个str
 * 2 指定字符串中不存在str
 */

​ 19 String s2 = s1.replace(‘o’, ‘a’);// 将原有字符串的字符替换为指定字符,替换所有

​ 20 String s3 = s1.replace(“He”, “aa”); // 使用指定的字面值替换原有字符串的字面值,形参相当于字符串,替换所有

​ 21 String s4 = s1.replaceFirst(“lo”, “ao”); // 将原有字符串的子字符串替换为指定字符串,只替换第一处

​ 22 boolean matches = str.matches("\d+"); // 判断原有的字符串是否匹配指定的正则表达式

​ 23 String str2 = str1.replaceAll("\d+", “,”).replaceAll("^,|,$", “”);// 将原有字符串满足指定正则表达式或指定字符串的部分

​ 替换为指定的字符串

​ 24 String[] split = str3.split("\."); // 根据指定的正则表达式或字符串将原有字符串进行拆分,返回一个String数组,形参为

​ 正则表达式或字符串

StringBuffer和StringBuilder的使用:
/**
 * 关于StringBuffer和StringBuilder的使用:
 * String、StringBuffer、StringBuilder三者的异同?
 * 相同点:
 *     三者底层都是使用char[]数组存储
 * 不同点:
 *     String:不可变的字符序列
 *     StringBuffer:可变的字符序列,线程安全,效率低。
 *     StringBuilder:可变的字符序列,线程不安全,效率高。
 *
 * 源码分析:
 *     String s = new String(); // new char[0];
 *     String s1 = new String("abc");  // new char[]{'a','b','c'};
 *
 *     StringBuffer sb = new StringBuffer(); // new char[16]; 相当于底层创建了一个长度是16的数组
 *     sb.append('a');  // value[0] = 'a';
 *     sb.append('b');  // value[1] = 'b';
 *
 *     StringBuffer sb1 = new StringBuffer("abc");  // char[] value = new char["abc".length()+16]{'a','b','c'};
 *
 * 问题1:System.out.println(sb1.length());  // 3
 * 问题2:底层数组的扩容问题:
 *       如果需要添加的数据底层数组装不下了,那么就需要扩容底层的数组。
 *       在默认的情况下,新的数组扩容到原来的  2倍+2 ,同时将原有数组的元素复制到新的数组中。
 *       在开发中建议大家使用的构造器:StringBuffer(int capacity) 或 StringBuilder(int capacity),
 *       在参数中指定底层创建的数组的长度,避免数组的扩容问题,使效率变高。
 * String,StringBuffer,StringBuilder三者效率的对比:
 *       StringBuilder > StringBuffer > String
 * String,StringBuffer,StringBuilder三者之间的转换:
 *       String---->StringBuffer:
 *             1 通过构造器的方式: new StringBuffer(str);
 *       StringBuffer---->String:
 *             1 通过构造器的方式: new String(sb);
 *             2 调用StringBuffer的toString(): sb.toString();
 */
StringBuffer的常用方法(StringBuilder的方法和它一模一样)

1 sb.append(1); // 提供了很多重载的append()方法,用于进行字符串拼接

2 sb.delete(5,10); // 删除指定范围内的内容

3 sb.replace(2,3,“qaq”); // 将指定范围内的字符串替换为指定的字符串

4 sb.insert(2,“xuge”); // 在指定位置插入 xxx , 提供了很多重载的方法

5 sb.reverse(); // 将当前字符串逆转

6 System.out.println(sb.indexOf(“q”)); // 返回指定字符串在当前字符串第一次出现的位置的索引值

7 System.out.println(sb.substring(2,6)); // 返回当前字符串在指定范围内的子字符串

8 System.out.println(“当前字符串的长度为”+sb.length()); // 返回当前字符串的长度

9 System.out.println(sb.charAt(2)); // 获取指定索引位置的字符

10 sb.setCharAt(2,’@’); // 将指定位置的字符修改为指定字符

总结StringBuffer中最常用方法(StringBuilder一样)
/**
 * StringBuffer常用方法:
 *     总结:
 *       1 增:append(xxx)
 *       2 删:delete(int start,int end)
 *       3 改:setCharAt(int n,char ch) 或 replace(int start,int end,String str)
 *       4 查:charAt(int n)
 *       5 插:insert(int index,xxx)
 *       6 长度: length()
 *       7 遍历字符:for() + charAt()  或 toString() 主要看字符串的内容
 */
jdk8之前日期和时间API测试
/**
 * jdk 8 之前日期和时间API测试
 *
 *    获取当前时间戳:System.currentTimeMillis() 调用System类中的静态方法
 *
 *    java.util.Date:
 *       1 两个构造器的使用:
 *           new Date(): 空参构造器,创建一个对应当前时间的Date对象
 *           new Date(162842143343l): 创建一个指定毫秒数的Date对象
 *       2 两个方法的使用:
 *           toString():显示当前的年月日时分秒
 *           getTime(): 返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
 *    java.sql.Date:
 *       1 对应着数据库中的日期类型
 *       2 如何实例化:
 *           new java.sql.Date(162842143343l): 创建一个指定毫秒数的java.sql.Date对象
 *       3 两个方法的使用:
 *             toString():显示当前创建的java.sql.Date对象对应的年月日
 *             getTime(): 返回当前创建的java.sql.Date对象与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
 * 如何将java.util.Date转换为java.sql.Date?
 *         Date date = new Date();
 *         Date date1 = new java.sql.Date(date.getTime());
 *         System.out.println(date1);
 */
/**
 * Calendar日历类(抽象类)的使用:注意此时日历类是可变的
 *      1 实例化:
 *         1 创建其子类的对象:new GregorianCalendar();
 *         2 调用其静态方法:getInstance();  推荐使用
 *      2 常用方法:
 *         1 get()
 *         2 set()
 *         3 add()
 *         4 getTime()
 *         5 setTime()
 */
 
 日历类中的方法:
 int days = instance.get(Calendar.DAY_OF_MONTH); // 获取这个月的第几天
 System.out.println("现在是这个周的第"+instance.get(Calendar.DAY_OF_WEEK)+"天"); // 获取这个周的第几天
 System.out.println("现在是这个年的第"+instance.get(Calendar.DAY_OF_YEAR)+"天"); // 获取这个年的第几天
 instance.set(Calendar.DAY_OF_MONTH,19);  // 修改Calendar对象的某个指定属性
 instance.add(Calendar.DAY_OF_MONTH,3); // 在Calendar对象的指定属性上加上指定数
 Date date = instance.getTime();  // 日历类转换为Date  注意此时日历类的对象是修改之后的
 instance.setTime(date); // 将指定时间的Date类转换为对应时间日历类
/**
 * SimpleDateFormat总结:用于对日期进行格式化和解析
 *      格式化:
 *        日期(java.util.Date)------>字符串:sdf.format(Date date);
 *      解析:
 *        字符串------>日期(java.util.Date): sdf.parse(String str);
 */
jdk8之后新日期时间API的使用
/**
 * LocalDate、LocalTime、LocalDateTime的使用:
 *      1 LocalDateTime使用频率最高
 *      2 跟Calendar类相似
 * Calendar类和Date类面临的问题是:
 *      1 可变性:像日期和时间这样的类应该是不可变的。
 *      2 偏移性: Date中的年份是从1900年开始,月份是从0开始。
 *      3 格式化: 格式化只对Date有用,对Calendar没用。
 *      4 他们的线程都不是安全的,不能处理闰秒等。
 */
public class LocalDateTimeTest {
    public static void main(String[] args) {
        // 如何实例化? 使用三个类的静态方法now()/of()
        // 创建对应当前时间的对象  LocalDate.now();
        System.out.println("创建对应当前时间的对象");
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDate);
        System.out.println(localTime);
        System.out.println(localDateTime);
        System.out.println("*********************************");
        // 创建指定时间的对象,没有偏移量  LocalDateTime.of(2019, 1, 1, 13, 6, 56);
        System.out.println("创建指定时间的对象,没有偏移量");
        LocalDateTime localDateTime1 = LocalDateTime.of(2019, 1, 1, 13, 6, 56);
        System.out.println(localDateTime1);
        System.out.println("*********************************");
        // 获取相关的属性  getXxx()
        System.out.println("获取相关的属性");
        int dayOfMonth = localDateTime.getDayOfMonth();
        DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
        int monthValue = localDateTime.getMonthValue();
        Month month = localDateTime.getMonth();
        System.out.println(dayOfMonth);
        System.out.println(dayOfWeek);
        System.out.println(monthValue);
        System.out.println(month);
        System.out.println("*********************************");
        // 设置相关的属性,体现不可变性   withXxx()
        System.out.println("设置相关的属性");
        LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(25);
        System.out.println("修改之前:"+localDateTime);
        System.out.println("修改之后:"+localDateTime2);
        System.out.println("*********************************");
        // 添加  体现不可变性
        LocalDateTime localDateTime3 = localDateTime.plusDays(10);
        System.out.println("添加之前:"+localDateTime);
        System.out.println("添加之后:"+localDateTime3);
    }
}
/**
 * DateTimeFormatter的使用:
 *     1 格式化或解析日期/时间,类似属于SimpleDateFormat
 *     2 如何实例化?
 *        1> 预定义的标准格式:
 *        2> 本地化相关的格式:
 *        3> 自定义格式(使用最多):调用静态方法ofPattern("yyyy-MM-dd HH:mm:ss");
 */
public class DateTimeFormatterTest {
    public static void main(String[] args) {
        // 重点: 自定义的方式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // hh表示12小时制,HH表示24小时
        // 格式化操作
        String format = formatter.format(LocalDateTime.now());
        System.out.println(format);
        // 解析操作
        TemporalAccessor parse = formatter.parse("2021-08-09 10:10:49");
        System.out.println(parse);

    }
}
/**
 * Instant类的使用:
 *    是时间线上的一个瞬时点,可以被用来记录应用程序的事件时间戳,类似于java.util.Date。
 *    1 如何实例化?
 *       1> 调用Instant类的静态方法:Instant.now();  获取本初子午线对应的标准Instant时间对象
 *       2> 调用Instant类的静态方法:Instant.ofEpochMilli(1628473189103l);  根据指定的毫秒数创建对应时间的Instant对象
 */
public class InstantTest {
    public static void main(String[] args) {
        // now(): 获取本初子午线对应的标准时间对象
        Instant now = Instant.now();  // 当前对象是对应伦敦的本初子午线的时间/0时区
        System.out.println(now);
        // 添加时间的偏移量
        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
        System.out.println(offsetDateTime);
        // 获取对应的毫秒数   1970/1/1
        long l = now.toEpochMilli();
        System.out.println(l);
        // 根据指定的毫秒数创建对应时间的Instant对象
        Instant instant = Instant.ofEpochMilli(1628473189103l);
        System.out.println(instant);
    }

}
BigDecimal和BigInteger的使用:

1 实例化的时候尽量使用传入参数为String的构造器(为了使浮点数的表示更加精准)

2 常用方法: 1 加法:add() b1.add(b2)

​ 2 减法:subtract() b1.subtract(b2)

​ 3 乘法:multiply() b1.multiply(b2)

​ 4 除法:divide() b1.divide(b2)

​ 5 绝对值:abs() b1.abs()

Math类的使用:

常用方法:

​ 1 static int max(int x,int y):返回x和y中的最大值,提供了很多重载的方法

​ 2 static int min(int x,int y):返回x和y中的最小值,提供了很多重载的方法

​ 3 static double exp(double a):返回e的a次幂

​ 4 static double pow(double a,double b):返回a的b次幂

​ 5 static double sqrt(double a):返回a的平方根

​ 6 static double cbrt(double a):返回a的立方根

​ 7 static double log(double a):返回a的自然对数,即lna的值

​ 8 static double log10(double a):返回以10为底a的对数,即lga

Comparable接口和Comparacor接口:
/**
 * java中的对象正常情况只能进行 == 和 != 不能使用 >   <
 * 在开发中需要对多个对象进行排序,就是需要比较对象的大小。
 * 如何实现呢?   使用一下两个接口的任何一个: Comparable  Comparator
 * Comparable接口的使用举例:
 *     1 像String、包装类等实现了Comparable接口,重写了CompareTo(obj)方法,给出了比较两个对象的方式。
 *     2 像String、包装类重写compareTo(obj)方法后,进行了从小到大的排列。
 *     3 重写compareTo(obj)规则:
 *         如果当前对象this大于形参对象obj时,返回正整数。
 *         如果当前对象this小于形参对象obj时,返回负整数。
 *         如果当前对象this等于形参对象obj时,返回0。
 *     4 对于自定义类如果需要排序,可以让自定义类实现Comparable接口,重写CompareTo(obj)方法。
 * Comparator接口使用背景:
 *     1 当元素的类型没有实现Comparable接口而又不方便修改代码,或者实现了Comparable接口的排序规则
 *     不适合当前的操作,那么就可以考虑使用 Comparator 接口进行排序。
 *     2 重写compare(Object o1,Object o2)方法规则:
 *         如果o1 > o2,返回正整数。
 *         如果o1 = o2,返回0。
 *         如果o1 < o2,返回负整数。
 * Comparable接口和Comparator接口使用的对比:
 *     1 Comparable接口的方式一旦写定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
 *     2 Comparator接口的方式属于临时性的比较。
 */

3 枚举类和注解

/**
 * 枚举类的使用:
 *    1 枚举类的理解:类的对象只有有限个,确定的数量,我们称此类为枚举类
 *    2 当需要定义一组常量时,强烈建议使用枚举类
 *    3 如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
 * 如何定义枚举类:
 *    1 jdk5.0之前自定义枚举类
 *    2 jdk5.0之后可以使用enum关键字定义枚举类
 * Enum类中的常用方法:
 *    1 values():返回当前枚举类类型的对象数组,该方法可以很方便的遍历枚举类对象。
 *    2 valueOf(String str):返回与形参字符串相对应的枚举类对象,注意形参字符串一定要是枚举类对象之中存在的。
 *    3 toString():如果没有重写此方法,返回当前枚举类对象所对应的常量名称。
 * 使用enum关键字定义的枚举类实现接口的情况:
 *    1 实现接口,在枚举类中实现接口中的抽象方法。
 *    2 让枚举类的每个对象分别实现接口中的抽象方法。
 */
自定义枚举类:
public class EnumTest {
    public static void main(String[] args) {
        System.out.println(season.SPRING);
        System.out.println(season.SUMMER);
        System.out.println(season.AUTUMN);
        System.out.println(season.WINTER);
    }
}

//自定义枚举类
class season {
    private final String seasonName;
    private final String seasonDesc;
    // 1 构造器私有化
    private season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    // 2 提供有限数量的对象
    public static final season SPRING = new season("春天","天街小雨润如酥");
    public static final season SUMMER = new season("夏天","映日荷花别样红");
    public static final season AUTUMN = new season("秋天","无边落木萧萧下");
    public static final season WINTER = new season("冬天","千树万树梨花开");

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    @Override
    public String toString() {
        return "season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }

}
使用enum关键字定义枚举类:

使用enum关键字定义的枚举类默认继承java.lang.Enum类,不是java.lang.Object类

public class EnumTest2 {
    public static void main(String[] args) {
        System.out.println(season1.SPRING);
        System.out.println(season1.values()[1]);
        season1 winter = season1.valueOf("WINTER");
        System.out.println(winter);
        season1.SUMMER.show();
        System.out.println("**************************");
        season1.SPRING.show();

    }
}
interface demo {
    void show();
}
enum season1 implements demo{
    // 1 提供当前枚举类的对象,多个对象之间用","隔开,最后一个对象用";"结尾
    SPRING("春天","天街小雨润如酥"){
        @Override
        public void show() {
            System.out.println("春天的花开");
        }
    },
    SUMMER("夏天","映日荷花别样红"){
        @Override
        public void show() {
            System.out.println("夏天夏天悄悄过去");
        }
    },
    AUTUMN("秋天","无边落木萧萧下"){
        @Override
        public void show() {
            System.out.println("你听啊秋末的落叶");
        }
    },
    WINTER("冬天","千树万树梨花开"){
        @Override
        public void show() {
            System.out.println("漫天大雪飘零做你的嫁衣");
        }
    };
    //2 声明Season1对象的属性,private final 修饰
    private final String seasonName;
    private final String seasonDesc;
    //3 私有化构造器,并给对象的属性赋值
    private season1(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    @Override
    public String toString() {
        return "season1{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }

//    @Override
//    public void show() {
//        System.out.println("这是"+seasonName);
//    }
}

Enum类的主要方法:

​ 1 values():返回枚举类型的对象数组,该方法可以便捷的遍历所有枚举值

​ 2 valueOf(String str):可以把一个字符串转换为对应的枚举类对象,注意字符串必须是枚举类对象的名称,否则会出现异常

​ 3 toString():返回当前枚举类对象的常量名称,如果没有重写toString()的话

注解的使用:
/**
 * 注解(Annotation)的使用:
 *     1 理解注解:
 *        1 jdk5.0新增。
 *        2 注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取并执行相应的处理,通过使用注解,程序员可以
 *        在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
 *        3 在javaEE中注解占据了非常重要的角色。
 *     2 注解的实例:
 *        1 生成文档相关的注解。
 *        2 在编译时进行格式检查(jdk内置的三个基本注解)
 *            1 @Override:限定重写父类方法,该注解只能用于方法。
 *            2 @Deprecated:表示修饰的元素(类、方法等)已过时。
 *            3 @SuppressWarnings:抑制编译器警告。
 *        3 跟踪代码的依赖性,代替配置文件的功能。
 *     3 自定义注解:
 *        1 注解声明为@interface
 *        2 内部定义的成员变量通常用value表示
 *        3 可以指定成员变量的默认值,使用default定义
 *        4 如果自定义注解没有成员变量,那么表明此注解是一个标识作用
 *        5 如果自定义注解有成员变量,在使用注解时需要指明成员变量的值
 *        6 自定义注解必须搭配注解的信息处理流程(使用反射)才有意义
 *        7 自定义注解通常都会指明2个元注解: @Retention   @Target
 *     4 jdk提供的4种元注解:
 *        1 @Retention:指定所修饰的注解的生命周期:SOURCE\CLASS(默认)\RUNTIME,
 *        只有声明为RUNTIME生命周期的注解才可以通过反射获取到。
 *        2 @Target:用于指定此注解可以修饰哪些元素(类、方法、属性等)。
 *        3 @Documented:表示所修饰的注解在被javadoc解析时保留下来。
 *        4 @Inherited:被它修饰的注解将具有继承性。
 *     5 通过反射获取注解的信息:以后再说 - -。
 *     6 jdk8中注解的新特性:
 *        1 可重复注解的使用:
 *            1 在MyAnnotation上声明@Repeatable,成员变量值为MyAnnotations.class。
 *            2 MyAnnotation的@Target、@Retention等元注解与MyAnnotations全部相同。
 *        2 类型注解:
 *            1 ElementType.TYPE_PARAMETER:表示该注解能写在任何类型变量的声明语句中,(如泛型声明,不能写在异常类型前)
 *            2 ElementType.TYPE_USE:表示该注解能写在使用类型的任何语句中。(可以写在异常类型前面)
 */
自定义注解的使用:
/**
 * 自定义注解
 */
// 设置自定义注解的生命周期
@Retention(RUNTIME)
// 设置自定义注解可以修饰的元素
@Target({METHOD,TYPE,TYPE_PARAMETER,TYPE_USE})
// 设置此注解具有继承性
@Inherited
// 可重复注解
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value();
}
// 设置自定义注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 设置自定义注解可以修饰的元素
@Target({ElementType.METHOD,ElementType.TYPE})
// 设置此注解具有继承性
@Inherited
// 可重复注解
public @interface MyAnnotations {
    MyAnnotation[] value() default "hello";
}
@MyAnnotation
@MyAnnotation("hi")
public class ZhuJieTest {

    public static void main(String[] args) throws @MyAnnotation("hi") RuntimeException {
        Annotation[] annotations = MyAnnotation.class.getAnnotations();
        for(Annotation annotation:annotations){
            System.out.println(annotation);
        }
    }
}
class demo<@MyAnnotation("hello") T> {

}

4 集合框架

Collection接口:

1 单列集合,用来存储一个一个的对象,和Map接口不同

2 在Collection接口实现类的对象中添加数据obj时,要求obj对象所在的类重写equals()

3 Collection接口的常用方法:

* Collection接口的常用方法:
*    add(Obj obj):向集合对象中添加一个元素。
*    addAll(Collection c):将形参集合c中的元素全部添加到当前的集合中。
*    size():获取当前集合对象的长度。
*    clear():清空集合中的所有数据。
*    isEmpty():判断当前集合中是否有元素。
*    remove(Object obj):移除形参中指定的元素,在判断时会调用obj对象所在类的equals()。返回值是boolean类型。
*    removeAll(Collection c):移除形参集合中的所有元素,在判断时会调用c集合中对象所在类的equals()。返回值是boolean类型。
*    contains(Object obj):判断集合中是否包含指定的对象。 在判断时会调用obj对象所在类的equals()。
*    containsAll(Collection c):判断形参集合中的所有数据是否都存在于当前的集合中。
*    retainAll(Collection c):求当前集合和形参集合中的元素的交集,直接把当前集合修改成交集。
*    equals(Collection c):判断当前集合对象和形参中集合对象数据是否相同,在判断时会调用形参集合中的对象所在类的equals()。
*                     如果两个集合是ArrayList则需要考虑顺序,因为ArrayList是一个有序的集合。
*    hashCode():返回当前对象的哈希值。此方法定义在java.lang.Object类中。
*    toArray():将当前集合转换为数组。
*         数组----->集合:Arrays.asList(new String[]{"AA","BB"}),调用Arrays类的静态方法。
*    iterator():返回Iterator接口的实例,用于遍历集合元素。
*/
List接口详解:
/**
 * List接口框架详解:
 *
 * List接口:存储有序的,可重复的数据。和Set接口相对。------->动态的数组
 * List接口下的ArrayList、LinkedList和vector三个实现类:
 *     1 ArrayList:作为List接口的主要实现类,线程不安全,效率高,底层使用Object[]数组存储。
 *     2 LinkedList:对于频繁的插入、删除操作,效率比ArrayList高,底层使用双向链表存储。
 *     3 Vector:作为List接口的最古老的实现类,线程安全,效率低,底层使用Object[]数组存储。
 * ArrayList的源码分析:
 *     jdk7:
 *        ArrayList list = new ArrayList(); // 底层创建了一个长度为10的Object[] elementData。
 *        list.add(123); // elementData[0] = new Integer(123)
 *        list.add(11); // 表示当前添加的元素是第11个元素,如果此次操作导致底层的数组的容量不够,则需要
 *        默认的情况下扩容为原来容量的1.5倍,同时将原有数组里的数组复制到新的数组中。
 *        总结:建议开发的时候选用带参数的构造器,ArrayList list = new ArrayList(int capacity);
 *        防止底层的数组扩容,使效率降低。
 *     jdk8:
 *        ArrayList list = new ArrayList(); // 底层Object[] elementData初始化为{},并没有创建长度
 *        为10的数组。
 *        list.add(123); // 第一次调用add()时,底层才创建长度为10的数组, elementData[0] = new Integer(123)
 *        ......
 *        后续的添加和扩容的操作与jdk7一样。
 *        总结:jdk7中的ArrayList的对象的创建类似单例模式的饿汉式,jdk8中的ArrayList的对象的创建类似单例模式的懒汉式,
 *        延迟了数组的创建,节省了内存。
 * LinkedList的源码分析:
 *     LinkedList list = new LinkedList(); // 内部声明了Node类型的first和last属性.
 *     list.add(); // 将123封装到Node类的对象中,创建了Node对象。
 *     其中Node类定义为如下格式:  体现了LinkedList双向链表的说法。
 *         private static class Node<E> {
 *         E item;
 *         LinkedList.Node<E> next;
 *         LinkedList.Node<E> prev;
 *
 *         Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
 *             this.item = var2;
 *             this.next = var3;
 *             this.prev = var1;
 *         }
 *     }
 * Vector的源码分析:jdk7和jdk8通过Vector()的构造器创建对象时,底层都创建了长度为10的数组,在扩容方面默认扩容为原来的
 *         2倍。
 * ArrayList、LinkedList、Vector三者的异同?
 *      同:三者都实现了List接口,存储数据的特点相同:有序、可重复的数据。
 *      异:请见上文 -.-。
 * List接口相较于Collection接口多出的方法:
 *      void add(int index,Object ele):在指定的位置index上插入ele元素。
 *      boolean addAll(int index,Collection ele):从指定的位置index将集合ele中所有元素添加进来。
 *      Object get(int index):获取指定位置index上的元素。
 *      int indexOf(Object obj):返回元素obj在当前集合首次出现的位置。
 *      int lastIndexOf(Object obj):返回元素obj在当前集合末次出现的位置。
 *      Object remove(int index):移除指定index位置的元素,并将此元素返回。
 *      Object set(int index,Object ele):将指定位置index上的元素设置为ele。
 *      List subList(int start,int end):相似于String的substring(int start,int end),返回从索引start到end位置的
 *      子集合。
 * 总结:List接口的常用方法:
 *      增:add()
 *      删:remove(int index) 或 remove(Object obj)
 *      改:set(int index,int Object)
 *      查:get(int index)
 *      插:add(int index,Object obj)
 *      长度:size()
 */
Set接口详解:
/**
 * Set接口详解:
 *
 * Set接口:存储无序的、不可重复的数据(类比高中讲的集合)。
 * Set接口中没有定义新的方法,使用的都是Collection接口中的方法。
 * Set接口下的HashSet、LinkedHashSet、TreeSet三个实现类:
 *      HashSet:作为Set接口的最主要实现类,线程不安全,可以存储null值。采用数组+链表的方式存储数据。
 *           LinkedHashSet:HashSet的子类,遍历其内部的数据时,可以按照添加的顺序遍历。对于频繁的
 *           遍历操作LinkedHashSet的查找效率高于HashSet。
 *      TreeSet:可以按照添加的元素的指定属性进行排序。
 * 理解无序性、不可重复性:(以HashSet为例)
 *      无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据元素的哈希值计算出
 *      在底层数组的索引位置进行添加。
 *      不可重复性:保证添加的元素在根据equals()比较时,返回false,即相同的元素只能有一个。
 * 添加元素的过程:(以HashSet为例)
 *      当我们向HashSet中添加元素a时,首先调用元素a的hashCode()方法,计算出元素a的哈希值,然后通过某种
 *      特定的算法计算出其在HashSet底层数组中的存储位置(即索引位置),判断此位置上是否已经有元素存在。
 *          如果此位置没有其他的元素存在,则元素a直接添加成功。  -----情况1
 *          如果此位置上已经存在元素b(或以链表的形式已经存在多个元素),则比较元素a和b的哈希值:
 *             如果哈希值不同,则元素a添加成功。-----情况2
 *             如果哈希值相同,则调用元素a的equals()方法:
 *                如果结果为true,元素a添加失败。
 *                如果结果为false,元素a添加成功。-----情况3
 *      对于添加成功的情况2和情况3,元素a与该位置上已经存在的元素以链表的形式存储。
 *      jdk7:元素a放到数组中,指向原来的元素。
 *      jdk8:原来的元素放在数组中,指向元素a。
 *      总结:七上八下。HashSet的底层采用数组+链表的结构。
 * 要求:向Set中添加数据,其所在的类一定要重写equals()和hashCode(),并且重写的equals()和hashCode()尽可能的保持
 *      一致性:相等的对象必须要有相等的散列码(哈希值)。
 * LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了2个引用,记录当前数据的前一个数据和后一个数据。
 * 优点:对于频繁的遍历操作LinkedHashSet的查找效率高于HashSet。
 *
 * TreeSet的使用:
 *      1 向TreeSet中添加的数据要求是相同类的对象。TreeSet用于排序。在TreeSet中不会存储相同的数据。
 *      2 两种排序方式:自然排序(实现Comparable接口)  和  定制排序(实现Comparator接口)。
 *      3 在自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()。
 *      4 在定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()。
 *      5 TreeSet的空参构造器默认自然排序,可以在构造器参数中指定定制排序。
 *      6 TreeSet底层使用红黑树实现存储。
 *
 */
Iterator迭代器的使用:
/**
 * Iterator迭代器的使用,用来遍历容器(集合或数组)
 * Iterator中的常用方法:
 *     hasNext():判断游标下是否还有元素存在。
 *     next():将游标下移一位并返回此时游标对应的元素。
 * 增强for循环中底层使用的就是迭代器。
 */
Map接口及其实现类:
/**   -----Map:双列数据,存储key-value,类似于高中的函数:y=f(x)。
 *        -----HashMap:作为Map接口的最主要实现类,线程不安全,效率高,可以存储null的key和value
 *            -----LinkedHashMap:保证在遍历map元素时可以按照添加的顺序遍历,因为在原有HashMap底层结构上
 *            添加了一对指针,指向前一个和后一个元素,对于频繁的遍历操作,执行效率高于HashMap。
 *        -----TreeMap:保证按照添加的元素进行排序,实现排序遍历,此时考虑元素的key的自然排序和定制排序。底层
 *        使用红黑树。
 *        -----Hashtable:作为Map接口最古老的实现类,线程安全,效率低,key和value不可以是null
 *            -----Properties: 常用来处理配置文件,key和value都是String类型。
 * HashMap底层:
 *      数组+链表(jdk7):
 *          HashMap map = new HashMap(); // 在实例化后,底层创建了一个长度是16的一维数组Entry[] table。
 *          ...可能已经执行过多次put操作....
 *          map.put(key1,value1); // 首先调用key1所在类的hashCode()计算出哈希值,用此哈希值经过某种算法得到
 *          在Entry数组中的存放位置。
 *          如果此位置上数据为空,此时key1-value1(entry)添加成功。
 *          如果此位置上数据不为空(此位置上存在一个或多个数据),比较key1和已经存在的一个数据或多个数据的哈希值。
 *             如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1(entry)添加成功。
 *             如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equals(key2)。
 *                如果返回false:此时key1-value1(entry)添加成功。
 *                如果返回true:使用value1替换value2。
 *          补充:关于情况2和情况3,此时此时key1-value1(entry)和原来的数据在数组中以链表的形式存储。
 *          在不断的添加过程中,会涉及到底层数组的扩容(当元素个数超出临界值并且当前元素直接存放的位置非空时才扩容),默认情况扩         容为原来的2倍,并将原有数据复制过来,返回一个新数组。
 *      数组+链表+红黑树(jdk8)
 *          jdk8相较于jdk7底层实现方面的不同:
 *              1 new HashMap():在底层没有创建一个长度为16的数组。
 *              2 jdk8中底层的数组是Node[],不是Entry[]。
 *              3 首次调用put()时,底层才创建长度为16的数组。
 *              4 jdk7底层结构只有数组+链表,jdk8中底层结构:数组+链表+红黑树,当数组的某一个索引位置上的元素以链表的形式存储的个数 > 8,
 *              并且当前数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储,不再以链表存储。使用红黑树使查找的效率变高。
 *              5 HashMap源码中的相关属性:
 *                 1 DEFAULT_INITIAL_CAPACITY: HashMap底层数组的默认容量。 16
 *                 2 DEFAULT_LOAD_FACTOR: HashMap的默认加载因子。  0.75f
 *                 3 threshold: 扩容的临界值,=容量*加载因子 => 12。
 *                 4 TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转换为红黑树存储。 8
 *                 5 MIN_TREEIFY_CAPACITY: 桶中的Node被树化时最小的hash表容量。  64
 *
 * 面试题:
 *     1 HashMap的底层实现原理?
 *     2 HashMap和Hashtable的异同?
 *     3 CurrentHashMap(分布式锁)与Hashtable的异同?
 * Map中结构的理解:
 *     1 Map中的key:无序的,不可重复,使用Set存储所有的key。key所在的类需要重写equals()和hashCode()。
 *     2 Map中的value:无序的,可重复,使用Collection存储所有的value。value所在类需要重写equals()。
 *     3 一个键值对key-value构成一个Entry对象。
 *     4 Map中的Entry:无序的,不可重复,使用Set存储所有的Entry对象。
 * Map中的常用方法:
 *     1 Object put(Object key,Object value): 添加或修改。
 *     2 void putAll(Map m): 将m中所有数据添加到当前map对象中。
 *     3 Object remove(Object key): 移除指定key的元素,并返回此元素的value值。
 *     4 void clear(): 清空当前map所有数据。
 *     5 Object get(Object key): 获取指定key对应的value值。
 *     6 boolean containsKey(Object key): 判断当前map对象是否包含指定的key。
 *     7 boolean containsValue(Object value): 判断当前map对象是否包含指定的value。
 *     8 int size(): 返回map对象中元素的个数。
 *     9 boolean isEmpty(): 判断当前map对象中元素是否为空。
 *     10 equals(obj
 ): 判断当前map对象与参数obj是否相等。
 *     11 Set keySet(): 得到所有的key,(用于遍历所有的value值)。
 *     12 Collection values(): 得到所有的value集合(用于遍历所有的value值)。
 *     13 Set entrySet(): 得到所有key-value构成的Set集合。
 *
 * Properties处理属性文件:用来处理配置文件
 *
 */

5 泛型和File

泛型的使用:

1 泛型类中的泛型在实例化的时候指定,或者子类在继承父类的时候指明具体的泛型,那么此时子类就不再是泛型类

2 泛型类中的泛型声明在属性或方法上时,不能用static修饰,因为泛型类中的泛型在实例化的时候指定

2 泛型方法中的泛型在调用次方法的时候指明具体的泛型

泛型通配符的使用:? (?就表示不确定)

例如类A是类B的父类,G 和 G 是没有关系的,二者共同的父类是:G<?>

使用通配符后数据读取和写入的要求:

1 写入: 对于List<?>,就不能向其内部添加数据,但可以添加null,因为null可以给任何引用数据类型变量赋值

2 读取: 允许读取数据,读取的数据类型为Object,不确定是什么类,但一定是Object类

有限制条件的通配符:

1 <? extends A> : <= ,G<? extends A>可以作为G和G的父类,其中B是A的子类

2 <? super A> : >= ,G<? super A>可以作为G和G的父类,其中B是A的父类

File类的使用:
/**
 * File类的使用:
 *   1 如何实例化?
 *       File(String filePath)
 *       File(String parentPath,String childPath)
 *       File(File parentFile,String childPath)
 *   2 相对路径:相较于当前module下,如果使用单元测试方法,相对路径是当前module下,main方法相对路径是当前工程下。
 *     绝对路径:包含盘符在内的文件或文件目录
 *   3 路径分隔符:
 *       File.separator   (File类的静态属性,分隔符)
 *   4 File类的理解:
 *       1 File类的一个对象代表一个文件或一个文件目录(文件夹)。
 *       2 File类声明在java.io包下。
 *       3 File类涉及到文件或文件目录的创建、删除、重命名、获取修改时间、获取文件大小等方法,并未涉及到文件的读取和写入操作,
 *       读取和写入操作必须使用IO流实现。
 *       4 后续File类的对象常常会作为参数传递到流的构造器中,指明读取和写入的"终点"。
 *   5 File类的常用方法:
 *       1 public String getAbsolutePath():获取文件的绝对路径
 *       2 public String getPath():获取文件路径
 *       3 public String getName():获取文件的名称
 *       4 public String getParent():获取上层文件目录的路径,如果没有上层文件目录,返回null
 *       5 public long length():获取文件的长度(即字节数),不能获取文件目录的长度,会返回0
 *       6 public long lastModified():获取最后一次修改的时间,返回毫秒数
 *       7 public String[] list():获取指定文件目录下的所有文件或者文件目录的名称数组
 *       8 public File[] listFiles():获取指定文件目录下的所有文件或文件目录的File数组
 *       9 public boolean renameTo(File dest):把文件重命名为指定的路径
 *         例如: file1.renameTo(file2):要想保证返回true,需要file1在硬盘中存在,file2在硬盘中不存在。相当于将file1移动
 *         file2的位置,并将file1的文件名称改为file2的文件名称。
 *       10 public boolean isDirectory():判断是否为文件目录
 *       11 public boolean isFile():判断是否是文件
 *       12 public boolean isExit():判断是否存在
 *       13 public boolean canRead():判断是否可读
 *       14 public boolean canWrite():判断是否可写
 *       15 public boolean isHidden():判断是否隐藏
 *       16 public boolean createNewFile():创建文件,若文件存在则不创建
 *       17 public boolean mkdir():创建文件目录,如果此文件目录存在,就不创建,如果此文件目录的上层目录不存在,也不创建
 *       18 public boolean mkdirs():创建文件目录,如果此文件目录的上层目录不存在,则一并创建
 *       19 public boolean delete():删除文件或文件夹
 *          注意:java中的删除不走回收站,你连反悔的机会都没有。
 */

6 IO流

/**
 * IO流的分类:
 *   根据操作数据单位的不同:字符流、字节流。
 *   根据数据的流向不同:输入流、输出流。
 *   根据操作的对象的不同:节点流、处理流。
 * 流的体系结构:
 *   抽象基类                 节点流(文件流)                                        缓冲流(处理流的一种)
 *   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[] cubf,0,len))               BufferedWriter(write(char[] cubf,0,len)/flush())
 *
 */
FileReader和FileWriter:
public class FileReaderWriter {
    /**
     * 说明:
     *    1 read()的理解:返回读入的一个字符,如果达到文件末尾返回-1。
     *    2 异常的处理:为了保证流最终一定可以执行关闭操作,需要使用try-catch-finally处理。
     *    3 读入的文件一定要存在,否则会出现FileNotFoundException。
     */
    @Test
    // 使用FileReader实现文件读取操作,一个一个字符读取。  (1.0版本)
    public void testFileReader1(){
        FileReader fr = null;
        try {
            // 1 实例化File类的对象,指明需要操作的文件
            File file = new File("helloReader.txt");
//        System.out.println(file.getAbsolutePath());
            // 2 提供具体的流
            fr = new FileReader(file);
            // 3 数据的读入操作
            int data;
            while ((data = fr.read()) != -1){
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if(fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    // 使用FileReader实现文件读取操作,使用char[]读取,读取速度更快。  (2.0版本)
    public void testFileReader2(){
        FileReader fr = null;
        try {
            File file = new File("helloReader.txt");
            fr = new FileReader(file);
            // 读入操作
            char[] cbuf = new char[5];
            int len;
            while ((len = fr.read(cbuf)) != -1){
    //            for(int i=0;i<len;i++){
    //                System.out.print(cbuf[i]);
    //            }
                String str = new String(cbuf,0,len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fr != null)
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    /**
     * 从内存中写出数据到硬盘上:
     *    1 输出操作对应的File可以是不存在的,不会出现异常。
     *    2 如果File对应的硬盘中的文件不存在,在输出的过程中,自动创建此文件。
     *    3 File对应硬盘中的文件存在:
     *         1 如果流使用的构造器是FileWriter(file,false)/FileWriter(file):对原有的文件进行覆盖。
     *         2 如果流使用的构造器是FileWriter(file,true):不会对原有的文件进行覆盖,而是直接追加到原有文件末尾。
     */
    public void testFileWriter() {
        FileWriter fw = null;
        try {
            File file = new File("helloWriter.txt");
            fw = new FileWriter(file);
            fw.write("春天的花开带走冬天的阴霾,\n");
            fw.write("夏天夏天悄悄过去,\n");
            fw.write("你听啊秋末的落叶,\n");
            fw.write("半生风雪吹不散花落时节的眼泪。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fw != null)
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    // 使用Reader和Writer实现文本文件的复制操作
    public void testReaderWriter(){
        FileReader fr = null;
        FileWriter fw = null;
        try {
            // 不能使用字符流处理字节的数据,否则打不开。
            File source = new File("readerCopy.txt");
            File dest = new File("writerCopy.txt");
            fr = new FileReader(source);
            fw = new FileWriter(dest);
            int len;
            char[] cubf = new char[10];
            while ((len = fr.read(cubf)) != -1){
                fw.write(cubf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 最后不要忘记关闭流
            try {
                if(fr != null)
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fw != null)
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
FileInputStream和FileOutputStream:
/**
 * 字节流测试:
 *    1 对于文本文件(.txt/.java/c/cpp),使用字符流处理。
 *    2 对于非文本文件(.jpg/.mp3/.mp4/.avi/.doc/.ppt)使用字节流处理。
 *    3 使用字节流处理文本文件时,如果文本中有中文,输出到控制条会出现乱码问题,但是可以进行复制操作。
 *    4 使用字符流处理非文本文件时,例如复制,会出现复制的文件打不开的问题。
 */
public class FileInputOutputStream {
    @Test
    /**
     * 使用字节流实现对图片的复制操作
     */
    public void testInputOutputStreamCopy(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File src = new File("srcDog.jpg");
            File dest = new File("destDog.jpg");
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            byte[] bbuf = new byte[1024];
            int len;
            while ((len = fis.read(bbuf)) != -1){
                fos.write(bbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if(fis != null)
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos != null)
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 实现指定路径文件的复制,从形参传入
    public void CopyFile(String srcFile,String destFile){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File src = new File(srcFile);
            File dest = new File(destFile);
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            byte[] bbuf = new byte[1024];
            int len;
            while ((len = fis.read(bbuf)) != -1){
                fos.write(bbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void testCopyFile(){  // 花费时间:6033
        String srcFile = "D:\\视频\\伪装者 TV版\\伪装者 TV版 01_超清.kux";
        String destFile = "D:\\视频\\伪装者 TV版\\伪装者 TV版 01_超清_复制版.kux";
        long start = System.currentTimeMillis();
        CopyFile(srcFile,destFile);
        long end = System.currentTimeMillis();
        System.out.println("复制所花费的时间为"+(end-start)+"毫秒");
    }

    @Test
    // 对图片的加密操作
    public void letSecret(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("srcDog.jpg");
            fos = new FileOutputStream("srcDogSecret.jpg");
            int len;
            byte[] buffer = new byte[20];
            while ((len = fis.read(buffer)) != -1){
//                错误的写法,增强for循环是把数组中的元素重新赋给一个变量b,^运算改变的是变量b的值,并没有改变数组元素的值。
//                for(byte b:buffer){
//                    b = (byte) (b ^ 5);
//                }
                // 对写出的图片进行加密操作
                for(int i =0;i<len;i++){
                    buffer[i] = (byte) (buffer[i] ^ 5);
                }

                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis != null)
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos != null)
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    // 对图片的解密操作
    public void letNotSecret(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("srcDogSecret.jpg");
            fos = new FileOutputStream("srcDogNotSecret.jpg");
            int len;
            byte[] buffer = new byte[20];
            while ((len = fis.read(buffer)) != -1){
                // 对写出的图片进行加密操作
                for(int i =0;i<len;i++){
                    buffer[i] = (byte) (buffer[i] ^ 5);
                }

                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(97^2);
    }

}
BufferedReader和BufferedWriter:
/**
 *  使用缓冲流实现对文本文件的复制
 */
public class BufferedReaderWriter {

    @Test
    public void copyFile(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader("readerCopy.txt"));
            bw = new BufferedWriter(new FileWriter("writerCopyBuffered.txt"));
//            方式一:
//            char [] buffer = new char[20];
//            int len;
//            while ((len = br.read(buffer)) != -1){
//                bw.write(buffer,0,len);
//            }
//            方式二: 缓冲流BufferedReader可以每次读一行
            String data;
            while ((data = br.readLine()) != null){
                // 注意readLine()方法不会读出换行符,需要手动换行
                // 方式一:
//                bw.write(data+"\n");
                // 方式二:
                  bw.write(data);
                  bw.newLine();   // 换行
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(br != null)
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(bw != null)
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
BufferedInputStream和BufferedOutputStream:
/**
 * 处理流之一:缓冲流的使用
 *    作用:提高文件的读取、写入速度。
 *    提高读写速度的原因:缓冲流的内部提供了一个缓冲区。
 *    关闭流时先关外层流,再关内层流,但是外层关了之后内层自动关,就别费事了。
 *    处理流就是嵌套在已有流的基础上。
 */
public class BufferedInputOutputStream {

    // 使用缓冲流实现对非文本文件的复制
    // 实现指定路径文件的复制,从形参传入
    public void CopyFile(String srcFile,String destFile){
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            File src = new File(srcFile);
            File dest = new File(destFile);
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            byte[] bbuf = new byte[1024];
            int len;
            while ((len = bis.read(bbuf)) != -1){
                bos.write(bbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if(bis != null)
                    bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(bos != null)
                    bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void testCopyFile(){   // 花费时间:1721
        String srcFile = "D:\\视频\\伪装者 TV版\\伪装者 TV版 01_超清.kux";
        String destFile = "D:\\视频\\伪装者 TV版\\伪装者 TV版 01_超清_复制版.kux";
        long start = System.currentTimeMillis();
        CopyFile(srcFile,destFile);
        long end = System.currentTimeMillis();
        System.out.println("复制所花费的时间为"+(end-start)+"毫秒");
    }

}
InputStreamReader和OutputStreamWriter的使用:
/**
 * 处理流二: 转换流的使用
 *    1 转换流,属于字符流
 *        InputStreamReader:将一个字节输入流转换为字符输入流
 *        OutputStreamWriter:将一个字符输出流转换为字节输出流
 *    2 作用:提供字节流与字符流之间的转换
 *    3 解码:字节、字节数组------>字符、字符数组
 *      编码:字符、字符数组------>字节、字节数组
 *    4 字符集:(了解)
 *        1 ASCII: 美国标准信息交换码,用一个字节的7位就可以表示
 *        2 ISO8859-1: 拉丁码表、欧洲码表,使用一个字节的8位表示
 *        3 GB2312: 中国的中文编码表。最多2个字节编码所有的字符
 *        4 GBK: 中国的中文编码表升级,融合了更多的中文文字符号,最多2个字节编码
 *        5 Unicode: 国际标准码,融合了人类目前使用的所有字符,为每个字符分配唯一的字符码
 *        6 UTF-8: 变长的编码方式,可用1-4个字节表示一个字符
 *        7 ANSI编码: 通常指平台的默认编码,例如英文操作系统是ISO-8859-1,中文系统是GBK
 */
public class InputOutputStreamReaderWriter {
    @Test
    // 使用字节流读取文本中数据到控制台上,这里使用转换流将字节输入流转换为字符输入流
    public void readText() {
        InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(new FileInputStream("readerCopy.txt"),"utf-8");
            int len;
            char[] cubf = new char[20];
            while ((len = isr.read(cubf)) != -1) {
                String str = new String(cubf,0,len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(isr != null)
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    // 使用转换流将读取的文件使用GBK编码集写入到一个新的文件中
    public void copyTest(){
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        try {
            isr = new InputStreamReader(new FileInputStream("readerCopy.txt"),"utf-8");
            osw = new OutputStreamWriter(new FileOutputStream("writerCopyZhuanHuanLiu.txt"),"gbk");
            int len;
            char[] cbuf = new char[20];
            while ((len = isr.read(cbuf)) != -1){
                osw.write(cbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(isr != null)
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(osw != null)
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
其他处理流的使用:
/**
 * 其他流的使用:
 *    1 标准的输入、输出流:
 *    2 打印流的使用:
 *        1 两个打印流:PrintStream、PrintWriter
 *        2 构造器:new PrintStream(new FileOutputStream(String path),true) // 创建打印输出流,设置位自动刷新(写入
 *        换行符或字节 '\n' 时会自动刷新输出缓冲区)
 *        3 System.setOut(ps) // 把标准输出流的终点改为指定文件
 *    3 数据流的使用:
 *        1 两个数据流: DataInputStream   DataOutputStream
 *        2 作用: 用于读取和写出基本数据类型的变量或字符串
 *        3 从文件读取数据的时候要按写的顺序读取,否则读出来的数据会乱
 */
public class OtherStream {
    // 从键盘输入字符串,转换为大写,如果输入e或exit则退出程序
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            System.out.println("请输入字符串,输入e或exit退出程序");
            br = new BufferedReader(new InputStreamReader(System.in));
            String str;
            while ((str = br.readLine()) != null){
                if("e".equals(str) || "exit".equals(str)){
                    System.out.println("成功退出程序!");
                    break;
                }
                System.out.println(str.toUpperCase());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(br != null)
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    // 使用打印流输出ASCII字符到指定文件中
    public void printStreamTest(){
        PrintStream ps = null;
        try {
            // 创建打印流
            ps = new PrintStream(new FileOutputStream("printStream.txt"),true);
            if(ps != null){
                System.setOut(ps);
            }
            for(int i = 0; i <= 255; i++){
                System.out.print((char)i);
                if(i %30 == 0){
                    System.out.println();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(ps != null)
            ps.close();
        }
    }

    @Test
    // 练习:将内存中的字符串、基本数据类型的变量写入到文件中
    public void dataStreamWriteTest(){
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("dataStream.txt"));
            dos.writeUTF("旭哥我爱你");
            dos.writeBoolean(true);
            dos.writeInt(20);
            dos.writeDouble(1.23);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(dos != null){
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test
    // 练习:将文件中的基本数据类型、字符串读到内存中输出
    public void dataStreamRead(){
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("dataStream.txt"));
            System.out.println(dis.readUTF());
            System.out.println(dis.readBoolean());
            System.out.println(dis.readInt());
            System.out.println(dis.readDouble());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(dis != null)
                dis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
相对路径在IDEA中和Eclipse中的区别?

​ idea:如果使用单元测试方法,相对路径基于当前的Module

​ 如果使用main()方法测试,相对路径基于当前的Project

​ Eclipse:不论是单元测试方法还是main()方法,相对路径都基于当前的Projcet

处理流—对象流的使用:

1 ObjectInputStream和ObjectOutputStream:用于存储和读取基本数据类型数据或对象的处理流,它的强大之处就是可以把java中的对象写入到数据源中,也能把对象从数据源中还原回来。

2 序列化:用ObjectOutputStream保存基本数据类型或对象的机制

3 反序列化:用ObjectInputStream读取基本数据类型或对象的机制

4 ObjectOutputStream和ObjectInputStream不能序列化static或transient修饰的成员变量

5 序列化机制:对象序列化机制允许把java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点,当其他程序获取了这种二进制流,就可以恢复成原来的java对象

String类对象的序列化操作:
@Test
// 使用ObjectOutputStream将String类的对象序列化,持久化到硬盘中的文件中
public void test1(){
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream("对象流测试.txt"));
        String s = "我爱北京天安门";
        oos.writeObject(s);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(oos != null)
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
String类对象的反序列化操作:
@Test
// 将硬盘中的文件反序列化,还原为java中String类的对象
public void test2(){
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("对象流测试.txt"));
        String str = (String) ois.readObject();
        System.out.println(str);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            if(ois != null)
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
自定义Cat类:
public class Cat implements Serializable {
    private static final long serialVersionUID = 25485424157415L;  // 必须写的一个全局静态常量

    private String name;
    private Integer age;
    private transient String color;   // 表示该变量不可以被序列化
    private Integer id;
    public Cat() {
    }

    public Cat(String name, Integer age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", id=" + id +
                '}';
    }
}
自定义Cat类对象的序列化操作:
@Test
// 使用ObjectOutputStream将自定义类Cat的对象序列化,持久化到硬盘中的文件中
public void test3(){
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream("对象流测试--自定义类.txt"));
        Cat cat = new Cat("杨桃",2,"蓝色");
        oos.writeObject(cat);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(oos != null)
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
自定义类Cat类对象的反序列化操作:
@Test
// 使用ObjectInputStream把硬盘文件反序列化,还原回java对象
public void test4(){
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("对象流测试--自定义类.txt"));
        Cat cat = (Cat) ois.readObject();
        System.out.println(cat);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            if(ois != null)
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Cat类中需要满足以下条件,才可以被序列化:

1 需要实现接口:Serializable

2 当前类需要提供一个long类型的全局静态常量:SerialVersionUID (必须写,为当前类作一个标记,为了还原时能够找到相对应的类,如果不写会出现异常,找不到序列化时对应的类)

3 除了当前Cat类需要实现Serializable接口之外,还需要保证其内部的所有属性也是可序列化的(默认情况下基本数据类型可以被序列化)

RandomAccessFile(随机存取文件流)的使用:

1 RandomAccessFile直接继承于java.lang.Object类,实现了DataInput,DataOutput接口

2 RandomAccessFile既可以作为一个输入流,也可以作为一个输出流

3 如果RandomAccessFile作为输出流时,写出到的文件不存在,则在执行过程中自动创建,如果写出的文件存在,则会对原有的文件内容进行覆盖(默认情况下从头开始覆盖)

使用RandomAccessFile实现对图片文件的复制操作:
@Test
// 使用RandomAccessFile实现对图片文件的复制操作
public void test1(){
    RandomAccessFile rafRead = null;
    RandomAccessFile rafWrite = null;
    try {
        rafRead = new RandomAccessFile("RandomAccessFileSrc.png","r");
        rafWrite = new RandomAccessFile("RandomAccessFileDest.png","rw");
        byte[] buffer = new byte[20];
        int len;
        while ((len = rafRead.read(buffer)) != -1){
            rafWrite.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(rafRead != null)
            rafRead.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if(rafWrite != null)
            rafWrite.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
使用 “xyz” 替换文本文件的三个字符:
@Test
// 使用 "xyz" 替换文本文件的三个字符
public void test2(){
    RandomAccessFile raf = null;
    try {
        raf = new RandomAccessFile("RandomAccessFile文本测试.txt","rw");
        raf.write("xyz".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(raf != null)
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
使用RandomAccessFile对文本文件进行插入操作:
@Test
// 使用RandomAccessFile对文本文件进行插入操作
public void test3(){
    RandomAccessFile rafRead = null;
    RandomAccessFile rafWrite = null;
    try {
        rafRead = new RandomAccessFile("RandomAccessFile文本测试.txt","r");
        rafWrite = new RandomAccessFile("RandomAccessFile文本测试.txt","rw");
        // 实例化StringBuilder时指定底层数组的长度,防止扩容
        StringBuilder sb = new StringBuilder((int) new File("RandomAccessFile文本测试.txt").length());
        String str;
        rafRead.seek(3);
        // 使用StingBuilder把索引 3 之后的数据缓存起来
        while ((str = rafRead.readLine()) != null){
            sb.append(str);
        }
        rafWrite.seek(3);
        rafWrite.writeBytes("xyz");
        rafWrite.writeBytes(sb.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(rafRead != null)
            rafRead.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if(rafWrite != null)
            rafWrite.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7 IO流与网络编程(通过网络进行数据传输)

网络编程中有两个主要的问题:

1 如何精确的定位网络上的一台或多台主机,定位到主机上特定的应用

2 找到主机后如何进行可靠高效的数据传输

网络编程中的两个要素:

1 对应问题1:IP和端口号

2 对应问题2:提供网络通信协议,TCP/IP参考模型(应用层,传输层,网络层,物理+数据链路层),每一层有具体的协议

通信要素一:IP和端口号

1 IP:唯一表示Internet上的计算机(通信实体)

2 在Java中使用InetAddress类表示IP

3 IP的分类: IPv4 和 IPv6 ; 万维网 和 局域网

4 域名:www.baidu.com www.vip.com www.atguigu.com

5 本地回路地址:127.0.0.1 对应着 localhost

6 如何实例化InetAddress:(InetAddress对象格式是: 域名/IP地址)

​ 1 getByName(String host) : 获取指定IP,参数可以是域名

​ 2 getLocalHost() : 获取本机IP

7 InetAddress类的常用方法:

​ 1 getHostName() : 获取域名

​ 2 getHostAddress() : 获取IP地址

8 端口号的理解:

​ 1 端口号:对应正在计算机上运行的进程

​ 2 要求:不同的进程拥有不同的端口号

​ 3 范围:被规定为一个 16 位 的整数 : 0-65535

9 端口号与IP地址的组合得出一个网络套接字:Socket

InetAddress类和常用方法测试:
public static void main(String[] args) throws UnknownHostException {
    InetAddress inet = InetAddress.getByName("www.baidu.com");
    System.out.println(inet.toString());    // InetAddress对象格式是: 域名/IP地址
    System.out.println(inet.getHostName());   // 获取域名
    System.out.println(inet.getHostAddress());  // 获取IP地址
    System.out.println("*******************************");
    InetAddress localHost = InetAddress.getLocalHost();     // 获取本机的IP
    System.out.println(localHost);
    InetAddress inet2 = InetAddress.getByName("127.0.0.1");  // 获取本机IP
    System.out.println(inet2);
    System.out.println(inet2.getHostAddress());  // 获取本机IP地址
    System.out.println("*******************************");
    InetAddress inet3 = InetAddress.getByName("14.215.177.38");
    System.out.println(inet3);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQ3MpR5q-1638260550224)(C:\Users\huawei123\AppData\Roaming\Typora\typora-user-images\image-20210901102506975.png)]

通信要素二:网络通信协议

传输层两个重要协议:TCP协议 (打电话) UDP协议(发短信、信息)

实现TCP的网络编程1

例题一:客户端将信息发送给服务器,服务器将信息显示在控制台上

客户端代码步骤:

1 创建Socket对象,指明服务器端的IP和端口号

2 获取一个输出流,用于传输数据

3 用输出流写出数据

4 关闭资源

服务端代码步骤:

1 创建服务器端的ServerSocket,指明自己的端口号

2 调用accept()表示接受来自客户端的Socket

3 获取一个输入流

4 从输入流中读取数据

5 关闭资源

6 代码演示:

public class TCPTest1 {
    @Test
    // 客户端
    public void client(){
        OutputStream os = null;
        Socket socket = null;
        try {
            // 创建Socket对象,指明服务器的IP和端口号
            socket = new Socket(InetAddress.getLocalHost(), 9999);
            // 使用Socket对象获取一个输出流,用于写出数据
            os = socket.getOutputStream();
            // 用输出流写出数据
            os.write("你好啊,我是客户端".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            try {
                if(os != null)
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(socket != null)
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    @Test
    // 服务端
    public void server(){

        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            // 创建服务端的ServerSocket,指明自己的端口号
            serverSocket = new ServerSocket(9999);
            // 调用accept()表示接受来自客户端的Socket
            socket = serverSocket.accept();
            // 获取一个输入流
            is = socket.getInputStream();
            // 从输入流中读取数据
            byte[] buffer = new byte[5];
            int len;
            baos = new ByteArrayOutputStream();
            while ((len = is.read(buffer)) != -1){
                baos.write(buffer,0,len);
            }

            String s = baos.toString();
            System.out.println(s);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            try {
                if(baos != null)
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(is != null)
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(socket != null)
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(serverSocket != null)
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

反射机制

理解类的加载过程:(Class类是带泛型的)

1 程序经过javac.exe编译命令以后,会生成一个或多个字节码文件(.class)。

2 接着我们使用java.exe对某个字节码文件进行解释运行,相当于将这些字节码文件加载到内存中,此过程就成为类的加载,加载到内存中的类,我们就称为运行时类,作为Class类的一个实例。

3 换句话说,Class的实例就对应着一个运行时类。

4 加载到内存中的运行时类,会缓存一定的时间,在此时间段内,我们可以通过不同的方式获取此运行时类。

获取Class实例的四种方式:(前三个需要掌握!!)

方式一:调用运行时类的属性 .class :Class clazz1 = Person.class;

方式二:通过运行时类的对象:Class clazz2 = person.getClass();

方式三:调用Class类的静态方法 forName(String classPath):Class clazz3 = Class.forName(“com.atguigu.java.Person”);

方式四:使用类的加载器:Class clazz3 = ReflectTest.class.getClassLoader().loadClass(“com.atguigu.java.Person”);

基本上啥都可以获取Class类的实例(枚举类、注解、外部类、内部类、基本数据类型、数组、接口、Class、void),只要数组的元素类型和维度一样,都是同一个Class类实例
使用ClassLoader加载配置文件:

注意使用类的加载器读取配置文件时,此时文件的默认的相对路径在src下,不是当前工程下。

通过反射创建运行时类的对象:

在javabean中,要求提供一个空参的构造器的原因:

​ 1 便于通过反射,创建运行时类的对象

​ 2 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

@Test
public void test1() throws IllegalAccessException, InstantiationException {
    Class<Cat> clazz = Cat.class;
    // 调用此方法创建对应运行时类(Cat)的对象,内部调用的是Cat类的空参构造器,所以Cat类必须提供空参的、访问权限够用(通常设为public)的构造器
    Cat cat = clazz.newInstance();
    System.out.println(cat);
}
举例体会反射的动态性:

编译时不知道创建哪个类的对象,只有在运行的时候才确定要创建哪个类的对象

/**
 * 举例体会反射的动态性
 */
public class ReflectionTest {
    // 体会反射的动态性
    public static void main(String[] args) {
        Random random = new Random();
        ReflectionTest rt = new ReflectionTest();
        int num = random.nextInt(3); // 0-3
        String classPath;
        if(num == 0){
            classPath = "java.util.Date";
        }else if(num == 1){
            classPath = "java.lang.Object";
        }else {
            classPath = "reflect.Cat";
        }
        Object instance = null;
        try {
            instance = rt.getInstance(classPath);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(instance);
    }

    public Object getInstance(String classPath) throws Exception {
        Class<?> clazz = Class.forName(classPath);
        return clazz.newInstance();
    }
}
通过反射获取运行时类内部的结构:
通过反射调用运行时类内部的结构:

反射的应用—动态代理

java8新特性

Lambda表达式的使用:

1 举例:

2 格式:

3 Lambda表达式的使用(6种情况)

理解类的加载过程:(Class类是带泛型的)

1 程序经过javac.exe编译命令以后,会生成一个或多个字节码文件(.class)。

2 接着我们使用java.exe对某个字节码文件进行解释运行,相当于将这些字节码文件加载到内存中,此过程就成为类的加载,加载到内存中的类,我们就称为运行时类,作为Class类的一个实例。

3 换句话说,Class的实例就对应着一个运行时类。

4 加载到内存中的运行时类,会缓存一定的时间,在此时间段内,我们可以通过不同的方式获取此运行时类。

获取Class实例的四种方式:(前三个需要掌握!!)

方式一:调用运行时类的属性 .class :Class clazz1 = Person.class;

方式二:通过运行时类的对象:Class clazz2 = person.getClass();

方式三:调用Class类的静态方法 forName(String classPath):Class clazz3 = Class.forName(“com.atguigu.java.Person”);

方式四:使用类的加载器:Class clazz3 = ReflectTest.class.getClassLoader().loadClass(“com.atguigu.java.Person”);

基本上啥都可以获取Class类的实例(枚举类、注解、外部类、内部类、基本数据类型、数组、接口、Class、void),只要数组的元素类型和维度一样,都是同一个Class类实例
使用ClassLoader加载配置文件:

注意使用类的加载器读取配置文件时,此时文件的默认的相对路径在src下,不是当前工程下。

通过反射创建运行时类的对象:

在javabean中,要求提供一个空参的构造器的原因:

​ 1 便于通过反射,创建运行时类的对象

​ 2 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

@Test
public void test1() throws IllegalAccessException, InstantiationException {
    Class<Cat> clazz = Cat.class;
    // 调用此方法创建对应运行时类(Cat)的对象,内部调用的是Cat类的空参构造器,所以Cat类必须提供空参的、访问权限够用(通常设为public)的构造器
    Cat cat = clazz.newInstance();
    System.out.println(cat);
}
举例体会反射的动态性:

编译时不知道创建哪个类的对象,只有在运行的时候才确定要创建哪个类的对象

/**
 * 举例体会反射的动态性
 */
public class ReflectionTest {
    // 体会反射的动态性
    public static void main(String[] args) {
        Random random = new Random();
        ReflectionTest rt = new ReflectionTest();
        int num = random.nextInt(3); // 0-3
        String classPath;
        if(num == 0){
            classPath = "java.util.Date";
        }else if(num == 1){
            classPath = "java.lang.Object";
        }else {
            classPath = "reflect.Cat";
        }
        Object instance = null;
        try {
            instance = rt.getInstance(classPath);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(instance);
    }

    public Object getInstance(String classPath) throws Exception {
        Class<?> clazz = Class.forName(classPath);
        return clazz.newInstance();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值