Java SE

一,Java介绍

1,Java是什么?

Java由Sun公司于1995年5月推出的面向对象的程序设计语言。它吸收了C/C++的优点,是一种健壮的语言,具有跨平台,安全性高等特性。

2,Java为什么能跨平台?

Java之所以能在各种操作系统运行是因为Java有针对各个操作系统的虚拟机,因此想要运行Java程序只需装上对应的Java虚拟机即可。(**注意:**Java虚拟机不是跨平台的

3,Java两种核心的机制

(1)Java虚拟机:

​ 可以理解成一个可运行 Java 字节码的虚拟计算机系统,它有一个解释器组件,可以实现 Java 字节码和计算机操作系统之间的通信,对于不同的运行平台,有不同的jvm。

(2)垃圾回收器:

​ JVM 提供了一种系统线程跟踪存储空间的分配情况。并在 JVM 的空闲时,检查并释放那些可以被释放的存储空间。垃圾回收器在 Java 程序运行过程中自动启用,程序员无法精确控制和干预。

4,JDK与JRE的区别?

  1. Jdk是Java开发工具集,一般是针对于开发人员使用的,其中包含了jre和一些开发工具(Java 编译器(javac.exe)、Java 运行时解释器(java.exe)、Java 文档化化工具 (javadoc.exe)及其它工具及资源)
  2. Jre是运行java运行时环境,一般是针对于客户使用的

5,Java语法

  1. 注释 :java 程序有三种注释方式
  2. 单行注释://注释
  3. 多行注释:/注释/
  4. 文档注释:/*注释/
  5. java 代码的位置
  6. class 必须编写在.java 文件中 (具体详情参考 helloWorld 工程)
  7. 语法规则:
  8. java 是严格区分大小写的
  9. java 是一种自由格式的语言
  10. 代码分为结构定义语句和功能执行语句
  11. 功能执行语句的最后必须用分号结束

6,jdk安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGiOCKG6-1609159647287)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps1.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wCP2PKB-1609159647291)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps2.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ptXzKdo4-1609159647294)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps3.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNP2F58q-1609159647298)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps4.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-68XrLSAp-1609159647300)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps5.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fcZgyYrJ-1609159647302)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps6.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QhKkO01q-1609159647303)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps7.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FuUuc5nb-1609159647304)(file:///C:\Users\张浪浪\AppData\Local\Temp\ksohtml1732\wps8.jpg)]

二,基础语法

1,数据类型

(1)基本数据类型(四类八大种)

  1. 整型:long (8个字节,默认值0),short (2个字节,默认值0),int (4个字节,默认值0),byte (1个字节,默认值0,范围:-128~127)
  2. 浮点型:float (4个字节,默认值0.0f),double(8个字节,默认值0.0d)
  3. 布尔:boolean (1个字节,默认值false)
  4. 字符:char (2个字节,默认值‘\u0000 ’)

(2)引用数据类型

  • 接口
  • 数组等

2,类型转换

  • boolean 类型不能转换成任何其它数据类型。
  • 自动类型转换:容量小的类型自动转换成容量大的数据类型
  • byte,short,int -> float ->long ->double
  • byte,short,int 不会互相转换,它们三者在计算时会转换成 int 类型

3,标识符

  • 由字母、数字、下划线(_)和美元符号($)组成
  • 不能以数字开头
  • 区分大小
  • 长度无限制
  • 不能是 Java 中的保留关键字(goto,const)

三,流程控制

1,分支结构

(1)if条件语句

if 条件结构是根据条件判断之后再做处理

  • if(条件语句){…}
  • if (条件语句){…}else{…}
  • if (条件语句){…}else if(条件语句){…}
  • if (条件语句){…}else if(条件语句){…}else{…}

(2)switch语句

switch(表达式){

case 取值 1: 语句块 1;break;

case 取值 n: 语句块 n;break;

default: 语句块 n+1;break;

}

switch 语句有关规则

  1. 表达式的返回值必须是下述几种类型之一:int, byte, char, short,String;
  2. case 子句中的取值必须是常量,且所有 case 子句中的取值应是不同的;
  3. default 子句是可选的;
  4. break 语句用来在执行完一个 case 分支后使程序跳出 switch 语句块;如果 case 后面没有写 break 则直接往下面执行!
  5. Case 后面的执行体可写{ }也可以不写{ }

2,循环语句

(1)for循环

语法:for(初始化参数;判断条件 ;更新循环变量){

循环体;

}

(2)while循环

符合条件,循环继续执行;否则,循环退出

特点:先判断,再执行

语法:while(条件表达式){

//语句块;

}

(3)do-while循环

先执行一遍循环操作,符合条件,循环继续执行;否则,循环退出

特点:先执行,再判断 ,最少执行一次循环

语法:do {

循环操作

}while ( 条件表达式 );

四,数组

1,概述

数组是相同数据类型的多个数据的容器,它既可以存储基本数据类型也可以存储引用数据类型

2,数组的创建

格式 1. 数据类型[] 数组名称 = new 数据类型[数组长度];

格式 2. 数据类型[] 数组名称 = {数组内容 1,数组内容 2,数组内容 3…数组内容 n};

格式 3. 数据类型[] 数组名;

格式 3 属于只创建了数组引用名, 并未在内存创建数组空间。

格式 4. 数据类型[] 数组名称 = new 数据类型[]{内容 1,内容 2,内容 3…内容 n};

3,数组常用算法

(1)冒泡排序

原理:

- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的

数。

- 针对所有的元素重复以上的步骤,除了最后一个。

- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

  • 升序排列的口诀:

N个数字来排队

两两相比小靠前,

外层 循环length-1

内层循环length-i-1

  • 降序排序的口诀:

N个数字来排队

两两相比大靠前,

**外层 循环length-**1

内层循环length-i-1

代码实现:
/**
 * 冒泡排序
 * @param args
 */
public static void main(String[] args) {
	int[] nums = {20,15,18,13,30,60};
	int temp;
	//外层循环控制的是, 比较的轮数。
	//外层循环次数: length-1
	for(int i=0;i<nums.length-1;i++) {
		//内层循环控制的是,每轮比较的次数
		//第i轮(i从0开始计算), 比较次数为:length-i-1
		for(int j=0;j<nums.length-i-1;j++) {
			if(nums[j]>nums[j+1]) {
				//两两相比, 满足移动条件
				temp = nums[j];
				nums[j] = nums[j+1];
				nums[j+1] = temp;
			}
		}
	}
	
	
	//排序已经完成。 下面是遍历打印查看的过程
	for(int i=0;i<nums.length;i++) {
		System.out.println(nums[i]);
	}
	
	
	
}

(2)二分查找

首先,假设数组中元素是按升序排列,将数组中间位置的数据与查找数据比较,如果两者相等,则查找成功;否则利用 中间位置记录将数组分成前、后两个子数组,如果中间位置数据大于查找数据,则进一步查找前子数组,否则进一步查 找后子数组。 重复以上过程,直到找到满足条件的数据,则表示查找成功, 直到子数组不存在为止,表示查找不成功。

代码实现:
/**
 * 二分查找(折半查找)
 */
public static void main(String[] args) {
	int[] nums = {10,20,30,40,50,60,70,80,90};
	
	//要查找的数据
	int num = 20;
	
	//关键的三个变量:
	//1.	最小范围下标
	int minIndex = 0;
	//2.	最大范围下标
	int maxIndex = nums.length-1;
	//3.	中间数据下标
	int centerIndex = (minIndex+maxIndex)/2;
	while(true) {
		System.out.println("循环了一次");
		if(nums[centerIndex]>num) {
			//中间数据较大
			maxIndex = centerIndex-1;
		}else if(nums[centerIndex]<num) {
			//中间数据较小
			minIndex = centerIndex+1;
		}else {
			//找到了数据  数据位置:centerIndex
			break;
		}
		
		if(minIndex > maxIndex) {
			centerIndex = -1;
			break;
		}
		//当边界发生变化, 需要更新中间下标
		centerIndex = (minIndex+maxIndex)/2;
	}
	
	System.out.println("位置:"+centerIndex);
	
}

五,面向对象基础

1,面向对象思想

(1)概述

面向对象(Object Oriented)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,是一种对现

实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。

面向对象是相对于面向过程来讲的,指的是把 相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系

统建模,更贴近事物的自然运行模式。

面向过程到面向对象思想层面的转变:

面向过程关注的是执行的过程,面向对象关注的是具备功能的对象。

面向过程到面向对象,是程序员思想上 从执行者到指挥者的转变。

(2)三大思想

面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP

  1. OOA:面向对象分析(Object Oriented Analysis)
  2. OOD:面向对象设计(Object Oriented Design)
  3. OOP:面向对象程序(Object Oriented Programming

(3)三大特征

  • 封装性:所有的内容对外部不可见
  • 继承性:将其他的功能继承下来继续发展
  • 多态性:方法的重载本身就是一个多态性的体现

2,类与对象

类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。

类似生活中的图纸与实物的概念。)

类必须通过对象才可以使用,对象的所有操作都在类中定义。

类由属性和方法组成

3,创建对象内存分析

(1)栈内存

存储的是: 基本数据类型的数据 以及 引用数据类型的引用!

栈存储的特点是, 先进后出

存储速度快的原因:

栈内存, 通过 ‘栈指针’ 来创建空间与释放空间 !

指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 !

(2)堆内存

存放的是类的对象

堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用 时长 ,堆内存中内存的释放是由GC(垃圾回收器)完成的

圾回收器 回收堆内存的规则:

当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 !

(3)方法区

存放的是

- 类信息

- 静态的变量

- 常量

- 成员方法

方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)

