Java进阶

 

  • idea和eclipse
    • eclipse的快速生成
      • main函数:main
      • 输出语句:syso
    • idea的快速生成
      • main函数:psvm
      • 输出语句:sout
    • 在idea中一个project相当于eclipse当中的一个workspace,在空的工程下新建Module(模块),IDEA中模块类似于eclipse当中的project
      • eclipse的组织方式:workspace->project
      • idea的组织方式:project->module
    • idea的字体设置:
      • file->settings->输入font->设置字体样式以及字号大小
    • idea是自动保存,不需要ctrl+s
    • idea添加构造方法/setXxx/getXxx:alt+insert
  • 抽象类
    • 什么是抽象类?
      • 类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类,类本身是不存在的,所以抽象类无法创建对象,无法实例化。
    • 抽象类属于什么数据类型?
      • 抽象类也属于引用数据类型,编译之后也是一个class字节码文件
    • 抽象类怎么定义?
      • [修饰符列表]  abstract   class 类名{
        • 类体;
      • }
    • 抽象类是无法实例化的,无法创建对象,所以抽象类都是用来被子类继承的,所以final和abstract不能联合使用,这两个关键字是对立的
    • 抽象类的子类可以是抽象类
    • 抽象类虽然无法实例化,但抽象类有构造方法,这个构造方法供子类使用
    • 抽象类关联到一个概念,抽象方法,什么是抽象方法呢?
      • 抽象方法只有方法的声明,没有方法体,以分号结尾
      • public  abstract  void  doSome();
    • 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
    • 重要结论:
      • 一个非抽象的类继承抽象类,必须将抽象类中的抽象方法重写
      • 一个抽象的类继承抽象类,可以不去将抽象类中的抽象方法重写
    • 抽象类和非抽象之间可以使用多态,面向OCP原则
    • 面试题:Java语言中凡是没有方法体的方法都是抽象方法
      • 不对,错误的
      • Object类中就有很多方法没有方法体,都是以;结尾的,但他们都不是抽象方法,例如:
        • public native int hashCode();
        • 这个方法底层调用了C++写的动态链接库程序
        • 前面修饰符列表中没有abstract,有一个native,表示调用JVM本地程序
  • 接口
    • 接口也是一种引用数据类型,编译之后也是一个class字节码文件
    • 接口是完全抽象的,(抽象类是半抽象的),或者也可以说接口是特殊的抽象类,特殊在他是完全抽象的
    • 接口怎么定义,语法是什么?
      • [修饰符列表]  interface  接口名{}
    • 接口支持多继承,一个接口可以继承多个接口
    • 接口中只包含两部分内容,一部分是常量,一部分是抽象方法,接口中的所有元素都是public修饰的
    • 接口中的抽象方法定义时,public abstract修饰符可以省略
    • 接口中的常量,public static final可以省略
    • 类和类是继承(单继承),接口和接口是继承(多继承),类和接口之间是实现implements(多实现)
    • 当一个非抽象类实现接口,必须将接口中的所有抽象方法全部实现
    • 接口和非抽象之间可以使用多态,面向OCP原则
    • 继承和实现都存在的话,extends关键字在前,implements关键字在后,如果没写继承,则默认继承Object类
    • 接口在开发中的作用:
      • 注意:接口在开发中的作用类似于多态在开发中的作用:
      • 多态:面向抽象编程(将Dog/Cat写成Animal),不要面向具体编程,降低程序的耦合度,提高程序的扩展力、
      • 总结:面向接口编程,可以降低程序的耦合度,提高程序的扩展力
      • 接口离不开多态,接口+多态才可以达到降低程序的耦合度,提高程序的扩展力
    • 类型和类型之间的关系:
      • is a
        • Cat is a Animal(猫是一个动物)
        • 凡是能够满足is a的表示“继承关系” 
        • A extends B 
      • has a
        • I have a Pen(我有一只笔)
        • 凡是能够满足has a关系的表示“关联关系”
        • 关联关系通常以“属性的形式存在”
        • A{
        •    B b;
        • }
      • like a:
        • Cooker like a FoodMenu(厨师像一个菜单一样)
        • 凡是能够满足like a关系的表示“实现关系”
        • 实现关系通常是:类实现接口
        • A implements B
    • 抽象类和接口的区别:
      • 抽象类是半抽象的
      • 接口时完全抽象的
      • 抽象类中有构造方法
      • 接口中没有构造方法
      • 类之间是单继承
      • 接口之间可以多继承
      • 类可以实现多个接口
      • 接口中只允许出现常量和抽象方法
  • Object类中的方法在哪找?
    • 第一:去源代码当中(但是这种方式比较麻烦,因为源代码也比较难)
    • 第二: 去查阅java的类库的帮助文档
  • 什么是API(Application Program Interface)?
    • 应用程序编程接口
    • 整个JDK类库就是一个javase的API
    • 每一个API都会配置一套API帮助文档
  • Object类需要了解的方法:
    • protected Object clone()//负责对象克隆
    • public int hashCode()//获取对象哈希值
    • public boolean equals(Object obj)//判断两个对象是否相等
    • public String toString()//将对象转换成字符串形式
    • protected void finalize()//垃圾回收器负责调用的方法
  • Object的toString方法
    • public String toString() {
      • return getClass().getName() + "@" + Integer.toHexString(hashCode());
    • }
    • 返回的是:类名@对象的内存地址通过哈希算法转换为十六进制的形式
    • SUN公司设计toString方法的目的?
      • toString方法的设计目的是:通过调用这个方法可以将一个“Java对象”转换成字符串形式
    • SUN公司开发Java语言的时候,建议所有的子类都去重写toString方法
    • 输出引用的时候。默认会调用引用的toString方法
  • Object的equals方法
    • public boolean equals(Object obj){
      • return (this == obj);
    • }
    • 以后编程的过程当中,都要通过equals方法来判断两个对象是否相等
    • 判断基本数据类型是否相等直接使用“==”就可以,==判断的是值的大小,所以引用数据类型不能用“==”判断
    • 在Object类的equals方法中,默认采用的是“==”判断两个Java对象是否相等,而“==”判断的是两个对象的内存地址是否相等,所以老祖宗的equals方法不够用,需要重写equals方法
    • 改良之后的代码

    • 再次改良之后的代码 

    • 判断基本数据类型的数据用==,判断引用数据类型的数据用equals
  •  String类

    • String类已经重写了equals方法,比较两个字符串相等不能使用==,必须使用equals
    • String类已经重写了toString方法
  • Object的finalize方法
    • protected void finalize()  throws Throwable { }
    • finalize()方法有一个没有代码的方法体,而且这个方法是protected修饰的
    • 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法,不像equals,toString方法是需要写代码调用的,finalize方法只需要重写,重写完将来自动会有程序来调用
    • 当一个Java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法
    • finalize()方法实际上是SUN公司为Java程序员准备的一个时机,垃圾销毁时机,如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中
    • java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到,种种条件下,又可能启动,也有可能不启动
    • 有一段代码可以建议垃圾回收器启动:System.gc();,只是建议,可能启动,也可能不启动,只是启动的概率高了一些
  • Object的hashCode方法
    • public native int hashCode();
    • 这个方法不是抽象方法,带有native关键字,底层调用C++程序
    • hashCode方法返回的是哈希码
      • 实际上就是一个Java对象的内存地址,经过哈希算法,得出的一个值
      • 所以hashCode方法的执行结果可以等同看做一个Java对象的内存地址
  • 内部类:
    • 在类的内部又定义了一个新的类。被称为内部类
    • 分类:
      • 实例内部类:类似于实例变量
      • 静态内部类:类似于静态变量
      • 局部内部类:类似于局部变量
        • 不能使用访问权限修饰符和static修饰
      • 匿名内部类:
        • 匿名内部类是一种特殊的局部内部类,属于局部内部类的一种,它是通过匿名类实现接口
        • 学习匿名内部类主要是以后阅读别人的代码时,可以理解,并不代表以后都要这样写,因为匿名内部类有两个缺点:
          • 缺点一:太复杂、太乱、可读性差
          • 缺点二:类没有名字,以后想重复使用,不能用
    • 匿名内部类例子:
    • 内部类的修饰符:private、protected、public及默认修饰符 
    • dosome方法中的局部内部类,在其他方法内不能使用
    • 使用内部类编写的代码可读性很差,能不用尽量不用 
  • 数组( 数组没有对应的类文件)
    • 一维数组:
      • 数组是一种引用数据类型,不属于基本数据类型,父类也是Object
      • 数组实际上是一个容器,可以同时容纳多个元素
      • 数组当中可以存储“基本数据”类型的数据,也可以存储“引用数据类型”的数据
      • 数组因为是引用数据类型,所以数组对象在堆内存当中
      • 数组当中如果存储的是java对象的话,实际存储的是对象的“引用(内存地址)” 

      • 数组一旦创建,长度不可变
      • 数组的分类:一维数组、二位数组、三维数组、多维数组...........
      • 所有的数组对象都有length属性,用来获取数组中元素的个数
      • Java中的数组要求数组中元素的类型统一,比如int类型数组只能存储int类型,Person类型数组只能存储Person类型
      • 数组在内存方面存储的时候,数组中的元素内存地址是连续的,这是数组存储元素的特色,数组实际上是一种简单的数据结构
      • 所有数组都是拿第一个小方框的内存地址作为整个数组的内存地址,因为这样就可以算出后面数组的内存地址
      • 数组中每一个元素都是有下标的,下标从0开始,以1递增,最后一个数组的下标是length-1
      • 数组数据结构的优点和缺点?
        • 优点:
          • 查询/查找/检索某个下标上的元素时效率极高,可以说查询效率最高的一个数据结构
            • 为什么检索效率高?
            • 第一:数组中元素的内存地址在空间存储上是连续的
            • 第二:每一个元素类型相同,所以占用空间大小一样
              • 对于基本数据类型,保存的是数据,每个元素占用空间大小相同,对于引用数据类型,数组保存的是内存地址,而内存地址占用的空间大小是一样的
            • 第三:知道第一个元素的内存地址,有知道下标,所以通过一个数学表达式就可以某个下标上元素的内存地址,然后直接通过内存地址定位元素,所以数组的检索效率最高
            • 数组中存储100个元素,或者100万个元素,在元素检索方面,效率是一样的,因为数组中的元素查找的时候,不会一个一个找,是通过数学表达式计算出来的(算出一个内存地址,直接定位)
        • 缺点:
          • 第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或向后移的操作
          • 第二:数组不能存储大数据量,因为很难在内存空间上找到一快特别大的内存空间
          • 注意:对于数组中最后一个元素的增删,是没有效率影响的
      • 怎么声明/定义一个一维数组?
        • int[] array1;或int ayyar1[];//不建议这种,这是C++风格
        • double[] array2;
        • String[] array3;
        • Object[] array4;
      • 怎么初始化一个一维数组?
        • 包括两种方式:静态初始化一维数组、动态初始化一维数组
        • 静态初始化一维数组(定义的同时初始化):
        • int[] array={1,2,,3,4,5};
        • int[] array=new int[]{1,2,3,4,5};
        • 动态初始化一维数组(先定义,后初始化):
        • int[] array=new int[5];//5表示数组元素的个数,每个元素默认值0
        • 什么时候采用静态初始化方式,什么时候采用动态初始化方式?
        • 当创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式
        • 当创建数组的时候,不确定数组中存储哪些元素时,采用动态初始化方式来预先分配内存空间、
          • 对于静态初始化,上面这种方式不能,但可以int[] array={1,2,3,4};然后将array作为实参传进去或者形参写成new int[]{1,2,3,4}
      • public static void main(String[] args){}

        • System.out.println(args.length);//值是0,代表数组创建了只是没有任何数据,args并不是null

        • 其实这个数组是留给用户的,用户可以在控制台输入参数
        • 例如这样运行程序:java ArrayTest abc def xyz
        • 那么这个时候JVM自动将”abc def xyz“通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组中
        • 把abc def xyz 转换成字符串数组{“abc”,“def”,“xyz”}
      • 数组的扩容:

        • 在Java开发中,数组长度一旦确定不可变,那么数组满了怎么办?
        • 数组满了,需要扩容
        • Java中对数组的扩容:先建一个大容量的数组,然后将小容量的数组中的数据一个一个拷贝到大数组当中
        • 结论:数组扩容效率较低,因为涉及到拷贝的问题,所以在以后的开发中请注意,尽可能少的进行数组的拷贝,可以在创建对象的时候预估计多少合适,最好预估准确,这样可以减少数组的扩容次数,提高效率
          •  对象拷贝的是对象的内存地址
    • 二维数组:

      • 二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组
        • 三维数组其实是一个二维数组,特殊在这个二维数组当中的每一个元素是一个一维数组
      • 二维数组的静态初始化:
        • int[][] a={{1,2,3,},{4},{5,6,7}};
        • int[][] array=new int[][]{{1,2,3,},{4},{5,6,7}};
      •  二维数组的动态初始化:

        • int array[][]=new int[3][4]; 
    • 常见的算法:
      • 排序算法:冒泡排序、选择排序.......
      • 查找算法:二分法查找.........
      • 以上的算法其实在Java中已经封装好了,直接调用就可以,只不过面试可能会碰上
      • 算法在Java中不需要精通,Java已经封装好,要排序调用方法就行,例如:Java中提供了一个数组工具类:
      • java.util.Arrays;//Arrays是一个数组工具类,其中sort()方法可以排序,是静态方法,直接使用类名调用就行
      • 冒泡排序:
        • //5 3 2
        • //3 5 2
        • //3 2 5 
        • //2 3
      •  选择排序:

        • 选择排序比冒泡排序的效率高,高在交换位置的次数上,选择排序的交换位置是有意义的
        • 循环一次,然后找出参加比较的这堆数据中最小的,拿着这个最小的值和最前面的数据交换位置
        •  选择排序和冒泡排序的比较次数没变,但是交换次数明显减少了

      • 二分法查找:二分法查找要建立在排序的基础之上

        • 二分法查找效率要高于“一个挨着一个”的这种查找方式

        • 二分法查找算法的终止条件:一直折半,直到中间的那个元素恰好是被查找的元素

    •  Arrays工具类:

      • Arrays是SUN公司为程序员已经写好的一个工具类,所有方法都是静态的,直接类名调用
      • java.util.Arrays
      • int[] arr={10,21,30,5,3,8,19,22};
      • Arrays.binarySearch(arr,22);//用来对数组进行二分法查找
      • Arrays.sort(arr);//用来对数组进行排序
      • 主要使用两个方法:查找和排序
  • String类
    •  无论String a=“abc”;还是String a=new String("abc");a中都保存的是内存地址

    • 所以字符串比较的时候不能用双等号,双等号不保险 ,String类已经重写了equals方法,所以应该调用String类的equals方法

    •  String a="abc",  a.equals("abc");  和  "abc".equals(a)  建议使用 "abc".equals(a),可以避免空指针异常

    • 关于String类的常用构造方法:
    • String类的常用方法:

      •  charAt方法:
      • compareTo方法:

      • contains方法

      •  endsWith方法

      • startWith方法
      • equals方法

      • equalsIgnoreCase方法

      • getBytes方法
      • indexOf方法
      • lastIndexOf方法

      • isEmpty方法:length长度为0,则为空,

        • 判断数组长度和字符串长度不一样:
          • 判断数组长度是length属性
          • 判断字符串长度是length方法
      • replace方法
      • split方法

      • substring方法

      •  toCharArray方法

      •  toLowerCase方法

      • toUpperCase方法

      • trim方法

      •  valueOf方法(String中只有这一个方法是静态的,不需要new对象)

        • printin()方法在底层都会调用String.valueOf()
  •  StringBuffer类

    • 思考:我们在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题?
      • 因为java中的字符串是不可变的,每一次拼接都会产生新的字符串
      • 这样会占用大量的方法区内存,造成内存空间的浪费
        • String s="abc";
        • s+="hello";
        • 就以上两行代码,就导致在方法区字符串常量池中创建了3个对象:
          • "abc"
          • "hello"
          • "abchello"
    • 如果以后需要进行大量的字符串拼接操作,建议使用JDK中自带的:
      • java.lang.StringBuffer
      • java.lang.StringBuilder
    • 如何优化StringBuffer的性能?
      • 在创建StringBuffer的时候尽可能给定一个初始化容量
      • 最好减少底层数组的扩容次数(因为每扩容一次底层就会调用System.arraycopy方法),预估计一下,给一个大一点些的初始化容量
    • String不可变:
    • StringBuffer可变:
    • StringBuffer和StringBuilder区别:

      • StringBuffer中的方法都有:synchronizaed关键字进行修饰,表示StringBuffer在多线程环境下是安全的
      • StringBuilder中的方法都没有:synchronizaed关键字进行修饰,表示StringBuilder在多线程环境下运行是不安全的
    • 面试题:
      • String为什么是不可变的?
        • 我看过源代码,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变,并且被final修饰的引用一旦指向某个对象之后,不可再指向其他对象,所以String是不可变的!
          • jdk1.8及以前String使用的是char数组,jdk1.9及以后使用的是byte数组。
        • “abc”无法变成“abcd”
      • StringBuilder和StringBuffer为什么是可变的呢?
        • 我看过源代码,StringBuffer和StringBuilder内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer和StringBuilder的初始化容量我记得应该是16,当存满之后,底层调用了数组拷贝的方法,System.arrayCopy()方法,是这样扩容的,所以StringBuilder和StringBuffer适合于使用字符串的频繁拼接
  • 包装类:
    • 装箱和拆箱:
      • 装箱:将基本数据类型转换为包装器类型;
      • 拆箱:将包装器类型转换为基本数据类型
    • 构造方法
    • 访问最大值和最小值
    • 自动装箱和拆箱

        • ==这个运算符不会触发自动拆箱机制,只有+ - * /等运算的时候才会触发
    • 数字格式化异常:
    • 字符串转换为int类型

  • java对日期的处理 

      • 注意:字符串的格式的日期格式和SimpleDateFormat对象指定的日期格式要一样,不然会出现异常
    • 获取自1970年1月1日 00:00:00 000到当前系统的总毫秒数

  •  简单总结一下System类相关属性和方法:

      • System.out【out是System类的静态变量】
      • System.out.println()【println()方法不是System类的,是PrintStream类的方法】
      • System.gc()【建议启动垃圾回收器】
      • System.currentTimeMillis【获取自1970年1月1日 00:00:00 000到当前系统的总毫秒数】
      • System.exit(0)【退出JVM】
      • System.arraycopy(.....)【数组的扩容,拷贝数组】
        • public static native void arraycopy(Object src,  int  srcPos,
          Object dest, int destPos,
          int length);
  • 数字格式化:
  • BigDecimal

  •  Random

  •  枚举

    • 思考:以上的这个方法设计没毛病,挺好,返回true和false两种情况,但是在以后的开发中,有可能遇到一个方法的执行结果可能包括三种情况,四种情况,五种情况等等,但是每一种情况都是可以数清楚的,一枚一枚列举出来的,这个布尔类型就无法满足了,此时需要使用Java中的枚举类型

    •  枚举:一枚一枚列举出来的,才建议使用枚举类型

    • 枚举编译之后也是生成class文件
    • 枚举也是一种引用数据类型
    • 枚举中的每一个值可以看作是常量
    • 枚举怎么定义:
      • enum 枚举类型名{
        • 枚举值1,枚举值2;
      • }
    • 结果只有两种情况的建议使用布尔类型,结果超过2种并且还是可以一枚一枚列举出来的,建议使用枚举
      • 例如:颜色、四季、星期
    • switch在高版本也支持枚举
  • 异常:

    • UML(统一建模语言):
      • 画UML图的工具有很多,例如Rational Rose(收费的)、starUML等...
      • 什么是UML?有什么用?
        • UML是一种统一建模语言
        • 一种图标式语言(画图的)
        • UML不是只有java中使用,只要是面向对象的编程语言,都有UML
        • 一般画UML图的都是软件架构师/系统分析师,这些级别的人员使用的
        • 在UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等
        • 在Java软件开发当中,java软件开发人员必须能看懂
    • java异常的结构图: 

      •  错误分为:语法错误、运行错误、逻辑错误

        • 语法错误:
          • 是指由于编程中输入不符合语法规则而产生的。程序编译就通不过,程序不能运行起来。此类错误最简单,调试起来比较容易。
          • 表达式不完整、缺少必要的标点符号、关键字输入错误、数据类型不匹配、循环语句或选择语句的关键字不匹配等。通常,编译器对程序进行编译的过程中,会把检测到的语法错误以提示的方式列举出来,又称为编译错误。
        • 运行错误:
          • 指程序在运行过程中出现的错误。程序通过语法错误检测,但是运行的时候出现错误,导致程序被迫终止,此类错误有特定的发生条件,因此能够准确的定位错误代码段,因而调试也比较方便。
          • 除法运算时除数为0 、数组下标越界、文件打不开、磁盘空间不够、数据库连接错误等。
        • 逻辑错误:
          • 程序运行后,没有得到设计者预期的结果,这就说明程序存在逻辑错误。这种错误在语法上是有效的,但是在逻辑上是错误的。
          • 使用了不正确的变量,指令的次序错误,循环的条件不正确,程序设计的算法考虑不周全等。
      • 编译时异常又被称为受检异常/受控异常,运行时异常又被称为未受检异常/未受控异常

         编译时异常和运行时异常,都是在运行阶段发生的,因为程序只有在运行阶段才可以new对象,异常的发生就是new异常对象,编译阶段异常是不会发生的,

      • 编译时异常为什么而得名?

        • 因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,而得名
      •  编译时异常和运行时异常区别:
        • 编译时异常一般发生的概率比较高
          • 举个例子:
            • 你看到外面下雨,倾盆大雨
            • 你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)
            • 而且这种异常的概率很高,所以我们出门之前要拿一把伞
            • 拿一把伞就是对生病异常的发生之前的一种处理方式
        • 运行时异常一般发生的概率比较低
          • 举个例子
            • 小明走在街上,可能会被天上的飞机轮子砸到
            • 被飞机轮子砸到也算一种异常,
            • 但是这种异常发生概率低
            • 在出门之前你没必要对这种发生概率较低的异常进行预处理
            • 如果你预处理这种异常,你将活得很累
        • 假设Java中没有对异常进行划分,没有分为,编译时异常和运行时异常,所有的异常都需要在编写程序阶段进行预处理,将是怎样的效果呢?
          • 首先,这样的话,程序的绝对安全的,但是程序员编写代码太累,到处都是处理异常的代码
    • Java语言对异常的处理方式包括两种:
      • 第一种方式:在方法的声明位置使用throws关键字
        • 异常发生之后,如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生了,只有一个结果,终止java程序
      • 第二种方式:使用try...catch方式进行异常的捕捉
    • 异常处理方式代码:

      • 向上抛:
      •  try...catch

    • 注意:

      • 只要异常没有捕捉,采用上报的方式,后续的代码就不会执行 

      •  另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行

      • try..catch捕捉异常之后,后续的代码可以执行
    • try..catch深入
      • catch小括号中的类型,可以是具体的异常类型,也可以是异常类型的父类型
      • catch可以写多个,建议catch的时候,精确的一个一个处理,这样有利于程序的调试
      • catch写多个的时候,从上到下,必须从小到大 

      • JDK8的新特性:
    • 异常对象有两个非常重要的方法:

      • 获取异常简单的描述信息
        • String msg=exception.getMessage();
      • 打印异常追踪信息
        • exception.printStackTrace();
      • java后台打印异常堆栈追踪信息采用了异步线程的方式
    • 发生异常和打印堆栈信息
      • 发生异常
      • 打印堆栈信息 
    •  我们以后查看异常追踪信息,应该怎么看,可以快速调试程序?

      • 异常追踪信息,从上往下一行一行看
      • 但需要注意,SUN公司写的代码就不用看了(看包名就知道是自己的还是SUN的),主要问题是出现在自己编写的代码上
    • finally
      • 补充:如果初始化的语句用在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译不通过。如果在catch或finally里也有,则可以通过编译。
      • 发生异常后,引用就赋不上值,就为空
      • 总之:放在finally中的代码是一定会执行的,但是try中如果是System.exit(0),finally就不执行了
    • 自定义异常类:
    • finalize()是什么?
      • 垃圾回收机器(Garbage Collection),也叫GG,垃圾回收器主要有以下特点:
      • 1. 当对象不再被程序所使用的时候,垃圾回收器将会将其回收

      • 2. 垃圾回收是在后台运行的,我们无法命令垃圾回收器GC马上回收资源,但是我们可以告诉他可以尽快回收资源(System.gc()和Runtime.getRuntime().gc())

      • 3. 垃圾回收器在回收某个对象的时候,首先会调用该对象的finalize()方法

      • 4. GC主要针对堆内存

    •  final、finalize、finally的区别?

      • final是一个关键字,表示最终的,不可变的
      • finally也是一个关键字,和try联合使用,使用在异常处理机制中,finally语句块中的代码是一定会执行的
      • finalize()是Object类中的一个方法,作为方法名出现,所以finalize是标识符,这个方法是由垃圾回收器GC负责调用的,对象一旦调用了finalize方法就表示标记为可回收的,之后会被垃圾回收器回收
    • throws和throw区别
      • throws在方法的声明位置上使用,表示上报异常给声明者
      • throw手动抛出异常
  • 集合
    • 什么是集合?有什么用?
      • 集合实际上就是一个容器,可以来容纳其他类型的数据,但数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型
    • 集合为什么说在开发中使用较多?
      • 集合是一个容器,是一个载体,可以一次容纳多个对象,在实际开发中,假设连接数据库,数据库当中有10条数据,那么假设把这10条记录查询出来,在Java程序中会将10条数据封装成10个Java对象,然后将10个Java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将数据一个个展现出来
    • 集合不能直接存储基本数据类型的数据,另外集合也不能直接存储java对象,集合当中存储的是java对象的内存地址(或者说,集合存储的是引用)
      • list.add(100);//其实是自动装箱Integer
      • 注意:
        • 集合在java中本身是一个容器,是一个对象
        • 集合中任何时候存储的都是引用
    • 数组和集合的区别:
      • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型
      • 当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。
    • 集合的内存空间可以连续也可以不连续,元素类型可以相同也可以不同
    • 在java中每一个不同的集合,底层会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构当中,什么是数据结构? 数据存储的结构就是数据结构,不同的数据结构,存储方式不同,例如:数组、二叉树、链表、哈希表等等

    • 在Java集合这一章,需要掌握的不是精通数据结合,Java中已经将数据结构实现了,已经写好了常用的集合类,你只需要掌握怎么用?在什么情况下选择哪一种集合去使用即可
    • 集合在java.util.*;下,所有的集合类和集合接口都在java.util包下
    • java集合分为两大类:
      • 一类是单个方式存储元素:
        • 单个方式存储元素,这一类集合中的超级父接口:java.util.Collection
      • 另一类是键值对儿的方式存储元素:
        • 以键值对的方式存储元素,这一类集合中的超级父接口:java.util.Map
    • 集合的继承结构图
        • 这里列举的List和Set只是常用的,还有其他的
      • 类之间的关系
        •  依赖:一个类作为另一个类的返回值类型,形参类型,局部变量类型(虚线箭头)
        • 关联:一个类作为另一个类的成员变量类型 (实线箭头)
        • 继承:子类和父类之间,子接口和父接口之间(实线空心三角)
        • 实现:子类和接口之间(虚线空心三角)
      • 总结: 

        • ArrayList:底层是数组
          • ArrayList初始化容量是10,扩容是增长到原来的1.5倍,原来时4,现在就是6(4+4>>1=6)
            • 但是底层先创建了一个容量为0 的数组,当添加第一个元素的时候,初始化容量为10(优化了)
          • ArrayList底层是Object类型的数组Object[]
          • ArrayList集合因为底层是数组,所以尽可能少的扩容,因为数组扩容效率比较低,因为数组一旦创建长度不可变,数组扩容必须创建一个新的数组,然后进行拷贝,建议使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量
          • 数组优点是检索效率比较高,缺点是随机增删效率低,并且数组无法存储大数据量的元素,因为很难找到一块非常巨大的连续的内存空间,但向数组末尾增删元素效率很高,不受影响,检索效率高是因为每个元素占用空间大小相同,内存地址是连续的,知道首元素地址,知道下标,通过数学表达式计算出元素的内存地址,所以检索效率高
          • 面试题:这么多集合中,你用哪个最多?
            • 答:ArrayList集合
            • 因为数组末尾添加元素,效率不受影响,一般也都是在末尾加元素
            • 另外,我们检索/查找某个元素的操作比较多,数组检索效率高
        • LinkedList:底层是双向链表
          • 链表的优点:由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率效率较高,在以后的开发中,如果遇到随机增删集合中的元素的业务比较多的时候,建议使用LinkedList
          • 链表的缺点:不能通过数学表达式计算被查找的内存地址,每一次查找都是从头节点开始遍历,直到找到为止,所以LinkedList集合检索/查找效率较低
          • 链表没有初始化容量
          • 补充:一般集合添加元素都是在末尾添加,所以ArrayList用的多,而且ArrayList集合是非线程安全的,所以效率高
        • Vector:底层是数组,线程安全的,效率低,使用较少
        • HashSet:底层是HahMap,放到HashSet集合中的元素,等同于放到HashMap集合的key部分了
        • TreeSet:底层是TreeMap,放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了
        • HashMap:底层是哈希表
        • Hashtable:底层是哈希表,只不过是线程安全的,效率较低,很少使用
        • Properties:是线程安全的,并且key和value只能存储字符串String
        • TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
    • 关于java.util.Collection接口中的常用方法
    • 迭代器:

        • 集合对象只要发生改变(增加/删除/修改),迭代器必须重新获取 

        • 当集合结构发生了改变,迭代器没有重新获取时,调用next方法时:java.util.ConcurrentModificationException
      • 迭代器删除元素和集合删除元素区别
        • 集合删除元素
        • 迭代器删除元素
        • 结论:在迭代元素的过程中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素
        • 获取集合迭代器对象,迭代器用来遍历集合,此时相当于当前集合的状态拍了一个快照,迭代器迭代的时候会参照这个快照进行迭代,当用集合自带的remove方法删除元素,和快照对比发现不一样,再调用next方法时就会出现异常,而迭代器的remove会将快照也进行改变,所以可以
    • boolean contains(Object o)
      • 是判断集合是否包含某个元素,包含返回true,不包含返回false
      • contains方法底层是怎么判断集合中包含某个元素呢?
        • 调用了equals方法进行比对
        • equals方法返回true,就表示包含这个元素
      •  结论:所以放在集合中的元素需要重写equals方法 

    • boolean remove(Object o)

      • 删除集合中的某个元素

      •  remove底层也调用了equals方法 

    • List集合特有方法:
      • List集合存储元素特点:有序、可重复
        • 有序:List集合中元素有下标,从0开始,以一递增
        • 可重复:存储一个1,还可以再存储1
      • List既然是Collection接口的子接口,那么肯定list接口有自己“特色”的方法(以下只列出常用的方法)
        • void add(int index,Object element)//在列表指定位置插入指定元素,对于数组来说,这个方法使用的不多,因为涉及到数组位移的问题
        • Object get(int index)//取出指定元素的下标,Set集合没有该方法,因为没有下标
        • int indexOf(Object o)//获取指定元素第一次出现的索引
        • int lastIndexOf(Object o)//获取指定元素最后一次出现的索引
        • Object remove(int index)//删除指定索引处的元素
        • Object set(int index,Object element)
      • void add(int index,Object element)
      • Object get(int index):List集合有自己特有的遍历方式,根据for循环和get方法就可以得到元素
      • int indexOf(Object o)
      • int lastIndexOf(Object o)
      • E remove(int index)

      • Object set(int index,Object element)
    •  计算机英语增删改查这几个单词要知道: 

      • 增:add、save、new

      • 删:delete、drop、remove
      • 改:update、set、modify
      • 查:find、get、query、select
    • ArrayList的构造方法:
    •  链表数据结构: 

      • 对于链表数据结构来说,基本的单元是节点Node
      • 对于单向数据链表来说,任何一个节点Node都有两个属性:
        • 第一:存储的数据          第二:下一节点的内存地址
      • 链表的内存地址可以是不连续的
      • 链表随机增删效率高,因为不涉及到大量元素的位移问题
      • 链表查询效率低,因为每次都要从头节点开始寻找
      • 双向链表源码分析:

    •  Vector  

      •  底层也是一个数组
      • 初始化容量:10
      • 扩容是原来的2倍
        • 10——>20——>40
      • Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低,使用较少了
      • 怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
        • 使用集合工具类:
          • java.util.Collections;
        • java.util.Collection 是集合接口
        • java.util.Collections 是集合工具类
    • 泛型(JDK5.0之后,推出泛型机制) 

      • 泛型的好处:
        • 第一:集合中存储的元素类型统一了
        • 第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型
      • 泛型的缺点
        • 导致集合中存储的元素缺乏多样性,但大多数业务中,集合中元素的类型还是统一的,所以泛型特性被大家认可
      • A instanceof B表示的含义:A通常是引用变量,B通常是一个类或者是接口
        • A是否是B的实例
        • A是否是B子类的实例
        • A是否是B接口实现类的实例
      • JDK8之后引入了:自动类型推断机制(又称为钻石表达式)

      • 自定义泛型
    • 增强for循环(foreach)

    • hashSet集合 

    • SortedSet集合:

    • Map集合 

      • Map遍历方式

        • 第一种方式,获取所有的key,通过遍历key,来遍历value
        •  第二种方式:Set<Map.Entry<K,V>> entrySet()将Map集合转换为Set集合

        •  总结:第二种方式效率高,因为获取key和value都是直接从node对象中获取的属性值,这种方式比较适合于大数据量,第一种方式是先获取key值,然后通过key在哈希表中寻找value值,效率太低

      • 哈希表/散列表数据结构

        •  hashMap集合底层是哈希表/散列表数据结构

        • 哈希表是一个怎样的数据结构?

          • 哈希表是一个数组和单向链表的结合体,一维数组中每一个元素是一个单向链表
          • 数组:在查询方面效率很高,随机增删方面效率很低
          • 单向链表:在随机增删方面效率很高,查询方面效率很低
          • 哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点
      • HashMap集合底层源代码
        • public class HashMap{
        •  //HashMap底层实际上是一个数组(一维数组)
        •  Node<K,V>[] table;
        •  //静态的内部类HashMap.Node
        •  static class Node<K,V>{
        • final int hash;//哈希值是key的hashCode()方法的执行结果,hash值通过哈希函数/算法,可以计算出数组元素的下标
        • final K key;
        • V value;
        • Node<K,V> next; //下一个节点的内存地址
        • }
        • }
      • 最主要掌握的是:
        • map.put(k,v);
        • v=map.get(k);
      • HashMap集合的key部分特点:
        • 无序,不可重复
        • 为什么无序?因为不一定挂到哪个单向链表上
        • 不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复,key重复的话value会覆盖
      • 放在HashMap集合key部分的元素其实就是放到了HashSet集合中
      • 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法
      • 哈希表HashMap使用不当无法发挥性能!
        • 假设将所有的hashCode()方法返回值固定为某个值,那么会导致哈希表底层变成了单向链表,这种情况我们称为散列分布不均匀
        • 什么是散列分布均匀呢?
          • 假设有100个元素,10个单向链表,那么每一个单向链表有10个节点,这是最好的是散列分布均匀的
        • 假设将所有的hashCode()方法返回值都定为不一样的值可以吗,有什么问题?
          • 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念,也属于散列分布不均匀
        • 散列分布均匀需要你重写hashCode方法时有一定的技巧
        •  HashMap集合的默认初始化容量是16,默认加载因子是0.75

          • 这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容,扩容之后的容量是原来的2倍
          • 重点:记住HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率
      • 两个对象的equals相同,两个对象的hashCode必须相同,两个对象的equals不同,两个对象的hashCode可以相同,也可以不同
      •  向Map中集合中存,以及从Map集合中取,都是先调用hashCode方法,然后再调用equals方法,equals方法可调用,也可能不调用

        • 拿put(k,v)举例,什么时候equals不会调用?
          • k.hashCode()方法返回哈希值
          • 哈希值经过哈希算法转换成数组下标
          • 数组下标位置上如果是null,equals不需要执行
        • 拿get(k)举例,什么时候equals不会调用?
          • k.hashCode()方法返回哈希值
          • 哈希值经过哈希算法转换成数组下标
          • 数组下标位置上如果是null,equals不需要执行
        • 在重写了equals方法之后,equals方法返回值为true的,hash值按规定也必须相同,因为以前hashCode返回的是对象内存地址,所以应该重写
        • Student类重写了hashCode方法之后

        •  在JDK8之后,如果哈希表的单向链表中元素超过8个,单向链表这种数据结构变成红黑树数据结构,当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表,这种方式也是为了提高检索效率,二叉树的检索会再次缩小检索范围,提高效率

      • HashMap和HashTable的K,V是否可以为null

        • HashMap:

          • 可以看到,在key==null的时候,将key的hash值置为0,从而解决了当key为null时,走hashCode方法导致空指针异常,value也可以为null

        • HashTable
        •  总结:

          • HashMap的key和value都可以为null
          • HashTable的key和value都不能为null
          • HashMap和HashTable底层都是哈希表的数据结构
          • HashTable的初始化容量是11,默认加载因子是0.75,扩容是:(原容量*2)+1
      • Properties
        • 目前只需要掌握Properties属性类的相关方法即可
        • Properties是一个Map集合,继承HashTable,properties的key和value都是String类型的
        • Properties被称为属性类
        • Properties是线程安全的
      •  TreeSet集合 

        • TreeSet集合底层实际上是一个TreeMap(TreeSet构造方法会new一个TreeMap对象,增加元素底层是将元素加到TreeMap的key部分了)
        • TreeMap底层是一个二叉树
        • 放到TreeSet集合中的元素,等同于放到了TreeMap集合key部分了
        • TreeSet集合中的元素:无序,不可重复,但是可以按照元素的大小顺序自动排序,称为:可排序集合
        •  对自定义的类型来说,TreeSet可以排序吗 

          • 对于以下Person类型来说,无法排序,因为没有指定Person对象之间的比较规则,谁大谁小没有说明

            •  出现这个异常原因是,Person类没有实现java.lang.Comparable接口

          • 底层源代码

          • TreeSet集合可排序的方式有两种:

            • TreeSet集合可排序的第一种方式:实现Comparable接口

              • 先按年龄升序,如果年龄一样再按照姓名升序

            •  TreeSet集合中元素可排序的第二种方式:使用比较器的方式

            •  最终结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:

              •  第一种:放在集合中的元素实现java.lang.comparable接口

              • 第二种:在构造TreeSet或着TreeMap集合的时候给他传一个比较器进去
            • Comparable和Comparator怎么选择呢?
              • 当比较规则不会发生改变的时候,或者说比较规则只有1个的时候,建议实现Comparable接口
              • 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口
            • TreeSet和HashMap的key都不能为null,HashSet的key可以为null
      • Collections工具类
        • java.util.Collection集合接口
        • java.util.Collections集合工具类,方便集合的操作
        • 对于自定义的类型进行排序时:
          • 第一种方法:实现Comparable接口

          • 第二种方法:使用比较器方式

          • Set集合怎么实现排序

    •  集合总结: 

      •  List集合:  

      • Set集合

        • Set集合因为没有下标,所以没有get方法,get方法只能在List集合中使用 

  •  IO流

    • 什么是IO?

      • I:Input

      • O:Output 

      • 通过IO可以完成硬盘文件的读和写
    • IO流的分类(有多种分类方式):

      • 一种方式是按照流的方向进行分类

        • 以内存为参照物

          • 往内存中去叫做输入(Input),或者叫做读(Read)

          • 从内存中出来,叫做输出(Ouput),或者叫做写(Write)

      • 另一种是按照读取数据方式不同进行分类:
        • 有的流是按照字节的方式读取数据,一次读取一个字节byte,等同于一次读取8个二进制位(这种流是万能的),什么类型的文件都可以读取,包括:文本文件、图片、声音文件、视频文件...........
          • a中国bc张三fe
          • 第一次读:一个字节,正好读到'a'
          • 第二次读:一个字节,正好读到'中'字符的一半
          • 第三次读:一个字节,正好读到'中'字符的另一半
        • 有的流是按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件,只能读取纯文本文件,连word文件也不能
          • 假设文件file.java,采用字符流的话这样读:
            • a中国bc张三fe
            • 第一次读:'a'字符('a'字符在windows系统中占用1个字节)('a'在windows中占用一个字节,'a'在Java中占两个字节)
            • 第二次读:'中'字符('中'字符在windows系统中占用2个字节)
      • 综上所述:
        • 流的分类:
          • 输入流、输出流
          • 字节流、字符流
    • java IO流有四大家族:
      • java.io.InputStream     字节输入流
      • java.io.OutputStream  字节输出流
      • java.io.Reader             字符输入流
      • java.io.Writer               字符输出流
        • 四大家族的首领都是抽象类(abstract class)
        • 所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法,流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费很多资源,流毕竟是一个管道,是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费很多资源,养成好习惯,用完流一定要关闭
        • 所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush方法
          • 养成一个好习惯,输出流在最终输出之后,一定要记得flush刷新一下,这个刷新表示将管道中剩余未输出的数据强行输出完,刷新的作用就是清空管道
          • 如果没有flush可能会导致丢失数据
        • 注意:java中只要类名以”Stream“结尾的都是字节流,以”Reader/Writer“结尾的都是字符流
    • java.io包下需要掌握的流有16个:
      • 文件专属:
        • java.io.FileInputStream
        • java.io.FileOutputStream
        • java.io.FileReader             
        • java.io.FileWriter               
      • 转换流:(将字节流转换成字符流)
        • java.io.InputStreamReader             
        • java.io.OutputStreamWriter               
      • 缓冲流专属
        • java.io.BufferedInputStream
        • java.io.BufferedOutputStream 
        • java.io.BufferedReader 
        • java.io.BufferedWriter          
      • 数据流专属
        • java.io.DataInputStream
        • java.io.DataOutputStream
      • 标准输出流
        • java.io.PrintWriter
        • java.io.PrintStream
      • 对象流专属
        • java.io.ObjectInputStream
        • java.io.ObjectOutputStream
    • java.io.FileInputStream进行文件读取(从硬盘到内存)
      • 对程序进行改进 

        • 分析这个程序的缺点:一次读取一个字节byte,这样内存和硬盘交互的太频繁了,基本上时间/资源都耗费在交互上面了,能不能一次读取多个字节呢?可以 

      • 再次改进:

      • 最终版:
      •  FileInputStream的其他常用方法

      • FileOutputSteam文件字节输出流,负责写(从内存到硬盘) 

          • 如果文件还没创建,路径中不能有还没创建的目录,因为他只能创建文件
        •  FileOutputStream(File file, boolean append) append为true表示以追加的方式在原文件末尾写入,不会清空原文件 

      • 文件复制原理(一边读,一边写)但现在目录不能拷贝,后面学

    • FileReader文件字符输入流,只能读取普通文本 ,读取文本内容时,比较方便,快捷 

    •  FileWriter文件字符输出流,负责写的,只能输出普通文本

    •  复制文本文件:使用FileReader和FileWriter进行拷贝的话,只能拷贝“普通文件”,java文件也是普通文件,能用记事本编辑的就是普通文件。和后缀没关系

    • BufferedReader带有缓冲区的字符输入流
      • 使用带有缓冲区的流不需要自定义char数组或者自定义byte数组,自带缓冲
      • 当一个流的构造方法需要一个流的时候,这个被传近进来的流叫做“节点流”
      • 外部负责包装的这个流叫做包装流\处理流
      • 对于包装流来说,关闭的时候只需要关闭外层就行,里面的节点流会自动关闭

      • 代码1:

    • 转换流BufferedReader 以及缓冲流InputStreamReader:

    • 转换流BufferedReader 

    •  数据专属流:DataOutputStream和DataInputStream:

    •  标准输出流:java.io.PrintWriter和ava.io.PrintStream 

    •  File类 

    •  目录拷贝 

    • 序列化和反序列化 

      • 参与序列化和反序列化的对象必须实现Serializable接口

        • 通过源代码发现,Serializable接口只是一个标志接口,这个接口当中什么代码都没有

        • 那么他起到什么作用呢?
          • 起到标识的作用,标志的作用,Java虚拟机看代这个类实现类这个接口,可能会对这个类进行特殊待遇,Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号
        • 序列化版本号有什么用呢
      • 反序列化:
      • 可以一次序列化多个对象吗?

        • 可以,可以将对象放到集合当中,序列化集合
      • transient关键字修饰的变量表示游离的,不参与序列化操作!带有static关键字的变量也不能参与序列化

        • name不参与序列化

      •  关于序列化版本号 

        • 如何自动生成序列化版本号?

          • 选中类名 alt+回车
      •  IO和Properties联合使用

        • IO流:文件的读和写

        • Properties:是一个Map集合,key和value都是String类型

  •  多线程 

    • 多线程的静态方法,和对象是没有关系的,写在哪个栈里面,就哪个栈对象执行

    • 什么是进程?什么是线程?

      • 进程是一个应用程序(一个进程是一个软件)

      • 线程是一个进程中的执行场景/执行单元

      • 一个进程可以启动多个线程 

    • 对于Java程序来说,挡在DOS命令窗口中输入:

      •  java HelloWorld 回程之后

      • 会先启动JVM,而JVM就是一个进程(JVM本质上也是个软件)

      •  JVM再启动一个主线程调用main方法

      • 同时启动一个垃圾回收线程负责看护,回收垃圾

      • 最起码现在的java程序中至少有两个线程并发

      • 一个是执行main方法的主线程,一个是垃圾回收线程

    • 进程和线程的关系?

      •  一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动

      • 资源分配给进程,同一个进程的所有线程共享该进程所有资源。

    • 注意: 

      •  进程A和进程B的内存独立不共享

        • 魔兽游戏是一个进程
        • 酷狗音乐是一个进程
        • 这两个进程是独立的,不共享资源
      • 线程A和线程B呢?

        • 在Java语言中,堆内存和方法区内存共享

        • 但是栈内存独立,一个线程一个栈 

        • 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发
    • Java中之所以有多线程,目的就是为了提高程序的处理效率
      • 比如main方法一个线程,垃圾回收一个线程,在main方法线程执行的时候,有垃圾时就可以启动垃圾回收器线程回收垃圾,main方法可以继续往下执行,谁执行完成谁就结束,两者互不干扰,效率提高 
    • 思考一个问题:
      • 使用了多线程机制之后,main方法结束,是不是程序也可能不会结束,main发结束只是主线程结束了,其他的栈(线程)可能还在压栈弹栈
    • 分析一个问题,对于单核的CPU来说,真的可以做到真正的多线程并发吗?
      • 对于多核CPU电脑来说,真正的多线程并发是没有问题的
        • 4核的cpu电脑来说,真正的多线程并发是没有问题的
      • 什么是真正的多线程并发?
        • t1线程执行t1的
        • t2线程执行t2的
        • t1不会影响t2,t2也不会影响t1,这叫做真正的多线程
      • 单核CPU表示只有一个大脑
        • 不能够做到真正的多线程并发,但是可以给人一种多线程并发的感觉,对于单核的CPU来说,在某一个时间点上实际上只能处理一件事,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是,多个事情同时来做!!!
          • 线程A:播放音乐
          • 线程B:运行魔兽游戏
          • 线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在执行,给我们的感觉是同时并发的
    • 分析以下程序有几个线程(除了垃圾回收线程之外):
    •  Java语言中,实现线程有两种方式,哪两种方式呢?

      •  java支持多线程机制,并且Java已经将多线程实现了,我们只需要继承就行了

        • 第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法

          • 亘古不变的道理:方法体中的代码永远都是按照自上而下的顺序依次执行的,在执行myThread.start();只是为了开辟一个新的栈空间,只要新的栈空间开辟出来了,start方法就结束了,分支线程会自动调用run方法执行,主线程也会继续往下执行

          •  以下程序的输出结果:有先有后,有多有少 这是怎么回事?

        • 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法

          •  注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活

          • 用匿名内部类:

    • 线程生命周期:

      • JVM线程调度:

        • 线程调度是指系统为线程分配处理器使用权的过程 

    • 怎么获取当前线程对象:

    • 线程的sleep方法(进入阻塞状态)

    • 关于Thread.sleep方法的一个面试题

    • slee睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?

      •  注意这个不是中断线程的执行,是中断线程的睡眠

    • 在Java中怎么强行终止一个线程

      • 这种方式存在很大的缺点,容易丢失数据,因为这种方式是直接将线程杀死了

      • 线程没有保存的数据将会丢失,不建议使用

    •  怎么合理的终止一个线程的执行,这种方式很常用

    •  (这部分内容属于了解)关于线程调度

      • 常见的线程调度模型有哪些

        • 抢占式调度模型:优先让优先级高的线程使用,如果优先级一样,则随机调度

        • 分时调度模型 :所有线程平均分配CPU使用时间

          • 有一些编程语言,线程调度模型采用这种方式

      • Java中提供了哪些方法是和线程调度有关系的呢

        • 实例方法:
          • void setPriority(int new Priority)  设置线程的优先级

          • int getPriority()  获取线程优先级

            • 最低优先级是1,默认优先级是5,最高优先级是10 

            • 优先级越高,(大概率)越大的可能抢到CPU,是个大概率事件
          • void join() 合并线程
            • class MyThread1 extends Thread{
              • public void doSome(){
              • MyThread2 t=new MyThread2();
              • t.join();//当前线程进入阻塞状态t线程执行,直到t线程执行结束,当前线程才可以执行
              • }
            • }
            • class MyThread2 extends Thread{
            • }
            • 这里并不是说真的合并成一个栈,另一个栈不在了,而是栈之间协调了
        • 静态方法:

          • static void yield() 让位方法

            • 暂停当前正在执行的线程对象,并执行其他线程 

            • yield方法不是阻塞方法,让当前线程让位,让给其他线程

            • yield方法的执行会让当前线程从“运行状态”回到“就绪状态”(sleep方法是从运行状态回到阻塞状态) 

            • 虽然让了,但是也不一定能抢到 

    •  关于多线程环境下,数据安全问题

      •  为什么这个是重点:

        • 以后再开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写
        • 做重要的是,你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要的是关注这些数据,在多线程并发环境下是否是安全的(重点!!!)
      • 什么时候数据在多线程并发的环境下会存在安全问题呢?
        • 条件一:多线程并发
        • 条件二:有共享数据
        • 条件三:共享数据有修改的行为
      • 怎么解决线程安全问题呢?
        • 当多线程并发环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全的问题,怎么解决这个问题呢?
          • 线程排队执行(不能并发)
          • 用排队机制解决线程安全问题
          • 这种被称为“线程同步机制”
          • 专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队
          • 线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率,数据不安全,没有效率的事
        • 说到线程同步这块,涉及到这两个专业术语:
          • 同步编程模型
            • 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等待谁,这种编程模型叫做异步编程模型,对于单核CPU来说,其实就是多线程并发
          • 异步编程模型
            • 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说t2线程执行的时候,必须等待t1线程的执行结束,两个线程之间发生了等待关系,这就是异步编程模型
            • 效率较低,线程排队执行
          • 异步就是并发,同步就是排队
        • 银行账户:
          • 不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
        • 使用线程同步解决线程安全问题:
          • 当运行状态的线程遇到synchronized关键字代码块时候,会进入锁池lockpool,在锁池中找共享对象的对象锁,线程进入锁池找共享对象的对象锁的时候,会释放之前占有的CPU时间片,有可能找到,有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片

          • 把this换成actno也可以,因为actno是是实例变量,一个对象只有一份,只要是共享的对象就行

          • this 换成obj不可以,因为obj是局部变量,每次调用这个方法就会创建一个新对象

          • 写成“abc”也可以,因为”abc“在字符串常量池中,地址固定,但不要这么写,因为这个字符串是所有线程对象都共享的,就会导致你去存款,其他所有人都要等你,不用这样,只需同一个账户对象之间发生共享就行
        • Java中有三大变量?
          • 实例变量:在堆中
          • 静态变量:在方法区中
          • 局部变量:在栈中
            • 以上三大变量中:
              • 局部变量永远不会存在线程安全问题
              • 因为局部变量不共享(一个线程一个栈)
              • 局部变量在栈中,所以局部变量永远不会共享
              • 实例变量在堆中,堆只有一个
              • 静态变量在方法区中,方法区只有一个
              • 堆和方法区都是多线程共享的,所以可能存在线程安全问题
        • 扩大同步范围,效率会更低
        • synchronized出现在实例方法上:
        • synchronized的三种写法

          • 第一种:同步代码块

            •  灵活

            • synchronized(线程共享对象){
            • 同步代码块
            • }
          • 第二种:在实例方法上使用synchronized
            • 表示共享对象一定是this
            • 并且同步代码块是整个方法体
          • 第三种:在静态方法上使用synchronized关键字
            • 表示找类锁
            • 类锁永远只有1把
            • 就算创建了100个对象,那么类锁也只有1把
      • 死锁
      •  聊一聊,我们以后开发中应该怎么解决线程安全问题 

        •  是一上来就选择线程同步吗?synchronized

          • 不是 ,synchronized会让程序的效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下在选择线程同步机制

        • 第一种方案:尽量使用局部变量代替实例变量和静态变量
        • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了,(1个线程一个对象,100个线程100个对象)
        • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了,线程同步机制
    • 守护线程:
      • java语言种线程分为两大类:
        • 一类是:用户线程
        • 一类是:守护线程(后台线程)
        • 其中具有代表性的就是:垃圾回收线程(守护线程)
      • 守护线程的特点:
        • 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
      • 注意:主线程main方法是一个用户线程,主线程不能设置为守护线程。因为一个线程可以在它运行之前被设置为守护进程,并且一旦程序启动主线程就开始运行,因此不能被设置为守护线程。
      • 守护线程用在什么地方?
        • 每天00:00的时候系统数据自动备份
        • 这个需要使用定时器,并且可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没必要进行数据备份了
    •  定时器

      • 定时器的作用:

        • 间隔特定的时间,执行特定的程序
          • 每周都要进行银行账户的总账操作
          • 每天要进行数据的备份操作
      • 在实际的开发中,每隔多久就执行一段特定的程序, 这种需求是很常见的,那么在Java中其实其实可以采用多种方式实现
        • 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务,这种方式是最原始的定时器(比较low)
        • 在Java的类库中已经写好了一个定时器,java.util.Timer,可以直接拿来使用,不过这种方式在目前的开发中也很少使用,因为现在很多高级的框架都支持定时任务
        • 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器任务
      • java.util.Timer
        • 构造方法:Timer(){}
        • void schedule(TimerTask task,Date firstTime,long period)
          • 安排指定的任务在指定的时间开始开始,每隔多久执行一次
          • TimerTask实现了Runnable
        • 创建Timer的对象时候会自动开启一个Timer-0线程,调用的schedule方法会在Timer对象创建的线程中执行
    •  实现线程的第三种方式:实现Callable接口(JDK新特性)

      • 这种方式实现的线程可以获取线程的返回值

      • 之前讲解的那两种方式是无法获取线程的返回值的,因为run方法返回void
      • 思考:
        • 系统委派一个线程去执行一个任务,该线程执行完任务,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
          • 使用第三种方式:实现Callable接口方式
      • 这种方式的优点是:可以获取到线程的执行结果
      • 这种方式的缺点是:效率较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
    •  关于Object类中的wait和notify方法(生产者和消费者模式)

      • wait和notify方法不是线程对象的方法,是Java中任何一个Java对象都有的方法,因为这两个方式是Object类中自带的,wait方法和notify方法不是通过线程对象调用,不是这样的,t.wait(),也不是这样的t .notify()...不对

      • wait()方法作用?
        • Object o=new Object()
        • o.wait();
        • 表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止,
        • o.wait方法的调用,会让当前线程(正在o对象上活动的线程)进入等到状态
      • notify()方法作用
        • Object o=new Object();
        • o.notify();
        • 表示:唤醒正在o对象上等待的线程
        • 还有一个notifyAll方法:这个方法是唤醒o对象上处于等待的所有线程
        • wait方法和notify方法是建立在synchronized线程同步基础之上的,因为多线程要同时操作一个仓库,有线程安全问题

  •  反射机制

    • 反射机制有什么作用?

      • 通过java语言中的反射机制可以操作字节码文件

      • 优点类似于黑客(可以读和修改字节码文件)

    • 反射机制的相关类在哪个包下?

      • java.lang.reflect.*;

    • 反射机制相关的类有哪些?

      • java.lang.Class:代表整个字节码,代表一个类型,代表整个类

      • java.lang.reflect.Method:代表字节码中的方法字节码
      • java.lang.reflect.Constructor:代表字节码中的构造方法字节码
      • java.lang.reflect.Field:代表字节码中的属性字节码(静态变量+实例变量)
    • 代码
      • //java.lang.class
      • public class User{
        • //Field
        • int no;
        • //Constructor
        • public User(){
        • }
        • public User(int no){
          • this.no=no;
        • }
        • //Method
        • public void setNo(int no){
          • this.no=no;
        • }
        • public int getNo(){
          • return no;
        • }
    • 获取字节码的三种方式:
    • 获取到Class,能干什么?

    • 验证反射机制的灵活性:

      • java代码写一遍,在不改变源代码的基础之上,可以做到不同对象的实例化,非常之灵活,符合OCP的开闭原则,对扩展开放,就修改关闭

      • 后期要学习框架,而在工作中,也都是使用高级框架:

        • 包括:Spring、SpringMVC、MyBatis、Spring 、Struts、Hibernate

        • 这些高级框架底层实现原理,都采用了反射机制,所以反射机制还是很重要的,学会了反射机制有利于你理解刨析框架底层源代码
    • 研究一下:Class.forName()发生了什么?
    • 研究一下文件路径问题:

      • 怎么获取一个文件的绝对路径,以下讲解的这种方式是通用的,但前提是:文件需要在类路径下,才能用这种方式

    • 以流的形式直接返回
    • 资源绑定器:

    •  关于JDK中自带的类加载器

      • 什么是类加载器ClassLoader?

        • 专门负责类加载的命令/工具

      • JDK中自带了3个类加载器
        • 启动类加载器
        • 扩展类加载器
        • 应用类加载器
      • 假设有这样一段代码:
        • String s="abc";
        • 代码在开始执行之前,会将所需要的类加载到JVM当中,通过类加载器加载,看到以上代码,类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?
          • 首先通过“启动类加载器”加载
            • 注意:启动类加载器专门加载:D:\app\java\jre\lib\rt.jar
            • rt.jar中都是JDK最核心的类库
          • 如果通过“启动类加载器”加载不到,会通过“扩展类加载器”加载
            • 注意:扩展类加载器专门加载:D:\app\java\jre\lib\ext\*.jar
          • 如果“扩展类加载器”没加载到,会通过“应用类加载器”加载
            • 注意:应用类加载器专门加载:classpath路径下的jar包/class文件
      • 双亲委派机制:
        • Java为了保证程序安全,防止核心API被随意篡改,使用了双亲委派机制
        • 优先从“启动类加载器”中加载,这个称为“父”,“父”无法加载到,再从扩展类加载器中加载,这个称为“母”,双亲委派。如果都加载不到,才会考虑从"应用类加载器中国加载”,直到加载到为止
    • 获取Field
    • 反编译Field

    • 通过反射机制访问对象的属性

    • 可变长度参数

    • 反射Method

    • 反编译方法

    • 通过反射机制怎么调用一个对象的方法 

    • 反编译Constructor

    • 反射机制调用构造方法

    •  获取父类和父接口:

  • 注解annotation

    •  注解,或者叫做注释类型,英文单词是:Annotation

    • 注解Annotation是一种引用数据类型,编译之后也是生成xxx.class文件

    • 怎么自定义注解呢?语法格式?

      • [修饰符列表] @interface 注解类型名{

    • 注解怎么使用,用在什么地方?

      • 第一:注解使用时的语法格式是:

        • @注解类型名

      • 第二:注解可以出现在类上、接口上、枚举上、属性上、方法上等.......

        •   注解还可以出现在注解上

    • JDK内置了哪些注解

      • java.lang包下的注释类型:

        • 掌握

          • 用@Deprecated注释的程序元素

          • 不鼓励程序员使用这样的元素,通常是因为他很危险或存在更好的选择

        • 掌握

          • Override表示一个方法声明打算重写超类中的另一个方法声明

        •  不用掌握:

          • SuppreWarnings指示应该在注释元素(以及包含该元素中的所有程序元素)中取消显示指定的编译器警告

    •  什么是元注解?

      • 只能用来标注”注解类型“的”注解“,称为元注解

      • 常见的元注解有哪些?

        • Target

        • Retention

      • 关于Target注解:

          • 这是一个元注解,用来标注“注解类型”的注解

          • 这个Target注解用来标注“被标注的注解”可以出现在哪些位置上

          • @Target(ElementType.METHOD) :表示“被标注的注解”只能出现在方法上

          • @Target(value={ElementType.CONSTRUCTOR,ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElemetType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
            表示该注解可以出现在:构造方法上、成员变量上(字段上)、局部变量上、方法上等等
      • 关于Retention注解:

        •  这是一个元注解,用来标注“注解类型”的注解

        • 这个Retention注解用来表示“被标注的注解”最终保存在哪里
        • @Retention(RetentionPolicy.SOURCE):表示该注解只被保存在java源文件中
        • @Retention(RetentionPolicy.CLASSS):表示该注解被保存在class字节码文件中
        • @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取
    • 注解中定义属性:
    • 如果一个注解的属性名是value时,属性名可以省略

    • 注解的属性除了value还有其他值的话,就不能省略value了

    •  注解的属性类型可以是什么

    •  反射注解:

      • 代码:

        • 如果@Retention(RetentionPolicy.RUNTIME)不是RUNTIME,反射机制就不能读取到该注解

      • 通过反射注解获取注解对象的属性值

    • 注解在开发中有什么作用

      •  需求:假设有这样一个注解,叫做:@Id,这个注解只能出现在类上面,当类上有这个注解的时候,要求这个类中必须有一个int类型的属性,如果没有这个属性,就报异常,如果有这个属性,则正常执行

        • 如果类中没有id属性:

        •  

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

           

 

posted @ 2023-07-17 21:31  媛~~  阅读(17)  评论(0编辑  收藏  举报