java基础整理(自用)
java基础
jre和jdk
- jre是java runtime environment 的缩写。指java运行环境,包含jvm虚拟机和java核心类库
- 我们自己编写代码需要用到java存放在jre中已经写好的java文件
idea快捷键
1.基本数据类型
整数类型:
- byte 1b
- short 2b
- int 4b
- long 8b
浮点类型
- float 4b
- double 8b
字符串类型
- char 2b
boolean类型 1/8b常写为1b
2.引用类型
引用数据类型(对象类型)
类 class
接口 interface
数组 []
3.注意事项
float类型和long类型定义的时候要在后面加上F/L
long num = 30L;
float num1 = 3.1415926F;
char类型定义的时候只能定义一个字符
char a = 'Abc'; 这个是错的
char a = 'A' 这是对的
4.进制
整数拓展:二进制(0b)开头、十进制、八进制(0)、十六进制(0X)
5.转义字符
\t \n 制表符
6.变量
java变量是程序中最基本的存储单元,其中包括变量名,变量类型和作用域
变量作用域:类变量、实例变量、局部变量
public static int all = 0;//类变量
String s = "AAC" //实例变量
public void instead(){
int i = 0; //局部变量
}
a++ 先执行再自增
++a 先自增再执行
7.java的优点与特性
- 简单性
- 面向对象
- 可移植性
- 高性能
- 分布式
- 动态性
- 多线程
- 安全性
- 健壮性
8.java的三大版本
- java se 标准版开发
- java me 嵌入式开发
- java ee 企业级开发
9.配置环境变量
环境变量—新建—JAVA_HOME—输入jdk路径
path——新建——%JAVA_HOME%\bin
新建——%JAVA_HOME%\jre\bin
10.注释
//这个是单行注释
/*
这个是多行注释
*/
/**
* @description 文档注释
* @author
*/
11.位运算符+隐式转换+ASCII字符
位运算符
A = 0011 1100
B = 0000 1101
A&B = 0000 1100 //都是1为1 其余为0
A|B = 0011 1101 //有1为1 其余为0
A^B = 0011 0001 //相同为0 不同为1
~B = 1111 0010 //取反
<< *2 //左移 乘以2
>> /2 //右移 除以2
隐式转换
数据取值范围小的自动转换为大的
byte--short--int--long--float--double--
char--
byte、short 、char类型进行运算时候自动转换为int
public static void main(String[] args){
byte a;
byte b;
int c = a+b;
}
java存在常量优化机制:
byte d = 3+4;
这里3和4是两个 常量,java存在常量优化机制,在编译的时候就让3,4 相加,然后判断是否在byte的取值范围之内。在范围内就通过编译,不在就报错。
ASCII字符
字符 十进制
0 48 相差48 字符1的十进制是49
A 65 大小写相差32 char v = ‘A’ ; v = (char)('A'+32); 输出v的话结果是a
a 97
12.三元运算符+逻辑运算符
三元运算符
int a = 10;
int b = 10;
a+=b //a=a+b
a-=b //a=a-b
//字符串连接符 +
System.out.println("j"+a+b) //前面是字符串自动变为字符串拼接 结果为j1010
System.out.println(a+b+"j")//前面是运算 结果为 20j
x ? y : z
//if(x==true) x=y; else x=z
例: 50<60 ? "对":"不对"
逻辑运算符
& 逻辑与 a & b,a和b都是true,结果为true,否则为false
| 逻辑或 a | b,a和b都是false,结果为false,否则为true
^ 逻辑异或 a ^ b,a和b结果不同为true,相同为false
! 逻辑非 !a,结果和a的结果正好相反
&与&& |与||的区别
逻辑与&,无论左边真假,右边都要执行。
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
逻辑或|,无论左边真假,右边都要执行。
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。
13.包机制
为什么?是什么?怎么用?
为了更好地组织类 java提供了包机制,用于区别类名的命名空间
一般利用公司域名倒置作为包名
com.baidu.www
14.javaDoc
@author
@version //版本号
@param //参数名
@return //返回值情况
@throws //异常抛出情况
@since //指明需要最早使用的jdk版本
15.do...while循环
do{
}while( );
和while区别:
- while先判断后执行,dowhile先执行后判断
- dowhile总是保证循环至少被执行一次
16.增强for循环
注:重点是用来循环数组和集合
for(声明语句:表达式)
{
//代码句子
}
-
声明语句:声明新的局部变量,该变量的类型必须和数组元素类型匹配,其作用域限定在循环语句块,其值与数组元素的值相等。
-
表达式:表达式是要访问的数组名,或者是返回值为数组的方法
int[] numbers = {10,20,30};
for(int x:numbers){
System.out.println(x);
}
17.方法里面的参数+参数传递总结
参数
- 实际参数:实际调用传递给方法的参数--方法调用()里面
- 形式参数:用来定义作用的参数--方法定义的()里面
- java都是值传递
- 如果调用的方法改动了堆里面的值,那么变量的值再原main函数里会发生改变,否则不然
-
- 例如int a[] =
- 如果 change(a)这个方法里面有 a[0] = 3;,那么change再栈里面被弹出以后,main函数里面的数组还是变成了{3,2,3,4,5}.因为change方法改变了堆内存里面的值.
总结
java的实参如何传递给方法形参?
java方法中的参数传递只有一种方式: "值传递",即将实际参数值的副本(复制品)传递给方 法的形参接收 而实参本身不受影响
分类:
1 形参是基本数据类型: 将实参基本数据类型变量的"数据值" 传递给形参
int a=10;
int b=a;
2 形参是引用数据类型: 将实参引用数据类型变量的"地址值" 传递给形参
int[] p=new int[5];
int[] p1=p;
18.方法的重载
- 重载就是一个类中 有相同的函数名称,但是参数不同
- 方法重载规则:
-
- 方法名称必须相同
- 参数列表必须不同(个数不同、类型不同或者参数排列顺序不同等)
- 方法的返回类型可以相同也可以不同
- 仅仅返回类型不同不足以成为方法的重载
就近类型向上匹配原则.
public static void main(String[] args) {
int a=10;
int b=20;
m(a,b);
//这个时候 编译器回去找哪个方法
/*
没有void m(int a,int b),所以自动向上匹配 类型变为double 因为有一个double int 和一个 int double,JVM不知道调用哪个 所以报错
*/
}
//public static void m(int a,int b){}
public static void m(int a, double b) {
System.out.println("int double");
}
public static void m(double b, int a) {
System.out.println("double int");
}
public static void m(double a, double b) {
System.out.println("double double");
}
19.内存分析
- 堆:
- 存放new的对象和数组
- 可以被所有线程共享,不会存放别的对象引用
- 栈:
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
- 方法区:
- 可以被所有线程共享
- 包含所有的class和static变量
20.冒泡排序
- 两层循环 外层表示轮数,里层表示比较
-
- 比较数组中两个相邻的元素,如果第一个数比第二个数大,就交换他们的位置
- 每一次比较,都会产生一个最大/最小的数字
- 下一轮则可以减少一次排序
- 依次循环,直到结束
public class Demo02 {
public static void sort(int[] array){
int temp = 0;
//外层循环定义循环次数
for (int i = 0;i<array.length-1;i++){
//比较大小
for (int j = 0;j<array.length-1-i;j++){
//从小到大排序,每一次循环把最大的数放到最后,所以array.length-1 -i 因为后面倒数i个数已经是比较过的最大的了 所以-i减少循环次数
if(array[j]>array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] a = {2,5,98,1,0,3,789,53,6};
sort(a);
for(int x:a){
System.out.print(x+",");
}
}
}
21.稀疏数组
- 当一个数组中大部分元素都是0或者为同一值得数组时,可以使用稀疏数组来保存该数组。
- 稀疏数组的处理方式:
-
- 记录数组一共有几行几列,多少不同的值
- 把具有不同值得元素得行列值记录在一个小规模数组中,从而缩小程序的规模
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始数组
System.out.println("原始数组");
for (int[] ints : array1) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
//稀疏数组: a[0][0]:行 a[0][1]:列 a[0][2]:值
/*
转换为稀疏数组保存
1.获取有效值的个数
* */
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (array1[i][j]!=0){
sum++;
}
}
}
System.out.println("有效值个数"+sum);
//2.创建一个稀疏数组
//行数为有效值个数+1(首行存放行、列、值(有效值值得个数)三个属性) 列数固定为3
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;
//3.遍历二维数组 把非零的值 存放到稀疏数组中
int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j]!=0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
//4.输出稀疏数组
System.out.println("稀疏数组");
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i][0]+"\t"
+array2[i][1]+"\t"
+array2[i][2]+"\t");
}
22. 面向对象
- 面向对象编程 oop
- 本质就是以类的方式组织代码,以对象的组织(封装)数据
- 抽象
- 三大特性
-
- 封装
- 继承
- 多态
- 从认识论角度考虑,对象是具体事物类是对对象得抽象
- 从代码运行角度考虑,类是对象得模板
23.封装
-
高内聚,低耦合: 高内聚指的是内部操作细节自己完成,不允许外部操作,低耦合:仅暴露少量外部方法提供给外部使用
-
封装(数据的隐藏)
24.super注意点
- super调用父类的构造方法,必须在构造方法的第一个
- super必须只能出现在子类的方法或者构造方法中
- super和this不能同时调用构造方法
和this比较:
1.代表的对象不同
this:本身调用者这个对象
super代表父类存储空间的标识(父类对象的引用)
2.前提
this:没有继承也可以使用
super:只能在继承条件中使用
3.构造方法:
this():本身的构造
super():父类的构造
25.二进制
java整数常量提供了四种表现形式:
- 二进制
- 十进制
- 八进制
- 十六进制
8421码
-
2进制转8进制----3个数一组 算出来具体值之后,数值拼接
-
2进制转16进制----4个数一组 算出来具体值之后,数值拼接
0b111 000---(八进制)70
0b11 1000--(十六进制) 0x38
26.构造方法
构造 创建对象的时候所调用的方法 例如 int i = new int();这里的int()就是构造方法
格式:
- 方法名需要和类名相同
- 没有返回值类型 连void都没有
- 没有具体返回值(不能由return带回具体的结果)
27.String
- Java中所有的""引起来的都是字符串的对象
并且字符串常量不可改变,所有的字符串值的改变都是对象的替换--即会再堆内存中新增一个地址 然后把地址赋给变量名 不会原有地址里面的值改变,
1当对字符串重新赋值时 需要重写指定内存区域赋值 不能使用原有的进行赋值
2当对现有的字符串进行连接操作的时候 也需要重新指定内存区域赋值,不能使用原有的
3任何对字符串的修改都不是在原有的内存区域修改 String 通过字面值的方式去给一个字符串变量赋值, 此时字符串值保存在字符串常量池中字符串常量池中是不会存储相同内容的字符串的
String常见的构造方法
1.String() 空参构造 创建一个空字符串""
2.String(char[] chs) 根据传递的char类型数组中的字符创建字符串
//可以把字符数组转换字符串的方法
3.String(String s) 根据传递的字符串去创建字符串
4.String s = "";直接赋值(最常用)
5 String(byte bytes[], int offset, int length)//从数组中第几个开始取 取出几
- ==比较基本数据类型的时候 比较的是数据值
-
- 比较引用数据类型比较的是堆内存中的地址值
String类型特点
String a = "abc";
String b = "abcde";
String c = a+"de";
System.out.println(a==c);
//结果是false
字符串的+操作:创建StringBuilder对象,将内容通过append()方法添加到 StringBuilder对象空间内
使用toString()方法,将StringBuilder转换为String类型,又得到一个新的 String对象(有自己的内存地址)(相当于再堆里新开辟了一块空间 生成了新的地址)
String s5 = "a" + "b"+"c";
System.out.println(a==s5);
//结果为true String内部有优化机制
两个或者两个以上的字符串常量相加,在预编译的时候“+”会被优化,
相当于把两个或者两个以上字符串常量自动合成一个字符串常量
String类的各种方法
-
获取字符串中的字符 charAt(int index)
-
将字符串转换为字符数组 toCharArray() 返回值char[]----- char[] chars = a.toCharArray();
-
replace("旧值","新值"):使用新值替换旧值并返回替换完成后的字符串
- replaceAll(String regex,String replacement)使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。
- replaceFirst(String regex,String replacement)替换第一次出现的 第二次出现的不会替换
-
split(切割的规则):返回的是切割之后得到的字符串数组
注意:
- 如果切割之后得到的全是空字符串,认为都是无效的数据,都不要了
- 如果是中间夹杂的空字符串,认为是有效数据会进行保留,但是最后一个非空字符串的元素,后面的空字符串认为是无效的不进行保留.但是之前的空字符串会被保存
-
截取字符串substring方法:
- substring(int beginIndex)从传入的索引位置开始截取到字符串的末尾,并将得到的字符串返回
- substring(int beginIndex,int endIndex),截取[beginIndex,endIndex),并将得到 的字符串返回包头不包尾
28.StringBuilder概述
-
和String的对比
String:""创建完成不可变,改变值就是对象的替换很浪费内存StringBuilder:可变的字符串缓冲区,弥补字符串不可变的特点,用来操作字符串(改变字符串的内容)
构造方法:无参构造 和 带参构造
StringBuilder() StringBuilder(String str)
-
常用方法 :
1.append(元素),返回当前的StringBuilder对象
2.reverse(),反转
3.length(),统计StringBuilder中存储多少个字符
4.toString(),将StringBuilder转换为String类型,目的:为了使用String类中的方法链式编程 -
提高效率原理:每次使用StringBuilder的对象,进行append的时候,操作的都是同一个内存空间
-
StringBuilder创建的默认容量是16 可以添加16个字符,满了以后数组扩容,新容量 = 2*旧容量+2
29.集合和数组
-
特点:存储容量是可变的
-
区别:
- 共同:都是用来存储数据的容器
- 不同:数组长度是固定的 集合长度是可变的
-
ArryList<> list = new ArrayList();
<>填写你想要的数据类型 不写表示可以存储所有数据类型
ArrayList<>
30.继承
概念
继承描述的是事物之间所属关系.java中的继承是在一个现有类的基础上构建一个新的类,新的类叫子类.现有的叫父类 子类会自动拥有父类所有可继承的属性和方法 (私有的属性不可以继承)
-
格式:
class 子类 extends 父类()
-
注意点
- JAVA中只有单继承,没有多继承,但是可以多层继承
- 单继承:子类只能拥有一个父类
- 不支持多继承:子类不能拥有多个父类
- 多层继承:A继承B B继承C
- 在Java中,所有类都直接或者间接继承object类
- JAVA中只有单继承,没有多继承,但是可以多层继承
-
优点
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
-
缺点:
- 继承是侵入性的
- 降低了代码的灵活性
- 继承关系 导致子类必须拥有父类的非私有方法
- 增强了代码的耦合性
开发的原则:高内聚低耦合
高内聚:尽量让一个类拥有独立解决问题的能力
低耦合:降低类和类之间的关系
继承的访问顺序
子类局部变量-->子类成员变量-->父类局部变量-->父类成员变量.....
super关键字
public class Person{//父类
public Person(){
System.out.println("Person无参构造执行了!");
}
}
public class Student extends Preson{//子类
public Student(){
System.out.println("Student无参构造执行了");
}
}
public class Test{
public static void main(String[] args){
Student student = new Student();
}
}
/*
结果是:
Person无参构造执行了!
Student无参构造执行了
说明继承里面的无参构造有一行隐形代码 super();
调用无参构造的时候默认先调用super();
如果写出来 必须放在代码第一行 this()同理
因此this和super不能同时出现在同一个无参构造里面
*/
构造方法的访问特点
- 子类初始化之前 一定要先完成父类初始化. 原因如下:
- 子类在初始化时有可能要使用到父类的数据 如果父类没有完成初始化 子类将无法使用父类的数据
- 在子类初始化自己的this空间之前先完成super空间的二初始化
- 子类在初始化时有可能要使用到父类的数据 如果父类没有完成初始化 子类将无法使用父类的数据
- 默认访问的是空参?
- 因为Object作为基类只有空参构造
31.构造器
- 和类名相同
- 没有返回值
作用
- new本质在调用构造方法
- 初始化对象的值
- 构建对象
注意点:
-
没有定义的时 依旧可以调用 Person person = new Person();
这时候程序会有一个隐式的无参构造方法
-
当定义有参构造以后,还想使用无参构造,必须要显式的写出来 不然会标红.
32.方法重写
public class A extends B{
public static void test(){
System.out.println("A=>test");
}
}
public class B{
public static void test(){
System.out.println("B=>test");
}
}
public class test {
public static void main(String[] args){
//静态方法:方法的调用之和定义的数据类型有关(左边)
A a = new A();
a.test();
//继承中父类的引用可以指向子类
B b = new A();
b.test();
}
}
/*结果是
*A=>test
*B=>test
*/
方法的调用之和定义的数据类型有关
继承中父类的引用可以指向子类
public class A extends B{
public void test(){
System.out.println("A=>test");
}
}
public class B{
public void test(){
System.out.println("B=>test");
}
}
public class test {
public static void main(String[] args){
//非静态方法
A a = new A();
a.test();
//继承中父类的引用可以指向子类
B b = new A();//子类重写了父类的方法
b.test();//B
}
}
/*结果是
*A=>test
*A=>test
*/
意义:
- 沿袭父类功能
- 增加自己特有方法
重写:
- 需要有继承关系,子类重写父类方法!
- 方法名必须相同
- 参数列表必须相同
- 权限修饰符范围可以扩大但是不能缩小
- 抛出异常范围可以缩小但是不可以扩大
不可以被重写的几种:
- static方法 属于类 它不属于实例
- final 常量
- private方法
注意
-
如果返回值类型是基本数据类型,子类重写方法的返回值类型要和父类相同
-
如果返回值类型是引用数据类型,子类重写方法的返回值类型要小于等于父类
- 一般情况下 ,要求子类返回值类型和父类相同
33.static关键字
特点:
- 被static修饰的成员,会被该类所有对象[共享]
- 被static修饰的成员 会随着类的加载而加载,优先于对象
- 可以通过类名.的方式进行调用
{
System.out.println("匿名代码块");
}
static {
System.out.println("静态代码块");
}
public Test()
{
System.out.println("构造方法");
}
public static void main(String[] args) {
Test test = new Test();
System.out.println("============");
final Test test1 = new Test();
}
/*运行结果:
静态代码块
匿名代码块
构造方法
============
匿名代码块
构造方法*/
//静态代码块执行过一次以后不执行
34.多态
-
同一种方法可以根据发送对象的不同而采用多种不同的行为方式
(同一对象在不同时刻的表现形式)
-
一个对象的类型是确定的 但是指向对象的引用类型有很多(父类或者有关系的类)
public class A extends B{
public void test(){
System.out.println("A=>test");
}
public void eat(){
System.out.println("eat");
}
}
public class B{
public void test(){
System.out.println("B=>test");
}
}
//一个对象的实际类型是确定的
new A();
new B();
//父类的引用指向子类
//对象能执行哪些方法 主要看对象左边的类型 和右边关系不大
//子类能调用的方法都是自己的或者继承父类的
A a = new A();
//父类可以指向子类 但是不能调用子类独有的方法
B b = new A();
b.test();//B
注意事项:
- 多态是方法的多态 属性没有多态
- 父类和子类 有联系
- 存在条件:
- 继承关系
- 方法需要重写
- 父类的引用指向子类的对象 Father f1 = new Son();
- 父类数据类型变量接受子类对象的地址
- 成员变量 Fu fu = new Zi();
- 编译看左边 运行看左边(成员变量不存在重写)
- 编译时期 只知道Fu f 从Fu.class中查找成员变量 能够找到 编译通过 找不到 编译失败
- 运行时 成员变量不存在重写 使用哪个数据进行访问 就是哪个类自己的成员变量
- 成员方法
- 编译看左边 运行看右边(重写的方法)
- 编译时期 只知道Fu f 从Fu.class中查找成员方法 能够找到 编译通过 找不到 编译失败
- 运行时期 new Zi() 此时完成子类自己this空间初始化
- 重写时候
- 进行重写:在自己this空间存在一个和父类方法声明完全一样的成员方法 优先使用自己重写方法
- 没有重写:从super空间找从父类继承到的方法进行使用(多态就失去了意义)
- 好处和弊端
- 好处
- 提高代码扩展性
- 具体体现:定义方法时候 使用父类型作为参数 该方法可以接收父类的任意子类对象
- 提高代码扩展性
- 弊端
- 无法调用子类的特有方法
- 好处
多态中的转型
-
向上转型
- 父类引用指向子类对象 Fu fu = new Zi();
-
向下转型
- 从父类类型转换回子类类型
3.转型存在的风险
-
不清楚父类类型变量/接口类型变量接受的是哪一个子类/实现类对象转换造成和目标类型不匹配的情况
-
解决方法
-
instanceof关键字:
-
转换之前用instanceof判断
- 判断类型--强转--调用
变量名 instanceof 目标类型
如果变量名保存的类型和目标类型一致 返回true 可以向下转型.
- 实际类型 instanceof 目标类型 返回值是boolean类型
-
-
35.权限修饰符
36.开闭原则
对扩展内容开放 代码主体对修改内容关闭
37.抽象类
定义:
- 抽象方法:将共性的方法抽取到父类以后,发现实现逻辑无法在父类中给出明确的 描述,所以定义抽象方法
- 定义格式
//抽象类的定义
public abstract class 类名 {}
- 抽象类: 在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类!
- 定义格式
//抽象方法的定义
public abstract void eat();
特点:
-
抽象类和抽象方法必须使用 abstract 关键字修饰
-
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
-
抽象类不能实例化
-
抽象类可以有构造方法
-
抽象类的子类
要么重写抽象类中的所有抽象方法
要么把自己变成抽象类
38.final关键字
-
fianl关键字的作用
- final代表最终的意思,可以修饰成员方法,成员变量,类
-
final修饰类、方法、变量的效果
-
fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
-
final修饰方法:该方法不能被重写
-
final修饰变量:表明该变量是一个常量,不能再次赋值
-
变量是基本类型,不能改变的是值
-
变量是引用类型,不能改变的是地址值,但地址里面的内容是可以改变的
-
举例
public static void main(String[] args){ final Student s = new Student(23); s = new Student(24); // 错误 s.setAge(24); // 正确 }
-
-
final修饰成员变量初始化时机:
- 在创建的时候直接给值
- 在构造方法结束之前完成赋值
-
39.代码块
代码块概述 (理解)
在Java中,使用 { } 括起来的代码被称为代码块
代码块分类 (理解)
-
局部代码块
-
位置: 方法中定义
-
作用: 限定变量的生命周期,及早释放,提高内存利用率
-
示例代码
public class Test { /* 局部代码块 位置:方法中定义 作用:限定变量的生命周期,及早释放,提高内存利用率 */ public static void main(String[] args) { { int a = 10; System.out.println(a); } // System.out.println(a); } }
-
-
构造代码块
-
位置: 类中方法外定义
-
特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
-
作用: 将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
-
示例代码
public class Test { /* 构造代码块: 位置:类中方法外定义 特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行 作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性 */ public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student(10); } } class Student { { System.out.println("好好学习"); } public Student(){ System.out.println("空参数构造方法"); } public Student(int a){ System.out.println("带参数构造方法..........."); } }
-
-
静态代码块
-
位置: 类中方法外定义
-
特点: 需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
-
作用: 在类加载的时候做一些数据初始化的操作
-
示例代码
public class Test { /* 静态代码块: 位置:类中方法外定义 特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次 作用:在类加载的时候做一些数据初始化的操作 */ public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(10); } } class Person { static { System.out.println("我是静态代码块, 我执行了"); } public Person(){ System.out.println("我是Person类的空参数构造方法"); } public Person(int a){ System.out.println("我是Person类的带...........参数构造方法"); } }
-
40.接口
1.区别
-
普通类:只有具体实现
-
抽象类:具体实现和规范抽象方法
-
接口:只有规范
2.特点
- 接口的关键字是interface
- 被接口中定义的所有的类都是抽象的 默认含有 public abstract(所以写的时候可以不写 直接写 void run()😉
- 被接口定义的常量都是默认用public static final修饰
- 接口要有实现类,实现接口的类必须重写接口中所有方法
- 接口不可以被实例化,没有构造方法
- 接口可以多实现 但是必须要重写里面的所有方法(同抽象类)
3. 注意事项
-
使用方式:
- 类实现接口 重写内部的抽象方法
- 将实现类定义为抽象类 不需要重写抽象方法 标准的沿袭
-
类和接口的关系
- 类实现接口中所拥有接口中描述的功能,具体交给实现类进行重写
- has---a的关系
-
继承和实现关系同时存在的情况下 成员方法的使用顺序是怎么样的?
-
子类有,先用自己的,子类没有,去父类找,父类也没有,就去接口中找.
先去继承关系下找,找不到再去接口找.
-
继承的成员方法优先于接口的成员方法
-
-
当接口和直接父类出现了同名的成员变量 如何区分?
-
调用父类用super.
调用接口用接口名.
因为成员变量不存在重写
-
4.JDK8中接口的默认方法
-
JDK8版本后 允许在接口中定义非抽象方法 但是需要使用default关键字修饰.这些方法就是默认方法
-
格式 :public default 返回值类型 方法名(参数列表)
public default void show(){
}
-
-
默认方法不是抽象方法,所以不强制重写.但是可以被重写,重写时要去掉default关键字
-
public可以省略 default不可以省略
-
如果实现了多个接口 ,多个接口存在同名的默认方法,那么子类就必须要对该方法进行重写
-
-
JDK中允许定义static静态方法
-
格式 public static 返回值类型 方法名(参数列表)
-
只能通过接口名+.的方式进行调用.不能通过实现类名或者对象名进行调用
-
为什么只能通过接口名调用?
静态方法不可以被重写 如果子类对象访问静态方法
如果子类存在该静态方法 优先使用子类.
如果子类中不存在静态方法 就去super空间找
-
在实现关系下 子类是不能直接从implements空间查找静态方法
-
-
Jdk9版本中接口成员的特点
- 接口中私有方法定义格式:
- private 返回值类型 方法名 (参数列表){}
- private static 返回值类型 方法名 (参数列表){}
41.内部类
定义
内部类就是在一个类的内部定义另一个类
分类
-
成员内部类
public class NeiBuLei { private int id = 10; public void out(){ System.out.println("这是一个外部类方法"); } public class Inner{ public void in(){ System.out.println("这是一个内部类方法"); } //获得外部类的私有属性 public void getId(){ System.out.println(id); } } } public static void main(String[] args) { //实例化外部类 NeiBuLei neiBuLei = new NeiBuLei(); //通过外部类实例化内部类 NeiBuLei.Inner inner = neiBuLei.new Inner(); inner.in(); inner.getId(); } //成员内部类可以获得外部类的私有属性和方法 //在主函数中实例化要通过外部类进行
-
静态内部类
- 外部类类名.内部类类名 内部类对象名 =
public class NeiBuLei { private int id = 10; public void out(){ System.out.println("这是一个外部类方法"); } public static class Inner{ public void in(){ System.out.println("这是一个内部类方法"); } //这个时候id就拿不到了 因为在静态类加载的时候id并没有加载,静态类没办法直接访问非静态属性.可以通过static修饰id获取 public void getId(){ System.out.println(id); } } }
-
局部内部类
public class NeiBuLei { int a = 10; public void method(){ //局部内部类:写在方法中 里面可以写方法 外界无法访问 但是该类可以访问到外部类的成员 也可以访问方法内的局部变量 class Inner(){ public void in(){ int b = 20; System.out.println(a); System.out.println(b); } } } }
-
匿名内部类
格式:
new 类名或者接口名(){
重写方法;
}
理解:
将 继承/实现 ** , 方法重写 ** ,创建对象 三个步骤放到了同一步进行.
解释:
实现了UserService接口的/继承了某个类的 一个实现类/子类对象
public class Test{ public static void main(String[] args){ //匿名内部类:没有名字初始化类 不用将实例保存到变量中 new Apple().eat(); // new UserService()其实就是实现了UserService接口的类 但是她没有名字.返回值是一个UserService对象 UserService userservice = new UserService(){ @Override public void hello(){ System.out.println("hello"); } }; userservice.hello(); /* 或者可以直接 new UserService(){ @Override public void hello(){ System.out.println("hello"); } }.hello();调用重写的方法 */ } } class Apple{ public void eat(){ System.out.println("1"); } } interface UserService{ void hello(); }
42.Lambda表达式
package com.itheima.test;
public class test {
public static void main(String[] args) {
new Apple().eat();
//实现goUserService的方法:
goUserService( new UserService() {
@Override
public void hello() {
System.out.println("hello");
System.out.println("123")
}
});
//使用lambda表达式
goUserService(() -> {
System.out.println("hello");
System.out.println("123");
});
//当输出语句只有一行时 可以改成
goUserService(() -> System.out.println("hello"));
public static void goUserService(UserService userService){
userService.hello();
}
}
interface UserService {
void hello();
}
1.Lambda表达式标准格式
-
格式:(形式参数)->
-
形式参数:如果有多个参数就用逗号隔开,没有参数空白即可
-
->:英文的中划线和大于符号组成,固定写法 代表指向动作
-
代码块:是具体我们要做的事情,也就是我们以前写的方法体内容
使用前提:
- 有一个接口
- 接口当中有且只有一个抽象方法
2.优化lambda表达式
格式1:对象名::方法名
格式2类名::静态方法名
格式3类名::new(有疑问)
3.匿名内部类和lambda表达式的区别
- 类别:
匿名代码块: 接口 抽象类 具体类
lambda表达式: 接口
- 使用限制
匿名代码块: 对内部抽象方法的数量没有限制
lambda表达式: 只有一个抽象方法的接口
- 实验原理:
匿名代码块: 会生成.class的字节码文件 存储在硬盘中
lambda表达式: 也会生成 但是会在内存中使用完毕就销毁 不会存储在硬盘
43.API
Math类的方法
-
public final Math
-
Math里面的方法都是被static修饰的
-
构造方法是private修饰 私有化 目的是不让当前类创建对象
Math.abs();//取绝对值
Math.ceil(double a);//向上取整
Math.floor(double a);//向下取整
Math.round(float a);//四舍五入取整
Math.max();//返回两个数最大的
Math.min();//返回两个数最小的
Math.pow(a,b);//a的b次幂
Math.random();//取0.0-1.0之间的随机数 范围是[0.0,1.0),想取任意范围的随机数需要加上一个new random().nextInt();
Math.sqrt();//开根号
System类方法
-
public final System
-
System里面的方法都是被static修饰的
-
构造方法是private修饰 私有化 目的是不让当前类创建对象
static void exit(int status) :结束JVM虚拟机,非0表示异常终止
static long currentTimeMillis() :获取当前系统时间的毫秒值
单位精确度:毫秒 1秒 = 1000毫秒
从计算机时间原点开始计算毫秒值
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) :
数据源数组(数据从哪里来)
从哪个索引开始
目的地数组(到哪里去)
到哪个索引位置
拷贝多少个
注意:
1.索引操作数组的方法,都要注意索引越界情况,并且传递的数组不能为null
2.目的地数组可以装不满,但是不能装不下
Object类方法
- public Object()
- Object类是所有类的超类(基类)(直接或者间接父类)
- Object类的toString方法得到的是地址值
Object.toString()//返回变量的地址值
Object.equals()//默认比较地址值 底层用"=="判断 重写可以比较内容 使用alt+insert可以自从生成
**StringBuilder类里面没有equals方法 继承的是Object类的方法 **
String类里面的equals方法是先判断是不是字符串 不是直接返回false
Objects
- Objects里面的方法都是被static修饰的
toString(对象)//返回参数中对象的字符串表示形式
toString(对象,默认字符串)//返回对象的字符串表现形式 如果对象为空 返回第二个参数
isNUll(对象)//对象是null返回true 不是返回false
nonNull(对象)//对象是null返回false 不是返回rue
BigDecimal类
构造方法:
BigDecimal(double val)
BigDecimal(Stringval)
BigDecimal db1 = new BigDecimal (3.14);
如果想要精确计算 要使用BigDecimal()中的字符串构造
**运算顺序是按照方法的的调用顺序来 **
BigDecimal bd1 = new BigDecimal();
bd1.add();//加法
bd1.substract();//减法
bd1.multiply();//乘法
bd1.divide();//除法
//BigDecimal是精确计算,因此遇到了无限循环的数会报错 所以要用下面的这个方法
bd1.divide(另一个BigDecimal对象,精确几位,舍入模式);
//举例:
bd1.divide(bd2,2,BigDecimal.Round_up);
//舍入模式分为: 旧: 新:
//进一法:(BigDecimal.Round_up) RoundingMode.up
//去尾法:(BigDecimal.Round_floor) RoundingMode.floor
//四舍五入:(BigDecimal.Round_up) RoundingMode.half_up
基本类型包装类
Integer类---int类型的包装类
jkd1.5特性 :
自动装箱:
-
装箱:把一个基本数据类型变量对应的包装类
-
自动:java底层会自动帮我们调用valueof的方法
自动拆箱:
- 拆箱:把一个包装类型变成对应的基本数数据类型C
- 自动:java底层会自动帮我们调用valueof的方法
Integer i3 = 100;//自动装箱机制
i3+=200;//i3 = i3+200
//先自动拆箱把i3转换成int类型然后
//+200最后再转换成Integer类型赋值给i3
System.out.print
注意:
- 使用包装类型时先进行非空的判断
44.数组高级操作
递归
二分查找
- 条件:数组的元素按照大小顺序排列
- 核心:每次查找去掉一半范围
- 范围时数组的序列[0,arr.length-1]
- 值在左边 min不变 max = mid-1[min,mid-1]
- 值在右边 max不变 min = mid+1[mid+1,max]
- 值存在 返回当前mid的值
- 值不存在 min=mid+1>max
冒泡排序
- 双重循环 外层是循环的次数 n个数据就是n-1次
- 内层循环进行数据比较 每比较一次都会把最大值放到最右边 因此减少一次比较次数
递归
快速排序
-
基准数的选取
- 一般使用数组最左侧数字作为基准数
- 选取基准数以后 该数字不动 从另一侧开始查找
-
数据的交换
- 从右边开始找比基准数大的
- 从左边找比基准数小的
- 交换两个值的位置
- 继续找 一直到指向同一个索引
- 基准数归位 把基准数和同一所引换位
-
效果
- 基准数左侧都是比自己小的 右侧都是比自己大的
Arrays类
Arrays.toString(int[] a);//把数组转换成字符串
/*1.如果存储的时基本数据类型 打印出来是
[数据值,数据值,....]
2.如果存储的是引用数据类型
如果重写Object的toString方法 则按照重写的格式来
如果没有重写 打印出来的是[地址值,地址值,....]
*/
Arrays.sort(int[] a);//排序 按照数字顺序排列指定数组
Arrays.binarySearch(int[] a,int key);
//利用二分法查找返回元素的索引 若不存在则返回 -key-1,存在则返回实际的索引
//前提:数组元素要有顺序
45.时间日期类
计算机中的起始时间:1970年1月1日 00:00:00(计算机的时间原点)
Date类
- Date代表了一个特定时间 精确到毫秒
- Date构造方法
- public Date()表示默认时间 也就是计算机的当前时间
- public Date(long date) 表示指定时间 从计算机时间原点开始 过了指定毫秒(参数)的时间.我们在东八区 所以要加八小时
- 成员方法
- public long getTime() 获取当前时间对象毫秒值
- public void setTime(long time) 设置时间
SimpleDateFormat类
- 可以对Date对象进行格式化和解析
- 年y 月M 日d 时H 分m 秒s
- 构造方法
- public SimpleDateFormat()
- public SimpleDateFormat(String pattern)//传递时间格式进去
- 成员方法
- public final String format(Date date):把日期格式化成日期/时间字符串可以用于把时间按照固定格式展示
- public Date parse(String source):从给定字符串开始解析文本生成日期可以用于对时间进行计算
JDK8以后新增的日期类
- LocalDate 表示日期(年月日)
- LocalTime 表示时间(时分秒)
- LocalDateTime 表示日期+时间(年月日时分秒)
-
LocalDateTime.now()获取当前时间
-
LocalDateTime.of(2020,11,11,11,11,11) 把指定的年月日时分秒初始化成一个LocalDateTime对象
-
-
-
toLocalDate():保留年月日
-
toLoaclDate();保留时分秒
-
-
-
-
plus系列 加法 minus系列 减法 with系列 修改
- 加法/减法正负数都可以 会自动加减
- with系列写的值必须合理 不然会报错
-
- 计算时间间隔
-
-
注意事项:
46.异常
概述
-
异常:就是程序出现了不正常的情况.注意 语法错误不在异常体系中
-
RuntimeException是运行时期异常 其余是编译时期异常
-
异常体系结构
-
-
- 当代码出现了异常 那么就在这里创建一个异常对象 new NullPointerException()
- 看自己有没有处理异常的代码
- 写了就按照程序里的来 没写,交给方法的调用者处理
- 调用者是JVM JVM默认处理异常做了以下几件事
- 将异常信息以红色字体展示在控制台上
- 停止程序运行
异常处理方式
-
格式:
- 修饰符 返回值类型 方法名(形式参数列表) throws 异常类名{
可能出现异常的代码
}
-
throws关键字的作用
- 没有手动处理
- 告诉调用者 该方法可能出现异常 需要进行处理
- 不处理最终还是交给调用者 也就是虚拟机进行处理
-
编译时期异常的处理方式
- 如果没有手动处理而是使用throws声明 需要在使用该异常存在的方法上
- 都要手动声明 否则编译失败
-
运行时期的异常处理方式
- 不需要手动声明
throws和throw区别
-
throws:
- 用在方法的生命的后面 跟的是异常类名
- 表示声明异常,调用该方法可能会出现这样的异常
-
throw:
- 用在方法体内 跟的是异常对象名 throw new NuLLPointerException()
- 表示手动抛出异常对象 由方法体内语句处理
Throwable
- 常用方法
- getMessage();//输出产生异常原因
- toString();//输出异常的第一行
- printStackTrace();
自定义异常
-
编写
public class XxxException extends RuntimeExceptiom{ //运行时期异常 里面只要有无参构造和代餐构造 public XxxException(){ } public XxxException(String message){ super(String); } } public class XxxException extneds Exception{ //编译时期异常 public XxxException(){ } public XxxException(String message){ super(String); } }
-
使用
-
方法内手动抛出异常
throw new XxxException("异常信息");
-
47.集合和数组
集合和数组区别:
-
集合的长度可变 数组不可变
-
集合不可以存基本数据类型的数据.如果集合要存储基本数据类型,那么实际存储的是他们的包装类
-
ArrayList<int> list1 = new ArrayList<int>(); //这么写是错的 集合不可以存基本数据类型的数据 ArrayList<Integer> list1 = new ArrayList<Integer>(); //这是对的 存储的是基本数据类型的包装类--自动装箱
-
-
没写<>存储的是Object类型的数据 获取得到的也是object类型数据
-
当两者存储自定义类对象时:
- 没有重写toString,打印地址
- 重写,打印属性值
集合的体系机构
- 集合:分为单列和双列
- 单列: 存储一列数据 底层是Collection
- 双列:存储两列数据 key value 底层是Map
Conllection集合
概述
-
单列集合的顶层接口
-
JDK不提供此接口的任何直接实现.他提供更具体的子接口的实现
创建Collection集合的对象
- 多态的方式
- 具体实现类ArrayList
方法
-
-
removeif()---根据条件进行删除
- removeif底层会遍历集合,得到集合的每一个元素
- s依次表示集合的每一个元素
- 把元素拿去lambda表达式中判断一下
- 返回结果是true 删除 false不删除
-
Collection<String> collection = new ArrayList;//不能new Collection() //Collection是个接口 不可以被实例化 collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); collection.add("dddd"); collection.removeif(+ (String s)->{ return s.length()==3; //删除长度是3的元素 } ); System.out.println(collection); //打印结果为[dddd]迭代器
-
-
boolean remove(Object o):删除元素
- 传递参数是元素本身,找到该元素在数组里出现的第一次的索引位置
- 使用System.arraycopy()方式进行删除,把要删除元素后面的所有元素复制一遍 然后覆盖
-
E remove(int index)
- 根据传递的索引位置 结合存储元素的底层数组 获取要删除的元素并且保存
- 使用system.arraycopy方法进行删除
- 返回之前保存的被删除的元素值
迭代器
Collection集合的遍历
Iterator:迭代器,集合的专用遍历方式
- Iterator
iterator():返回集合中迭代器对象 该迭代器对象默认指向当前集合的0索引.
方法:
- boolean hasNext():判断当前位置是否有元素可以被取出
- E next():获取当前位置元素 把迭代器移向下一个索引
- remove():指向谁删除谁 不需要传参数
原理:
-
Iterator<String> it = collection.iterator();
//创建了一个迭代器 并且指向数组的0索引
while (it.hasNext()){//判断当前位置是否有元素可以被取出 有返回true 没有返回false
System.out.println(it.next());//获取当前位置元素 把迭代器移向下一个索引
}
作用: 遍历集合
- ConcurrentModifactionException()并发修改异常
- ``` java
package com.itheima.mydemo.my_Iterator;
import java.util.ArrayList;
import java.util.Iterator;
public class Test03 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("1");
list.add("1");
list.add("1");
list.add("1");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if ("1".equals(s)){
list.remove(s);
}
}
/*
异常信息:在使用迭代器遍历的同时使用集合自己的添加方法和删除方法
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.itheima.mydemo.my_Iterator.Test03.main(Test03.java:17)
1.ArrayList$Itr是ArrayList集合内部类Itr
2.对象获取方法iterator()返回的应该就是ArrayList$Itr
3.内部调用:
1.next
2.checkForComodification*/
private class Itr implements Iterator<E> {
int cursor;//默认初始化值为0
int lastRet = -1;//迭代器删除使用
int expectedModCount;
Itr() {
//初始化成员变量expectedModCount
//问题:ArrayList.this.modCount
//此变量表示集合的操作数,每执行一次集合自己的增删方法,都会增加一次modCount
//执行完毕集合增删方法之后,获取迭代器对象,此时使用集合modCount初始化expectedModCount
//目前:expectedModCount和modCount一致
//代码中我们执行5次添加操作,modCount值为5
//expectedModCount值也为5
this.expectedModCount = ArrayList.this.modCount;
}
//判断当前位置是否有元素
//size表示当前集合元素的个数以及当前指向的可以插入的索引位置
public boolean hasNext() {
//cursor当前迭代器指向的箭头,此时成员变量,int类型,默认初始化值为0
return this.cursor != ArrayList.this.size;//不相等,表明没有达到size位置(没有元素位置)
//返回true
}
public E next() {
this.checkForComodification();//判断并发修改异常的方法
int i = this.cursor;//cursor当前迭代器指向的索引位置
if (i >= ArrayList.this.size) {//查找的位置超出size位置
throw new NoSuchElementException();//没有元素
} else {//有元素
Object[] elementData = ArrayList.this.elementData;//获取集合底层数组
if (i >= elementData.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = i + 1;//先将箭头向后移动
return elementData[this.lastRet = i];//再使用之前的值获取元素并返回
//lastRet记录当前指向位置
}
}
}
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();//进行并发修改异常判断
try {
ArrayList.this.remove(this.lastRet);//lastRet记录的就是当前迭代器指向的位置
//此时使用ArrayList集合自己的删除方法,传递索引,增加modCount的次数
this.cursor = this.lastRet;//此时cursor保存的是当前位置
this.lastRet = -1;//重新初始化lastRet留作下一次删除使用
this.expectedModCount = ArrayList.this.modCount;//重新初始化expectedModCount,让二者保持一致
//只要两个值相同,不会出现并发次改异常
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
final void checkForComodification() {
//判断当前集合的操作数和当前迭代器对象的expectedModCount是否一致
//expectedModCount是在迭代器对象创建的时候被初始化并且没有改变
//如果使用集合自己的增删方法,此时modCount发生改变,此时不一致
//抛出并发修改异常
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
}
}
```
- 产生原因:
- 使用迭代器遍历集合的同时集合使用了自己的增删方法
- 解决方法:
- 使用普通for循环
- 使用迭代器自身的方法
###### 增强for循环
- 使用条件:只有实现了Iterable接口的类才能使用增强for循环进行遍历
- 基本格式: for(数据类型 变量名:数组/集合对象名){
输出打印...System.out.print(i);//i是数组的元素 不是索引
}
- 增强for本身也是使用的迭代器 使用时候注意并发修改异常.
###### List集合
List本身也是个接口 因此不能new 创建对象要用到多态
``` java
List<泛型> list = new ArrayList();
概述:
- 有序集合 存储顺序
- 用户可以精准的控制列表中每个元素插入的位置.用户可以通过整数索引访问元素,并搜索列表中的元素
- 和Set不同 list允许重复
特点:
- 有序
- 允许重复
- 有索引
方法:
-
int lastIndexOf(Object o):获取指定元素在集合中最后一次出现的位置 ,不存在则返回-1
-
static
List of(E...elements):使用同种元素构建list集合 -
ArrayList list = new ArrayList(List.of(1,2,3,4))
-
-
List
subList(int fromIndex,int toIndex):截取[fromIndex,toIndex)的元素 返回新集合 -
List list = new ArrayList(); list.subList(0,6);//截取[0,6)序号的数据
-
-
T[] toArray (T[] a):将集合转换为存储相同数据类型的数组 -
<T> T[] toArray(T[] a):将集合转换为存储相同数据类型的数组 //使用方法: /*方法一: 1.自定义数组,类型是集合存储的元素数据类型 2.长度指定为集合的长度 3.将数组名作为参数传递给toArray() 情况: 1.数组能装下,直接存储元素到数组并返回 2.数组装不下,使用当前数组的数据类型创建新数组和集合元素个数相同长度,进行存储并返回 方法二: 直接调用toArray(new 数据类型[0]) -- 推荐使用 此时直接根据传递的数组数据类型创建和集合等长的数组 存储元素并返回 */ //方法一: /* Integer[] arr = new Integer[list.size()]; list.toArray(arr); System.out.println(Arrays.toString(arr));*/ //方法二: /* Integer[] integers = list.toArray(new Integer[0]); System.out.println(Arrays.toString(integers));*/ List list = new ArrayList(List.of(2,588,1000,10000)); Integer[] a = new Integer[list.size()]; System.out.println(list.toArray(a));//打印出来是转换成的数组的地址 System.out.println(Arrays.toString(list.toArray(a)));//打印出来才是转换好的数组
-
-
list集合中有两个删除方法
- 删除指定得元素 返回值表示当前元素是否删除成功
- 删除指定索引得元素 返回值表示实际删除的元素
-
remove():修改指定索引的元素 返回被修改的元素
- 被替换的元素就不存在了
数据结构
数据结构是计算机存储,组织数据的方式
- 栈
- 先进后出的模型 一头开(栈顶)一头闭(栈底)
- 数据进入栈模型被称为进栈/压栈
- 数据出栈模型叫弹栈/出栈
- 队列
- 先进先出的模型 两头开(前端)(后端)
- 数组---查询快 增删慢的模型,内存空间是连续的
- 通过地址值和索引定位 查询任意数据耗时相同 查询速度快
- 删除数据时要把每个后面数据前移 删除效率低
- 添加要把添加位置后面没个数据后移在添加元素 添加效率低
- 链表---查询慢 增删快的模型,每一个节点都是单独的对象(Node对象)
- 单向链表:
- 结点:
- 结点的存储位置(地址)
- 一个结点里面封装了具体的数据和下一个结点的地址
- 头节点存储了自己的信息和指向空地址(下一个还没创建 所以存储为空 创建了就是下一个空间的地址值)
- 查找时候只能从头节点开始查找
- 结点:
- 双向链表:
- 结点:
- 里面一个空间封装了前一个数据的地址值 自己本身的值 和下一个空间的地址值
- 查找时候先判断距离头结点近还是尾结点近
- 结点:
- 单向链表:
ArrayList集合
-
ArrayList:
- 底层结构是数组elementData 查询快 增删慢
- 底层数组默认长度是10 使用空参构造的时候默认长度为0
-
创建时同时创建一个size变量 不仅表示数组长度 还表示下一次要操作的索引
- 超出原本范围后 会创建一个1.5倍长度的数组 然后把原有数组的数据拷贝到新数组(自动扩容)
- ArrayList集合首次添加(一个一个添加)
- 使用空参构造创建ArrayList集合对象,此时什么也没做
- 首次执行添加方法,底层扩容为默认容量10
- 存储数据超出容量,进行扩容为原来的1.5倍
-
ArrayList集合首次添加(批量添加)
- 通过ArrayList集合的构造方法传递List.of(元素)
- 通过ArrayList集合转换为对应的数组 赋值给elementData,此时elemenData的元素和List.of得元素个数一致
-
扩容 按照底层长度进行计算 每次扩容为原来的1.5倍
LinkedList集合
-
底层数据结构是双向链表 查询慢 增删快
-
-
LinkedList核心成员变量:
- Node
first:用来记录链表第一个Node对象的地址 - Node
Last:用来纪律链表最后一个Node对象的地址
- Node
-
补充方法
-
static void shuffle(List<?> list)//打乱list集合元素顺序
-
Collections.shuffle(list);
-
-
static<T extends Comparable<? super T>> void sort(List
list) -
Collections.sort(list);
-
-
48.泛型
-
是什么
-
提供了编译时期的类型检测机制(限定数据类型)
-
泛型的好处
- 把运行期间的问题提到了编译期间
- 避免了强制类型转换
-
可以使用的地方
- 类----------泛型类
- 方法----泛型方法
- 接口----泛型接口
-
泛型类
-
格式
-
修饰符 class 类名<类型>
-
public class Guess<E>{ }
-
-
创建对象时必须要给这个泛型确定具体的数据类型
-
继承关系下的使用
-
子类继承泛型父类 没有给定指定的泛型类型 将子类变成泛型类,创建对象时确定具体类型
public static void main(String[] args) { Demo02 demo02 = new Demo02();//这个时候如果在创建对象时也不给定具体类型 那么他调用SayHello方法里面的类型就是SayHello(Object t) Demo02<String> demo02 = new Demo02(); //这个时候给了定义 那么里面的类型就是String t } class Demo01<T>{ public void SayHello(T t){ System.out.println("hello"+t); } } //继承的类可以自主扩展 /* class Demo02<E,T> extends Demo01<E>{ } 这样不仅有父类的泛型 也有自己的泛型 */ class Demo02<E> extends Demo01<E>{ }
-
子类继承泛型父类 指定父类泛型具体类型 子类变成普通类型
public static void main(String[] args) { Demo02 demo02 = new Demo02();// demo02.SayHello("s");//这时候实在继承的时候指定了 那么就是String t; } class Demo01<T>{ public void SayHello(T t){ System.out.println("hello"+t); } } class Demo02<E> extends Demo01<String>{ }
-
-
-
泛型方法
-
格式
-
修饰符 <类型> 返回值类型 方法名(类型 变量名)
-
public
void show(T t) -
public <T> void show(T t){ }
-
-
-
继承关系下的使用
- 子类继承父类没有重写父类的泛型方法 此时创建子类对象调用方法 根据传递的参数确定泛型的具体类型
- 子类继承父类 重写父类的泛型方法 需要保留泛型声明
-
-
泛型接口
-
泛型接口的使用方式
-
实现类也不给具体的数据类型,这个时候要在调用的时候确认类型
-
实现类确定具体的数据类型
-
接口继承接口(扩展接口的功能)
- 接口继承接口 指定父接口泛型的具体类型
- 接口继承接口 不指定父接口泛型具体类型 子接口要定义父接口的泛型类型
//参考泛型类
-
-
泛型接口的定义格式:
-
修饰符 interface 接口名<类型>
-
public interface modfiy
-
public interface GUess<E>{ }
-
-
-
通配符
- 类型通配符:<?>
- ArrayList<?>:表示元素类型位置的ArrayList 他的元素可以匹配任何类型
- 但是不能把元素添加到ArrayList中(没有?的类) 获取出来的也是父类类型
- 类型通配符上限:<?extends 类型>
- ArrayList<? extends Number>:表示类型是Number或者其他子类型(范围向下)
- 类型通配符上限:<? super 类型>
- ArrayList<? super Number>:表示类型是Number或者其他父类(范围向上)
/*
用一个方法同时打印Integer类型的集合和String类型的集合
这个时候就可以用通配符"?"了 类型不确定
*/
public static void main(String[] args){
ArratList<Integer> list1 = new ArrayList<>();
ArratList<String> list2 = new ArrayList<>();
printList(list1);
printList(list1);
}
public static void printList(ArrayList<?> list){
System.out.println(list);
}
49.Set集合
概述
-
特点
- 不可以重复
- 存取顺序不一致
- 没有索引 所以不能通过for遍历 也不能通过索引来获取删除集合里面的元素
-
遍历
- 迭代器
- 增强for
- foeEach
TreeSet集合
-
不包含重复元素
-
没有带索引的方法
-
可以按照元素规则进行排序
-
想使用TreeSet 必须指定排序的规则
-
自然排序Comparable的使用
- 使用空参构造创建TreeSet集合
- 自定义的类里面要实现Comparable接口
- 重写里面的compareTo方法
-
简单原理
-
比较器排序Comparator
- TreeSet带参构造方法使用的是比较器排序对元素进行排序
- 比较器排序 就是让集合构造方法接收Comparator的实现类对象 重写compare(T o1.T o2);
- 重写方法时 一点更要注意排序规则 必须按照要求的主要条件和次要提条件来写
-
当两种排序规则同时存在时,比较器的规则覆盖掉自然排序的规则
数据结构---二叉树
-
树里面每一个元素都是节点 由四个部分组成
-
- C的父结点记录的是A结点的地址值
- A结点的右子节点记录的是C结点的地址值 左子节点记录的是B结点的地址值
- 如果没有子节点 那么最后的子节点(B C)左右地址值为null
- A结点父节点地址值为null
-
度:每一个节点的子节点数量
- 二叉树中 任意一个节点的度值要<=2
-
层数=高度 有四层高度就是4
-
根节点是二叉树的最顶层结点
二叉查找树(二叉排序树/二叉搜索数)
特点:
- 每一个左子节点比自己小 每一个右子节点比自己大
- 每一个节点最多有两个子结点
二叉查找树添加节点:
规则:
- 小的存左边
- 大的存右边
- 一样的不存
平衡二叉树
特点:
- 二叉树左右两个子树高度差不超过1
- 任意节点的左右两个子树都是一颗平衡二叉树
左旋:
- 触发时机:当添加一个节点以后 该数不再是一颗平衡二叉树
- 定义:将根节点的右侧向左拉 原先的右子节点变为新的父节点 再把多余的自己电出让给已经降级的根节点当右子节点
右旋:
- 和左旋基本相反
- 将根节点的左侧往右拉 左子节点变成新的父节点 并且把多余的右子节点出让 给已经即将记得根节点当左子节点
旋转的四种情况:
- 左左 (右旋)
- 当根节点左子树的左子树有节点插入 导致二叉树不平衡
- 左右 (左子树左旋 然后整体右旋)
- 当根节点左子树的右子树有节点插入 导致二叉树不平衡
- 右右 (左旋)
- 当根节点右 树的右子树有节点插入 导致二叉树不平衡
- 右左 ( 先右子树右旋 然后整体左旋)
- 当根节点右子树的左子树有节点插入 导致二叉树不平衡
50.红黑树
概念:
-
一种自平衡二叉查找树
-
特殊的二叉查找树,红黑树的每一个节点上都有存储位表示结点的颜色 每一个结点可以是红或者黑;
-
红黑树不是高度平衡的 他的平衡是通过红黑规则实现的
-
存储信息比二叉树的信息多了一个color属性
红黑规则:
- 每一个节点都是红色或者黑色的
- 根点必须是黑色的
- 如果一个节点没有子节点或者父节点,则该结点相应的指针属性为Nil,这些Nil视为叶节点,每个叶节点是黑色的
- 如果某一个节点是红 色,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)
- 对于每一个节点,该结点但到其所有后代叶节点的简单路径(不能回头)上,均包含相同数目的黑色节点
添加节点的颜色:
- 添加结点的颜色可以是红色的也可以是黑色的
- 默认是红色效率更高
如何保证红黑规则:
左边长就右旋 右边长就左旋
51.HashSet
特点
-
底层是哈希表
-
不能保证存储取出顺序完全一致
-
没有带索引的方法 不能使用普通for循环进行遍历
-
元素唯一(是Set集合)
-
哈希值:是JDK根据对象的地址或者属性值算出来的int类型的整数
- Object类中有一个方法可以获取对象的哈希值
- public int hashCode():根据对象地址值计算出来的哈希值(同一对象 地址值相同 哈希值相同)
public static void main(String[] args) { Student student = new Student("zhangsan",10); Student student1 = new Student("zhangsi",20); System.out.println(student.hashCode());//189568618 System.out.println(student.hashCode());//189568618 System.out.println(student1.hashCode());//793589513 }
- 我们可以对object类中的hashCode进行重写 重写以后一般是根据对象属性值计算hashCode 此时和地址值没有任何关系了.如果不同对象的属性值是一样的 那么计算出来的哈希值也是一样的
-
哈希冲突问题:不同对象的属性值不同,但是计算得到的哈希值可能是相同的.此时需要使用重写的Equals方法对对象进行区分
-
Jdk8之前是数组+链表实现.Jdk8以后是数组+链表+红黑树实现
HashSet1.7版本原理
-
底层:哈希表(数组+链表实现)
-
- 图中第一个添加的值是头节点 因此打印的时候是从头节点开始向上打印
-
索引位置:(哈希值%数组长度)
-
加载因子:决定哈希表在什么时候扩容
当属数组里面存入16*0.75=12个元素的时候 数组就会扩容为原来的两倍
JDK8底层优化
- 哈希表(数组+链表+红黑树实现).
- 当链表过长的时不利于添加和查询.当链表长度超过8时候会自动转换成红黑树
注意点
public class MyHash01 {
public static void main(String[] args) {
Student student = new Student("zhangsan",10);
Student student2 = new Student("zhangsan",10);
Student student1 = new Student("zhangsi",20);
HashSet<Student> hs = new HashSet<>();
hs.add(student);
hs.add(student1);
hs.add(student2);
System.out.println(hs);
}
}
//当存储自定义对象时,需要重写hashCode()和equals()方法 不然会没办法去重
//重写hashCode():根据对象属性值计算哈希值,哈希值用来计算对象存储在数组中的位置
//重写equals():当存储索引相同时,用来比较对象属性值
52.Map集合
概述
- Interface Map<K,V> K:键的数据类型;V:值的数据类型
- 键不不能重复 值可以重复
- 键和值是一一对应的
- (键+值)称之为键值对,Java中称为Entry对象
map.put(key,value);//添加键值对
//如果添加的键存在 ,那么会put方法覆盖掉原有的value值 并且返回原有value值
//如果添加值不存在的,返回的是默认初始化值null
boolean containsKey(Object key)//判断集合是否包含指定键
boolean containsValue(Object value)//判断集合是否包含指定值
map集合没有实现Iterable 因此不能使用迭代器.增强for 也没有索引
遍历方法
-
map.forEach((key,value)->System.out.println(key+"---------"+value))
-
map.keySet();获取里面所有的key值
-
map.entrySet();返回的是Set集合.set里面装的是键值对对象(entry对象)
Entry里面装的是键和值
Set<Map.Entry<String,String>> entryset = map.entrySet();
遍历集合 获取每一个Entry值 然后通过getKey()或者getValue()进行获取值
public static void main(String[] args) {
Map map = new HashMap();
map.put("a", "1");
map.put("b","2");
map.put("c","3");
map.put("d","4");
map.put("e","4");
Set keyset = map.keySet();
//通过增强for遍历
for(String key:key){
//通过key值得到value值
String value = map.get(key);
}
Collection values = map.values();
//获取键值对对象
Set<Map.Entry<String,String>> entryset = map.entrySet();
for (Map.Entry<String, String> entry : entryset) {
//获取Entry对象里面的key值
entry.getKey();
//获取value对象里面的key值
entry.getValue();
}
System.out.println(map.keySet());//[a, b, c, d, e]
System.out.println(map.values());//[1, 2, 3, 4, 4]
System.out.println(map.entrySet());//[a=1, b=2, c=3, d=4, e=4]
}
53.HashMap
HashSet底层使用的是HashMap
-
底层结构:哈希表
-
依赖hashCode和equals方法保证键的唯一
-
如果键要存储的是自定义对象,需要重写hashCode和equals方法
原理解析
-
将数据封装为Entry对象(键和值)
-
按哈希值,结合数组长度计算索引位置
-
为null 直接添加.不为null
-
equals方法比较属性值. 相同:覆盖就职
-
不同 新的Entry对象添加到数组 老的挂在下面形成链表
Jdk8版本开始,链表长度为8,转换成红黑树
-
-
使用建议:
- 如果有必要使用自定义类对象作为键 一定要重写hashCode和equals方法
- 建议一般使用Integer/String作为键,去重性好
54.TreeMap
原理
- 底层红黑树是根据键进行排序 和值没有关系
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象 需要实现Comparable接口或者在创建对象的时候给出比较器的排序规则
注意:
- 如果键存储的是自定义对象 需要实现Comparable接口或者在创建对象的时候传递比较器的排序规则
55.可变参数
定义:
- 就是形参的个数是可以变化的.当形式参数确定,但是个数不确定,就可以使用可变参数
- 修饰符 返回值类型 方法名(数据类型...变量名)
- public static intSum(int... number)
注意事项:
- 可变参数的底层是数组 这里的变量其实是一个数组
- 如果一个方法有多个参数,包括可变参数,可变参数要放在最后(可变参数会接收传入的所有数据,放在前面的话后面传入的其他数据就被可变参数接受了 所以会报错)
创建不可变集合
-
集合中创建不可变集合方法
- Set.of("a","b","c")---传递的set集合里面不能有重复的值,不然会报错
-
在list Set Map接口中,都存在of方法 可以创建一个不可变集合
-
特点:这个集合不能添加,修改,删除
-
作用:可以结合集合的带参构造,实现集合批量添加
- ArrayList
list = new ArrayList<>(List.of(1,2,3,4))
- ArrayList
-
在Map中,有一个Map.ofEntries(Map.entry(key,value),Map.entry(key,value),Map.entry(key,value))方法
- 可以提高代码阅读性
- 把键值对封装entry对象再封装
56.Stream流
三类方法
- 获取Stream流(开启流水线)
- 创建一条流水线,并把数据放到流水线上准备操作
- 中间方法(流水线上的才做)
- 流水线上的操作
- 一次操作完毕以后 还可以继续进行其他操作
- 终结方法(关闭流水线)
- 一个Stream流只有一个终结方法
- 是流水线上的最后一个操作
-
获取方法
-
单列集合
-
可以使用Collection接口中默认方法生成stream()流
defalut Stream
stream() 集合对象.stream().forEach();
-
-
双列集合
-
间接生成流
-
可以先通过keySet或者entrySet获取一个Set集合 在获取Stream流
-
HashMap<String,Integer> hm = new HasMap<>(); //ketSet //先获取所有的键 //再把这个Set集合所有的键放到Stream流中 hm.keySet().stream().forEach(s->System.out.println(s)) //entrySet //先获取所有的键值对对象 //再把这个Set集合的所有键值对对象放到Stream流中 hm.entrySet().stream().forEach(s->System.out.println(s))
集合对象.keySet().stream()
集合对象entrySet().stream()
-
-
数组
-
Arrays中静态方法stream生成流
Arrays.stream(数组名)
-
-
同种数据类型的多个数据
-
1,2,3,4,5...
-
"aaa","bbb","ccc"....
Stream.of(1,2,3,4);
-
-
-
中间方法filter
list.stream().filter()
- 中间方法:对流中的数据进行过滤的方法
- filter方法获取流中每一个数据
- 方法中的s 就依次表示流中的每一个数据
- 我们只要对他进行判断
- **Predicate接口中的 boolean test (T t)方法 **
- true 保留 false 去除(都是流中的数据)
-
其他常用中间方法
- limit(long maxSize):截取指定参数个数的数据(截取前maxSize个数据)
- skip(long n):跳过指定参数个数的数据(去掉前n个 保留后面的)
- concat(Stream a,Stream b):把a流和b流的数据合到一起
- distinct():去掉流中重复的元素,(依赖hashCode和equals方法.存储的数据要重写这两个方法)
注意contact(Stream a,Stream b)方法合并两个流以后并不是创建了一个新的对象.因此 其中一个使用了终结方法会导致流关闭,另一个报错
Stream.concat(stream, stream1).forEach(System.out::println); System.out.println("-----------------------"); Stream.concat(stream, stream1).distinct().forEach(System.out::println); //Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
-
stream流常见终结方法
执行过这个方法以后,该流就从源头被关闭了 就不能再操作了
- void forEach(Consumer action):对每个元素执行操作
- long count();返回流中元素个数
- Stream流的收集操作
- 在Stream流中无法直接修改集合,数组等数据源中的数据(使用流的方法操作数据)
- 在Stream流中,使用容器的方法操作数据,会影响数据源中的数据
-
收集方法(也是终结方法)--toList和toSet和toMap()
-
R collect(Collector collector)
-
toMap()方法
-
ArrayList<String> list1 = new ArrayList<>(); list1.add("zhangsan,23"); list1.add("lisi,24"); list1.add("wangwu,25"); ``` Map<String,Integer> map = list1.stream().filter(s -> Integer.parseInt(s.split(",")[1])>=24).collect(Collectors.toMap(s->s.split(",")[0], s->Integer.parseInt(s.split(",")[1])));
57.file和io流
1.io流:可以对本地的文件进行读取或者保存
2.file:读写时候告诉文件在哪
对文件本操作,包括创建删除等
File类的构造方法
File:文件和目录路径的抽象表示
-
文件和目录可以通过File封装成对象
-
File封装的仅仅是一个路径名 可以存在或者不存在
- 构造方法
绝对路径/相对路径
- 绝对路径:从盘符开始
- File file1 = new File("D:\itheima\a.txt")
- 相对路径:相对当前项目/模块下的路径
- File file2 = new File("a.txt");
- File file3 = new File("模块名\\a.txt");
File成员方法
-
创建方法
-
-
creatNewFile()方法:
- 文件的写路径必须存在,不然会报错
- 如果创建文件/文件夹时候父级的路径有权限,那么创建文件会报错,创建文件夹是false
-
-
删除方法
-
-
File的获取和判断方法
1.- 返回false:
- 文件夹内部有内容
- 路径不存在
- 要删除的文件/文件夹有权限
- String getAbsolutePath(); 获取绝对路径
- String getPath(); 获取相对路径
- 返回false:
-
public File[] listFiles()
-
返回这个抽象路径名表示的目录中的文件和目录的File对象数组
-
进入这个文件夹,获取这个文件夹里面的所有的文件和文件夹File对象,并把这些File对象都放在一个数组中返回,包括隐藏文件和隐藏文件夹
-
注意事项
- 调用者不存在时 exits()
- 返回null
- 调用者是一个文件时 ifFile()
- 返回null(遍历时候报错)
- 调用者是一个空文件夹时
- 返回一个长度是0的数组
- 调用者一个有内容的文件夹时
- 进入这个文件夹,获取这个文件夹里面的所有的文件和文件夹File对象,并把这些File对象都放在一个数组中返回,
- 当调用者是一个有权限才能进入的文件夹时
- 返回一个null
- 调用者不存在时 exits()
-
-
删除带有文件的文件夹操作
- 带一点递归的思想
- 递归方法体不一定执行 所以不需要出口
private static void deleteDir(File file) {
//1.判断File路径是否存在
if (!file.exists()){
System.out.println("路径不存在");
return;
}
//获取当前路径下的路径
File[] files = file.listFiles();
//files保存的值
//1.权限 2.文件 -- null
if (files == null && !file.isFile()){
System.out.println("权限不足");
return;
}
//判断是不是文件,是的话删除
if(file.isFile()){
System.out.println("1");
file.delete();
return;
}
//遍历
for (File filePath : files) {
if (filePath.isFile()){
System.out.println("filePath.getName() = " + filePath.getName());//打印文件名
filePath.delete();//是文件就删除
}else {//是文件夹
System.out.println("filePath = " + filePath);
deleteDir(filePath);//是文件夹,递归
}
}
file.delete();//删除自身
}
- 统计文件夹各种文件的个数
IO流
目的:
-
将数据写到文件中
-
从数据中读取数据
-
内存在读写
IO流的分类
- 按照流向区分:输入流 输出流
- 按照数据类型分:1.字节流----操作所有类型文件 2. 字符流------只能操作纯文本文件
什么是纯文本文件?----用记事本打开能读得懂,那么这样的文件就是纯文本文件
字节流
一次只读一个字节
-
步骤
-
创建字节输出流的对象 FileOutPutStream(String path)/FileOutPutStream(new File(String path))
-
注意点
-
如果没有文件 就自动创建,需要保证文件的父级路径存在
-
如果文件存在,会自动清空文件
-
-
-
写数据write(int i)
- 注意点:
- 传递一个整数时,实际写道文件中的是码表中对应的字符
- 注意点:
-
释放资源
- 注意点
- 告诉操作系统不用这个文件了
- 注意点
//创建字节输出流的对象 FileOutPutStream fos = new FileOutPutStream("D:\\a.txt"); FileOutPutStream fos = new FileOutPutStream(new File("D:\\a.txt")); //写数据 fos.write(97); //释放资源 fos.close();
- 字节流写数据的三种方式
write(int b)//写一个数据 write(byte[] b)//写一个数组 write(byte[] b,int off,int len)//写一个数组的一部分 write(字符串对象.getBytes()) String s = "嘿嘿嘿"; byte[] b = s.getBytes();
- 换行和追加写入
换行: "\r\n".getBytes();
追加写入:
new FileOutputStream(String path,boolean a); new FileOutputStream(file f,boolean a); //第二个参数是续写开关,没有传递 默认传入为false,创建对象时清空文件内容在写入 //写为true 表示追加写入
- 字节输入流
//如果文件不存在会报错 FileInputStream fis = new FileInputStream(File file); //想看到字符数据,要强转成char //一次只读一个字节 int read = fis.read(); System.out.println(read); fis.close()
- 字节流读数据
//使用循环 fis.read()==-1; //循环读取,当读取到字节数据值为-1时,停止循环
- 文件的复制
复制文件:把内容从一个文件读出来(数据源++),然后写入另一个文件(目的地)
//1.创建一个FileInputStream,关联数据源路径 File file = new File("com-itheima-edu-info-manager-v1\\aaa\\aaa.txt"); FileInputStream fis = new FileInputStream(file); //2.创建FileOutputStream,关联目的地路径 FileOutputStream fos = new FileOutputStream("com-itheima-edu-info-manager-v1\\aaa\\bbb\\bbb.txt"); //3.循环读写 while(fis.read()!=-1){ fos.write(fis.read()); } //4.关;流 fis.close(); fos.close();
- 提高拷贝速度
定义小数组拷贝,一次可以读写多个数据
public int read(byte[] b):从输入流读取最多b.length个字节的数据 //返回的是读入缓冲区的总字节数,也就是实际的读取字节个数. byte bytes[] = new byte[1024]; int len;//本次读到的有效字节个数 while ((len = fis.read(bytes))!=-1){ fos.write(bytes,0,len); }
- 提高的是在硬盘和内存中转移的效率
- 一次读一个字节数组的方法:
- public int read (byte[] b) 从输入流读取最多b.length字节的数据
-
-
字节缓冲流
- BufferOutputStream:()字节缓冲输出流
- BufferInputStream() :字节缓冲输入流
- 字子节缓冲流仅仅提供缓冲区,读写数据还是要靠字节流对象进行操作
缓冲流一次写一个字节:
//创建一个字节缓冲输入流
//在底层创建了一个默认长度是8192的byte[]数组
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(String path));
//创建一个字节缓冲输出流
//在底层创建了一个默认长度是8192的byte[]数组
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(String path));
int b ;
while((b=bis.read())!=-1){
bos.write(b);
}
//方法底层会把流关闭
bis.close();
bos.close();
优点:减少了硬盘和内存之间数据传递的次数,提高了性能
缓冲流一次写一个字节数组:
//创建一个字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(String path));
//创建一个字节缓冲输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(String path));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bis.close();
bos.close();
字符流
- 编码表
- ASCII码表
- GBK码表(中文字符以两个字节形式存储)
- Unicode码表(万国码 中文字符以三个字节的形式存储 UTF-8是编码格式)
window默认码表为GBK 一个
中文字符两个字节
idea和以后默认编码格式是UTF-8 一个中文字符三个字节
字符流在读取非文本文件的时候,会把文件的二进制按照系统的编码格式转换成字符再传输.二进制字节转换的时候编码格式里面的值很多有没有,所以在转换回去会出现打不开等等现象
- 编码和解码
byte[] getBytes()
//使用平台默认字符集把String编码为一系列字节,结果存储到新的字节数组中
byte[] getBytes(String charsetName)//指定编码格式
byte[] byte1 = {,,,,,};
String s1 = new String(bytes1);//使用默认解码格式解码
String s2 = new String(bytes1, "GBK")//使用指定格式解码
3.字符流读取中文的过程
- 字符流=字节流+编码表
- 不管在哪张码表中,中文的第一个字节一定是负数!
- 读到负数就一次读三个
- 写出数据
//创建字符输出流对象
FileWriter fw = new FileWriter(new File("bb\\a.txt"));
FileWriter fw = new FileWriter("bb\\a.txt");
//写出数据
fw.write(int c);
//释放资源
fw.close();
- 注意事项
- 创建字符输出流对象
- 如果文件不存在就创建,但是要保证父级路径存在
- 如果文件存在会清空
- 写数据
- 如果写的是int类型的整数,实际上写出的是码表对应的字母
- 如果写的是字符串,写出的是字符串本身原样写出
- 释放资源
- 每次结束都要释放
- 创建字符输出流对象
- flush()和close()方法
flush()//刷新流.刷新完毕后,还可以继续写数据
close()//关闭流,关闭之前会自动刷新,一旦关闭不能写数据
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter(new File("com-itheima-edu-info-manager-v1\\test\\a.txt"));
fw.write(97);
//写入以后不刷新文件是没有数据的
fw.flush();
//刷新流 刷新了后可以看到数据,并且还可以继续对文件进行操作
fw.close();
}
- 字符流的读取(底层字节流)
//创建字符输出流对象
FileReader fr = new FileWriter(new File("bb\\a.txt"));
FileReader fr = new FileWriter("bb\\a.txt");
//读取数据
//一次读取一个字符
int ch
while((ch = fr,read())!-1)
{
System.out.print((char) ch)
}
//一次读取多个字符
char[] chars = new char[1024];
int len;
while((len = fr.read(chars))!=-1){
System.out.print((new String(chars,0,len))
}
//释放资源
fr.close();
- 字符缓冲输入流----可以把数据高效的读取到内存
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("pathName"));
//读取数据
//一次读取多个字符
char[] chars = new char[1024];
int len;
while((len = fr.read(chars))!=-1){
System.out.print((new String(chars,0,len))
}
//释放资源
fr.close();
-
字符缓冲输出流----可以把数据高效的写入到文件
//创建字符缓冲输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("pathName")); //写出数据 bw.write("97"); char[] chars = {97,98.99,100} bw.write(chars); bw.write(chars,0,3); bw.write("heimachengxuyuan1"); bw.write("heimachengxuyuan1",0,5); //刷新 bw.flush(); //关流 bw.close();
-
缓冲流-特有方法
BufferedReader特有方法 readLine():一读读一整行
BufferedWrite特有方法 newLine():换行
转换流
- 概念
-
- 分为输入和输出
- 输入:InputStreamReader 字节流转换成字符流
- 输出:OutputStreamWriter 字符流转换成字节流
- 使用场景
- 解决乱码现象
InputStreamReader isr = new InputStreamReader(new FileStreamReader("D:\\a.txt"),"gbk")//第一个是文件位置 第二个是码表名
- jdk11之前
- InputStreamReader isr = new InputStreamReader(new FileStreamReader("D:\a.txt"),"gbk")
- OutputStreamReader isr = new OutputStreamReader(new FileStreamReader("D:\a.txt"),"gbk")
- jdk11以后
- FileReader(String path,CharsetforName(String charset));
- FileWriter(Sting path,forName(String charset),boolean append)
对象操作流
作用:
-
把对象转换为二进制数据进行操作
-
可以把对象以字节的形式写到本地文件,直接打开文件读不懂的
是需要再次用对象操作流读到内存中
-
序列化.
- 对象操作输入流(对象反序列化流)
- 对象操作输出流(对象序列化流)
-
如果想要类能被序列化,那么对象必须实现Serilizable接口
-
序列化流
ObjectOutputStream ops = ObjectOutputStream(new FileOutputStream(String path));//创建对象
writeObject(Object o);//把对象转成二进制写入
ops.close();
- 反序列化流----读入到末尾出现异常
ObjectInputStream ois = ObjectInputStream(new FileInputStream(String path));//对象
Object o = ois.readObject();//获取对象
User o = (User)ois.readObject();
读到末尾怎么办才能不结束程序?
- 使用try{}catch{}捕获异常
//死循环读取
//循环内捕获EOFException异常,捕获到了进行break操作
while (true){
try {
System.out.println(ois.readObject().toString());
}catch (EOFException e){
break;
}
}
- 把对象存储容器里,容器里面的对象也必须实现序列化!
//把对象存储容器里,然后把容器塞进对象流里序列化
//取出的时候取出的是容器
ObjectOutputStream oop = new ObjectOutputStream(new FileOutputStream("com-itheima-edu-info-manager-v1\\User.txt"));
ArrayList<User> arr = new ArrayList<>();
arr.add(user);
arr.add(user1);
//在本地文件中写入的就是一个集合
oop.writeObject(arr);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("com-itheima-edu-info-manager-v1\\User.txt"));
//取出来的也是集合
ArrayList<User> list = (ArrayList<User>) ois.readObject();
for (User user2 : list) {
System.out.println(user2);
}
ois.close();
- 注意点
- 用对象序列化流序列化一个对象后,修改原本的javaBean会出现什么?
//只要一个类实现了Serilizable接口,就表示这个类的对象可以被序列化
//serialrsionUID 序列号
//如果我们自己没有定义,虚拟机会自动计算出一个序列号.
//如果我们修改了类的信息,那么虚拟机会再次生成一个序列号
把文件对象读到内存,本地序列号和类里面的序列号不一致,导致报错.
//解决办法:
手动给出不可变的序列号
private static final long serialVersionUID = 1231245L;
- 如果对象某个成员变量的值不想被序列化.怎么办?
//变量前面加上 transient 关键字.该关键字标记的成员变量不参加序列化过程
private transient psd;
Properties集合
- 概述
- 是Map集合
- 有直接操作字符串的方法(键和值都是String类型)
- 有和IO流相关的方法
- 作为Map集合的基本使用
-
Properties prop = new Properties(); //增 prop.put("张三","23"); //删 prop.remove(key); //改 prop.put(key);//如果键不存在就添加,存在就覆盖 //查 prop.get(key); //遍历 keySet(); entrySet();
-
特有方法
//1 setProperty(key,value) //设置集合的键和值,都是String类型 底层调用Hashtable方法put prop.setProperty("江苏","南京"); //2 getProperty(key) //对应get方法 prop.getProperty("江苏"); //3 Set<String> stringPropertyNames() //对应keySet()方法
-
和IO流相关的方法
- load(Reader r)//字符流
- load(InputStram i)//字节流
void load(Reader reader)//将本地文件的键值对数据读取到集合中(读) void store(Writer writer String comments)//将集合中数据以键值对形式保存在本地(写)
prop.store(fw,"abc");-------abc是properties文件的注释,以#abc的样子体现在头部
58.多线程
-
指从硬件或者软件上实现多个线程并发执行的技术(需要硬件的支持)
-
每一个软件都是独立的进程,cpu在多个进程之间告诉切换,从而实现多个软件同时进行.
注意: 只是看起来同时进行但是在同一时刻只有一个软件真正的运行
并发和并行
-
并行:同一时刻,有多个指令在多个cpu上同时执行
-
并发:同一时刻,有多个指令在单个cpu上交替执行
进程和线程
- 进程:正在运行的软件
- 独立性:一个独立运行的基本单位,是cpu分配资源和调度的独立单位
- 动态性:进程的实质是程序一次执行的过程,是一条执行路径
- 并发性:任何进程都可以同其它进程一起并发执行
- 线程:是进程种单个顺序控制流,是一条执行路径
- 单线程:一个进程只有一条执行路径
- 多线程:一个进程有多条执行路径
- 线程是cpu调度和执行的单位.
多线程的实现方式
-
继承Thread类
- 定义一个类继承Thread类
- 重写run()方法
- 创建类的对象,调用start()方法启动线程
-
实现Runable接口
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 把Runnable接口实现类对象转换为Thread类对象,为了调用start()方法
- 启动线程
-
问题:
---为什么要重写run()方法? 因为run()是用来封装被线程执行的代码 ---run()方法和start()方法的区别? run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。 start():启动线程;然后由JVM调用此线程的run()方法
-
实现Callable和Future接口的方式实现
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法----call()方法有返回值,返回值表示程序运行以后的结果
- 创建MyCallable类的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
- Threard类的构造方法只能接受Runnable实现类对象,不能接收Callable的实现类对象.需要转换换成Thread类对象,因为FutureTask都实现了Runnable接口
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
启动线程 - 再调用get方法,就可以获取线程结束之后的结果(FutureTask对象.get())
- get()是阻塞方法.如果没有开启线程或者没有执行完毕,此时调用get()方法会一直等到线程执行完毕,获取结果.
- 一定要先开启call(),在调用get()
-
优缺点
- 实现Runnable
- Callable接口
- 优点:扩展性强,实现该接口的同时还可以继承其他的类。
- 缺点:编程相对复杂,不能直接使用Thread类中的方法
-
继承Thread类 :
- 优点:编程比较简单,可以直接使用Thread类中的方法
- 缺点:可以扩展性较差,不能再继承其他的类
Thread类相关方法
-
获取和设置线程名称
- 获取线程的名字
- String getName( ):返回此线程的名称
- 线程有默认名称 格式:Thread-编号 编号从0开始,每创建一条就加以
- Thread类中设置线程的名字
- void setName(String name):将此线程的名称更改为等于参数 name
- 通过构造方法也可以设置线程名称
- 获取线程的名字
-
获得当前线程的对象
- public static Thread currentThread():返回对当前正在执行的线程对象的引用
- 线程休眠
- public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
线程调度
-
多线程的并发运行:
- 计算机中的CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码。各个线程轮流获得CPU的使用权,分别执行各自的任务。(一个一个执行 不提供的切换)
-
调度模型
- 分时调度模型:所有线程轮流使用CPU
- 抢占式调度模型:优先让优先级高的使用CPU 同等级就随机.Java种采用抢占式调度.
- 线程的优先级
-
public final void setPriority(int newPriority) 设置线程的优先级(线程最大优先级是10,最小是1 默认是5)
-
public final int getPriority() 获取线程的优先级
- 设置守护/后台线程
-
陪伴主线程执行任务的线程
-
public final void setDaemon(boolean on):设置为守护线程(true/false)
当普通线程执行完毕后,守护线程不会执行完毕,但是不会立刻停止
Java种常见的守护线程:
垃圾回收器gc();
伴随着执行任务的线程执行,并且CPU空闲的时候进行垃圾回收
当执行任务的线程执行完毕后,垃圾回收器一会也就结束了
买票注意点
Java多线程经典买票案例:
使用Callable方式
线程安全问题
- synchronized(任意对象) {
多条语句操作共享数据的代码
}- 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
- 当线程执行完出来了,锁才会自动打开
- 要保证锁的对象是唯一的
- 同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
1.继承Thread类
创建多个Thread类子对下个 需要将成员位置书写的锁对象进行static进行修饰
private static Object lock =new Object()
2.实现接口
由于接口创建一般指创建一个作为参数传递,此时锁对象可以不用static修饰
同步方法
-
同步方法:就是把synchronized关键字加到方法上
- 格式:
修饰符 synchronized 返回值类型 方法名(方法参数) - 同步代码块和同步方法的区别:
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
- 格式:
-
同步代码块可以指定锁对象,同步方法不能指定锁对象
-
同步方法的锁对象是什么呢?
this -
同步静态方法:就是把synchronized关键字加到静态方法上
- 格式:
修饰符 static synchronized 返回值类型 方法名(方法参数) - 同步静态方法的锁对象是什么呢?
类名.class
- 格式:
Lock锁
-
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
-
Lock中提供了获得锁和释放锁的方法
- void lock():获得锁
- void unlock():释放锁
-
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock的构造方法
- ReentrantLock():创建一个ReentrantLock的实例
- 注意!!!要保证锁对象ReentrantLock唯一
- ReentrantLock的构造方法
死锁
生产者和消费者模型
//等待唤醒机制
- 概念:
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻
-
当前案例中存在几个角色?
1.生产者(厨师) 2.消费者(吃货) 3.存储数据的容器(桌子)
-
这些角色做了哪些事情?
消费者: 1,判断容器中有没有数据 2,如果没有,线程等待 3,如果有,消费数据 4,消费数据之后,修改容器状态,唤醒等待的生产者生产数据,容器中数据的总数量-1 生产者: 1,判断容器中是否有数据 2,如果有,线程等待,如果没有,生产数据 3,修改容器状态 3,唤醒等待的消费者消费数据 存储数据的容器: 1, 记录数据总数量 2, 有数据,记录为true 3, 没有数据,记录为false 4, 消费数据后,总数量-1
生产者和消费者-代码实现
[问题]
- 生产者和消费者模型中使用了哪些线程等待唤醒的方法?
[答案]
-
生产者和消费者模型中使用了哪些线程等待唤醒的方法?
方法 功能 void wait() 当前线程等待,直到另一个线程调用该对象的 notify()或 notify All()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notify All() 唤醒正在等待对象监视器的所有线程
[练习]
示例代码:
[Desk.java 容器(桌子类)]
public class Desk {
/*
存储数据的容器:
1, 记录数据总数量
2, 有数据,记录为true
3, 没有数据,记录为false
4, 消费数据后,总数量-1
*/
//定义标记
//true:桌子上有汉堡包的,此时允许吃货执行
//false:桌子上没有汉堡包,此时允许厨师执行
public static boolean flag = false;
//汉堡包的总数量
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
[Cooker.java 生产者(厨师类)]
public class Cooker extends Thread {
/*
生产者:
1,判断容器中是否有数据
2,如果有,线程等待,如果没有,生产数据
3,修改容器状态
3,唤醒等待的消费者消费数据
*/
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(!Desk.flag){
//生产数据
System.out.println("厨师正在生产汉堡包");
Desk.flag = true;
Desk.lock.notifyAll();
}else{
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
[Foodie.java 消费者(吃货类)]
public class Foodie extends Thread {
/*
消费者:
1,判断容器中有没有数据
2,如果没有,线程等待
3,如果有,消费数据
4,消费数据之后,修改容器状态,唤醒等待的生产者生产数据,容器中数据的总数量-1
*/
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.flag){
//有数据,消费数据
System.out.println("吃货在吃汉堡包");
Desk.flag = false;
Desk.lock.notifyAll();
Desk.count--;
}else{
//没有数据,等待
//使用锁对象调用等待和唤醒的方法.
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
[Demo.java 测试类]
public class Demo {
public static void main(String[] args) {
Foodie f = new Foodie();
Cooker c = new Cooker();
f.start();
c.start();
}
}
生产者和消费者-代码改写
示例代码:
[Desk.java]
public class Desk {
private boolean flag;//标记
private int count;//数据总数
private final Object lock = new Object();//锁对象
public Desk() {
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
}
[Cooker.java]
public class Cooker extends Thread {
private Desk desk;
public Cooker(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while(true){
synchronized (desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
if(!desk.isFlag()){
System.out.println("厨师正在生产汉堡包");
desk.setFlag(true);
desk.getLock().notifyAll();
}else{
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
[Foodie.java]
public class Foodie extends Thread {
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while(true){
synchronized (desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
if(desk.isFlag()){
System.out.println("吃货在吃汉堡包");
desk.setFlag(false);
desk.getLock().notifyAll();
desk.setCount(desk.getCount() - 1);
}else{
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
[Demo.java]
public class Demo {
public static void main(String[] args) {
Desk desk = new Desk();
Foodie f = new Foodie(desk);
Cooker c = new Cooker(desk);
f.start();
c.start();
}
}
生产者和消费者-阻塞队列基本使用
[问题]
- 阻塞队列的分类
- 阻塞队列的常用方法
[答案]
-
阻塞队列的分类
ArrayBlockingQueue:底层是数组,有界 LinkedBlockingQueue:底层是链表,无界
-
阻塞队列的常用方法
ArrayBlockingQueue<E>(容量):根据当前容量创建阻塞队列 put(元素) :添加元素,当添加元素的数量超过阻塞队列长度,不能继续添加,进行阻塞 E take() :获取元素,当阻塞队列中没有元素时,不能再继续取出,进行阻塞
生产者和消费者-阻塞队列实现
示例代码:
[Cooker.java]
public class Cooker extends Thread {
private ArrayBlockingQueue<String> bd;
public Cooker(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true) {
try {
bd.put("汉堡包");
System.out.println("厨师放入一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
[Foodie.java]
public class Foodie extends Thread {
private ArrayBlockingQueue<String> bd;
public Foodie(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true) {
try {
String take = bd.take();
System.out.println("吃货将" + take + "拿出来吃了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
[Demo.java]
public class Demo {
public static void main(String[] args) {
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(bd);
Cooker c = new Cooker(bd);
f.start();
c.start();
}
}
线程状态(生命周期)
虚拟机中现成的六种状态:
- 新建状态
- 就绪状态
- 阻塞状态
- 等待状态
- 计时等待状态
- 结束状态
线程池
为什么要使用线程池?
1.线程调度:根据任务合适的数量,创建数量合适的线程并且分配任务
2.线程的重复利用: 线程执行完毕任务不是立刻销毁,而是进行回收,继续分配任务
excutors创建默认线程池
- 创建一个池子.池子是空的-----创建Executors中的静态方法
- 有任务执行时,创建线程对象,任务执行完毕,线程对象归还给池子-------submit方法
- 所有任务执行完毕,关闭连接池----shutdown方法
- 创建对象
static ExecutorSerivce newCachedThreadPool() 创建一个默认线程池(最多为int范围)
static newFixedThreadPool(int nThreads)创建一个指定最多数量的线程池
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors---可以帮助我们控制线程池对象
//ExecutorsService --- 可以帮助我们控制线程池
executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+"在执行了");
});
//Thread.sleep(2000);
executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
/*结果是:
pool-1-thread-2在执行了
pool-1-thread-1在执行了
*/
//如果中间加入Thread.sleep(2000);那么结果是
//pool-1-thread-1在执行了
//pool-1-thread-1在执行了
//睡眠时间线程1已经执行完毕了 醒了后已经有了空闲的线程1因此不会创建新的线程2
//newFixedThreadPool创建出来的池子也是空的 后面的参数表示最大值而不是初始值.
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
线程池---ThreadPoolExecutor
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
构造方法:
ThreadPoolExecutor{
int corePoolSize, //核心线程最大数量 值>=0
int maximumPoolSize, //线程池中最大线程数量,值>0 最大线程数量=核心线程数量+临时线程数量
// 最大线程数量>=核心线程数量
long keepAliveTime, //临时线程存活时间>0
TimeUnit unit, //存活时间的时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列 不能为null
ThreadFactory threadFactory, //默认创建线程方式 不能为null
RejectedExecutionHandler handler//任务拒绝策略 不能为null
//什么时候拒绝---当提交的任务大于池子中最大线程数量+队列容量
//如何拒绝---任务拒绝策略的四种
}
//任务拒绝策略的四种
ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy():丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
//下面代码运行的后缀是90-99 --前面的任务执行不到,只会执行后面的
ThreadPoolExecutor.CallerRunsPolicy():调用任务的run()方法绕过线程池直接执行。
//下面代码运行后结果是pol线程和main线程一起运行一直到99
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
0,//核心线程数量
1,//最大线程数量 = 核心+临时
2,//临时线程存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(10),//阻塞队列
Executors.defaultThreadFactory(),//默认创建线程方式
// new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
// new ThreadPoolExecutor.DiscardPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()--前面的任务执行不到,只会执行后面的
// new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 100; i++) {
int finalI = i;
threadPoolExecutor.submit(() -> System.out.println(Thread.currentThread().getName()+"正在执行任务"+ finalI));
}
threadPoolExecutor.shutdown();
}
volatile关键字
作用:
-
强制线程每次操作线程中的共享数据时候都去看一下共享数据是否更新
-
修饰共享数据 ,没有限制操作共享数据的线程数量
-
共享数据只发生改变,则更新线程栈变量副本中保存的当前共享数据
-
共享数据的值没有发生改变,则直接操作共享数据
synchronized问题解决
- 使用同步代码块/同步方法时.将线程共享数据代码枷锁,此时只允许一条线程操作共享数据
原子性
概念:
-
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,
要么所有的操作都不执行,多个操作是一个不可以分割的整体。 -
count++不是一个原子性操作在做这个操作的时候cpu的执行权有可能被别人抢走
-
volatile关键字只能保证每次在使用共享数据时候是最新值,但是不能保证原子性
原子类AtomicInteger
构造方法:
- public AtomicInteger(): 初始化一个默认值为0的原子型Integer
- public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
方法:
- int get(): 获取值
- int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
- int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
- int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
- int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
AtomicInteger原理
自旋锁+CAS算法
CAS算法的三个核心变量:
- 内存值V
- 旧预期值A
- 要修改成的值B
情况如下:
- 预期值A==内存值V
- 表示此时没有其他线程修改共享数据,修改成功,再将内存值V改成要修改的B
- 预期值A!=内存值V
- 表示此时已经有了其他线程修改共享数据,修改失败,放弃操作,进行自旋,重新获取内存值V
悲观锁和乐观锁
HashTable
- HashMap是线程不安全的(多线程环境下可能会存在问题)
- 为了保证数据的安全性,我们可以使用Hashtable保证数据安全,但是Hashtable效率低下
- 只要有一个线程访问Hashtable 就会把整张表锁起来.等这个线程执行完了以后才让其他线程操作.所以效率低下.
- Hashtable使用的是悲观锁
ConcurrentHashMap
- 线程安全的 效率较高.JDK7和JDK8底层原理不一样
JDK7原理---ConcurrentHashMap
JDK1.8原理---ConcurrentHashMap
CountDownLatch
- 使用场景:
- 让某一条线程等待其他线程执行完毕以后再执行
- 方法
- 构造方法:
- public CountDownLatch(int count):构造一个以给定计数初始化的countDownLatch(给一个计数)
- 成员方法
- public void await():让线程等待
- public void countDown():计数器的count-1.如果count=0 释放所有等待的线程
- 构造方法:
并发工具类Semaphore
作用:控制访问特定资源的线程数量
59.网络编程
网络编程
概述
- 在网络通信协议下,不同计算机上运行的程序可以进行数据传输
- 三要素:
- ip地址:设备在网络中的地址,唯一标识
- 端口号:应用程序在设备中的唯一标识
- 协议:数据在网络中传输的规则,例如UDP和TCP协议
-
IP---互联网协议地址.常见分为Ipv4和Ipv6
-
Ipv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节.为了方便使用,采用点分十进制表示法
-
Ipv6:采用128位地址长度,每16个字节一组,分成8组十六进制数.采用冒分十六进制表示法
-
常见命令--ipconfig
ping Ip地址/域名
-
特殊IP:127.0.0.1:本地回环地址,一般表示本机Ip
-
- InetAddress类
- 没有对外提供构造方法
static InetAddress getByName(String host)//确定主机名称的Ip地址
String getHostName()//获取此Ip地址的主机名
String getHostAddress()//返回文本显示的IP地址字符串
- 端口
端口号:用两个字节表示的整数 取值范围是0-65535,普通应用程序的可用范围**[1024,65535] **前面的被知名程序占用了
注意:一个端口号只能被一个应用程序使用
- 协议
计算机网络中 练接合通信的规则被称为网络通信协议
- UDP
- 用户数据报协议(User Datagram Protocol)
- 面向无连接通信协议
- 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输,一次最多64K
- TCP
- 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议
- 速度慢,没有大小限制,数据相对安全
UDP通信程序
-
构造方法
方法名 说明 DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口 DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口 -
相关方法
方法名 说明 void send(DatagramPacket p) 发送数据报包 void close() 关闭数据报套接字 void receive(DatagramPacket p) 从此套接字接受数据报包 -
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
-
代码演示
-
public class SendDemo { public static void main(String[] args) throws IOException { //创建发送端的Socket对象(DatagramSocket) // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口 DatagramSocket ds = new DatagramSocket(); //创建数据,并把数据打包 //DatagramPacket(byte[] buf, int length, InetAddress address, int port) //构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。 byte[] bys = "hello,udp,我来了".getBytes(); DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086); //调用DatagramSocket对象的方法发送数据 //void send(DatagramPacket p) 从此套接字发送数据报包 ds.send(dp); //关闭发送端 //void close() 关闭此数据报套接字 ds.close(); }}
-
UDP三种通讯方式
-
单播
单播用于两个主机之间的端对端通信
-
组播
组播用于对一组特定的主机进行通信,
用户可用的范围是[224.0.1.0~]
-
广播
广播用于一个主机对整个局域网上所有主机上的数据通信,
广播的地址:255.255.255.255
UDP组播的实现
TCP通信程序
- 发送数据
-
构造方法
方法名 说明 Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号 Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号 -
相关方法
方法名 说明 InputStream getInputStream() 返回此套接字的输入流 OutputStream getOutputStream() 返回此套接字的输出流 -
相关代码
//1.创建客户端的socket对象 //2.使用socket对象获取输出流 写出数据 //3.关流 释放资源 //1.创建客户端的socket对象 Socket sc = new Socket("127.0.0.1",10000); //2.使用socket对象获取输出流 写出数据 OutputStream os = sc.getOutputStream(); os.write("哈哈 盾立".getBytes()); //3.关流 释放资源 os.close(); sc.close();
- 接收数据
-
构造方法
方法名 说明 ServletSocket(int port) 创建绑定到指定端口的服务器套接字 -
相关方法
方法名 说明 Socket accept() 监听要连接到此的套接字并接受它 -
相关代码
//1. 创建服务器端socket对象ServerSocket //2. 接收连接客户端的Socket对象 //3. 使用接受的Socket对象 获取输入流 读数据 //4. 关流 //1. 创建服务器端socket对象ServerSocket ServerSocket ss = new ServerSocket(10000); //2. 接收连接客户端的Socket对象 Socket s = ss.accept(); //3. 使用接受的Socket对象 获取输入流 读数据 InputStream inputStream = s.getInputStream(); byte[] bytes = new byte[1024]; int len = inputStream.read(bytes); System.out.println(new String(bytes,0,len)); //4. 关流 释放资源 inputStream.close(); ss.close();
-
注意事项
- accept方法是阻塞的,作用就是等待客户端连接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流 - read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
UUID类
生成一个随机且唯一的UUID对象
UUID.randomUUID().toString()生成唯一对象
三次握手和四次挥手
-
三次握手
- 四次挥手
-
示例代码
public class ServerDemo { public static void main(String[] args) throws IOException { //创建服务器端的Socket对象(ServerSocket) //ServerSocket(int port) 创建绑定到指定端口的服务器套接字 ServerSocket ss = new ServerSocket(10000); //Socket accept() 侦听要连接到此套接字并接受它 Socket s = ss.accept(); //获取输入流,读数据,并把数据显示在控制台 InputStream is = s.getInputStream(); byte[] bys = new byte[1024]; int len = is.read(bys); String data = new String(bys,0,len); System.out.println("数据是:" + data); //释放资源 s.close(); ss.close(); } }
60.类加载器
作用
- 负责将.class文件(存储的物理文件)加载在到内存中
类加载的过程
- 类加载时机-----用到就加载,不用不加载
- 创建类的实例(对象)
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
- 类的加载过程
-
加载
- 通过包名+类名获取这个类 准备用流进行传输
- 通过流把字节码文件加载到内存中
- 加载完毕会创建这个类的.class对象
-
链接---验证,准备,解析的总称
-
验证:
看一下文件中的信息是否符合虚拟机的规范 有没有安全隐含
-
准备:
在class文件中初始化变量
-
解析
如果本类中用到了其他的类 就找到其他的类
-
-
初始化
静态变量复置以及初始化其他资源
-
类加载器的分类
-
分类
启动类加载器(Bootstrap class loader):虚拟机内置加载器
平台类加载器(Platform class loader):负责加载JDK中特殊的模块
系统类加载器(System class loader):负责加载用户类路径上所指定的类库
双亲委派模型
ClassLoader中两个方法
-
方法介绍
方法名 说明 public static ClassLoader getSystemClassLoader() 获取系统类加载器 public InputStream getResourceAsStream(String name) 加载某一个资源文件.参数:文件的路径..返回值:字节流 -
示例代码
public class ClassLoaderDemo2 { public static void main(String[] args) throws IOException { //static ClassLoader getSystemClassLoader() 获取系统类加载器 //InputStream getResourceAsStream(String name) 加载某一个资源文件 //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //利用加载器去加载一个指定的文件 //参数:文件的路径(放在src的根目录下,默认去那里加载) //返回值:字节流。 InputStream is = systemClassLoader.getResourceAsStream("prop.properties"); Properties prop = new Properties(); prop.load(is); System.out.println(prop); is.close(); } }
61.反射
概述
-
反射机制
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意属性和方法;
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
获取class对象的方式
-
Class类中的静态方法forName("全类名")----源代码阶段
- 全类名:包名+类名
-
类名.class属性-----Class对象阶段
- Class clazz =Student.class;
-
利用对象得getClass方法-----RunTime运行时阶段
- 对象名.geClass()方法
- 对象名.geClass()方法
public static void main(String[] args) throws ClassNotFoundException {
//Class.forName(全类名)方法
Class clazz1 = Class.forName("com.itheima.fanshe.Student");
System.out.println(clazz1);
//类名.class属性
Class clazz2 = Student.class;
System.out.println(clazz2);
//对象名.getClass()方法
Student student = new Student();
Class clazz3 = student.getClass();
System.out.println(clazz3);
//对比
System.out.println(clazz1==clazz2);
System.out.println(clazz1==clazz3);
System.out.println(clazz3==clazz2);
}
/*
class com.itheima.fanshe.Student
class com.itheima.fanshe.Student
class com.itheima.fanshe.Student
true
true
true
*/
获取Class类的对象
-
方法介绍
方法名 说明 Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组 Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组 Constructor getConstructor(Class<?>... parameterTypes) 返回单个公共构造方法对象//不能访问私有 Constructor getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造方法对象//可访问私有 -
示例代码
public class Student { private String name; private int age; //私有的有参构造方法 private Student(String name) { System.out.println("name的值为:" + name); System.out.println("private...Student...有参构造方法"); } //公共的无参构造方法 public Student() { System.out.println("public...Student...无参构造方法"); } //公共的有参构造方法 public Student(String name, int age) { System.out.println("name的值为:" + name + "age的值为:" + age); System.out.println("public...Student...有参构造方法"); } } public class ReflectDemo1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //method1(); //method2(); //method3(); //method4(); } private static void method4() throws ClassNotFoundException, NoSuchMethodException { // Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): // 返回单个构造方法对象 //1.获取Class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); Constructor constructor = clazz.getDeclaredConstructor(String.class); System.out.println(constructor); } private static void method3() throws ClassNotFoundException, NoSuchMethodException { // Constructor<T> getConstructor(Class<?>... parameterTypes): // 返回单个公共构造方法对象 //1.获取Class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //小括号中,一定要跟构造方法的形参保持一致. Constructor constructor1 = clazz.getConstructor(); System.out.println(constructor1); Constructor constructor2 = clazz.getConstructor(String.class, int.class); System.out.println(constructor2); //因为Student类中,没有只有一个int的构造,所以这里会报错. Constructor constructor3 = clazz.getConstructor(int.class); System.out.println(constructor3); } private static void method2() throws ClassNotFoundException { // Constructor<?>[] getDeclaredConstructors(): // 返回所有构造方法对象的数组 //1.获取Class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); Constructor[] constructors = clazz.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } } private static void method1() throws ClassNotFoundException { // Constructor<?>[] getConstructors(): // 返回所有公共构造方法对象的数组 //1.获取Class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); Constructor[] constructors = clazz.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } } }
Constructor类用于创建对象的方法
-
方法介绍
方法名 说明 T newInstance(Object...initargs) 根据指定的构造方法创建对象 setAccessible(boolean flag) 设置为true,表示取消访问检查 -
示例代码
// Student类同上一个示例,这里就不在重复提供了 public class ReflectDemo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //T newInstance(Object... initargs):根据指定的构造方法创建对象 //method1(); //method2(); //method3(); //method4(); } private static void method4() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { //获取一个私有的构造方法并创建对象 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //2.获取一个私有化的构造方法. Constructor constructor = clazz.getDeclaredConstructor(String.class); //被private修饰的成员,不能直接使用的 //如果用反射强行获取并使用,需要临时取消访问检查 constructor.setAccessible(true); //3.直接创建对象 Student student = (Student) constructor.newInstance("zhangsan"); System.out.println(student); } private static void method3() throws ClassNotFoundException, InstantiationException, IllegalAccessException { //简写格式 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //2.在Class类中,有一个newInstance方法,可以利用空参直接创建一个对象 Student student = (Student) clazz.newInstance();//这个方法现在已经过时了,了解一下 System.out.println(student); } private static void method2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //2.获取构造方法对象 Constructor constructor = clazz.getConstructor(); //3.利用空参来创建Student的对象 Student student = (Student) constructor.newInstance(); System.out.println(student); } private static void method1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //2.获取构造方法对象 Constructor constructor = clazz.getConstructor(String.class, int.class); //3.利用newInstance创建Student的对象 Student student = (Student) constructor.newInstance("zhangsan", 23); System.out.println(student); } }
小结
-
获取class对象
三种方式: Class.forName(“全类名”), 类名.class, 对象名.getClass()
-
获取里面的构造方法对象
getConstructor (Class... parameterTypes) getDeclaredConstructor (Class... parameterTypes)
-
如果是public的,直接创建对象
newInstance(Object... initargs)
-
如果是非public的,需要临时取消检查,然后再创建对象
setAccessible(boolean) 暴力反射
Class类获取成员变量对象的方法
-
方法分类
方法名 说明 Field[] getFields() 返回所有公共成员变量对象的数组 Field[] getDeclaredFields() 返回所有成员变量对象的数组 Field getField(String name) 返回单个公共成员变量对象 Field getDeclaredField(String name) 返回单个成员变量对象 -
示例代码
public class Student { public String name; public int age; public String gender; private int money = 300; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", money=" + money + '}'; } } public class ReflectDemo1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { // method1(); //method2(); //method3(); //method4(); } private static void method4() throws ClassNotFoundException, NoSuchFieldException { // Field getDeclaredField(String name):返回单个成员变量对象 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取money成员变量 Field field = clazz.getDeclaredField("money"); //3.打印一下 System.out.println(field); } private static void method3() throws ClassNotFoundException, NoSuchFieldException { // Field getField(String name):返回单个公共成员变量对象 //想要获取的成员变量必须是真实存在的 //且必须是public修饰的. //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取name这个成员变量 //Field field = clazz.getField("name"); //Field field = clazz.getField("name1"); Field field = clazz.getField("money"); //3.打印一下 System.out.println(field); } private static void method2() throws ClassNotFoundException { // Field[] getDeclaredFields():返回所有成员变量对象的数组 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取所有的Field对象 Field[] fields = clazz.getDeclaredFields(); //3.遍历 for (Field field : fields) { System.out.println(field); } } private static void method1() throws ClassNotFoundException { // Field[] getFields():返回所有公共成员变量对象的数组 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取Field对象. Field[] fields = clazz.getFields(); //3.遍历 for (Field field : fields) { System.out.println(field); } } }
Field类用于给成员变量赋值的方法
-
方法介绍
方法名 说明 void set(Object obj, Object value) 赋值 Object get(Object obj) 获取值 -
示例代码
// Student类同上一个示例,这里就不在重复提供了 public class ReflectDemo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException { // Object get(Object obj) 返回由该 Field表示的字段在指定对象上的值。 //method1(); //method2(); } private static void method2() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException { //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取成员变量Field的对象 Field field = clazz.getDeclaredField("money"); //3.取消一下访问检查 field.setAccessible(true); //4.调用get方法来获取值 //4.1创建一个对象 Student student = (Student) clazz.newInstance(); //4.2获取指定对象的money的值 Object o = field.get(student); //5.打印一下 System.out.println(o); } private static void method1() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException { // void set(Object obj, Object value):给obj对象的成员变量赋值为value //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect4.Student"); //2.获取name这个Field对象 Field field = clazz.getField("name"); //3.利用set方法进行赋值. //3.1先创建一个Student对象 Student student = (Student) clazz.newInstance(); //3.2有了对象才可以给指定对象进行赋值 field.set(student,"zhangsan"); System.out.println(student); } }
Class类获取成员方法对象的方法
-
方法分类
方法名 说明 Method[] getMethods() 返回所有公共成员方法对象的数组,包括继承的 Method[] getDeclaredMethods() 返回所有成员方法对象的数组,不包括继承的 Method getMethod(String name, Class<?>... parameterTypes) 返回单个公共成员方法对象 Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回单个成员方法对象 -
示例代码
public class Student { //私有的,无参无返回值 private void show() { System.out.println("私有的show方法,无参无返回值"); } //公共的,无参无返回值 public void function1() { System.out.println("function1方法,无参无返回值"); } //公共的,有参无返回值 public void function2(String name) { System.out.println("function2方法,有参无返回值,参数为" + name); } //公共的,无参有返回值 public String function3() { System.out.println("function3方法,无参有返回值"); return "aaa"; } //公共的,有参有返回值 public String function4(String name) { System.out.println("function4方法,有参有返回值,参数为" + name); return "aaa"; } } public class ReflectDemo1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //method1(); //method2(); //method3(); //method4(); //method5(); } private static void method5() throws ClassNotFoundException, NoSuchMethodException { // Method getDeclaredMethod(String name, Class<?>... parameterTypes): // 返回单个成员方法对象 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取一个成员方法show Method method = clazz.getDeclaredMethod("show"); //3.打印一下 System.out.println(method); } private static void method4() throws ClassNotFoundException, NoSuchMethodException { //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取一个有形参的方法function2 Method method = clazz.getMethod("function2", String.class); //3.打印一下 System.out.println(method); } private static void method3() throws ClassNotFoundException, NoSuchMethodException { // Method getMethod(String name, Class<?>... parameterTypes) : // 返回单个公共成员方法对象 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取成员方法function1 Method method1 = clazz.getMethod("function1"); //3.打印一下 System.out.println(method1); } private static void method2() throws ClassNotFoundException { // Method[] getDeclaredMethods(): // 返回所有成员方法对象的数组,不包括继承的 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取Method对象 Method[] methods = clazz.getDeclaredMethods(); //3.遍历一下数组 for (Method method : methods) { System.out.println(method); } } private static void method1() throws ClassNotFoundException { // Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的 //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取成员方法对象 Method[] methods = clazz.getMethods(); //3.遍历 for (Method method : methods) { System.out.println(method); } } }
Method类用于执行方法的方法
-
方法介绍
方法名 说明 Object invoke(Object obj, Object... args) 运行方法 参数一: 用obj对象调用该方法
参数二: 调用方法的传递的参数(如果没有就不写)
返回值: 方法的返回值(如果没有就不写)
-
示例代码
public class ReflectDemo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // Object invoke(Object obj, Object... args):运行方法 // 参数一:用obj对象调用该方法 // 参数二:调用方法的传递的参数(如果没有就不写) // 返回值:方法的返回值(如果没有就不写) //1.获取class对象 Class clazz = Class.forName("com.itheima.myreflect5.Student"); //2.获取里面的Method对象 function4 Method method = clazz.getMethod("function4", String.class); //3.运行function4方法就可以了 //3.1创建一个Student对象,当做方法的调用者 Student student = (Student) clazz.newInstance(); //3.2运行方法 Object result = method.invoke(student, "zhangsan"); //4.打印一下返回值 System.out.println(result); } }
反射获取成员变量并使用
- 获取类 class对象
- 获取成员变量
- 给成员变量进行赋值/获取值
62.XMl
语法规则
- 后缀名必须为XML
- 文档声明必须在第一列
- 必须存在根标签
- 可以定义注释信息
- xml不可以已存在以下特殊字
&'lt; < 小于
&'gt; > 大于
&'amp; & 和号
&'apos; ' 单引号
&'quot; " 引号
Students>student#$*3>name+age+info再按一下tab可以一键生成
Students>student表示根标签下面有studnet标签
'#'表示id的书写
'$'表示自增
'*3'表示书写三个
'>name+age+info'表示下面有三个同级的标签 name和age和info
Dom解析思想
DTD约束
-
什么是约束
用来限定xml文件中可使用的标签以及属性
-
约束的分类
- DTD
- schema
-
编写DTD约束
-
步骤
-
创建一个文件,这个文件的后缀名为.dtd
-
看xml文件中使用了哪些元素
可以定义元素 -
判断元素是简单元素还是复杂元素
简单元素:没有子元素。
复杂元素:有子元素的元素;
-
-
代码实现
<!ELEMENT persons (person)> <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)>
-
引入DTD约束的三种方法
-
引入本地dtd
''
-
在xml文件内部引入
''
-
引入网络dtd
''
-
-
DTD语法
-
定义元素
定义一个元素的格式为:
简单元素: EMPTY: 表示标签体为空
ANY: 表示标签体可以为空也可以不为空
PCDATA: 表示该元素的内容部分为字符串
复杂元素:
直接写子元素名称. 多个子元素可以使用","或者"|"隔开;
","表示定义子元素的顺序 ; "|": 表示子元素只能出现任意一个 "?"零次或一次, "+"一次或多次, "*"零次或多次;如果不写则表示出现一次
schema约束
-
schema和dtd的区别
-
schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
-
一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
-
dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
-
schema 语法更加的复杂
-
-
编写schema约束
-
步骤
1, 创建一个文件,这个文件的后缀名为.xsd。
2,定义文档声明
3,schema文件的根标签为:
4,在中定义属性:
xmlns=http://www.w3.org/2001/XMLSchema
5,在中定义属性 :
targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。
6,在中定义属性 :
elementFormDefault="qualified“,表示当前schema文件是一个质量良好的文件。
7,通过element定义元素
8,判断当前元素是简单元素还是复杂元素
-
-
代码实现
<?xml version="1.0" encoding="UTF-8" ?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itheima.cn/javase"
elementFormDefault="qualified"
>
<!--定义persons复杂元素-->
<element name="persons">
<complexType>
<sequence>
<!--定义person复杂元素-->
<element name = "person">
<complexType>
<sequence>
<!--定义name和age简单元素-->
<element name = "name" type = "string"></element>
<element name = "age" type = "string"></element>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
补充:
schema监视器:
<sequence>:限定内部的element按照顺序出现在xml文件中
<choice>:内部包裹的多个element,选择其中一个出现在xml文件中
element的属性:
Order 指示器:
All
Choice:选择
Sequence:按顺序
Occurrence 指示器:
maxOccurs:最多不超过,上边界
minOccurs:最少不少于,下边界
[min,max]
[代码实现]
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.ilovesmallmovie.com"
elementFormDefault="qualified">
<element name="students">
<complexType>
<sequence>
<element name="student" minOccurs="0" maxOccurs="2"><!--限制数量-->
<complexType>
<sequence>
<element name="name" type="string" maxOccurs="2"></element>
<element name="age" type="string"></element>
<element name="gender" type="string"></element>
<choice><!--选择-->
<element name="tel" type="string"></element>
<element name="email" type="string"></element>
</choice>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
63.枚举
特点
-
所有枚举类都是Enum的子类
-
我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
-
每一个枚举项其实就是该枚举的一个对象
-
枚举也是一个类,也可以去定义成员变量
-
枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
-
枚举类可以有构造器,但必须是private的,它默认的也是private的。
枚举项的用法比较特殊:枚举("");
-
枚举类也可以有抽象方法,但是枚举项必须重写该方法
[方法]
String name()获取枚举项的名称
int ordinal()返回枚举项在枚举类中的索引值
int compareTo(E o)比较两个枚举项,返回的是索引值的差值
String toString()返回枚举常量的名称
static <T> T valueOf(Class<T> type,String name)获取指定枚举类中的指定名称的枚举值values()获得所有的枚举项
[代码实现]
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER;
}
public class EnumDemo {
public static void main(String[] args) {
// String name() 获取枚举项的名称
String name = Season.SPRING.name();
System.out.println(name);
System.out.println("-----------------------------");
// int ordinal() 返回枚举项在枚举类中的索引值
int index1 = Season.SPRING.ordinal();
int index2 = Season.SUMMER.ordinal();
int index3 = Season.AUTUMN.ordinal();
int index4 = Season.WINTER.ordinal();
System.out.println(index1);
System.out.println(index2);
System.out.println(index3);
System.out.println(index4);
System.out.println("-----------------------------");
// int compareTo(E o) 比较两个枚举项,返回的是索引值的差值
int result = Season.SPRING.compareTo(Season.WINTER);
System.out.println(result);//-3
System.out.println("-----------------------------");
// String toString() 返回枚举常量的名称
String s = Season.SPRING.toString();
System.out.println(s);
System.out.println("-----------------------------");
// static <T> T valueOf(Class<T> type,String name)
// 获取指定枚举类中的指定名称的枚举值
Season spring = Enum.valueOf(Season.class, "SPRING");
System.out.println(spring);
System.out.println(Season.SPRING == spring);
System.out.println("-----------------------------");
// values() 获得所有的枚举项
Season[] values = Season.values();
for (Season value : values) {
System.out.println(value);
}
}
}
64.注解
注解-注解的优势
[问题]
- 什么是注解?
- 注解和注释的区别
- 使用注解作为配置文件的优势
[答案]
-
什么是注解?
对程序的解释说明 -- 给JVM看的
-
注解和注释的区别
都是对程序的解释说明 注解:给JVM看的 注释:给程序员看的
-
使用注解作为配置文件的优势
1.对比反射: 使用反射手段,运行某个类中的某些方法 读取配置信息中内容,使用反射创建对象,调用方法 2.使用注解: 只需要在想要运行的方法上添加注解 此时可以使用反射运行添加注解的方法
[补充]
Java注解根据使用时期不同进行分类:
1..java源文件阶段:
例如:@Override:
检查当前添加该注解方法是否符合重写方法的规则
符合:编译通过
不符合,编译报错(注解位置)
在[完成编译]之后,被丢弃
2..class文件阶段
此时添加的注解,会在JVM运行.class文件时被丢弃
3.运行时阶段
存活到JVM运行程序的时候
注解-注解的概述
[Java中提供的一些注解]
1.@Override:
提供编译检查,查看添加该注解的方法是否符合重写规则
符合,编译通过,生成class文件时被丢弃
不符合,编译失败,注解报错
2.@Deprecated
表示当前方法是过时方法,存活到运行时阶段
1.方法调用时候会出现中划线 -- 通过编译,可以运行
2.使用javacdoc生成api手册,会提示已过时 -- 通过编译
3.@SuppressWarnings("all") 或者 (value = "all")
压制警告,使用要慎重
可以书写在方法声明上或者类声明上
注解-自定义属性
[格式]
public @interface 注解名{
定义属性:
public 数据类型 属性名 () default 默认值;
或者:
public 数据类型 属性名 ();
注意:不书写默认值,需要在使用注解的位置
在注解()内部传递没有默认值的属性需要的数据值
传参数需要指定是哪一个 属性名 = 值
属性数据类型:
1.基本数据类型
2.String
3.Class
4.枚举
5.注解
6.以上类型的一维数组
}
[代码实现]
public @interface Anno2 {
}
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER;
}
public @interface Anno1 {
//定义一个基本类型的属性
int a () default 23;
//定义一个String类型的属性
public String name();
//定义一个Class类型的属性
public Class clazz() default Anno2.class;
//定义一个注解类型的属性
public Anno2 anno() default @Anno2;
//定义一个枚举类型的属性
public Season season() default Season.SPRING;
//以上类型的一维数组
//int数组
public int[] arr() default {1,2,3,4,5};
//枚举数组
public Season[] seasons() default {Season.SPRING,Season.SUMMER};
//value。后期我们在使用注解的时候,如果我们只需要给注解的value属性赋值。
//那么value就可以省略
public String value();
}
//在使用注解的时候如果注解里面的属性没有指定默认值。
//那么我们就需要手动给出注解属性的设置值。
//@Anno1(name = "itheima")
@Anno1("abc")
public class AnnoDemo {
}
注解-特殊属性value
如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
注解-自定义注解练习
[需求]
自定义一个注解@Test,用于指定类的方法上,如果某一个类的方法上使用了该注解,就执行该方法
[操作步骤]
- 自定义一个注解Test,并在类中的某几个方法上加上注解
- 在测试类中,获取注解所在的类的Class对象
- 获取类中所有的方法对象
- 遍历每一个方法对象,判断是否有对应的注解
[代码实现]
//表示Test这个注解的存活时间
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Test {
}
public class UseTest {
//没有使用Test注解
public void show(){
System.out.println("UseTest....show....");
}
//使用Test注解
@Test
public void method(){
System.out.println("UseTest....method....");
}
//没有使用Test注解
@Test
public void function(){
System.out.println("UseTest....function....");
}
}
public class AnnoDemo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
//1.通过反射获取UseTest类的字节码文件对象
Class clazz = Class.forName("com.itheima.myanno3.UseTest");
//创建对象
UseTest useTest = (UseTest) clazz.newInstance();
//2.通过反射获取这个类里面所有的方法对象
Method[] methods = clazz.getDeclaredMethods();
//3.遍历数组,得到每一个方法对象
for (Method method : methods) {
//method依次表示每一个方法对象。
//isAnnotationPresent(Class<? extends Annotation> annotationClass)
//判断当前方法上是否有指定的注解。
//参数:注解的字节码文件对象
//返回值:布尔结果。 true 存在 false 不存在
if(method.isAnnotationPresent(Test.class)){
method.invoke(useTest);
}
}
}
}
注解-元注解
[问题]
- 什么是元注解?
[答案]
元注解就是描述注解的注解
[元注解介绍]
@Target指定了注解能在哪里使用
@Retention注解的生命周期(注解在哪个阶段生效)
@Inherited表示修饰的自定义注解可以被子类继承
@Documented表示该自定义注解,会出现在API文档里面。
[代码实现]
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD}) //指定注解使用的位置(成员变量,类,方法)
@Retention(RetentionPolicy.RUNTIME) //指定该注解的存活时间
//@Inherited //指定该注解可以被继承
public @interface Anno {
}
@Anno
public class Person {
}
public class Student extends Person {
public void show(){
System.out.println("student.......show..........");
}
}
public class StudentDemo {
public static void main(String[] args) throws ClassNotFoundException {
//获取到Student类的字节码文件对象
Class clazz = Class.forName("com.itheima.myanno4.Student");
//获取注解。
boolean result = clazz.isAnnotationPresent(Anno.class);
System.out.println(result);
}
}
65.单元测试
Junit概述
Junit是Java语言非常重要的一款单元测试工具
Junit特点
- JUnit是一个开放源代码的测试工具。
- 提供注解来识别测试方法。
- JUnit测试可以让你编写代码更快,并能提高质量。
- JUnit优雅简洁。没那么复杂,花费时间较少。
- JUnit在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。
使用步骤
- 将junit的jar包导入到工程中 junit-4.9.jar
- 编写测试方法该测试方法必须是公共的无参数无返回值的非静态方法
- 在测试方法上使用@Test注解标注该方法是一个测试方法
- 选中测试方法右键通过junit运行该方法
相关注解
注解说明
注解 | 含义 |
---|---|
@Test | 表示测试该方法 |
@Before | 在测试的方法前运行 |
@After | 在测试的方法后运行 |
66.日志
-
概述
程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储。
-
日志与输出语句的区别
输出语句 日志技术 取消日志 需要修改代码,灵活性比较差 不需要修改代码,灵活性比较好 输出位置 只能是控制台 可以将日志信息写入到文件或者数据库中 多线程 和业务代码处于一个线程中 多线程方式记录日志,不影响业务代码的性能
日志体系结构和Log4J
-
体系结构
-
Log4J
Log4j是Apache的一个开源项目。
通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件等位置。
我们也可以控制每一条日志的输出格式。
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
入门案例
-
使用步骤
- 导入log4j的相关jar包
- 编写log4j配置文件
- 在代码中获取日志的对象
- 按照级别设置记录日志信息
配置文件详解
-
三个核心
-
Loggers(记录器) 日志的级别
Loggers组件在此系统中常见的五个级别:DEBUG、INFO、WARN、ERROR 和 FATAL。
DEBUG < INFO < WARN < ERROR < FATAL。
Log4j有一个规则:只输出级别不低于设定级别的日志信息。
-
Appenders(输出源) 日志要输出的地方
把日志输出到不同的地方,如控制台(Console)、文件(Files)等。
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
-
Layouts(布局) 日志输出的格式
可以根据自己的喜好规定日志输出的格式
常用的布局管理器:
org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
-
-
配置根Logger
-
-
格式
log4j.rootLogger=日志级别,appenderName1,appenderName2,…
-
日志级别
OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。
-
appenderName1
就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。
例如:log4j.rootLogger=INFO,ca,fa
-
-
ConsoleAppender常用的选项
-
ImmediateFlush=true
表示所有消息都会被立即输出,设为false则不输出,默认值是true。
-
Target=System.err
默认值是System.out。
-
-
FileAppender常用的选项
-
ImmediateFlush=true
表示所有消息都会被立即输出。设为false则不输出,默认值是true
-
Append=false
true表示将消息添加到指定文件中,原来的消息不覆盖。
false则将消息覆盖指定的文件内容,默认值是true。
-
File=D:/logs/logging.log4j
指定消息输出到logging.log4j文件中
-
-
PatternLayout常用的选项
-
ConversionPattern=%m%n
设定以怎样的格式显示消息
-