4,构造函数

作用:用于对象初始化。

执行时机: 在创建对象时,自动调用

特点:所有的Java类中都会至少存在一个构造方法。 如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代 码! 如果自行编写了任意一个构造器, 则编译器不会再自动生成无参的构造方法。

5,方法重载

  • 方法名称相同, 参数类型或参数长度不同, 可以完成方法的重载 ! 方法的重载与返回值无关!
  • 方法的重载 ,可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能

六,面向对象进阶

1,封装(关键字:private)

在开发中, 为了避免出现逻辑错误, 我们建议对所有属性进行封装,并为其提供setter及getter方法进行设置和取得操作。

2,this关键字

在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作:

  • 调用类中的属性
  • 调用类中的方法或构造方法
  • 表示当前对象

3,static关键字

概述:

static表示“静态”的意思,可以用来修饰成员变量和成员方法(后续还会学习 静态代码块 和 静态内部类)。

static的主要作用在于创建独立于具体对象的域变量或者方法

简单理解:

被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访

问。

并且不会因为对象的多次创建 而在内存中建立多份数据

重点:

  • 静态成员 在类加载时加载并初始化。
  • 无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 )
  • 在访问时: 静态不能访问非静态 , 非静态可以访问静态 !

4,代码块

(1)普通代码块

在方法中出现的 代码块, 我们称其为普通代码块。

作用:限定变量生命周期,及早释放,提高内存利用率。

(2)构造代码块

在类中方法外的代码块, 我们称其为构造代码块, 在每次对象创建时执行, 执行在构造方法之前。

作用:多个构造方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法之前执行。

(3)静态代码块

在类中使用static修饰的成员代码块, 我们称其为静态代码块, 在类加载时执行。 每次程序启动到关闭 ,只会

执行一次的代码块。

作用:用于加载驱动。

(4)同步代码块

在后续多线程技术中学习。

(5)面试题

构造方法 与 构造代码块 以及 静态代码块的执行顺序:

静态代码块 --> 构造代码块 --> 构造方法

七,面向对象高级

1,抽象类

抽象类必须使用abstract class声明

一个抽象类中可以没有抽象方法。抽象方法必须写在抽象类或者接口中。

格式:

abstract class 类名{ // 抽象类

}

(1)常见问题

  1. 抽象类能否使用final声明?

不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。

  1. 抽象类能否有构造方法?

能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的

构造方法(默认是无参的),之后再调用子类自己的构造方法。

(2)抽象类与普通类的区别

抽象类必须用public或procted 修饰(如果为private修饰,那么子类则无法继承,也就无法实现其

抽象方法)。默认缺省为 public

抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化。

如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必

须定义为 abstract类

2,接口

(1)接口的继承

接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承,例如:

interface C extends A,B{

}

(2)接口和抽象类的区别

1、抽象类要被子类继承,接口要被类实现。

2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。

3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现

5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明

静态方法)

6、接口不能有构造方法,但是抽象类可以有

3,多态

对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性

就从此而来。

ps: 方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。

重载: 一个类中方法的多态性体现

重写: 子父类中方法的多态性体现

4.instanceof

作用:

判断某个对象是否是指定类的实例,则可以使用instanceof关键字

格式:

实例化对象 instanceof 类 //此操作返回boolean类型的数据

5,Object类

Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object

(1)toString

建议重写Object中的toString方法。 此方法的作用:返回对象的字符串表示形式。

Object的toString方法, 返回对象的内存地址

(2)equlse

建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对

象。

Object的equals方法:实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和

y ,当且仅当x和y引用同一对象( x == y具有值true )时,此方法返回true 。

equals方法重写时的五个特性:

自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。

对称性 :对于任何非空引用值x和y , x.equals(y)应该返回true当且仅当y.equals(x)回报

true 。

传递性 :对于任何非空引用值x , y和z ,如果x.equals(y)回报true个y.equals(z)回报true

,然后x.equals(z)应该返回true 。

一致性 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,前

提是未修改对象上的equals比较中使用的信息。

非空性 :对于任何非空的参考值x , x.equals(null)应该返回false 。

