Java尚硅谷核心知识

  • 一、Java基础部分
    • 1.1 Java语言的主要特性
    • 1.2 Java程序运行机制及运行过程
      • 1.2.1Java两种核心机制
    • 1.3Java语言的环境搭建
      • 1.3.1 为什么要为JDK设置环境变量
      • 1.3.2 JDK、JRE、JVM
  • 二、Java基本语法
    • 2.1 Java的关键字和保留字
      • 2.1.1 return关键字的使用:
      • 2.1.2 clone
    • 2.2 变量
    • 2.3Java的数据类型
      • 2.3.1 引用类型包括:
      • 2.3.2 基本类型包括:
      • 2.3.1 自动提升
      • 2.3.2 强制准换
    • 2.4运算符
  • 三、数组
    • 3.1创建数组的内存结构的解析:
    • 3.2一维数组的内存解析
    • 3.3二维数组的内存解析
    • 3.4算法中数组的应用
    • 3.5需要注意的问题
  • 四、oop--万物皆对象
    • 4.1类和对象的关系
      • 4.1.1对象的内存解析
      • 4.1.2对象数组的内存解析
      • 4.1.3匿名对象
    • 4.2 类的成员之一:属性(成员变量)
      • 4.2.1成员变量(属性)和局部变量的内存解析
    • 4.3类的成员之二:方法
      • 4.3.1方法的重载和重写
      • 4.3.2 方法参数的值传递机制
      • 4.3.3方法的递归
    • 4.4类的成员之三:构造器
      • 4.4.1拓展:构造器的典型:应用JavaBean
    • 4.5 this关键字:指向本地的属性、方法和构造器
    • 4.6super关键字调用父类中的指定操作
    • 4. 7oop三大特性
      • 4.7.1封装
      • 4.7.2继承
        • 4.7.2.1 何为继承
        • 4.7.2.2 继承的体现:
        • 4.7.2.3 Java中关于继承性的规定
      • 4.7.3多态
        • 4.7.3.1 重载
        • 4.7.3.2 重写:子类中可以根据需要对从父类中继承来的方法进行改造
    • 4.8"=="和"equals"
      • 4.8.1 equals()方法的使用
    • 4.9包装类
      • 4.9.1装箱
      • 4.9.2拆箱
      • 4.9.3字符串-->基本类型
      • 4.9.4基本类型-->字符串
    • 4.10关键字static、abstract、final
      • 4.10.1关键字static
      • 4.10.2关键字final
      • 4.10.3关键字abstract
      • 4.10.4 接口Interface
      • 4.10.5 接口和抽象类的区别
      • 4.11内部类
        • 4.11.1 成员内部类
        • 4.11.2 匿名内部类
  • 五、异常处理
    • 5.1异常类型
      • 5.1.0 堆溢出和栈溢出:
      • 5.1.1运行时异常:
      • 5.1.2编译时异常:
    • 5.2异常处理机制
      • 5.2.1 异常处理的方式一:try-catch-finally
        • 5.2.1.1 异常的处理:抓抛模型
        • 5.2.1.2 try-catch-finally的使用
        • 5.2.1.3 说明以及体会:
      • 5.2.2 异常处理的方式二:throws + 异常类型
      • 5.2.3练习
    • 5.3总结:
  • 六、多线程
    • 6.1多线程的优点
    • 6.2进程与线程
    • 6.3并行与并发
    • 6.4线程的创建
      • 6.4.1继承Thread类
        • 6.4.1.1注意:
      • 6.4.2实现Runnable接口
      • 6.4.3两种创建线程的区别
        • 6.4.3.1Runnable的好处
      • 6.4.4 Thread类的有关方法
      • 6.4.5线程的调度
        • 6.4.5.1调度策略
        • 6.4.5.2线程的优先级
      • 6.4.6线程的补充
    • 6.5线程的生命周期
    • 6.6 线程的同步
      • 6.6.1线程同步解决方式之----synchronized
      • 6.6.2线程同步解决方式之---- Lock
      • 6.6.3synchronized 与 Lock 的对比
      • 6.6.4 线程同步的范围
      • 6.6.5释放锁的操作
      • 6.6.6不会释放锁的操作
      • 6.6.7线程同步的应用
      • 6.6.7 线程同步的栗子
        • 6.6.7.1 同步代码块
        • 6.6.7.2 同步方法
        • 6.6.7.3 lock锁
    • 6.7 线程的死锁问题
    • 6.8 线程的通信
      • 6.8.1 wait() 方法
      • 6.8.2 notify()/notifyAll()
      • 6.8.3线程通信的经典例题
    • 6.9 新增线程创建方式
      • 6.9.1 实现Callable接口
      • 6.9.1 使用callable栗子
      • 6.9.3 使用线程池
      • 6.9.4 使用线程池栗子
  • 七、Java常用类
    • 7.1 String的特性
      • 7.1.1 String对象创建的两种方式的内存解析
        • 7.1.1.1String对象创建的两种方式
        • 7.1.1.2 内存解析
      • 7.1.2 字符串对象是如何存储的
      • 7.1.3 字符串的相关练习
      • 7.1.4 String常用方法
    • 7.2 StringBuffer和StringBuilder
    • 7.3日期时间API
      • 7.3.1日期时间API的简单的使用
      • 7.3.2 时间标准化:SimpleDateFormat的用法
      • 7.3.3 Calendar类的用法
      • 7.3.4 JDK8以后的时间设置
    • 7.4比较器
      • 7.4.1 Comparable接口的使用举例: 自然排序
      • 7.4.2 Comparator接口的使用:定制排序
    • 7.5BigInteger与BigDecimal
  • 八、枚举类与注解
    • 8.1 Enum类中的常用方法
      • 8.1.1 在使用枚举类需要注意的地方
    • 8.2 注解(Annotation)
      • 8.2.1 四个源注解的介绍:
  • 九、Java集合
    • 9.1 Collection 接口
      • 9.1.1 Collection 接口常用方法
      • 9.1.2 Iterator迭代器接口
      • 9.1.2 Collection子接口之一:List接口
        • 9.1.2.1 ArrayList
        • 9.1.2.2 LinkedList
        • 9.1.2.3 Vector
        • 9.1.2.4 面试题:
      • 9.1.3 Collection子接口之二:Set接口
        • 9.1.3.1 Set实现类之一:HashSet
          • 9.1.3.1.1 向HashSet中添加元素的过程
          • 9.1.3.1.2 重写 hashCode() 方法的基本原则
        • 9.1.3.2 Set实现类之一:LinkedHashSet
        • 9.1.3.3 Set实现类之一TreeSet
    • 9.2 Map接口
      • 9.2.1 HashMap
        • 9.2.1.1 HashMap源码中的重要常量
        • 9.2.1.2 HashMap 1.7和1.8的改变
          • JDK1.7
          • JDK1.8
          • JDK1.8相较于之前的变化
        • 负载因子值的大小,对HashMap有什么影响
      • 9.2.2 LinkedHashMap
      • 9.2.3 TreeMap
      • 9.2.4 Hashtable
      • 9.2.5 Properties
    • 9.3 Collections工具类
  • 10、IO流
    • 10.1 File 类的使用
      • 10.1.1 路径分隔符
      • 10.1.2File类的获取功能
    • 10.2 IO 流体系
    • 10.3 字符流的转换
      • 10.3.1 从硬盘的文件中写入到内存中
      • 10.3.2 从内存中写出数据到硬盘的文件里
      • 10.3.3 从磁盘中读取到文件到内存中,在存储到另一块磁盘文件中
    • 10.4 字节流的转换
      • 10.4.1 从硬盘的文件中写入到内存中以及复制
      • 10.4.2 字节流图片视频文字的输入输出
    • 10.5 缓冲流的转换
    • 10.6 练习
  • 11、网络编程
    • 11.1 网络通信要素概述
    • 11.2 实现网络中的主机互相通信
    • 11.3 域名解析
    • 11.4 TCP 和 UDP
  • 12、反射
    • 12.1Java反射机制提供的功能
    • 12.2 获取Class类的实例
    • 12.3 类的加载过程
  • Java中的设计模式
    • 单例(Singleton)设计模式
      • 饿汉式
      • 懒汉式
    • JVM的组成

一、Java基础部分

1.1 Java语言的主要特性

  • Java是一种跨平台性的语言,满足一次编译,到处运行的语言(Java语言的运行依赖于JVM,不同的操作系统有不同的JVM)
  • Java是一种面向对象的编程语言
  • Java有一个很大的优势在于有自动回收垃圾的机制
  • Java语言是健壮的。Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。

1.2 Java程序运行机制及运行过程

  • Java程序的运行过程

  • 层次关系