6,包装类

  • 1 int Integer
  • 2 char Character
  • 3 float Float
  • 4 double Double
  • 5 boolean Boolean
  • 6 byte Byte
  • 7 short Short
  • 8 long Long

以上的八种包装类,可以将基本数据类型按照类的形式进行操作。

但是,以上的八种包装类也是分为两种大的类型的:

· Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个

数字。

· Object:Character、Boolean都是Object的直接子类

7,自动拆装箱

(1)装箱操作:

在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:

int temp = 10 ; // 基本数据类型

Integer x = new Integer(temp) ; // 将基本数据类型变为包装类

在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例

如:

Float f = 10.3f ; // 自动装箱

float x = f ; // 自动拆箱

System.out.println(f * f) ; // 直接利用包装类完成

System.out.println(x * x) ; // 直接利用包装类完成

(2)字符串转换

使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入

数据上使用较多。

在Integer类中提供了以下的操作方法:

public static int parseInt(String s) :将String变为int型数据

在Float类中提供了以下的操作方法:

public static float parseFloat(String s) :将String变为Float

在Boolean 类中提供了以下操作方法:

public static boolean parseBoolean(String s) :将String变为boolean

8,可变参数

一个方法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,但是在JDK 1.5之后提供了新的

功能,可以根据需要自动传入任意个数的参数。

语法:

返回值类型 方法名称(数据类型…参数名称){

//参数在方法内部 , 以数组的形式来接收

}

注意:

可变参数只能出现在参数列表的最后

八,异常

1,jvm默认处理异常的方式

当主函数接收异常会有两种处理方式

  1. 自己处理异常,并继续执行一下程序,也就是try,catch的方式
  2. 自己处理不了就抛给jvm,jvm收到异常,将该异常的名称以及信息打印到控制台上,并将将程序

2,编译器异常与运行期异常的区别

所有的RuntimeException及其子类的实例被称为运行期异常,其他均为编译期异常。

  • 编译器异常:java程序必须显示处理,否则就会出错,无法编译通过。
  • 运行期异常:无需显示处理,也可以和编译期异常异常一起处理

3,throw概述

在功能方法内部出现某种情况,程序将不能继续运行,需要进行跳转时,就用throw把异常抛出。

4,throw与throws的区别

  • throws:用在方法声明后面。跟的是异常类名。可以跟多个异常类名,用逗号隔开。表示抛出异常,由该方法的调用者处理
  • throw:用在方法内部,跟的是异常对象,只能抛出一个异常对象。表示抛出异常,由该方法体内的语句处理。

5,关键字finally特点

被finally控制的语句一定会执行,除非在此之前退出Java虚拟机(如:System.exit(0)),用于释放资源。

6,final,finally,finalize的区别

  • final:修饰类,该类不能被继承,修饰方法,该方法不能被重写,修饰的变量是常量。
  • finally:写在try catch语句用于释放资源
  • finalize:是一个方法,在垃圾回收是调用。

7,如何使用异常处理

原则:如果该功能内部可以将问题处理用try,处理不了,用throws

区别:后续程序需要继续运行用try,不需要继续运行用throws

九,常用类库

1,String概述

String是一个不可变的字符序列

(1)常用构造方法

public String():空构造

public String(byte[] bytes):把字节数组转成字符串

public String(byte[] bytes,int index,int length):把字节数组的一部分转成字符串

public String(char[] value):把字符数组转成字符串

public String(char[] value,int index,int count):把字符数组的一部分转成字符串

public String(String original):把字符串常量值转成字符串

public int length():返回此字符串的长度。

(2)String类的判断功能

boolean equals(Object obj):比较字符串的内容是否相同,区分大小写

boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写

boolean contains(String str):判断大字符串中是否包含小字符串

boolean startsWith(String str):判断字符串是否以某个指定的字符串开头

boolean endsWith(String str):判断字符串是否以某个指定的字符串结尾

boolean isEmpty():判断字符串是否为空。

(3)String类的获取功能

int length():获取字符串的长度。

char charAt(int index):获取指定索引位置的字符

int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引。

int indexOf(String str):返回指定字符串在此字符串中第一次出现处的索引。

int indexOf(int ch,int fromIndex):返回指定字符在此字符串中从指定位置后第一次出现处的索引。

int indexOf(String str,int fromIndex):返回指定字符串在此字符串中从指定位置后第一次出现处的索引。

String substring(int start):从指定位置开始截取字符串,默认到末尾。

String substring(int start,int end):从指定位置开始到指定位置结束截取字符串。

(4)String类的转换功能

byte[] getBytes():把字符串转换为字节数组。

char[] toCharArray():把字符串转换为字符数组。

static String valueOf(char[] chs):把字符数组转成字符串。

static String valueOf(int i):把int类型的数据转成字符串。

注意:String类的valueOf方法可以把任意类型的数据转成字符串。

String toLowerCase():把字符串转成小写。

String toUpperCase():把字符串转成大写。

String concat(String str):把字符串拼接。

(5)String类的其他功能

String replace(char old,char new):将old字符替换为new字符

String replace(String old,String new):将old字符串替换为new字符串

 String trim():去除字符串两端的空白

2,Arrays类的概述和方法使用

public static String toString(int[] a):转换为字符转

public static void sort(int[] a):将数组中的元素进行排序

public static int binarySearch(int[] a,int key):二分查找

3,Math类概述和方法使用

Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

public static int abs(int a):获取绝对值

public static double ceil(double a):向上取整

public static double floor(double a):向下取整

public static int max(int a,int b) :返回两个值中最大的值

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

public static double random():取随机数[0~1)

public static int round(float a) :四舍五入

public static double sqrt(double a):平方根

4,System类的概述和方法使用

System 类包含一些有用的类字段和方法。它不能被实例化。

public static void gc():垃圾回收
public static void exit(int status):退出java虚拟机
public static long currentTimeMillis():获取当前时间的毫秒值

5,BigDecimal类的概述和方法使用

由于在运算的时候,float类型和double很容易丢失精度,所以,为了能精确的表示、计算浮点数,Java提供了BigDecimal

不可变的、任意精度的有符号十进制数。

(1)构造方法

public BigDecimal(String val)

(2)成员方法

public BigDecimal add(BigDecimal augend):加

public BigDecimal subtract(BigDecimal subtrahend):减

public BigDecimal multiply(BigDecimal multiplicand):乘

public BigDecimal divide(BigDecimal divisor):除

6,Date类的概述和方法使用

Date类的概述

类 Date 表示特定的瞬间,精确到毫秒。

(1)构造方法

public Date()

public Date(long date)

(2)成员方法

public long getTime():获取当前时间的毫秒值

public void setTime(long time):设置时间

7,SimpleDateFormat类

(1)DateFormat类的概述

DateFormat 是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。是抽象类,所以使用其子类SimpleDateFormat

(2)SimpleDateFormat构造方法

public SimpleDateFormat()

public SimpleDateFormat(String pattern)

(3)成员方法

public final String format(Date date):将时间对象转换为对应格式的字符串

public Date parse(String source):将指定的字符串转换为时间对象

8,Calendar类的概述和获取日期的方法

(1)Calendar类的概述

Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

(2)成员方法

public static Calendar getInstance():获得Calender的子类对象

public int get(int field):获取对应的参数值

(3)成员方法

public void add(int field,int amount):日历参数加减操作

public final void set(int year,int month,int date):设置日历

十,集合和数据结构

1,常见的数据结构

常见的数据存储结构有:栈,队列,数组,链表,二叉树。

(1)栈

  • 概念:栈是一种只允许在栈顶进行插入和删除的线性表
  • 特点:栈的出后和入口都是栈顶。先进后出
  • 名词:压栈(存元素),弹栈(取元素)

(2)队列

  • 概念:队列是一种只允许在队头进行元素的删除,队尾进行添加元素的线性表
  • 特点:队列的入口和出口在两侧。先进先出

(3)数组

  • 概念:数组是有序的,在内存中开辟连续的存储空间
  • 特点:大小固定,增删慢,查找快。

(4)链表

  • 概念:链表是一组内存空间不必连续,但按照特定顺序排列的抽象数据类型。
  • 特点:空间不限制,增删快,查找慢。
  • 分类:
  1. 单链表:数据域加指针域,链尾指针指向null,遍历方式单一,从头到尾
  2. 双向链表:前驱,数据域和后继,链头前驱设置null,链尾后继设置null。遍历方式双向,如果将链头后继一直遍历到链尾后继定义为正向,那么反之为反向。
  3. 循环链表:单向循环链表,单向链表链尾指针指向链头数据域。双向循环链表:双向链表链头前驱指向链尾,链尾后继指向链头。

(5)二叉树

概念:二叉树是树的一种,每个节点最多有两个子树,及每个节点的度为2(结点度:节点的子树数)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOek1ZhG-1609159647305)(C:\Users\张浪浪\AppData\Local\Temp\1607580434066.png)]

  • 特点:当前节点的左节点小于右节点
  • 种类:

1,斜树:每个节点只有左节点或右节点

2,满二叉树:每个节点都有左右节点

3,完全二叉树:一个深度为n的二叉树,除了n层以外,每个节点都具有左右节点。且第n层的节点都连续聚集在最左侧

  • 性质:
  1. 第i层的二叉树的节点数最多有 2^(i-1) (i≥1)
  2. 深度为h的二叉树节点最多有 2^h-1 个结点(h≥1)
  3. 包含n节点的二叉树的深度最少为 log2 (n+1)
  4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1
  • 遍历:
  1. 先序遍历:先遍历根节点,再遍历左节点,最后是右节点 (根->左->右)
  2. 中序遍历:先遍历左节点,再遍历根节点,最后是右节点 (左->根->右)
  3. 后序遍历:先遍历左节点,再遍历右节点,最后根节点 (左->右->根)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmJP3N83-1609159647306)(C:\Users\张浪浪\AppData\Local\Temp\1607581446738.png)]

先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7

中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7

后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1

2,集合

1,Collection集合(单列集合的顶层接口)

	boolean add(E e):添加
	boolean remove(Object o):删除
	void clear():清空集合
	boolean contains(Object o):是否包含此对象
	boolean isEmpty():判断集合是否为空
	int size():获取集合大小
	Iterater iterator():获取迭代器

(1)List接口

元素可以重复,存储有序

void add(int index,E element)

E remove(int index)

E get(int index):根据索引获取集合中的元素

E set(int index,E element):给指定索引位置上的元素设置值

Iterater litsIterator():获取List集合特有的迭代器

遍历

size()+get();

iterator

listIterater()

List三个子类

  • ArrayList:底层数组实现,增删慢,查改快。线程不安全,效率高
  • Vector:底层数组实现,增删慢,查改快。线程安全,效率低
Vector特有遍历:
Eumeration e = vector.elements();
            while(e.hasMoreElements){
System.out.println(e.nextElement())

}
  • LinkedList:底层链表实现,增删快,查找慢,线程不安全,效率快。

(2)Set接口

元素唯一,无序

遍历

  • 调用iterator()方法得到Iterator,
  • 使用hasNext()和next()方法
  • b.增强for循环, 只要可以使用Iterator的类都可以用

HashSet原理:

我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数

当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象

如果没有哈希值相同的对象就直接存入集合

如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

将自定义类的对象存入HashSet去重复

  • 类中必须重写hashCode()和equals()方法

  • hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)

  • equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储

LinkedHashSet

LinkedHashSet的特点:可以保证怎么存就怎么取

TreeSet

特点
TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列

使用方式

1.自然顺序(Comparable)

  • TreeSet类的add()方法中会把存入的对象提升为Comparable类型
  • 调用对象的compareTo()方法和集合中的对象比较
  • 根据compareTo()方法返回的结果进行存储

2.比较器顺序(Comparator)

  • 创建TreeSet的时候可以制定 一个Comparator

  • 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序

  • add()方法内部会自动调用Comparator接口中compare()方法排序

  • c.两种方式的区别

    • TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
    • TreeSet如果传入Comparator, 就优先按照Comparator

2.Map(双列集合顶层接口)

Map接口和Collection接口的不同

  • Map是双列的,Collection是单列的
  • Map的键唯一,Collection的子体系Set是唯一的
  • Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效

Map集合的功能概述
添加功能
V put(K key,V value):添加元素。
如果键是第一次存储,就直接存储元素,返回null
如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
删除功能
void clear():移除所有的键值对元素
V remove(Object key):根据键删除键值对元素,并把值返回
判断功能
boolean containsKey(Object key):判断集合是否包含指定的键
boolean containsValue(Object value):判断集合是否包含指定的值
boolean isEmpty():判断集合是否为空
获取功能
Set<Map.Entry<K,V>> entrySet():???
get(Object key):根据键获取值
Set keySet():获取集合中所有键的集合
Collection values():获取集合中所有值的集合
长度功能

​ int size():返回集合中的键值对的个数

遍历

keySet()+get()

entyySet(),getKey()+getValue

三个子类

  1. HashMap:存取自定义对象,其中保证键唯一与HashSet中元素为一原理一样
  2. TreeeMap:键排序与TreeSet中元素一样
  3. LinkedHashMap:怎么存。怎么取

HashMap和Hashtable的区别

  • Hashtable是JDK1.0版本出现的,是线程安全的,效率低,HashMap是JDK1.2版本出现的,是线程不安全的,效率高
  • Hashtable不可以存储null键和null值,HashMap可以存储null键和null值

十一,IO流

1.概念

  • IO流用来处理设备之间的数据传输

  • Java对数据的操作是通过流的方式

  • Java用于操作流的类都在IO包中

  • 流按流向分为两种:输入流,输出流。

  • 流按操作类型分为两种:

      	字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
      字符流 : 字符流只能操作纯字符数据,比较方便。
    