1.2.1Java两种核心机制

  • JVM

    1. JVM是一个具有指令集并使用不同的存储区域。负责执行指令,管理数据,内存,寄存器。
    2. 对于不同的平台有不同的虚拟机,平台提供了对应的虚拟机才能运行
    3. 因为JVM的存在,实现了"一次编译,到处运行"
  • GC

    1. 自动监测对象是否超过作用域(可达性分析和引用计数
    2. 将不再使用的内存空间进行回收
    3. 但是Java程序任然会存在内存泄露和内存溢出问题

1.3Java语言的环境搭建

1.3.1 为什么要为JDK设置环境变量

在cmd中可以在任何地方调用JDK指令

  • cmd种常用指令:
dir : 列出当前目录下的文件以及文件夹
md : 创建目录
rd : 删除目录
cd : 进入指定目录
cd.. : 退回到上一级目录
cd\: 退回到根目录
del : 删除文件
exit : 退出 dos 命令行

1.3.2 JDK、JRE、JVM

JDK是Java开发工具包     (JDK=JRE+开发工具包)
JRE是Java运行环境       (JRE=JVM+Java标准类库)
JVM是Java虚拟机

二、Java基本语法

2.1 Java的关键字和保留字

被Java语言赋予了特殊含义,用做专门用途的字符串(单词)。

  • Java中有很多关键字,保留字是指以后的版本可能会用到(goto和const)
  • 在Java中变量的名字不能关键字或者保留字

2.1.1 return关键字的使用:

  1. 结束方法
  2. 针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据。

2.1.2 clone

克隆分为深克隆浅克隆,

Animal a2 = (Animal) a1.clone();

2.2 变量

Java是一种强类型语言

强类型语言:每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间.比如int a=10;编译前就为a分配了4个字节
弱类型语言:是指是在编译后确定数据类型,没有明显的类型,随着环境的不同,自动变换类型。比如:var a=10;编译前无法确定它的类型。

变量按作用范围可分为成员变量和局部变量

  • 成员变量: 在方法体外,类体内声明的变量

    1. 实例变量(不以static修饰的)
    2. 类变量(以static修饰的)
  • 局部变量: 在方法体内部声明的变量
    1. 形参(方法、构造器中定义的变量)
    2. 方法内、代码块内定义的局部变量

2.3Java的数据类型

Java的数据类型一共有两种。一种是基本类型,另一种是引用类型。

2.3.1 引用类型包括:

类(class)、接口(Interface)、数组(Array)

注意:字符串在类里面

2.3.2 基本类型包括:

byte、short、int(默认)、long、float、double(默认)、char、boolean

数据类型 字节数 位数 范围 默认值
byte 1 8 -128~127 0
short 2 16 -2^15 ~2^15-1 0
int 4 32 -2^31 ~2^31-1 0
long 8 64 -2^63 ~2^63-1 0
boolean 1 8 true、false false
char 2 16 0~65535
float 4 32 -2^128 ~ 2^128 0.0
double 8 64 -2^1024 ~ 2^1024 0.0

说明:float和double是怎么计算的
float、double和其它类型的计算方式是不一样的,float和double是采取科学计数法来进行计算的

float和double的范围是由指数的位数来决定的。其中float的指数位有8位,而double的指数位有11位。

float: 1bits(符号位) 8bits(指数位) 23bits(尾数位)
double 1bits(符号位) 11bits(指数位) 52bits(尾数位)

举例:

0  01111100  01000000000000000000000==0.15625
+ 124-127     1.25         1.25*2^-3=0.15625
符号位:0代表正1代表负
指数位:是一个无符号位存在一个偏移量127,计算出的结果需要与127做差
尾数位:尾数位占23个bit,它的表达上有一个特殊点,它被认为是24个bit,第一个bit取值1,且被隐藏掉:有了这个前提,我们就比较好理解尾数的表达了:这24个bit,依次表达2^0, 2^-1, 2^-2, 2^-23,隐藏的第一位表达的指就是1。所以:尾数位全0时表达的值是1,全1时表达的最大值2 - 2^-23。

注意:

  • Java中char的范围和c语言中char的范围有区别Java中char占用两个字节,范围是0–65535。
  • 1byte=8bit;
  • long a=12; float b=12;这两种情况不会报错,是因为自动提升的原因
  • byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型。所以有byte、short、char的混合运算时结果都为int类型

2.3.1 自动提升

基本类型中容量小的和容量大的一起运算时,会发生自动提升现象
注意:float的范围比long的还要大。是因为它们底层的存储结构不同。float/double使用科学计数法来存储,而long使用普通的二进制来存储,也就是说一个存的是多少次方,而一个是只有多少位。

2.3.2 强制准换

是指在大容量向小容量转换时发生的,在强制转换时会有精度损失

注意:类型转换都是基本类型之间的,String不能和基本类型之间进行转换,但是包装类可以和String进行转换。如: String a = “43”; int i = Integer.parseInt(a);

2.4运算符

  • 算术运算:+ - * /
  • 赋值运算:
short s=2;
s=s+2;//编译不通过,类型不一致
s+=2;//编译通过,运算时不会改变类型
  • 比较运算
    instanceof
 System.out.println("hello" instanceof String);//true
  • 逻辑运算
&:逻辑与
&&:短路与
|:逻辑或
||:短路或
^:异或。相同为0不同为1
!:非
  • 位运算
<<左移,相当于*2
>>右移,相当于/2
&与运算,都1为1
|或运算,有1为1
^异或运算  110^011=101==>5
~取反运算  ~6=7  (补码)0110<==>(补码)1001==>0111(源码)==>7
  • 三目运算符
    要求 " :"前后两种数据类型要一致
    举个栗子:
//如果a<60,将a的值置为0。
int a=a>60?a:0;
  • 运算符优先级

三、数组

数组是一种按照一定顺序排列,且能存储相同数据类型的集合。但是需要占用连续空间数据结构。
需要注意的是:

  • 长度一旦确定,就不能再进行修改。
  • 数组的索引是从0开始的而数据库库的索引是1开始的,在连接数据库时需要注意。

3.1创建数组的内存结构的解析:

3.2一维数组的内存解析

3.3二维数组的内存解析

就是两个一维数组组成的数组栈中的引用指向堆,堆中存的也是一个引用

3.4算法中数组的应用

  1. 数组元素的赋值(杨辉三角、回形数等)
  2. 求数值型数组中元素的最大值、最小值、平均数、总和等
  3. 数组的复制、反转、查找(线性查找、二分法查找)
  4. 数组元素的排序算法(稳定性)A和B相等排序后,A和B的次序不变(应用:商品首先根据销量排序,再根据价格排序,就需要排序的稳定性)

3.5需要注意的问题

         int [] arr=new int[] {1,2,3,4,5};System.out.println(arr);//[I@32a1bec0char [] arr1=new char[] {'a','b'};System.out.println(arr1);//ab

出现这种现象是因为重载。(将char型print和int型print进行重载)

四、oop–万物皆对象

在Java中将功能结构封装在类中,通过类的实例化来调用具体功能结构

主流编程语言要么是面向过程(强调考虑怎么做),要么是面向对象(强调谁来做)。程序员从面向过程的执行者转化成了面向对象的指挥者

注意:int 不是对象,因为一般情况下创建对象需要进行new。将int转换为包装类后即是对象。

举个栗子:把大象装进并冰箱

4.1类和对象的关系

面向对象的两个要素:类和对象

:是指对一类事物的概括,是抽象的、概念上的定义。类的成员包括属性和方法
对象:是指存在的一个真实个体,因而也称为实例(instance)。对象可以调用类的非私有成员。创建类的对象 = 类的实例化 = 实例化类
可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人

对象一般通过new来创建。类名 对象名 = new 类名();

4.1.1对象的内存解析

4.1.2对象数组的内存解析

4.1.3匿名对象

不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。

    new A().show();

注意:匿名对象主要使用在只需要进行一次方法调用

4.2 类的成员之一:属性(成员变量)

属性 = 成员变量 = field = 域、字段
使用形式: 修饰符 数据类型 属性名 = 初始化值 ;

  • private int age; //声明private变量 age

变量的分类:成员变量(属性,存在于方法体外部)与局部变量(存在于方法体内部)
Java中的变量都是先声明后使用,并且作用于自己的作用域

属性 局部变量
定义在类下 定义在方法,构造器等内部
可以用权限修饰符 不可以用权限修饰符
加载到堆中 加载到栈中
有默认初始值 没有初始值

4.2.1成员变量(属性)和局部变量的内存解析

4.3类的成员之二:方法

方法 = 成员方法 = 函数 = method

方法是类或对象行为特征的抽象,用来完成某个功能操作。

方法的分类:按照是否有形参及返回值。

4.3.1方法的重载和重写

  • 重载:参数个数或者参数型不同的同名方法。与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

举个例子:

    //返回两个整数的和int add(int x,int y){return x+y;}//返回三个整数的和int add(int x,int y,int z){return x+y+z;}//返回两个小数的和double add(double x,double y){return x+y;}
  • 重写:同一个方法子类对父类方法功能的改变
public class A {public void show() { System.out.println("ok"); }
}
class B  extends A{@Overridepublic void show() { System.out.println("no ok"); }
}

4.3.2 方法参数的值传递机制

Java只有值传递

  • 当传递类型是基本类型时,传递的就是参数真实的值,在方法中对值进行修改,不影响本身的值
  • 当传递类型时引用类型时,传递的就是地址的值,在方法中进行修改数据时,会影响本身的值

不是说只要传递的参数是引用类型的就会修改数据的值,他的大前提是传递前后两个引用指向的是同一块内存。

举个栗子:

String s="hello";
Test test=new Test();
test.change(s);
//    change(String s1){//        s1="hi";
//    }System.out.println(s);

4.3.3方法的递归

一个方法体内调用它自身,注意在递归时要注意弹出时,否则会导致跳不出循环,导致栈溢出

//计算1-100之间所有自然数的和
public int sum(int num){if(num == 1){return 1;
}else{return num + sum(num - 1);
} }

4.4类的成员之三:构造器

构造器的作用:创建对象;给对象进行初始化
构造器:默认构造器、非默认构造器

注意:

  • 多个构造器之间构成重载关系
  • 父类的构造器不可被子类继承

4.4.1拓展:构造器的典型:应用JavaBean

  • 有一个无参的公共的构造器
  • 有属性,且有对应的get、set方法
public class JavaBean {private String name; // 属性一般定义为private
private int age;
public JavaBean() {}
public int getAge() {return age; }
public void setAge(int age) {this.age = age; }
public String getName() {return name; }
public void setName(String n) {name = n; } }

4.5 this关键字:指向本地的属性、方法和构造器

this可以用来修饰、调用:属性、方法、构造器们可以理解为当前对象。
在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。

注意:
在类的构造器中,可以使用this()调用本类的其他构造器,在用this进行调用构造器时必须放在第一行

//boy类的marry方法
marry(Girl girl){System.out.println("I Love girlA");
}
//girl类的marry方法
marry(Boy boy){System.out.println("I Love boyA");boy.marry(this);//谁调用marry,this就是谁。boyA回应 I Love girlA
}
  • 我们可以用this来区分属性和局部变量。
this.name = name;

4.6super关键字调用父类中的指定操作

super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识

4. 7oop三大特性

  • 封装:隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。
  • 继承:一个子类继承一个父类,能够直接调用父类的非私有属性和方法,通过set、get也能访问私有属性。
  • 多态:分为运行时多态(overload)和编译时多态(override),多态的三个必要条件 :1.要有继承(实现implements) 2.要有重写(overWrite&overRide) 3.父类引用指向子类对象

4.7.1封装

将方法和属性进行私有化,对实现细节进行隐藏,只暴露出供外界使用的接口。使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作

封装的体现:Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作

修饰符 类内部 同一个包 不同包的子类 同一个工程
private
default
protected
public

注意:对于class的权限修饰只可以用public和default

4.7.2继承

4.7.2.1 何为继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。因为有了继承就可以省略掉很多冗余的代码。

4.7.2.2 继承的体现:

  1. 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
  2. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。

4.7.2.3 Java中关于继承性的规定

  • Java是单根继承,一个子类只能继承(extends)一个类。但是可以实现很多个接口
  • 子类继承了父类,可以直接使用父类的非私有的方法和属性。
  • 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
  • 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。

说明:所有的子类都会直接或间接继承Object类

4.7.3多态

  • 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
  • 多态性的使用前提: ① 类的继承关系 ② 方法的重写
  • 分为运行时多态(overload)和编译时多态(override)。编译时,看左边;运行时,看右边

4.7.3.1 重载

System.out.println()方法就是典型的重载方法,其内部的声明形式如下:

public void println(byte x)
public void println(short x)
public void println(int x)
public void println(long x)
public void println(float x)
public void println(double x)
public void println(char x)
public void println(double x)
public void println()
//这2个方法不能构成重载,会有编译错误。
public int A(int i);
public double A(int i);
//这2个方法可以形成重载
public int A(int i);
public double A(double i);

4.7.3.2 重写:子类中可以根据需要对从父类中继承来的方法进行改造

注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

Java对象的强制转换:

  • 从子类到父类的类型转换可以自动进行
  • 从父类到子类的类型转换必须通过造型(强制类型转换)实现
  • 无继承关系的引用类型间的转换是非法的
  • 在造型前可以使用instanceof操作符测试一个对象的类型

public class Test {public void method(Person e) { // 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
} }
public static void main(String[] args){Test t = new Test();
Student m = new Student();
t.method(m);
} }