2.IO流常用父类

(1)字节流的抽象父类:

​ * InputStream
* OutputStream

(2)字符流的抽象父类:

​ *** Reader**

	* Writer	

3,IO流(字节流)

3,IO流(字节流)

3,IO流(字节流)

(1)read()一次读取一个字节

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

(2)read()方法读取的是一个字节,为什么返回是int,而不是byte

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

(4)write()一次写出一个字节

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

(5) FileOutputStream的构造方法写出数据如何实现数据的追加写入

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

int read(byte[] b):一次读取一个字节数组

write(byte[] b):一次写出一个字节数组

available()获取读的文件所有的字节个数

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

(6)BufferedInputStream和BufferOutputStream

  • A:缓冲思想
    • 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,
    • 这是加入了数组这样的缓冲区效果,java本身在设计的时候,
    • 也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流
  • B.BufferedInputStream
    • BufferedInputStream内置了一个缓冲区(数组)
    • 从BufferedInputStream中读取一个字节时
    • BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 返回给程序一个
    • 程序再次读取时, 就不用找文件了, 直接从缓冲区中获取
    • 直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个
  • C.BufferedOutputStream
    • BufferedOutputStream也内置了一个缓冲区(数组)
    • 程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中
    • 直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里

(7)IO流(flush和close方法的区别)

  • flush()方法

用来刷新缓冲区的,刷新后可以再次写出

  • close()方法

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

4,IO流(字符流)

(1)字符流是什么

字符流是可以直接读写字符的IO流

字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.

(2)FileReader, FileWriter

FileReader类的read()方法可以按照字符大小读取

FileReader fr = new FileReader("aaa.txt");				//创建输入流对象,关联aaa.txt
	int ch;
	while((ch = fr.read()) != -1) {							//将读到的字符赋值给ch
		System.out.println((char)ch);						//将读到的字符强转后打印
	}
fr.close();												//关流 

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

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

(3)IO流(什么情况下使用字符流)

流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.

程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流

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

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

5,IO流(带缓冲的字符流)

BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率

BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率

BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));	//创建字符输入流对象,关联aaa.txt
	BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt"));	//创建字符输出流对象,关联bbb.txt
	
int ch;				
while((ch = br.read()) != -1) {		//read一次,会先将缓冲区读满,从缓冲去中一个一个的返给临时变量ch
	bw.write(ch);					//write一次,是将数据装到字符数组,装满后再一起写出去
}

br.close();							//关流
bw.close();  

(1)IO流(readLine()和newLine()方法)

BufferedReader的readLine()方法可以读取一行字符(不包含换行符号)

BufferedWriter的newLine()可以输出一个跨平台的换行符号"\r\n"

BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
	BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt"));
	String line;
	while((line = br.readLine()) != null) {
		bw.write(line);
		//bw.write("\r\n");					//只支持windows系统
		bw.newLine();						//跨平台的
	}
br.close();
bw.close(); 

6,IO流(使用指定的码表读写字符)

FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)

FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)

BufferedReader br = 									//高效的用指定的编码表读
			new BufferedReader(new InputStreamReader(new FileInputStream("UTF-8.txt"), "UTF-8"));
	BufferedWriter bw = 									//高效的用指定的编码表写
			new BufferedWriter(new OutputStreamWriter(new FileOutputStream("GBK.txt"), "GBK"));
	int ch;
	while((ch = br.read()) != -1) {
		bw.write(ch);
	}
br.close();
bw.close();

7,IO流(内存输出流)

(1)什么是内存输出流?

该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据

使用方式:

  1. 创建对象: new ByteArrayOutputStream()
  2. 写出数据: write(int), write(byte[])
  3. 获取数据: toByteArray()
FileInputStream fis = new FileInputStream("a.txt");
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	int b;
	while((b = fis.read()) != -1) {
		baos.write(b);
	}

//byte[] newArr = baos.toByteArray();				//将内存缓冲区中所有的字节存储在newArr中
//System.out.println(new String(newArr));
System.out.println(baos);
fis.close();

(2)面试题

定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)

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

8,IO流(打印流的概述和特点)

什么是打印流

该流可以很方便的将对象的toString()结果输出, 并且自动加上换行, 而且可以使用自动刷出的模式

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

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

使用方式

  • 打印: print(), println()

  • 自动刷出: PrintWriter(OutputStream out, boolean autoFlush, String encoding)

  • 打印流只操作数据目的

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

9,IO流(对象操作流)

什么是对象操作流

该流可以将一个对象写出, 或者读取一个对象到程序中. 也就是执行了序列化和反序列化的操作.

使用方式

写出: new ObjectOutputStream(OutputStream), writeObject()

读取: new ObjectInputStream(InputStream), readObject()

要写出的对象必须实现Serializable接口才能被序列化

十二,多线程

1,线程与进程的区别

  • 进程:进程是系统进行资源调度和分派的最小单位
  • 线程:线程是cpu分派和调度的基本单位

简而言之:一个程序最少有一个进程,一个进程最少有一个线程,进程有独立的内存单元,而多个

线程共享一片内存。

2,线程的并发和并行

  • 并行:两个任务同时进行,需要多核cpu的支持
  • 并发:两个任务同时请求进行,而cpu只能处理一个请求,因此两个任务只能交替进行

3,多线程的实现方式一

继承Thread

  1. 定义类继承Thread

  2. 重写run方法

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

  4. 创建线程对象
    . 开启新线程, 内部会自动执行run方法

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

4,多线程的实现方式二

实现Runnable

  1. 定义类实现Runnable接口

  2. 实现run方法

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

  4. 创建自定义的Runnable的子类对象

  5. 创建Thread对象, 传入Runnable

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

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

5,多线程实现方式三

  1. 编写类实现Callable接口 , 实现call方法
  2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
  3. 通过Thread,启动线程
class XXX implements Callable<T> { 
@Override 
public <T> call() throws Exception { 
return T; 
        } 
} 
FutureTask<Integer> future = new FutureTask<>(callable); 
new Thread(future).start();

6,.继承Thread和实现Runnable的区别

继承Thread

  • 好处是:可以直接使用Thread类中的方法,代码简单
  • 弊端是:如果已经有了父类,就不能用这种方法

实现Runnable接口

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

7,常用方法

getName():获取线程名字

setName():给线程设置名字

Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
					}
				}
			}

	Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 1000; i++) {
					System.out.println(this.getName() + "....bb");
				}
			}
		};
		t1.setName("芙蓉姐姐");
		t2.setName("凤姐");
		
		t1.start();
		t2.start();

有参构造:给线程设置名字

new Thread("康师傅") {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
					}
				}
			}.start();

Thread.currentThread(), 主线程也可以获取

Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒

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

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

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

		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 5; i++) {
					System.out.println(getName() + "...bb");
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		
		t1.setDaemon(true);						//将t1设置为守护线程
		
		t1.start();
		t2.start();

join():当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续

join(int):可以等待指定的毫秒之后继续

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

yield让出cpu

8,线程同步

什么情况下需要同步
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

(1)同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

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

class Printer {
				Demo d = new Demo();
				public static void print1() {
					synchronized(d){				//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
						System.out.print("锄");
						System.out.print("禾");
						System.out.print("日");
						System.out.print("当");
						System.out.print("午");
						System.out.print("\r\n");
					}
			}

			public static void print2() {	
				synchronized(d){	
					System.out.print("床");
					System.out.print("前");
					System.out.print("明");
					System.out.print("月");
					System.out.print("光");
					System.out.print("\r\n");
				}
			}
		}

(2)多线程(同步方法)

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

class Printer {
		public static void print1() {
			synchronized(Printer.class){	//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
			System.out.print("锄");
			System.out.print("禾");
			System.out.print("日");
			System.out.print("当");
			System.out.print("午");
			System.out.print("\r\n");
			System.out.print("\r\n");
			}
		}
		/*
		 * 非静态同步函数的锁是:this
		 * 静态的同步函数的锁是:字节码对象
		 */
		public static synchronized void print2() {	
			System.out.print("床");
			System.out.print("前");
			System.out.print("明");
			System.out.print("月");
			System.out.print("光");
			System.out.print("\r\n");
		}
	}

9,线程通信

什么时候需要通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的
如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

怎么通信

  • 如果希望线程等待, 就调用wait()
  • 如果希望唤醒等待的线程, 就调用notify();
  • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

10,sleep和wait方法的区别

  • sleep方法必须传入参数,参数就是时间,时间到了自动醒来
  • wait方法不是必须传入参数,如果没有参数,遇到wait就等待,需要被唤醒,如果传入参数,等参数时间
  • 到后等待
  • sleep方法在同步中不释放锁
  • wait方法在同步中释放锁

十三,网络编程

1,网路编程三大要素

  1. ip地址:是计算机在互联网中的唯一标识 . 就像人在社会中的身份证号码.
  2. 端口号:端口号是计算机中 程序的标识 . 用于在一台计算机中区分不同的应用程序 。口号在使用时 , 应尽量避免0-1024之间的端口号, 因为已经被一些知名的软件 和 windows操作系统所占用。
  3. 协议:是计算机与计算机之间交流的标准 . 是对数据的 传输速率, 传入接口, 步骤控制 出错控制 等等 制定的一套标准 !

2,TCP-OSI七层网络模型

物理层,数据链路层,网络层,传输层,会话层,表现层,应用层

3,TCP 协议 的 C/S程序

需要使用到两个类, 来编写TCP协议的 CS程序 .

(1)ServerSocket 搭建服务器

ServerSocket 常用构造方法: ServerSocket(int port); 创建一个基于TCP/IP协议的服务器 , 并绑定指定的端口号. 注意: 参数port的范围是: 0-65535 (建议1025-65535)

常用方法: Socket accept(); **** 等待客户端连接 . 此方法会导致线程的阻塞! 直到一个新的客户端连接成功, return Socket对象后, 线程在继续执行.

void close(); 释放占用的端口号 , 关闭服务器

(3).Socket 搭建客户端

**Socket 构造方法:Socket(String ip,int port) ,**创建一个套接字, 并连接指定ip和端口号的 服务器. 参数1. 服务器的ip地址 参数2. 服务器软件的端口号.

常用方法: OutputStream getOutputStream();返回的是 , 指向通信的另一端点的输出流

InputStream getInputStream(); 返回的是 , 指向通信的另一端点的输入流

void close(); 关闭套接字

注意:

在网络编程时, 获取输入输出流的操作 ,对于客户端与服务器来说是相对的

客户端的输入流, 输入的是服务器的输出流 输出的内容.

客户端的暑促刘, 输出到了服务器的输入流中.

所以 在使用时, 需要注意以下一点规则:

客户端与服务器获取流的顺序必须是相反的:

例如:

客户端先得到了输入流 , 那服务器必须先获取输出流

客户端: 

//1. 连接服务器 

Socket socket = new Socket("192.168.102.228",8888); 

//2. 得到输出流 

//2.1 得到输出流 

OutputStream os = socket.getOutputStream(); 

//2.2 将输出流, 转换为打印流 

PrintStream ps = new PrintStream(os); 

//3. 得到输入流 

//3.1 得到输入流 

InputStream is = socket.getInputStream(); 

//3.2 将字节输入流, 转换为字符输入流 , 并转换为逐行读取流InputStreamReader isr = new InputStreamReader(is); 

BufferedReader br = new BufferedReader(isr); 

//4. 循环接收用户输入 

Scanner input = new Scanner(System.in); 

while(true) { 

System.out.println("请输入要发送给服务器的内容:"); 

String text1 = input.nextLine(); 

//5. 将用户输入的内容, 发送给服务器 

ps.println(text1); 

//6. 接收服务器回复的消息 

String text2 = br.readLine(); 

System.out.println(text2); 

if("886".equals(text1)) { 

break; 

} 

} 

服务器: 

public static void main(String[] args) throws Exception { 

//1. 启动服务器, 并侦听8888端口号 

ServerSocket server = new ServerSocket(8888); 

//2. 打印提示 

System.out.println("服务器已启动 , 等待客户端连接中..."); 

//3. 等待客户端连接 

Socket socket = server.accept(); 

System.out.println("一个客户端连接成功:"+socket.getInetAddress().toString()); 

//4. 获取输入流 

//4.1 获取输入流 

InputStream is = socket.getInputStream(); 

//4.2 将输入的字节流 ,转换为字符流 

InputStreamReader isr = new InputStreamReader(is); 

//4.3 将字符流, 转换为逐行读取流 

BufferedReader br = new BufferedReader(isr); 

//5. 获取输出流 

//5.1 获取输出流 

OutputStream os = socket.getOutputStream(); 

//5.2 将字节输出流, 转换为打印流 

PrintStream ps = new PrintStream(os); 

while(true) { 

//6. 循环读取一行行的数据 ,读取操作会导致线程的阻塞, 直到客户端真的发送了数据, 

//服务器才能接到, 顺序继续执行下面的代码 

String text = br.readLine(); 

//7. 将这个文字, 再打印给客户端 

ps.println("服务器:"+text); 

if("886".equals(text)) { 

break; 

} 

}

十四,XMl与Json

1,XML语法格式

(1)XML文档声明

<?xml version="1.0" encoding="UTF-8"?>

(2)标记 ( 元素 / 标签 / 节点)

XML文档,由一个个的标记组成.

语法:

开始标记(开放标记): <标记名称>

结束标记(闭合标记): </标记名称>

标记名称: 自定义名称,必须遵循以下命名规则:

1.名称可以含字母、数字以及其他的字符

2.名称不能以数字或者标点符号开始

3.名称不能以字符 “xml”(或者 XML、Xml)开始

4.名称不能包含空格,不能包含冒号(:)

5.名称区分大小写

(3) 一个XML文档中, 必须有且且仅允许有一个根标记.

正例:                                          反例:
    
<names>                                        <name>李四</name> 
<name>麻子</name>     					    <name>张三</name> 
<name>张三</name> 
<name>李四</name> 
</names> 

(4) 标记可以嵌套, 但是不允许交叉.

正例:

<person> 

<name>李四</name> 

<age>18</age> 

</person> 
反例:

<person> 

<name>李四<age></name> 

18</age> 

</person> 

(5)标记的层级称呼 (子标记, 父标记 , 兄弟标记, 后代标记 ,祖先标记)

例如:

<persons> 

<person> 

<name>李四</name> 

<length>180cm</length> 

</person> 

<person> 

<name>李四</name> 

<length>200cm</length> 

</person> 

</persons> 

name是person的子标记.也是person的后代标记

name是persons的后代标记.案例:

name是length的兄弟标记.

person是name的父标记.

persons是name的祖先标记.

(6) 标记名称 允许重复

(7) 标记除了开始和结束 , 还有属性.

标记中的属性, 在标记开始时 描述, 由属性名和属性值 组成.

格式:

在开始标记中, 描述属性.

可以包含0-n个属性, 每一个属性是一个键值对!

属性名不允许重复 , 键与值之间使用等号连接, 多个属性之间使用空格分割.

属性值 必须被引号引住.

案例:

<persons> 

<person id="10001" groupid="1"> 

<name>李四</name> 

<age>18</age> 

</person> 

<person id="10002" groupid="1"> 

<name>李四</name> 

<age>20</age> 

</person> 

</persons> 

(8) 注释

注释不能写在文档文档声明前

注释不能嵌套注释

格式:

注释开始: <!–

注释结束: -->

2,XML解析

(1)SAX解析

解析方式是事件驱动机制 !

SAX解析器, 逐行读取XML文件解析 , 每当解析到一个标签的开始/结束/内容/属性时,触

发事件.

我们可以编写程序在这些事件发生时, 进行相应的处理.

优点:

分析能够立即开始,而不是等待所有的数据被处理

逐行加载,节省内存.有助于解析大于系统内存的文档

有时不必解析整个文档,它可以在某个条件得到满足时停止解析.

缺点:

\1. 单向解析,无法定位文档层次,无法同时访问同一文档的不同部分数据(因为逐

行解析, 当解析第n行是, 第n-1行已经被释放了, 无法在进行操作了).

\2. 无法得知事件发生时元素的层次, 只能自己维护节点的父/子关系.

\3. 只读解析方式, 无法修改XML文档的内容.

(2)DOM解析

是用与平台和语言无关的方式表示XML文档的官方W3C标准,分析该结构通常需要加载整个

文档和内存中建立文档树模型.程序员可以通过操作文档树, 来完成数据的获取 修改 删除等.

优点:

文档在内存中加载, 允许对数据和结构做出更改.

访问是双向的,可以在任何时候在树中双向解析数据。

缺点:

文档全部加载在内存中 , 消耗资源大.

(3)JDOM解析

目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一

个Java特定模型,JDOM一直得到大力推广和促进。

JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”

(根据学习曲线假定为20%)

优点:

使用具体类而不是接口,简化了DOM的API。

大量使用了Java集合类,方便了Java开发人员。

缺点:DOM4J解析XML 掌握

文档对象 Document

元素对象 Element

没有较好的灵活性。

性能不是那么优异。

(4) DOM4J解析

它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath

支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,

DOM4J是一个非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一

个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML。

3,JSON

(1)对象格式

一个对象, 由一个大括号表示.

括号中 描述对象的属性 . 通过键值对来描述对象的属性

(可以理解为, 大括号中, 包含的是一个个的键值对.)

格式:

键与值之间使用冒号连接, 多个键值对之间使用逗号分隔.

键值对的键 应使用引号引住 (通常Java解析时, 键不使用引号会报错. 而JS能正确解

析.)

键值对的值, 可以是JS中的任意类型的数据

(2)数组格式

[元素1,元素2…]

(3)在JSON格式中可以与对象互相嵌套

{ 

"name":"伟杰老师", 

"age":18, 

"pengyou":["张三","李四","王二","麻子",{ 

"name":"野马老师", 

"info":"像匹野马一样狂奔在技术钻研的道路上" 

}], 

"heihei":{ 

"name":"大长刀", 

"length":"40m" 

} 

}

4,JSON解析

(1)Gson

将JSON字符串转换为对象

引入JAR包在需要转换Java对象的位置, 编写如下代码:

对象 = new Gson().fromJson(JSON字符串,对象类型.class);

案例:

String json = "{\"id\":1,\"name\":\"金苹果\",\"author\":\"李伟杰 

\",\"info\":\"嘿嘿嘿嘿嘿嘿\",\"price\":198.0}"; 

Book book = new Gson().fromJson(json, Book.class); 

System.out.println(book); 

将对象转换为JSON字符串

引入JAR包 在需要转换JSON字符串的位置编写如下代码即可:*

String json = new Gson().toJSON(要转换的对象);

案例:

Book b = BookDao.find(); 

String json = new Gson().toJson(b); 

System.out.println(json); 

(2)FastJson

将对象转换为JSON字符串

引入JAR包

在需要转换JSON字符串的位置编写如下代码即可: String json=JSON.toJSONString(要转换的对象);

**案例:**

Book b = BookDao.find(); 

String json=JSON.toJSONString(b); 

System.out.println(json);

引入JAR包

将JSON字符串转换为对象

在需要转换Java对象的位置, 编写如下代码: 类型 对象名=JSON.parseObject(JSON字符串, 类型.class);

或 List<类型> list=JSON.parseArray(JSON字符串,类型.class);

案例:

String json = "{\"id\":1,\"name\":\"金苹果\",\"author\":\"李伟杰 

\",\"info\":\"嘿嘿嘿嘿嘿嘿\",\"price\":198.0}"; 

Book book = JSON.parseObject(json, Book.class); 

System.out.println(book);

十六,枚举 & 注解 & 反射

1、枚举

(1)定义格式

权限修饰符 enum 枚举名称 { 

实例1,实例2,实例3,实例4; 

}
public enum Level { 

LOW(30), MEDIUM(15), HIGH(7), URGENT(1); 

private int levelValue; 

private Level(int levelValue) { 

this.levelValue = levelValue; 

}

public int getLevelValue() { 

return levelValue; 

} 

}

(2)实现接口的枚举类

所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。 

每个枚举对象,都可以实现自己的抽象方法 

public interface LShow{ 

void show(); 

}

public enum Level implements LShow{ 

LOW(30){ 

@Override 

public void show(){ 

//... 

} 

}, MEDIUM(15){ 

@Override 

public void show(){ 

//... 

} 

},HIGH(7){ 

@Override 

public void show(){ 

//... 

} 

},URGENT(1){ 

@Override 

public void show(){ 

//... 

} 

};

private int levelValue;

(3)注意事项

一旦定义了枚举,最好不要妄图修改里面的值,除非修改是必要的。

枚举类默认继承的是java.lang.Enum类而不是Object类

枚举类不能有子类,因为其枚举类默认被fifinal修饰

只能有private构造方法

switch中使用枚举时,直接使用常量名,不用携带类名

不能定义name属性,因为自带name属性

不要为枚举类中的属性提供set方法,不符合枚举最初设计初衷。

2、注解

(1)简介

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和注释不同,Java 标注可以通过反射获取标

注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行

时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

主要用于:

编译格式检查,反射中解析,生成帮助文档,跟踪代码依赖等

(2)内置注解

  • @Override : 重写
  • @Deprecated:废弃
  • @SafeVarargs:忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
  • @FunctionalInterface: 函数式接口
  • @Repeatable:标识某注解可以在同一个声明上使用多次
  • SuppressWarnings:抑制编译时的警告信息。

(2)自定义注解

@Documented 

@Target(ElementType.TYPE) 

@Retention(RetentionPolicy.RUNTIME) 

public @interface MyAnnotation1 { 

参数类型 参数名() default 默认值; 

}
(01) @interface

使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是

一个Annotation。

定义 Annotation 时,@interface 是必须的。

注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完

成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。

(02) @Documented

类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该

Annotation,则表示它可以出现在 javadoc 中。

定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc

中。

(03) @Target(ElementType.TYPE)

前面我们说过,ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定

Annotation 的类型属性。

@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味

着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明"的注解。

定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地

方;若没有 @Target,则该 Annotation 可以用于任何地方。

  1. TYPE, /* 类、接口(包括注释类型)或枚举声明 */
  2. FIELD, /* 字段声明(包括枚举常量) */
  3. METHOD, /* 方法声明 */
  4. PARAMETER, /* 参数声明 */
  5. CONSTRUCTOR, /* 构造方法声明 */
  6. LOCAL_VARIABLE, /* 局部变量声明 */
  7. ANNOTATION_TYPE, /* 注释类型声明 */
  8. PACKAGE /* 包声明 */
(04) @Retention(RetentionPolicy.RUNTIME)

a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器

处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修

饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处

理完后,"@Override" 就没有任何作用了。

b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件

中,它是 Annotation 的默认行为。

c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并

且可由JVM读入

3,反射

(1),概述

JAVA反射机制是在运行状态中,获取任意一个类的结构 , 创建对象 , 得到方法,执行方法 , 属性 !;

这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制。

(2)、类加载器

java默认有三种类加载器,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader。

(01)BootstrapClassLoader(引导启动类加载器):

嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,引 导启动类加载器无法被应用程序直接使用。

(02)ExtensionClassLoader(扩展类加载器):

ExtensionClassLoader是用JAVA编写,且它的父类加载器是Bootstrap。

类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与 

文件系统。学习类加载器时,掌握Java的委派概念很重要。 

双亲委派模型:如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求 

转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的 

启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类) 

时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。

是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类 库。它的父加载器是BootstrapClassLoader

(03)App ClassLoader(应用类加载器):

App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文

件。它的父加载器为Ext ClassLoader

(3),得到Class的几种方式

(01)如果在编写代码时, 指导类的名称, 且类已经存在, 可以通过 包名.类名.class 得到一个类的 类对象

(02)如果拥有类的对象, 可以通过 Class 对象.getClass() 得到一个类的 类对象

(03)如果在编写代码时, 知道类的名称 , 可以通过 Class.forName(包名+类名): 得到一个类的 类对象

上述的三种方式, 在调用时, 如果类在内存中不存在, 则会加载到内存 ! 如果类已经在内存中存在, 不

会重复加载, 而是重复利用 !

(4),通过class对象 获取一个类的构造方法

(01) 通过指定的参数类型, 获取指定的单个构造方法

getConstructor(参数类型的class对象数组)

例如:构造方法如下: Person(String name,int age) 
得到这个构造方法的代码如下: 
Constructor c = p.getClass().getConstructor(String.class,int.class); 
(02) 获取构造方法数组

getConstructors();

(03)获取所有权限的单个构造方法

getDeclaredConstructor(参数类型的class对象数组)

(04)获取所有权限的构造方法数组

getDeclaredConstructors();

(5),Constructor 创建对象

常用方法: newInstance(Object… para)

调用这个构造方法, 把对应的对象创建出来

参数: 是一个Object类型可变参数, 传递的参数顺序 必须匹配构造方法中形式参数列表的顺 序!

setAccessible(boolean flag)

如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)

(6),通过class对象 获取一个类的方法

(01) getMethod(String methodName , class… clss)

根据参数列表的类型和方法名, 得到一个方法(public修饰的)

(02)getMethods();

得到一个类的所有方法 (public修饰的)

(03)getDeclaredMethod(String methodName , class… clss)

根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)