4.8"==“和"equals”

  • == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
  • equals没有被重写过默认也是==,但是进行重写(重写的原则:比较两个对象的实体内容是否相同),重写后会比较类中的相应属性是否都相等。

4.8.1 equals()方法的使用

  1. 是一个方法,而非运算符
  2. 只能适用于引用数据类型
  3. Object类中equals()的定义:
public boolean equals(Object obj) {return (this == obj);}

说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体

  1. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。

  2. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.

4.9包装类

  • 包装类使得一个基本数据类型的数据变成了类。针对八种基本数据类型定义相应的引用类型—包装类(封装类)

  • 包装类使得一个基本数据类型的数据变成对象,包装类内部有一个cache数组用来进行缓存(0–127)

4.9.1装箱

基本数据类型包装成包装类的实例

int i = 500;  Integer t = new Integer(i);

4.9.2拆箱

获得包装类对象中包装的基本类型变量

Integer a=10;  int b=a.intValue();

4.9.3字符串–>基本类型

int i = new Integer(“12”);

4.9.4基本类型–>字符串

int i=10;     String str=String.valueOf(i);

4.10关键字static、abstract、final

4.10.1关键字static

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量

静态变量(类变量):让一个类的所有实例共享数据(被static修饰的变量是类变量)
注意:

被static修饰的成员有以下特点:

  • 随着类的加载而加载,不依赖于对象而存在,优先于对象存在。
  • 不因对象的不同而改变,将这些属性设置为类属性或者类方法
  • 访问权限允许时,可不创建对象,可以通过"类.静态变量"的方式进行调用。
  • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

类变量 vs 实例变量内存解析:

说明:首先将所有人的属性置为中国,也可以根据自己的需要进行改变,不进行操作则nation属性为中国

4.10.2关键字final

注意:

  • 被final修饰的变量不能被修改,作为常量出现
  • 被final修饰的类是"太监类"不能被继承
  • 被final修饰的方法也不能被重写
  • final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。

4.10.3关键字abstract

用abstract关键字来修饰一个类或者方法,这个类叫做抽象类或者抽象方法,抽象类不能进行实例化,抽象类中一定有构造器,便于子类实例化时调用,开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作

注意:

  • 抽象方法只有方法的声明,没有实现细节
  • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
  • abstract不能用来修饰:属性、构造器等结构
  • abstract不能用来修饰私有方法、静态方法、final的方法、final的类

4.10.4 接口Interface

接口和类是并列的两个结构,接口通过让类去实现(implements)的方式来使用,接口中不能定义构造器的!意味着接口不可以实例化

接口中的全局常量是用public static final来进行修饰,方法用public abstract进行修饰

4.10.5 接口和抽象类的区别

实现类在继承父类或者实现接口时候,需要将全部抽象方法进行实现

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。

抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。

比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。
人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它.

所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。

  • 接口中的常量都是public final static的,方法都是public abstract的,可以省略,系统会自动加上
  • 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。
  • 接口可以多继承,抽象类不行
  • 接口定义方法,不能实现,而抽象类可以实现部分方法。
  • 接口中基本数据类型为static 而抽类象不是的。

当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的

所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。

4.11内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。

通俗的说就是两个类,其中类A包含于类B里面,只有B类需要调用A类,此时就可以将A类写进B类里面,形成内部类。

head类即为person类的内部类
public class person{class head{}
}

内部类分为成员内部类(静态、非静态)和局部内部类(方法内、代码块内、构造器内)以及匿名内部类

4.11.1 成员内部类

  • 一方面,可以作为外部类的成员:调用外部类的结构,也可以被static修饰,还可以被4种不同的权限修饰
  • 另一方面,作为一个类可以定义属性、方法、构造器等,也可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承。还可以被abstract修饰

4.11.2 匿名内部类

就是没有名字的一种嵌套类,一个接口/类的方法的某个实现方式在程序中只会执行一次,但为了使用它,我们需要创建它的实现类/子类去实现/重写。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。

假设当前有一个接口,接口中只有一个方法

public interface Interface01 {void show();
}

为了使用该接口的show方法,我们需要去创建一个实现类,同时书写show方法的具体实现方式

public class Interface01Impl implements Interface01{@Overridepublic void show() {System.out.println("I'm a impl class...");}
}

如果实现类Interface01Impl全程只使用一次,那么为了这一次的使用去创建一个类,未免太过麻烦。我们需要一个方式来帮助我们摆脱这个困境。匿名内部类则可以很好的解决这个问题。

我们使用匿名内部类

public static void main(String[] args) {Interface01 interface01 = new Interface01() {@Overridepublic void show() {System.out.println("这里使用了匿名内部类");}};//调用接口方法interface01.show();
}

五、异常处理

在Java语言中,将程序执行中发生的不正常情况称为“异常”

Java程序在执行过程中所发生的异常事件可分为两类:

  • Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理
  • Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使
    用针对性的代码进行处理。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界

5.1异常类型

Java.lang.Throwable|--------java.lang.Error:一般不编写针对性代码进行处理|-------java.lang.StackOverflowError|-------java.lang.OutOfMemoryError |--------Java.lang.Exception:可以进行异常处理|-------编译时异常(checked)|--------IOException|---------FileNotFoundException|--------ClassNotFoundException|--------运行时异常(unchecked)|--------NullPointerException|--------ArrayIndexOutofBoundException|--------ClassCastException|--------NumberFormatException|--------InputMismatchException|--------ArithmaticExceptio

5.1.0 堆溢出和栈溢出:

 public static void main(String[] args) {//1.栈溢出:java.lang.StackOverflowErrormain(args);//递归,自己不断调用自己出不来了//2.堆溢出:java.lang.OutOfMemoryError int[] arr = new int[1024*1024*1024];//开的空间大于Integer的最大内存1024*1024*1024=2^30,但是int[-2^30--2^30-1]。超出了int的可表示范围,弹出堆溢出错误}

5.1.1运行时异常:

//空指针异常
String a=null;
System.out.println(a.charAt(0));//数组索引越界
int []a=new int[3];
System.out.println(a[3]);//类型转换异常
Object obj=new Date();
String a=(String) obj;//NumberFormatException
String str="abc";//如果是String str="123"就不会报错
int num=Integer.parsentInt(str);//输入不匹配异常
int a=sc.nextInt();//输入非数字就会抛出该异常//算数异常
int  a=2/0;

5.1.2编译时异常:

     File file = new File("hello.txt");//Unhandled exception: java.io.FileNotFoundExceptionFileInputStream fis = new FileInputStream(file);int data = fis.read();while(data != -1){System.out.print((char)data);data = fis.read();}fis.close();

开发过程中的语法、逻辑错误不算异常
异常分为两类:

  • 一类是Error:是指Java虚拟机无法解决的严重问题。如JVM系统内部错误,栈溢出、资源耗尽,内存溢出(OutOfMemory)等等。一般情况下不能进行针对性代码进行处理
  • 另一类是Exception:遇见某些特殊情况才会发生比如1/0会有ArithmeticException,比如空指针、数组越界,类型转换异常。一般情况下,异常指的是Exception。
  • Exception又分为编译时异常和运行时异常。比如:除数为0,数组下标越界等

5.2异常处理机制

有两种方式处理异常:

  • try-catch-finally(是在想办法处理异常)
  • throws+异常类型(是在向上抛出异常,没有真正解决)

5.2.1 异常处理的方式一:try-catch-finally

5.2.1.1 异常的处理:抓抛模型

过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。

 关于异常对象的产生:① 系统自动生成的异常对象② 手动的生成一个异常对象,并抛出(throw)

过程二:“抓”:可以理解为异常的处理方式:① try-catch-finally ② throws

5.2.1.2 try-catch-finally的使用

  try{//可能出现异常的代码}catch(异常类型1 变量名1){//处理异常的方式1}catch(异常类型2 变量名2){//处理异常的方式2}catch(异常类型3 变量名3){//处理异常的方式3}....finally{//一定会执行的代码}

5.2.1.3 说明以及体会:

说明:

  1. finally是可选的。一旦选用,finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
  2. 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
  3. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
  4. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的 try-catch结构(在没有写finally的情况)。继续执行其后的代码
  5. catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
  6. 常用的异常对象处理的方式: ① String getMessage() ②printStackTrace()
  7. 在try结构中声明的变量,再出了try结构以后,就不能再被调用
  8. try-catch-finally结构可以嵌套

体会:

  • 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
    相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。

  • 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
    针对于编译时异常,我们说一定要考虑异常的处理。

5.2.2 异常处理的方式二:throws + 异常类型

  • "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

  • 体会:try-catch-finally:真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。

  • 开发中如何选择使用try-catch-finally 还是使用throws?

    1. 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
    2. 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。

5.2.3练习


运行结果:

5.3总结:

  • throw和throws的区别
    throw表示抛出一个异常类的对象,声明在方法内部
    throws属于异常处理的一种方式,声明在方法声明处

  • finally中声明的是一定会被执行的代码。即使catch中出现异常或者return,也会执行finally

  • "throws+异常类型"写在声明处。指明此方法执行时可能会抛出的异常类型。一旦方法体执行时,出现异常,会在异常代码生成一个异常类对象,此对象满足throws后异常类型时,就会被抛出。异常代码的后续就不会再执行

六、多线程

  • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合
  • 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径
    1. 若一个进程同一时间并行执行多个线程,就是支持多线程的
    2. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
    3. 一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

6.1多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

6.2进程与线程

进程是分配资源的基本单位,线程是执行的基本单位

6.3并行与并发

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

6.4线程的创建

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

6.4.1继承Thread类

步骤:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()
public class A {public static void main(String[] args) {MyThread myThread=new MyThread();myThread.start();}
}
class MyThread extends Thread{@Overridepublic void run() {System.out.println("ok");}
}

6.4.1.1注意:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU
    调度决定。
  3. 想要启动多线程,必须调用start方法。
  4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上
    的异常“IllegalThreadStateException”。

6.4.2实现Runnable接口

步骤:

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
public class A {public static void main(String[] args) {MyThread myThread=new MyThread();new Thread(myThread).start();}
}
class MyThread implements Runnable{@Overridepublic void run() {System.out.println("ok");}
}

6.4.3两种创建线程的区别

继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。

开发中:优先选择:实现Runnable接口的方式
原因:

  1. 实现的方式没有类的单继承性的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况。

6.4.3.1Runnable的好处

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源

6.4.4 Thread类的有关方法

  • void start(): 启动线程,并执行对象的run()方法
  • run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield():释放当前cpu的执行权
    1. 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
  • join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
    1. 低优先级的线程也可以获得执行
  • static void sleep(long millis):(指定时间:毫秒)
    1. 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
  • boolean isAlive():返回boolean,判断线程是否还活着

6.4.5线程的调度

6.4.5.1调度策略

  • 时间片
  • 抢占式:高优先级的线程抢占CPU

6.4.5.2线程的优先级

  • 线程的优先级等级:
    MAX_PRIORITY:10
    MIN _PRIORITY:1
    NORM_PRIORITY:5

  • 涉及的方法:
    getPriority() :返回线程优先值
    setPriority(int newPriority) :改变线程的优先级

  • 说明:
    低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

6.4.6线程的补充

Java中的线程分为两类:一种是守护线程,一种是用户线程

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  • Java垃圾回收就是一个典型的守护线程。
  • 若JVM中都是守护线程,当前JVM将退出。

6.5线程的生命周期

一个完整的生命周期中通常要经历如下的五种状态:

  • 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

线程状态转换图

6.6 线程的同步

private int tick = 100;
@Override
public void run(){while(true){if(tick>0){try{Thread.sleep(10);
}catch(InterruptedException e){ e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+“售出车票,tick号为: "+tick--);
} } }

上述代码出现了线程安全问题,是因为多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法是:只让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

6.6.1线程同步解决方式之----synchronized

操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

需要注意的是:在进行线程同步时,必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全

  • 同步代码块:
synchronized (对象){// 需要被同步的代码;
}

说明:

  1. 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
  2. 共享数据:多个线程共同操作的变量。比如:售票系统的ticket就是共享数据。
  3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。因为此时大家共用一个Runnable,
  • 同步方法。
    果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
public synchronized void show (String name){ }

6.6.2线程同步解决方式之---- Lock

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁

class A{private final ReentrantLock lock = new ReenTrantLock();
public void m(){lock.lock();
try{//保证线程安全的代码; }
finally{lock.unlock();
} } }

6.6.3synchronized 与 Lock 的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)

6.6.4 线程同步的范围

  1. 代码是否存在线程安全
    (1)明确哪些代码是多线程运行的代码
    (2)明确多个线程是否有共享数据
    (3)明确多线程运行代码中是否有多条语句操作共享数据
  2. 实际操作
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其
    他线程不可以参与执行。
    即所有操作共享数据的这些语句都要放在同步范围中
  3. 范围太小:没锁住所有有安全问题的代码。范围太大:没发挥多线程的功能

6.6.5释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

6.6.6不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程
    挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程

6.6.7线程同步的应用

单例设计模式之懒汉式(线程安全)

class Singleton {private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){if(instance==null){synchronized(Singleton.class){if(instance == null){instance=new Singleton();
} } }
return instance;
} }
public class SingletonTest{public static void main(String[] args){Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
} }

6.6.7 线程同步的栗子

多个窗口售票系统

6.6.7.1 同步代码块

  • Runnable
class Window1 implements Runnable{private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();@Overridepublic void run() {//        Object obj = new Object();while(true){synchronized (this){//此时的this:唯一的Window1的对象   //方式二:synchronized (dog) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}
}public class WindowTest1 {public static void main(String[] args) {Window1 w = new Window1();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}class Dog{}
  • Thread:
class Window2 extends Thread{private static int ticket = 100;private static Object obj = new Object();@Overridepublic void run() {while(true){//正确的
//            synchronized (obj){synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只会加载一次//错误的方式:this代表着t1,t2,t3三个对象
//              synchronized (this){if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":卖票,票号为:" + ticket);ticket--;}else{break;}}}}
}public class WindowTest2 {public static void main(String[] args) {Window2 t1 = new Window2();Window2 t2 = new Window2();Window2 t3 = new Window2();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}

6.6.7.2 同步方法

  • Runnable
class Window3 implements Runnable {private int ticket = 100;@Overridepublic void run() {while (true) {show();}}private synchronized void show(){//同步监视器:this//synchronized (this){if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;}//}}
}public class WindowTest3 {public static void main(String[] args) {Window3 w = new Window3();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}
  • Thread
class Window4 extends Thread {private static int ticket = 100;@Overridepublic void run() {while (true) {show();}}private static synchronized void show(){//同步监视器:Window4.class//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;}}
}public class WindowTest4 {public static void main(String[] args) {Window4 t1 = new Window4();Window4 t2 = new Window4();Window4 t3 = new Window4();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}

6.6.7.3 lock锁

class Window implements Runnable{private int ticket = 100;//1.实例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true){try{//2.调用锁定方法lock()lock.lock();if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);ticket--;}else{break;}}finally {//3.调用解锁方法:unlock()lock.unlock();}}}
}public class LockTest {public static void main(String[] args) {Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}

6.7 线程的死锁问题

死锁:

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等
  • 待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决办法:

  • 减少同步资源的定义
  • 避免嵌套同步

6.8 线程的通信

wait() 与 notify() 和 notifyAll()

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

注意:
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。

6.8.1 wait() 方法

  • 在当前线程中调用方法: 对象名.wait()
  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行

6.8.2 notify()/notifyAll()

  • 在当前线程中调用方法: 对象名.notify()
  • 功能:唤醒等待该对象监控权的一个/所有线程。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

6.8.3线程通信的经典例题

生产者、消费者问题:

class Clerk { // 售货员
private int product = 0;
public synchronized void addProduct() {if (product >= 20) {try {wait();
} catch (InterruptedException e)
{ e.printStackTrace();
}
} else {product++;
System.out.println("生产者生产了
第" + product + "个产品");
notifyAll();
} }
public synchronized void getProduct() {if (this.product <= 0) {try {wait();
} catch (InterruptedException e) {e.printStackTrace();
}
} else {System.out.println("消费者取走了第" +
product + "个产品");
product--;
notifyAll();
}} }
class Productor implements Runnable { // 生产者
Clerk clerk;
public Productor(Clerk clerk) {this.clerk = clerk; }
public void run() {System.out.println("生产者开始生产产品");
while (true) {try {Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) { e.printStackTrace();
}
clerk.addProduct();
} } }
class Consumer implements Runnable { // 消费者
Clerk clerk;
public Consumer(Clerk clerk) {this.clerk = clerk; }
public void run() {System.out.println("消费者开始取走产品");
while (true) {try {Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {e.printStackTrace();
}
clerk.getProduct();
} } }
public class ProductTest {public static void main(String[] args) {Clerk clerk = new Clerk();
Thread productorThread = new Thread(new Productor(clerk));
Thread consumerThread = new Thread(new Consumer(clerk));
productorThread.start();
consumerThread.start();
} }

6.9 新增线程创建方式

6.9.1 实现Callable接口

  • 与使用Runnable相比, Callable功能更强大些

    1. 相比run()方法,可以有返回值
    2. 方法可以抛出异常
    3. 支持泛型的返回值
    4. 需要借助FutureTask类,比如获取返回结果

说明:
Future接口:

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是
    否完成、获取结果等。
  • FutrueTask是Futrue接口的唯一的实现类
  • FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

6.9.1 使用callable栗子

//1.创建一个实现Callable的实现类
class NumThread implements Callable{//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}public class ThreadNew {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()new Thread(futureTask).start();try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}

6.9.3 使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3. 便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

6.9.4 使用线程池栗子

class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable//        service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}}

七、Java常用类

7.1 String的特性

  1. String是一个final类,代表不可变的字符序列
  2. String对象的字符内容是存储在一个字符数组value[]中的。
  3. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的字符串
  4. 拼接时只要其中有一个是变量,结果就在堆中。
  5. String实现了Serializable接口:表示字符串是支持序列化的。
  6. String实现了Comparable接口:表示String可以比较大小
  7. 通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中
        String s1 = "javaEEhadoop";String s2 = "javaEE" + "hadoop";String s3 = "javaEE";String s4 = s3 + "hadoop";System.out.println(s1 == s2);//trueSystem.out.println(s1 == s4);//false
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0

内存解析:

String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";

7.1.1 String对象创建的两种方式的内存解析

7.1.1.1String对象创建的两种方式

方式一:通过字面量定义的方式--直接在常量池中创建
方式二:通过new + 构造器的方式--先在堆中创建引用,然后在常量池中创建,并将堆中引用指向常量池

7.1.1.2 内存解析


String str1 = “abc”;与String str2 = new String(“abc”);的区别


字符串常量存储在字符串常量池,目的是共享
字符串非常量对象存储在堆中。

7.1.2 字符串对象是如何存储的

7.1.3 字符串的相关练习

        Person p1 = new Person();p1.name = "atguigu";Person p2 = new Person();p2.name = "atguigu";System.out.println(p1.name .equals( p2.name)); //trueSystem.out.println(p1.name == p2.name); //trueSystem.out.println(p1.name == "atguigu"); //trueString s1 = new String("bcde");String s2 = new String("bcde");System.out.println(s1==s2); //false
String s1 = "javaEE";
String s2 = "javaEE";
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false


结论:

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  • 只要其中有一个是变量,结果就在堆中
  • 如果拼接的结果调用intern()方法,返回值就在常量池中

注意:String使用陷阱

  • String s1 = “a”;
    说明:在字符串常量池中创建了一个字面量为"a"的字符串。
  • s1 = s1 + “b”;
    说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
  • String s2 = “ab”;
    说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。
  • String s3 = “a” + “b”;
    说明:s3指向字符串常量池中已经创建的"ab"的字符串。
  • String s4 = s1.intern();
    说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。

7.1.4 String常用方法

        String string="Thank you!";System.out.println(string.length());//计算string的长度System.out.println(string.charAt(0));//提取string第一个字符System.out.println(string.isEmpty());//判断string是否为空System.out.println(string.toUpperCase());//将string全部转成大写System.out.println(string.toLowerCase());//将string全部转成小写System.out.println(string.trim());//忽略前导空白和尾部空白System.out.println(string.equals("Thank you!"));//equalsIgnoreCase忽略大小写的相同System.out.println(string.concat(" You are my first choice."));//连接。输出为Thank you! You are my first choice.System.out.println(string.compareTo("Thank you!"));//进行比较,string==对象返回0,string>对象,返回一个正数,string<对象,返回一个负数System.out.println(string.substring(0, 5));//对特定区域及逆行截取System.out.println(string.endsWith("you!"));//判断字符串是不是以“xxx”结束的System.out.println(string.startsWith("Thank"));//判断字符串是不是以“xxx”开始的System.out.println(string.contains("ank"));//判断字符串是否包含“xxx”System.out.println(string.replace(" you", "s"));//replaceAll将所有的target替换成replacementSystem.out.println(string.split(" ")[0]);//将字符串是从“xxx”分开存进数组里面System.out.println(Arrays.toString(string.toCharArray()));//将字符串转换为数组

7.2 StringBuffer和StringBuilder

  • 大多数方法与String都是类似的,但是内部没有被final修饰,所以StringBuffer和StringBuilder可以对字符串内容进行增删,而不会产生新的对象。
  • StringBuffer比StringBuilder更加安全内部被synchronized所修饰
        StringBuffer s=new StringBuffer("hello ");System.out.println( s.append("java"));//在s后直接添加数据System.out.println(s.delete(0, 6));//删除索引指定的部分System.out.println(s.insert(0,"hi "));//在指定位置进行插入System.out.println(s.replace(0,2,"hello"));//在指定位置进行替换System.out.println(s.reverse());//反转字符串

注意:

  • 当append和insert时,如果原来value数组长度不够,可扩容。
  • 作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值
        String str = null;StringBuffer sb = new StringBuffer();sb.append(str);//添加的时候把null作为字符串处理的System.out.println(sb.length());//输出4(null的长度是4)System.out.println(sb);//输出nullStringBuffer sb1 = new StringBuffer(str);//初始化时把null作为空处理的System.out.println(sb1);//NullPointerException空指针异常

7.3日期时间API

7.3.1日期时间API的简单的使用

        Date date = new Date();//Date类的API不易于国际化,大部分已经被废弃System.out.println(date);System.out.println(System.currentTimeMillis());//获取当前系统时间(更优的方法进行获取时间戳)System.out.println(date.getTime());//Date()内部也是调用System.currentTimeMillis(),所以直接使用系统方法效率更高

7.3.2 时间标准化:SimpleDateFormat的用法

        //实例化SimpleDateFormat:使用默认的构造器SimpleDateFormat sdf = new SimpleDateFormat();//格式化:日期 --->字符串Date date = new Date();System.out.println(date);System.out.println(sdf.format(date);//解析:格式化的逆过程,字符串 ---> 日期System.out.println(sdf.parse("19-12-18 上午11:43"));SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//格式化System.out.println(sdf1.format(date));//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),否则,抛异常Date date2 = sdf1.parse("2020-02-18 11:48:27");System.out.println(date2);

7.3.3 Calendar类的用法

Calendar类可以定向的进行修改日期,可变性和定制标准化是他的两个特点。

        //1.实例化//方式一:创建其子类(GregorianCalendar)的对象//方式二:调用其静态方法getInstance()Calendar calendar = Calendar.getInstance();//2.常用方法//get()int days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);//返回当前日期的当前天System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//当前天是当前年的第几天//set()//calendar可变性calendar.set(Calendar.DAY_OF_MONTH,22);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);//add()calendar.add(Calendar.DAY_OF_MONTH,-3);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);//getTime():日历类---> DateDate date = calendar.getTime();System.out.println(date);//setTime():Date ---> 日历类Date date1 = new Date();calendar.setTime(date1);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);

7.3.4 JDK8以后的时间设置

localDateTime更加安全,不能将日期进行改变,但是也可以进行定制。

        //利用偏移量自定义日期Date date1 = new Date(2020 - 1900,9 - 1,8);System.out.println(date1);//Tue Sep 08 00:00:00 GMT+08:00 2020//now():获取当前的日期、时间、日期+时间LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now();System.out.println(localDate);System.out.println(localTime);System.out.println(localDateTime);//of():设置指定的年、月、日、时、分、秒。没有偏移量LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);System.out.println(localDateTime1);//getXxx():获取相关的属性System.out.println(localDateTime.getDayOfMonth());System.out.println(localDateTime.getDayOfWeek());System.out.println(localDateTime.getMonth());System.out.println(localDateTime.getMonthValue());System.out.println(localDateTime.getMinute());//体现不可变性//withXxx():设置相关的属性LocalDate localDate1 = localDate.withDayOfMonth(22);System.out.println(localDate);System.out.println(localDate1);LocalDateTime localDateTime2 = localDateTime.withHour(4);System.out.println(localDateTime);System.out.println(localDateTime2);//不可变性LocalDateTime localDateTime3 = localDateTime.plusMonths(3);System.out.println(localDateTime);System.out.println(localDateTime3);LocalDateTime localDateTime4 = localDateTime.minusDays(6);System.out.println(localDateTime);System.out.println(localDateTime4);

7.4比较器

自然排序:Comparable
定制排序:Comparator

7.4.1 Comparable接口的使用举例: 自然排序

  1. 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
  2. 像String、包装类重写compareTo()方法以后,进行了从小到大的排列
  3. 重写compareTo(obj)的规则:
    如果当前对象this大于形参对象obj,则返回正整数,
    如果当前对象this小于形参对象obj,则返回负整数,
    如果当前对象this等于形参对象obj,则返回零。
  4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
  • 栗子1:
        String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};Arrays.sort(arr);//默认从小到大排序System.out.println(Arrays.toString(arr));
  • 栗子2:
        Goods[] arr = new Goods[5];arr[0] = new Goods("lenovoMouse",34);arr[1] = new Goods("dellMouse",43);arr[2] = new Goods("xiaomiMouse",12);arr[3] = new Goods("huaweiMouse",65);arr[4] = new Goods("microsoftMouse",43);Arrays.sort(arr);System.out.println(Arrays.toString(arr));
public class Goods implements Comparable{//基本属性和方法//....//指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序@Overridepublic int compareTo(Object o) {if(o instanceof Goods){Goods goods = (Goods)o;//方式一:if(this.price > goods.price){return 1;}else if(this.price < goods.price){return -1;}else{return -this.name.compareTo(goods.name);}//方式二:
//           return Double.compare(this.price,goods.price);//Double.compare的内部和方式一是一样的}throw new RuntimeException("传入的数据类型不一致!");}
}

7.4.2 Comparator接口的使用:定制排序

  1. 背景:
    当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
  2. 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
    如果方法返回正整数,则表示o1大于o2;
    如果返回0,表示相等;
    返回负整数,表示o1小于o2。

栗子1:

        String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};Arrays.sort(arr,new Comparator(){//按照字符串从大到小的顺序排列@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof String && o2 instanceof  String){String s1 = (String) o1;String s2 = (String) o2;return -s1.compareTo(s2);}
//                return 0;throw new RuntimeException("输入的数据类型不一致");}});System.out.println(Arrays.toString(arr));}
  • 栗子2:
        Goods[] arr = new Goods[6];arr[0] = new Goods("lenovoMouse",34);arr[1] = new Goods("dellMouse",43);arr[2] = new Goods("xiaomiMouse",12);arr[3] = new Goods("huaweiMouse",65);arr[4] = new Goods("huaweiMouse",224);arr[5] = new Goods("microsoftMouse",43);Arrays.sort(arr, new Comparator() {//指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof Goods && o2 instanceof Goods){Goods g1 = (Goods)o1;Goods g2 = (Goods)o2;if(g1.getName().equals(g2.getName())){return -Double.compare(g1.getPrice(),g2.getPrice());//数字类型}else{return g1.getName().compareTo(g2.getName());//字符串类型}}throw new RuntimeException("输入的数据类型不一致");}});System.out.println(Arrays.toString(arr));}

7.5BigInteger与BigDecimal

Integer类作为int的包装类,能存储的最大整型值为-1+2^31。
Long类也是有限的,最大为-1+2^63。
如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。

BigInteger可以表示不可变的任意精度的整数

        BigInteger bi = new BigInteger("1243324112234324324325235245346567657653");BigDecimal bd = new BigDecimal("12435.351");BigDecimal bd2 = new BigDecimal("11");System.out.println(bi);//返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));

八、枚举类与注解

枚举类:类的对象只有有限个,确定的。我们称此类为枚举类

  • 枚举类分为自定义枚举类和关键字(enum)定义枚举类

8.1 Enum类中的常用方法

  • values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
  • toString():返回当前枚举类对象常量的名称
enum Season1 implements Info{//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束SPRING("春天","春暖花开"){@Overridepublic void show() {System.out.println("春天在哪里?");}},SUMMER("夏天","夏日炎炎"){@Overridepublic void show() {System.out.println("宁夏");}},AUTUMN("秋天","秋高气爽"){@Overridepublic void show() {System.out.println("秋天不回来");}},WINTER("冬天","冰天雪地"){@Overridepublic void show() {System.out.println("大约在冬季");}};
//...
}
    //values():返回所有的枚举类对象构成的数组Season1[] values = Season1.values();for(int i = 0;i < values.length;i++){System.out.println(values[i]);values[i].show();}
        //valueOf(String objName):返回枚举类中对象名是objName的对象。Season1 winter = Season1.valueOf("WINTER");//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException// Season1 winter = Season1.valueOf("WINTER1");System.out.println(winter);winter.show();

8.1.1 在使用枚举类需要注意的地方

  • 提供当前枚举类的对象,多个对象之间用",“隔开,末尾对象”;"结束
  • 在使用valueOf时,如果没有objName的枚举类对象,则抛异常:IllegalArgumentException

8.2 注解(Annotation)

8.2.1 四个源注解的介绍:

  • Retention:只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期
  • Target:指定被修饰的 Annotation 能用于修饰哪些程序元素
  • Documented:类将被javadoc 工具提取成文档
  • Inherited:具有继承性,其子类将自动具有该注解

九、Java集合

Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。

  • 数组在存储数据方面的弊端:

    1. 数组初始化以后,长度就不可变了,不便于扩展
    2. 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效不高。同时无法直接获取存储元素的个数
    3. 数组存储的数据是有序的、可以重复的。但是存储数据的特点单一

Java 集合可分为 Collection 和 Map 两种体系:

  • Collection接口:单列数据,定义了存取一组对象的方法的集合

    1. List:元素有序、可重复的集合
    2. Set:元素无序、不可重复的集合
  • Map接口:双列数据,保存具有映射关系“key-value对”的集合

9.1 Collection 接口

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

9.1.1 Collection 接口常用方法

        Collection col=new LinkedList();col.add("ok");col.add(1);System.out.println(Arrays.toString(col.toArray()));//将集合转换为数组System.out.println(col.contains("ok"));//判断集合中是否包含某个keySystem.out.println(col.size());//计算集合的大小col.remove(1);//按照索引来定向删除col.clear();//清空集合中的全部元素System.out.println(col.isEmpty());//判断集合是否为空

9.1.2 Iterator迭代器接口

用于遍历 Collection 集合中的元素,该接口有一个iterator()方法

        Collection coll = new ArrayList();coll.add(123);coll.add(456);Iterator iterator = coll.iterator();//方式一:迭代器while(iterator.hasNext()){//hasNext():判断是否还有下一个元素System.out.println(iterator.next());//next():①指针下移 ②将下移以后集合位置上的元素返回}//方式二:for-eachfor(Object obj:coll){System.out.println(obj);}

9.1.2 Collection子接口之一:List接口

List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引,List接口的实现类常用的有:ArrayList、LinkedList和Vector。

List的常用方法:

        List list=new LinkedList();list.add(0,"ok");//在指定位置添加元素System.out.println(list.get(0));//取指定位置元素System.out.println(list.indexOf("ok"));//在集合中首次出现的位置System.out.println(list.lastIndexOf("ok"));//在集合中最后出现的位置list.set(0,"hello");//设置指定index位置的元素为"hello"list.remove(0);//移除指定index位置的元素

9.1.2.1 ArrayList

ArrayList是对象引用的一个"变长"数组,默认扩容为原来的容量的1.5倍

  • JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
  • JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组

9.1.2.2 LinkedList

LinkedList是一个双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:

  1. prev变量记录前一个元素的位置
  2. next变量记录下一个元素的位置

9.1.2.3 Vector

大多数操作与ArrayList相同,区别之处在于Vector是线程安全的,但是默认扩容为原来的容量的2倍

9.1.2.4 面试题:

  1. ArrayList和LinkedList的异同

二者都线程不安全,相对线程安全的Vector,执行效率高。
此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

  1. ArrayList和Vector的区别

Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

9.1.3 Collection子接口之二:Set接口

Set 集合最大的特点就是不允许包含相同的元素

9.1.3.1 Set实现类之一:HashSet

元素是根据哈希算得的,没有顺序.元素可以有一个null

对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

9.1.3.1.1 向HashSet中添加元素的过程
  • 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
  • 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。

注意:
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

9.1.3.1.2 重写 hashCode() 方法的基本原则
  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()
    方法的返回值也应相等。
  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

9.1.3.2 Set实现类之一:LinkedHashSet

  • LinkedHashSet 是 HashSet 的子类
  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能

9.1.3.3 Set实现类之一TreeSet

  • TreeSet 可以确保集合元素处于排序状态,他的底层使用红黑树结构存储数据
  • TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。

9.2 Map接口

  • Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法。
  • Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类

下面以hashMap为例:

        Map map=new HashMap();map.put("a","A");//将指定key-value添加到(或修改)当前map对象中System.out.println(map.get("a"));//获取指定key对应的valueSystem.out.println(map.containsKey("a"));//是否包含指定的keySystem.out.println(map.size());//返回map中key-value对的个数System.out.println(map.isEmpty());//判断当前map是否为空System.out.println(map.get("a").equals("A"));//判断当前map和参数对象obj是否相等System.out.println(map.remove("a"));//移除指定key的key-value对,并返回valuemap.clear();//清空当前map中的所有数据

9.2.1 HashMap

JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)
JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。

  • HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
  • HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true

9.2.1.1 HashMap源码中的重要常量

  1. HashMap的默认容量:16
  2. HashMap的最大支持容量: 2^30
  3. HashMap的默认加载因子: 0.75
  4. Bucket中链表长度大于该默认值,转化为红黑树: 8

9.2.1.2 HashMap 1.7和1.8的改变

JDK1.7
  1. HashMap的存储结构:
    HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。而且新添加的元素作为链表的head。

  2. 添加元素的过程:
    向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的 entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果hash值不同,继续比较二者是否equals。如果返回值为true,则使用entry1的value去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。

  3. HashMap的扩容方式:
    当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

  4. HashMap什么时候进行扩容呢?
    当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的默认 值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

JDK1.8
  1. HashMap的存储结构:
    HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

  2. HashMap什么时候进行扩容和树形化呢?
    当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor 时 , 就会进行数组扩容 , loadFactor 的默认 值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的能。 当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

JDK1.8相较于之前的变化
  1. HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
  2. 当首次调用map.put()时,再创建长度为16的数组
  3. 数组为Node类型,在jdk7中称为Entry类型
  4. 形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
  5. 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。

负载因子值的大小,对HashMap有什么影响

  1. 负载因子的大小决定了HashMap的数据密度。
  2. 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
  3. 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
  4. 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数

9.2.2 LinkedHashMap

LinkedHashMap 是 HashMap 的子类。在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序,可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致

HashMap中的内部类:Node

static class Node<K,V> implements Map.Entry<K,V> {final int hash;
final K key;
V value;
Node<K,V> next; }

LinkedHashMap中的内部类:Entry

static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);
} }

9.2.3 TreeMap

  • TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序(自然排序和定制排序)。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据

9.2.4 Hashtable

Hashtable实现原理和HashMap相同,判断两个key相等、两个value相等的标准,与HashMap一致,功能基本相同。但是Hashtable是线程安全的且不允许使用 null 作为 key 和 value。

9.2.5 Properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件

Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);

9.3 Collections工具类

  • Collections 是一个操作 Set、List 和 Map 等集合的工具类
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
1. 排序操作:(均为static方法)reverse(List):反转 List 中元素的顺序shuffle(List):对 List 集合元素进行随机排序sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元进行排序swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
2. 查找、替换        Object max\min(Collection):根据元素的自然顺序,返回给定集合中的最大\小元素Object max\min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大\小元素int frequency(Collection,Object):返回指定集合中指定元素的出现次数void copy(List dest,List src):将src中的内容复制到dest中 boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值

10、IO流

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。
在Java程序中,对于数据的输入/输出操作以"流(stream)" 的方式进行。

10.1 File 类的使用

10.1.1 路径分隔符

  • windows和DOS系统默认使用“\”来表示
  • UNIX和URL使用“/”来表示

10.1.2File类的获取功能

public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null

10.2 IO 流体系

分类 字节输入流 字节输出流 字符输入流 字符输出流
基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter

10.3 字符流的转换

10.3.1 从硬盘的文件中写入到内存中

public void testFileReader(){FileReader fr = null;try {//1.实例化File类的对象,指明要操作的文件File file = new File("hello.txt");//相较于当前Module//2.提供具体的流fr = new FileReader(file);//3.数据的读入//read():返回读入的一个字符。如果达到文件末尾,返回-1//方式一:
//        int data = fr.read();
//        while(data != -1){//            System.out.print((char)data);
//            data = fr.read();
//        }//方式二:语法上针对于方式一的修改int data;while((data = fr.read()) != -1){System.out.print((char)data);}} catch (IOException e) {e.printStackTrace();} finally {//4.流的关闭操作
//            try {//                if(fr != null)
//                    fr.close();
//            } catch (IOException e) {//                e.printStackTrace();
//            }//或if(fr != null){try {fr.close();} catch (IOException e) {e.printStackTrace();}}}}

10.3.2 从内存中写出数据到硬盘的文件里

    public void testFileWriter() {FileWriter fw = null;try {//1.提供File类的对象,指明写出到的文件File file = new File("hello1.txt");//2.提供FileWriter的对象,用于数据的写出fw = new FileWriter(file,false);//3.写出的操作fw.write("I have a dream!\n");fw.write("you need to have a dream!");} catch (IOException e) {e.printStackTrace();} finally {//4.流资源的关闭if(fw != null){try {fw.close();} catch (IOException e) {e.printStackTrace();}}}}

10.3.3 从磁盘中读取到文件到内存中,在存储到另一块磁盘文件中

    @Testpublic void testFileReaderFileWriter() {FileReader fr = null;FileWriter fw = null;try {//1.创建File类的对象,指明读入和写出的文件File srcFile = new File("hello.txt");File destFile = new File("hello2.txt");//不能使用字符流来处理图片等字节数据
//            File srcFile = new File("爱情与友情.jpg");
//            File destFile = new File("爱情与友情1.jpg");//2.创建输入流和输出流的对象fr = new FileReader(srcFile);fw = new FileWriter(destFile);//3.数据的读入和写出操作char[] cbuf = new char[5];int len;//记录每次读入到cbuf数组中的字符的个数while((len = fr.read(cbuf)) != -1){//每次写出len个字符fw.write(cbuf,0,len);}} catch (IOException e) {e.printStackTrace();} finally {//4.关闭流资源//方式一:
//            try {//                if(fw != null)
//                    fw.close();
//            } 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();}try {if(fr != null)fr.close();} catch (IOException e) {e.printStackTrace();}}}

10.4 字节流的转换

10.4.1 从硬盘的文件中写入到内存中以及复制

/*此时处理异常的话,仍然应该使用try-catch-finallyInputStreamReader的使用,实现字节的输入流到字符的输入流的转换*/@Testpublic void test1() throws IOException {FileInputStream fis = new FileInputStream("dbcp.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集char[] cbuf = new char[20];int len;while((len = isr.read(cbuf)) != -1){String str = new String(cbuf,0,len);System.out.print(str);}isr.close();}/*此时处理异常的话,仍然应该使用try-catch-finally综合使用InputStreamReader和OutputStreamWriter*/@Testpublic void test2() throws Exception {//1.造文件、造流File file1 = new File("dbcp.txt");File file2 = new File("dbcp_gbk.txt");FileInputStream fis = new FileInputStream(file1);FileOutputStream fos = new FileOutputStream(file2);InputStreamReader isr = new InputStreamReader(fis,"utf-8");OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");//2.读写过程char[] cbuf = new char[20];int len;while((len = isr.read(cbuf)) != -1){osw.write(cbuf,0,len);}//3.关闭资源isr.close();osw.close();}

10.4.2 字节流图片视频文字的输入输出

 //使用字节流FileInputStream处理文本文件,可能出现乱码。@Testpublic void testFileInputStream() {FileInputStream fis = null;try {//1. 造文件File file = new File("hello.txt");//2.造流fis = new FileInputStream(file);//3.读数据byte[] buffer = new byte[5];int len;//记录每次读取的字节的个数while((len = fis.read(buffer)) != -1){String str = new String(buffer,0,len);System.out.print(str);}} catch (IOException e) {e.printStackTrace();} finally {if(fis != null){//4.关闭资源try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}/*实现对图片的复制操作*/@Testpublic void testFileInputOutputStream()  {FileInputStream fis = null;FileOutputStream fos = null;try {//File srcFile = new File("爱情与友情.jpg");File destFile = new File("爱情与友情2.jpg");//fis = new FileInputStream(srcFile);fos = new FileOutputStream(destFile);//复制的过程byte[] buffer = new byte[5];int len;while((len = fis.read(buffer)) != -1){fos.write(buffer,0,len);}} catch (IOException e) {e.printStackTrace();} finally {if(fos != null){//try {fos.close();} catch (IOException e) {e.printStackTrace();}}if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}//指定路径下文件的复制public void copyFile(String srcPath,String destPath){FileInputStream fis = null;FileOutputStream fos = null;try {//File srcFile = new File(srcPath);File destFile = new File(destPath);//fis = new FileInputStream(srcFile);fos = new FileOutputStream(destFile);//复制的过程byte[] buffer = new byte[1024];int len;while((len = fis.read(buffer)) != -1){fos.write(buffer,0,len);}} catch (IOException e) {e.printStackTrace();} finally {if(fos != null){//try {fos.close();} catch (IOException e) {e.printStackTrace();}}if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}@Testpublic void testCopyFile(){long start = System.currentTimeMillis();String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";//        String srcPath = "hello.txt";
//        String destPath = "hello3.txt";copyFile(srcPath,destPath);long end = System.currentTimeMillis();System.out.println("复制操作花费的时间为:" + (end - start));//618}

10.5 缓冲流的转换

  @Testpublic void BufferedStreamTest() throws FileNotFoundException {BufferedInputStream bis = null;BufferedOutputStream bos = null;try {//1.造文件File srcFile = new File("爱情与友情.jpg");File destFile = new File("爱情与友情3.jpg");//2.造流//2.1 造节点流FileInputStream fis = new FileInputStream((srcFile));FileOutputStream fos = new FileOutputStream(destFile);//2.2 造缓冲流bis = new BufferedInputStream(fis);bos = new BufferedOutputStream(fos);//3.复制的细节:读取、写入byte[] buffer = new byte[10];int len;while((len = bis.read(buffer)) != -1){bos.write(buffer,0,len);//                bos.flush();//刷新缓冲区}} catch (IOException e) {e.printStackTrace();} finally {//4.资源关闭//要求:先关闭外层的流,再关闭内层的流if(bos != null){try {bos.close();} catch (IOException e) {e.printStackTrace();}}if(bis != null){try {bis.close();} catch (IOException e) {e.printStackTrace();}}//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();}}//实现文件复制的方法public void copyFileWithBuffered(String srcPath,String destPath){BufferedInputStream bis = null;BufferedOutputStream bos = null;try {//1.造文件File srcFile = new File(srcPath);File destFile = new File(destPath);//2.造流//2.1 造节点流FileInputStream fis = new FileInputStream((srcFile));FileOutputStream fos = new FileOutputStream(destFile);//2.2 造缓冲流bis = new BufferedInputStream(fis);bos = new BufferedOutputStream(fos);//3.复制的细节:读取、写入byte[] buffer = new byte[1024];int len;while((len = bis.read(buffer)) != -1){bos.write(buffer,0,len);}} catch (IOException e) {e.printStackTrace();} finally {//4.资源关闭//要求:先关闭外层的流,再关闭内层的流if(bos != null){try {bos.close();} catch (IOException e) {e.printStackTrace();}}if(bis != null){try {bis.close();} catch (IOException e) {e.printStackTrace();}}//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();}}@Testpublic void testCopyFileWithBuffered(){long start = System.currentTimeMillis();String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";copyFileWithBuffered(srcPath,destPath);long end = System.currentTimeMillis();System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176}/*使用BufferedReader和BufferedWriter实现文本文件的复制*/@Testpublic void testBufferedReaderBufferedWriter(){BufferedReader br = null;BufferedWriter bw = null;try {//创建文件和相应的流br = new BufferedReader(new FileReader(new File("dbcp.txt")));bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));//读写操作//方式一:使用char[]数组
//            char[] cbuf = new char[1024];
//            int len;
//            while((len = br.read(cbuf)) != -1){//                bw.write(cbuf,0,len);
//    //            bw.flush();
//            }//方式二:使用StringString data;while((data = br.readLine()) != null){//方法一:
//                bw.write(data + "\n");//data中不包含换行符//方法二:bw.write(data);//data中不包含换行符bw.newLine();//提供换行的操作}} catch (IOException e) {e.printStackTrace();} finally {//关闭资源if(bw != null){try {bw.close();} catch (IOException e) {e.printStackTrace();}}if(br != null){try {br.close();} catch (IOException e) {e.printStackTrace();}}}}

10.6 练习

要求:获取文本上字符出现的次数,把数据写入文件。

    public void testWordCount() {FileReader fr = null;BufferedWriter bw = null;try {//1.创建Map集合Map<Character, Integer> map = new HashMap<Character, Integer>();//2.遍历每一个字符,每一个字符出现的次数放到map中fr = new FileReader("dbcp.txt");int c = 0;while ((c = fr.read()) != -1) {//int 还原 charchar ch = (char) c;// 判断char是否在map中第一次出现if (map.get(ch) == null) {map.put(ch, 1);} else {map.put(ch, map.get(ch) + 1);}}//3.把map中数据存在文件count.txt//3.1 创建Writerbw = new BufferedWriter(new FileWriter("wordcount.txt"));//3.2 遍历map,再写入数据Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();for (Map.Entry<Character, Integer> entry : entrySet) {switch (entry.getKey()) {case ' ':bw.write("空格=" + entry.getValue());break;case '\t'://\t表示tab 键字符bw.write("tab键=" + entry.getValue());break;case '\r'://bw.write("回车=" + entry.getValue());break;case '\n'://bw.write("换行=" + entry.getValue());break;default:bw.write(entry.getKey() + "=" + entry.getValue());break;}bw.newLine();}} catch (IOException e) {e.printStackTrace();} finally {//4.关流if (fr != null) {try {fr.close();} catch (IOException e) {e.printStackTrace();}}if (bw != null) {try {bw.close();} catch (IOException e) {e.printStackTrace();}}}}

11、网络编程

直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯

11.1 网络通信要素概述

  • IP和端口号
  • 网络通信协议

11.2 实现网络中的主机互相通信

  • 通信双方地址

    1. IP:唯一的标识 Internet 上的计算机。IP地址分类方式:IPV4 和 IPV6
    2. 端口号:标识正在计算机上运行的进程(程序),不同的进程有不同的端口号
  • 一定的规则(即:网络通信协议。有两套参考模型)
    1. OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
    2. TCP/IP参考模型(或TCP/IP协议):事实上的国际标准

11.3 域名解析

当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址

11.4 TCP 和 UDP

  • TCP协议:

    1. 使用TCP协议前,须先建立TCP连接,形成传输数据通道
    2. 传输前,采用“三次握手”方式,点对点通信,是可靠的
    3. TCP协议进行通信的两个应用进程:客户端、服务端。
    4. 在连接中可进行大数据量的传输
    5. 传输完毕,需释放已建立的连接,效率低
  • UDP协议:
    1. 将数据、源、目的封装成数据包,不需要建立连接
    2. 每个数据报的大小限制在64K内
    3. 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
    4. 可以广播发送
    5. 发送数据结束时无需释放资源,开销小,速度快

12、反射

  • 正常方式:引入需要的”包类”名称 —通过new实例化— 取得实例化对象
  • 反射方式:实例化对象— getClass()方法— 得到完整的“包类”名称

补充:
动态语言:在运行时代码可以根据某些条件改变自身结构
静态语言:运行时结构不可变

12.1Java反射机制提供的功能

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 在运行时处理注解
  7. 生成动态代理

12.2 获取Class类的实例

  1. 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
    实例:Class clazz = String.class;
  2. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
    实例:Class clazz = “www.atguigu.com”.getClass();
  3. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
    实例:Class clazz = Class.forName(“java.lang.String”);

12.3 类的加载过程

家宴准解除

Java中的设计模式

单例(Singleton)设计模式

单例模式只生成一个实例,减少了系统性能开销

饿汉式

class Singleton {// 1.私有化构造器
private Singleton() {}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {return single; }
}

懒汉式

class Singleton {// 1.私有化构造器
private Singleton() {}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {if(single == null) {single = new Singleton();
}
return single; }
}

JVM的组成

类加载子系统、运行数据区(内存)、执行引擎、本地接口库,本地方法库

运行时数据区包括:堆、JVM栈、程序计数器、方法区、本地方法栈

JVM栈:用来存储局部变量、对象引用
堆:存放对象实例,用来存储新创建的对象以及数组
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。有个静态域,用来存储被static修饰的静态方法属性或方法。

Java尚硅谷核心知识相关推荐

  1. java泛型_Java核心知识 基础五 JAVA 泛型

    泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型.泛型的本 质是参数化类型,也就是说所操作的数据类型被指定为一个参数.比如我们要写一个排序方法, 能够对整型数组.字符串数组甚 ...

  2. Java尚硅谷基础笔记-day4数组-数组常见算法

    第三章 数组 3.4 数组中涉及的常见算法 3.1 数组的概述 3.2 一维数组的使用 3.3 多维数组的使用 3.4 数组中涉及的常见算法 3.5 数组工具类的使用 3.6 数组使用中的常见异常 3 ...

  3. java尚硅谷 项目三《开发团队调度项目》最细致流程、总结

    目录 1.目标 2.项目细节 2.1功能 2.2主界面显示 2.3功能展示 2.3.1团队列表 2.3.2添加团队成员 2.3.3删除团队成员 2.3.4退出 3.项目包和类的说明,软件设计结构 4. ...

  4. 尚硅谷全套课件整理:Java、前端、大数据、安卓、面试题

    目录 Java 尚硅谷 IT 精英计划 JavaSE 内部学习笔记.pdf 尚硅谷 Java 基础实战之银行项目.pdf 尚硅谷 Java 技术之 JDBC.pdf 尚硅谷 Java 技术之 Java ...

  5. 抖音Java后端开挂,全靠这份啃了48天【Java进阶核心知识集+刷:25专题面试】

    人人都想进大厂,当然我也不例外.早在春招的时候我就有向某某某大厂投岗了不少简历,可惜了,疫情期间都是远程面试,加上那时自身也有问题,导致屡投屡败.突然也意识到自己肚子里没啥货,问个啥都是卡卡卡卡,后期 ...

  6. Elasticsearch顶尖高手系列:核心知识篇(一)

    目录 1.第1-4节 第01节:课程介绍 第02节:什么是Elasticsearch 第03节:Elasticsearch的功能.适用场景以及特点介绍 第04节:Elasticsearch核心概念:N ...

  7. 尚硅谷智慧校园 —— 5、学生管理功能实现

    目录 1.回显搜索条件中的班级选项 1.1.在 controller 添加方法 2.查询学生信息(分页带条件) 2.1.在 service 及其实现类添加方法 2.2.在 controller 添加方 ...

  8. 尚硅谷python核心基础教程笔记-第一章 计算机基础知识

    第一章 计算机基础知识(视频1-10) 课程介绍 课程名称:Python基础视频教程 讲师:尚硅谷教育,李立超(lichao.li@foxmail.com) 面向的层次:From Zero to He ...

  9. 尚硅谷Java入门视频教程导读及第一章

    尚硅谷Java入门视频教程导读及第一章 JAVA基础学习导读-编程入门 0.1概述 0.2 计算机硬件介绍 中央处理器(CPU) 存储设备 内存 比特(bit)和字节(byte) 内存 输入和输出设备 ...

最新文章

  1. [JavaScript] JavaScript 数组挖掘,不只是讲数组哟
  2. 诺奖得主本庶佑:CNS这些顶刊观点有九成不正确,不要盲从迷信,搞科研做到六个C更重要...
  3. Thinkphp整合各个功能
  4. 【学习笔记】分析函数(开窗函数)
  5. 动手学PaddlePaddle(0):新版本PaddlePaddle安装
  6. 猫和老鼠java下载安装_tomcat(Java服务器)
  7. python中计算如何实现_基于python如何实现计算两组数据P值
  8. 企业微信加密消息体_微信公众平台开发者中心安全模式消息体加解密实现
  9. php fizz,php 的 FizzBuzzWhizznbsp;nbsp;
  10. 新员工入职表_入职培训流程,五大步骤让员工顺利上岗,来之能战战之能胜
  11. java虚拟机工作原理_java虚拟机原理及工作原理都是什么?java虚拟机如何运行?...
  12. 记录一下我的游戏私服搭建(台服dnf)
  13. 关于SQL求同比、环比
  14. python爬取图片代码可替换网站_Python爬虫(批量爬取某网站图片)
  15. VmWare配置网络桥接模式,实现虚拟机联网。
  16. The 2019 ACM-ICPC China Shannxi Provincial Programming Contest B. Product(杜教筛+约数)
  17. Android设置RecyclerView的Header和Footer
  18. 什么是BIM开发?这篇文章可以告诉你
  19. 科技+卫生=智慧公厕,城市焕然一新!
  20. 教你一招轻松压缩MP4视频到最小

热门文章

  1. word计算机桌面加密,Word文档怎么加密 保护Word文档就靠这4招
  2. SecureCRTSecureFX(一):SecureCRT的介绍与下载安装
  3. 什么是域名服务器?域名服务器的作用是什么?
  4. 如何在Ubuntu上安装并使用Docker
  5. oracle中制表符,oracle中去掉文本中的换行符、回车符、制表符
  6. python学习 之 笔记一
  7. Windows Support Tools
  8. gitlab中国官方网站
  9. Flink 1.12.2 源码浅析 : JobGraph
  10. 触发器-trigger