(04)getDeclaredMethods();

得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)

(7)Method 执行方法

invoke(Object o,Object… para) :

调用方法 , 参数1. 要调用方法的对象 ,参数2. 要传递的参数列表

getName() :获取方法的方法名称

setAccessible(boolean flag) :如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)

(8)通过class对象 获取一个类的属性

(01) getDeclaredField(String filedName)

根据属性的名称, 获取一个属性对象 (所有属性)

(02) getDeclaredFields()

获取所有属性

(03)getField(String filedName)

根据属性的名称, 获取一个属性对象 (public属性)

(04)getFields()

获取所有属性 (public)

(9)Field 属性的对象类型

常用方法:

  1. get(Object o ); 参数: 要获取属性的对象,获取指定对象的此属性值
  2. set(Object o , Object value);参数1. 要设置属性值的 对象,参数2. 要设置的值 设置指定对象的属性的值
  3. getName() ;获取属性的名称
  4. setAccessible(boolean flag) ;如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性)

(10)获取类/属性/方法的全部注解对象

4,内省

(1)简介

基于反射 , java所提供的一套应用到JavaBean的API ,一个定义在包中的类 , 拥有无参构造器 所有属性私有,

所有属性提供get/set方法,实现了序列化接口 这种类, 我们称其为 bean类 .

Java提供了一套java.beans包的api , 对于反射的操作, 进行了封装 !

(2)Introspector

获取Bean类信息 

方法:

BeanInfo getBeanInfo(Class cls) 

通过传入的类信息, 得到这个Bean类的封装对象 

(3)BeanInfo

常用的方法: 

MethodDescriptor[] getPropertyDescriptors(): 

获取bean类的 get/set方法 数组 

(4)MethodDescriptor

常用方法: 

\1. Method getReadMethod(); 

获取一个get方法 

\2. Method getWriteMethod(); 

获取一个set方法 

有可能返回null 注意 ,加判断 !
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值