JAVA学习笔记
一 .刷题必备知识点
前情提要:IDEA快捷键
-
复制当前行至下一行:ctrl+d
-
删除当前行:ctrl+y
-
添加注释和取消注释:ctrl+/ (第一次为添加,第二次为取消)
-
代码自动格式化:ctrl+alt+L
-
快速运行程序:alt+r
-
自动生成构造器:alt + Fn + insert(注意,多选要按住shift)
-
自动分配变量名:.var
(如:先写“new Scanner(System.in)”,然后再在后面加上“.var”,变为:“new Scanner(System.in).var” 点击一下回车,即可自动生成new前面的语句“ Scanner scanner = ”,而且变量名是自动生成的,如scanner,scanner1,scanner2等等以此类推。)
- 模板:
(1)创建一个变量为i的for循环:fori
(2)Main()方法声明:main
(3)打印一个值:soutv
(4)输出一个参数:soutp
(5)输出一个字符串:sout(输出格式化字符串为:souf)
-
在失去自动代码提示时想要自动补全提示代码可以使用补全代码:alt+/
-
复制当前行至下一行:ctrl+alt+向下光标
-
在写scanner,random,arrays时,我们在main方法中写完了内部的代码时,不需要再外部手动导入,只需要按下alt+enter,即可自动匹配导入
-
查看类的层级关系:ctrl+h
-
遇到异常时可以选中异常可能出现的代码块,按下CTRL+ALT+T,然后再选中try/catch即可
1.保留小数方法
假设无穷小数为a,要保留到3位小数,则可以写为(String.format(“%.3f”,c)),可以自动做到四舍五入
2.Math类使用
(1)三角函数
算正弦余弦正切反正弦反余弦反正切:Math.sin() (其他为cos.tan.asin.acos.atan);
(2)开根号
Math.sqrt(a);
(3)绝对值:
Math.abs()
(4)取整
返回最接近的int或long型值:Math.round();
(5)最大和最小值
Math.max();Math.min();
(6)取随机数
返回一个随机数:Math.random();使用方法(举例):
import java.util.Random
Random r = new Random();
double a = Math.Random()* 100;
注意:random方法返回一个double值,可以强转为int,乘100的意思是random的范围为 [0,100)。若要自定义random取值范围,可以通过a+b实现,比如要得到36到70之间的随机数,那么应该写为*34+36;
3.substring使用方法
substring用于截取string中的字符
如果substring()的括号内只写了一个数字,那么即为从第一个字符为第0位,往后数到这个数字对应的字符,截取这个字符的下一个字符到末尾
String str = "abcd";
String s = str.substring(2);
System.out.println(s);//输出为d
4.java对于大小写非常敏感
System.out.println("Hello".equals("hello"));//False
5.split()方法
String a = "a,b,cc,d,eeE";
String[] b = a.split(",");//输出的b为[a,b,cc,d,eeE]
6.replace方法
String a = "balalala";
String b = a.replace("a","");//输出的b为"blll"
replace后括号里写的第一个是要替换的char类型字符,第二个是替换之后的字符,如果后面只写"",则意为将前一个字符删除。
7.nextLine()和hasNextLine()
(1)nextline()
为扫描仪移过当前一行并且返回跳过一行的输入
(2)hasNextIine()
返回一个布尔值,如果存在下一行的输入时返回true,否则返回false,一般放在while循环中,如果有下一行输入,则会进行循环,否则循环停止
8.四舍五入
- 将String内的小数取固定位数并四舍五入
//将m的值保留两位小数并且四舍五入
double m = 123.2355436;
String k = String.format("%.2f",m);//k的值为123.24
使用String.format()方法,String.format(“%.2f”,m)中的2指的是要保留几位数,m则为要转化的数
- 使用DecimalFormat进行四舍五入
import java.util.DecimalFormat;
DecimalFormat df = new DecimalFormat("0.00000000");
System.out.println(10/3);//3.00000000
-
Math.round()方法
double x = 9.992; int nx = (int)Math.round(x); System.out.println(nx);
9.数组排序(冒泡排序)
//升序
int arr[] = {1,54,123,75,234};
int temp = 0;
for(int i = 0;i<arr,length-1;i++){
for(int j = 0;j<-1-i;j++){
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//降序
int arr[] = new int [10];
for(int i = 0;i<arr.length;i++){
arr[i] = (int)(Math.random()*100)+1;
}int temp = 0;
for(int i = 0;i<arr.length;i++){
for(int j = i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
for(int i = 0;i<arr.length;i++){
System.out.println(arr[i]+"\t");
}
10.String 强转 int
int a = Integer.parseInt(str);
11.Arrays类
注意要:import java.utiL.Arrays;
Arrays常用方法
1.数组复制
(1)Arrays.copyOfRange
int a[] = new int[] { 18, 62, 68, 82, 65, 9 };
int b[] = Arrays.copyOfRange(a,0,3);//返回[18, 62, 68]
//注意,该方法为前闭后开
(2)Arrays.copyOf()
int arr[] = {1,2,3,4,5,6,7,8,9};
int[] ba = Arrays.copyOf(arr,arr.length);//第一个是被复制的数组,第二个是要复制数据的个数
2.转字符串
(toString()):
int a[] = {12,432,541,321};
String content = Arrays.toString(a);//[12, 432, 541, 321]
3.数组排序
(Arrays.sort())
int a[] = { 18, 62, 68, 82, 65, 9 };
Arrays.sort(a);//[9,18,62,65,68,82]
注意:该方法为从小到大排序
4.搜索
(binarySearch())
注意:此法为二分搜索法,必须先排序后才能搜索
如果找到了返回元素的索引,没找到返回-1
int a[] = { 18, 62, 68, 82, 65, 9 };
Arrays.sort(a);
Arrays.binarySearch(a,62);//返回1
5.比较两数组是否相同:
返回一个布尔值
int a[] = new int[] { 18, 62, 68, 82, 65, 9 }
int b[] = new int[] { 18, 62, 68, 82, 65, 8 };
Boolean k = Array.equals(a,b);
6.填充:
(fill())
用同一个值填充数组
int a[] = new int[10];Arrays.fill(a,5);
12.数据类型转换
Int转String:Integer.toString()
byte转String:byte.toString()
float转String:Float.toString()
long转String:String.valueOf();
double转String:String.valueOf();
char转String:Character.toString();或者+“”;
13.String 大小写转换
String s = "abCD";
String k = s.toUpperCase(s);//ABCD
String k = s.toLowerCase(s);//abcd
toUpperCase()转大写
toLowerCase()转小写
14.进制转化
15.next()和nextLine()的特性(错题回顾)
所以next()再检测的空格的时候就结束输出了,从而不会得到nextLine()的输入导致出错。解决方法:遇到这种情况全用nextLine()接收数据。
//错误示范!!!!!!!!!!!
Scanner scanner1 = new Scanner(System.in);
String a = scanner1.next();
String b = scanner1.nextLine();
//输入123,换行,输入asd时发现输入123时程序就已经结束并输出123了
//错误解析:next()方法读取到空白符就结束,而nextLine()读取到回车结束
//因此在你输入aaa之后的换行,自动判断nextLine()已经输入完毕了,所以a接收到了123,但是b只接收到了空格
解决方法:
方法一:不换行,直接输入123加上空格加上asd
//输入 123 asd
方法二:全部使用nextLine()存入数据,就可以换行
16.三大编程平台
Windows,Linux,Android
17.基本数据类型和引用数据类型的区别
-
基本数据类型在创建时,在栈上划分一块内存,将数值直接储存在栈上(返回的是具体的数据如数字或者true或false等)
-
引用数据类型在创建时首先要在栈上给其引用分配一个内存,而对象的具体信息都直接储存在堆内存上,然后由栈上的引用指向堆中对象的地址(返回的是对象的地址)
18.break语句功能
-
在switch中使流程跳出switch结构
-
循环中使流程跳出当前循环结构,遇到break时循环终止
-
多层循环中,break只能向外跳出一层
二 .必备知识点
1.Unicode 编码表
2.关键字(50+)
3.内存分配机制
4.四种标识符
-
类和接口名。每个字的首字母大写,含有大小写。
-
方法名。首字符小写,其余的首字母大写,含大小写。尽量少用下划线。
-
常量名。基本数据类型的常量名使用全部大写字母,字与字之间用下划线分隔。对象常量可大小混写。
-
变量名。见名知义,可大小写混写,首字符小写,字间分隔符用字的首字母大写。不用下划线,少用美元符号。
注意:java语言中规定标识符使用要求为大小写字母、数字、下划线、和美元符号组成,但不能以数字开头。不能为java中的关键字。
5.基本数据类型
byte,short,int,long,float,double,char,Boolean
(1)整形
Byte类型:关键字byte,内存存储占一个字节,八位,储存范围-128~127
Short类型:关键字short,内存储存占两个字节,16位,范围为-32768~32767
Int类型:关键字int,内存储存占4个字节,32位,范围为-2147483648~2147483647
Long类型:关键字long,内存储存占八个字节,64位,范围非常大
(2)浮点数据类型
单精度(float) 双精度(double)
java默认为double,如果要使用float则要在数据后加f或F
Float四个字节32位1.4E-45~3.4028235E38
Double八个字节64位4.9E-324~1.7976931348623157E308
(注意:5.12e2表示5.12乘以十的二次方,5.12E-2表示5.12除于十的二次方)
6.数据类型转化
(1)自然转换:由低精度向高精度数据类型转换
(2)强制转换:由高精度向低精度数据类型转换
7.原码反码补码
8.运算符和表达式
(1)赋值运算符
以上为运算复合赋值运算符,得到式子两边的值之后,将右边的值赋给左边的变量,如令num=100,age=1000,若num+=age,则num=1100。
(2)算数运算符
单双目是按照运算所需变量的个数来区分。
双目运算符: + , - , * , / , % —— 需要两个变量才可完成运算
单目运算符:++(自增1),–(自减1) —— 只需一个变量即可完成运算
(3)关系运算符
(4)逻辑运算符
用于连接boolean类型变量,常量或关系表达式计算后得到的boolean结果
-
&& (and)逻辑与,相当于“并且”(有0就为0)
Ture&&ture结果为ture
Ture&&false结果为false
False&&false结果为false
-
|| (or) 逻辑或,相当于“或者“(全0才为0)
Ture ||ture 结果为ture
False ||ture 结果为ture
False ||false 结果为false
-
! (not) 逻辑非,相当于"不是"(取反)
!ture 结果为false
!false 结果为ture
注意:逻辑与又称为短路与,假设a&&b,若a是false,则后边的b不会被计算,即不被执行,因为a已经决定了最终的结果为false。同理,短路或只要前面a为真,则后边就吧被计算,结果直接出来,为真。
(5)位运算符
将数据中在内存中二进制存储按照位运算计算得出最终结果
& 按位与相当于并且(对整形和布尔计算)
-
!按位或相当于或者(对整形和布尔计算)
- 按位非(只对整形计算)
^ 按位异或(对整形和布尔计算)
&对整数运算:1与1结果为1,否则为0
~如果位是0结果是1,位是1结果为0
按位异或为双目运算符,两边都相同,结果为false,^两边不同,结果为true(同false异true)
(6)特殊运算符
- 移位运算符
<<左移运算符,value<<num,value为数值,可为变量,也可以是整形的常数,num代表一个移动的数值,为非负数
>>右移运算符,符号位不变,左边补上符号位低位移出,高位的空位补符号位,即正数补0,负数补1,右移一位相当于除2,右移n位相当于除于2的n次方
>>>无符号向右移动,无符号位右移忽略符号位扩展,0补最高位,规则与右移相同,只是填充时无所谓左边数字是正是负,都用0填充,无符号右移一般只针对负数计算,因为对于正数来说这种运算没有意义。
(7)三元运算符
int result = a>b?a++:b--;//一真大师,a>b为真则a++,为假则b--
(8)运算符优先级
(9)保留字
int result = a>b ? a++ :b–;(只是一种案例,如果a>b则取第一个输出,如果不是则取第二个输出)(其后的++或者—是在输出之后再自增或者自减)
9.if-else
10.switch条件语句
Switch开关语只能是一个整形,要小于且不等于long精度,可以是char,byte,int,开关表达式可以是变量也可以是常量或字符串(需要大于等于java1.7版本才可以用字符串表示),不能为布尔类型
Case关键字后边必须是常量表达式,不能有变量
Break是隔断的意思,有 break则其 后方的语句不会被执行
Default只要在switch大括号之内即可,因为它自动判断是在所有case都不满足时会执行default。
穿透:如果语句块1执行完之后没有写break,就会直接执行语句块2,而不需要分析常量2是否匹配
11.标识符命名规则
(1)包名:一般为aaa.bbb.ccc
(2)类名:所有单词首字母大写,如aaaBbbCcc(驼峰命名法)
(3)变量名与方法名:第一个首字母小写,其后单词首字母均大写;如xxxYyyZzz
(4)常量名:所有字母均大写且单词间需要加下划线来连接:如AAA_BBB_CCC;
12.for循环控制
(1)语法:
for(循环变量初始化;循环条件;循环变量迭代){
循环操作(可以多条语句,也可以是一条语句);
}
(2)死循环:
for( ;;){
System.out.println(“ok”);
}
13.增强for循环
int arr[] = {1,2,3,4,5,6,7};
for(int j : arr){
System.out.println(j);//意为每一次循环都从arr中提取一个元素给j
}
(1)优点:简化迭代器的书写格式,其底层还是迭代器
(2)缺点:不能在增强循环里动态的删除集合内容、不能获取下标等
14.while循环
(1)注意:while循环为先判断后执行
(2)语法:
While(循环条件){
循环体(语句);
循环变量迭代;
}
15.do while 循环控制
(1)注意:do while 为先执行后判断,因此至少执行一次
(2)语法:
do{
循环体(语句);
循环变量迭代;
}while(循环条件);
boolean loop = true;
do{
for(int i = 0;i<100;i++){
if(i == 99){
loop = false;
}
}
}while(loop);
16.多重循环控制(含例题)
(1)for多重循环
for(int i = 0;i<2;i++){
for(int j = 0;j<3;j++){
System.out.println("i="+i+"j="+j);
}
}
(2)空心直角三角形
public static void star(int n) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) {
System.out.print(" ");
}
for (int j = 1; j <= 2 * i - 1; j++) {
if (j == 1 || j == 2 * i - 1 || i == n) {
System.out.print("*");
} else {
System.out.print(" ");
}
}
System.out.println();
}
}
(3)九九乘法表
17.标签(label)
Java 中的标签是为循环设计的,是为了在多重循环中方便的使用 break 和coutinue 。
int sum = 0;
int day = 1;
abc:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 8; j++) {
sum += 2;
if (sum >= 20) {
break abc;
}
}
sum--;
day++;
}
//abc标识的是外层循环,break abc指的是直接跳出外层循环。
18.equals 方法
System.out.println("Hello".equals("hello"));//输出false
19.continue
只能在循环语句中使用,使本次循环结束,直接进行下次循环
Continue可以用在for循环以及while循环中,当时不建议用在while循环中,容易导致死循环
20.return
(1)方法中用于返回结果
(2)循环中用于退出循环(如果放在main方法中会导致程序终止)
21.数组(含例题)
(1)数组默认值:int = 0,short = 0,byte = 0,long = 0,float = 0.0,double = 0.0,char = \u0000,Boolean = false,String = null
(2)动态初始化
语法:
数据类型 数组名[];或者是数据类型[]数组名
int a[];或者是int[]a;(还未分配空间)
创建数组:语法:数组名 = new 数据类型[大小];
a = new int[10];(为数组分配10个空间)
(3)静态初始化
语法:数据类型 数组名[]={元素值,元素值。。。}
例如:int a[]={1,2,3,4,5,};
(4)注意事项
(i)数组可以包含基本类型和引用类型,但是不能混用
(ii)使用步骤:声明数组并且开辟空间,给各个数组赋值,使用数组
(iii)数组下标是从0开始,使用在输出的时候,比如定义的变量为i,则可以写作”输出第”+(i+1)“个值”,便于观看
(iiii)假设int[]arr = new int[5],则有效下标位为0~4,初始值默认为0
(1)数组翻转
方法一:
方法二:
(2)数组扩容
(3)数组排序
(1)方法一:冒泡排序
升序
降序
(2)Arrays类中的sort()方法
(4)数组查找
存在返回索引,不存在返回-1
22.二维数组(含例题)
(1)动态初始化
int arr[][] = new int[2][3];
- 列数不确定
指二维数组中一维数组元素的个数不一定相同
假设有三个一维数组,但是元素个数不同,则可以创建:
int arr[][] = new int [3][];
- 先声明后开空间
int arr[];
arr = new int [3][3];
(2)静态初始化
int arr[][] = {{1,1,1},{2,2,2},{3,3,3,3}} ;//静态初始化时[]内不能填数字
杨辉三角
23. 对象
(1)属性
属性是类的一个组成部分,一般为基本数据类型,也可以为引用类型,如对象,数组
(i)成员变量 = 属性 = field(字段)
例子:Car(name,price,color)
(2)访问修饰符
控制属性的访问范围
有四种访问修饰符:public,proctected,默认,private
(3)对象基本格式
其中p1为对象名,即对象引用,而new person()才为创建的对象空间,为真正的对象
(4)创建对象
- 先声明后创建
Cat cat;声明对象
Cat = new Cat();创建
- 直接创建
Cat cat = new Cat();
- 创建对象流程图
1. 先加载person类信息(属性与方法信息,只会加载一次)
2. 在堆中分配空间,进行默认初始化,
3. 将地址赋给p,p指向对象
4. 进行指定初始化,如p.name = “jack” p.age = 10
(5)访问属性
基本语法
cat.name;
cat.age;
24.成员方法
(1)方法调用流程
(2)代码实现
介绍:public表示方法是公开的,void表示方法没有返回值,speak()其中speak为方法名,()为形参列表,{}为方法体,可以写我们要执行的代码
当我们定义好了方法之后,如果不去调用,则方法不会起作用,调用方法时,先创建对象,如上图Persion1 p1 = new Persion1();然后再调用,如上图p1.speak();
(3)返回数据类型
(i) 一个方法最多一个返回值,如果要有多个返回值,则可以使用数组
(ii) 返回类型可以为任意类型
(iii) 如果方法要求返回数据类型,则方法体中最后的执行语句必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
(iv) 如果方法是void,则方法体中可以没有return语句,或只写return;
多个返回值情况:即定义应该数组,然后将得到的多个值赋给数组,成为数组的元素,最后再输出数组。
(4)形参列表(含例题)
注意:方法定义时的参数为形式参数(形参),方法调用时传入的参数为实际参数,简称为实参
(1)计算数的积累
(2)计算两数之和
25.方法传参机制
方法中数值的变化会影响到主方法中数值变化,因此我们可以通过形参影响实参
思考题:如果我们让方法中的t=null,那会结果会输出什么
答案:1345433 (原因:因为当我们将C方法中的值置空,对main方法的值没有影响,置空的是C方法中的值,但是如果对C中进行赋值,则对main中的值有影响)
26.方法克隆
27.方法递归(含例题)
(1)规则
-
执行应该方法时,就创建应该新的受保护的空间(栈空间)
-
方法的局部变量是独立的,不会互相影响
-
如果方法中使用的是引用类型变量(如数组,对象),就会共享引用类型的数据
-
递归必须向退出递归的条件逼近,否则就会出现无限递归,会导致栈溢出
-
当方法执行完毕,或者遇到return时(只要在方法中遇到return就会返回,不论return位置在哪里),就会返回,谁调用就把数据返回给谁
(2)阶乘
- 方法一:
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int count = 1;
for (int i = 1; i <= n; i++) {
count*=i;
}
System.out.println(count);
-
方法二:
public static int hh(int n){ if(n<=1){ return 1; }else{ return n*hh(n-1); } }
(3)汉诺塔问题
(4)八皇后问题
28.方法重载
介绍:Java中允许一个类中出现多个同名方法,但是要求形参列表不一致,减轻起名,记名的麻烦
注意:方法名相同,参数列表必须不同(形参的个数,类型不同等),返回类型无要求,参数名不一样但是形参个数,类型相同也不行
29.可变参数
java中允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法
-
基本语法:访问修饰符,返回类型,方法名(数据类型…形参名){}
-
运用:如果要连续求多个数据的和,只有参数个数不同时,可以使用可变参数。写为:
public int sum(int ...nums);//这一行是精髓,使用…即可接收任意个数的int类型的数
(1).int…表示对是接收可变参数,类型为int,即可以接收多个int(0~多),如果上面都不传,那么其长度为0
(2).使用可变参数时,可以当作数组来使用,即nums可以作为数组,如可以写nums.length,之后如果要求和,那就直接当作数组进行遍历求和
(3).可变参数实参可以为数组
(4).可变参数的本质就是数组
(5). 可变参数可以和普通类型参数一起放在形参列表中,但是必须保证可变参数放在末尾
(6).一个形参只可出现一个可变参数
3.示范:
30.int转化char类型(含例题)
例题1:已知String类型的s内均为小写字母,要求每一个字母都根据26位字母表向后移动n位并输出
例题2:计算字符串中字母出现次数(哈希表)
String str = "sdnasjhdasdaksnfcjdshdfufhaosinfdsjncxkjz";
Map<Character,Integer> map = new HashMap<Character,Integer>();
char[] arr = str.toCharArray();
for (char ch : arr) {
if (map.containsKey(ch)) {
Integer old = map.get(ch);
map.put(ch, old + 1);
} else {
map.put(ch,1);
}
}
System.out.println(map);
31.将String类型转化为char类型数组
String s = "12345";
char arr[] = s.toCharArray();
32.compareTo()方法
用于将Number对象与方法的参数进行比较可比较byte,long,Integer,string,int,double,float等。该方法用于两个相同数据类型的比较,不能比较两个不同数据类型的数据,最终返回一个值
- Integer类型中compareTo()的作用
public class Test{ public static void main(String args[]){
Integer x = 5;
System.out.println(x.compareTo(3));
System.out.println(x.compareTo(5));
System.out.println(x.compareTo(8));
} }//分别输出1,0,-1;
· 如果指定的数与参数相等返回0。
· 如果指定的数小于参数返回 -1。
· 如果指定的数大于参数返回 1。
- String类型中compareTo()中的作用
String a = "aa3456";
String b = "aa4456";
System.out.println(a.compareTo(b));
如果首字母不同,则返回两个字符串ASCII码的差值
如果相同,则返回两个字符串的长度差
返回的数如果大于0则表示a的长度大于b
小于0则表示a的长度小于b
相等则表示a和b完全相同。
-
compareTo在treeMap中的作用
可以将String类型的字符串按照首字母在26字母内的排序来排列,o1-o2是正排,o2-o1是逆排
33.intern()方法
String s1 = "hd";String s2 = "java";
String s3 = (s1+s2).intern();
//返回的就是常量池中的"hdjava"的地址
34.作用域
-
java中主要变量是属性(成员)变量和局部变量,局部变量位在成员方法中定义的变量。
-
作用域分类
全局变量:即属性,作用整个类体
局部变量:除属性外的其他变量,作用域位定义在它的代码块中
-
全局变量可不赋值直接使用,局部变量赋值了才能用(全局变量有默认值,int类型默认值为0,double为0.0);
-
属性和局部变量可以重名,访问时遵循就近原则(就近指最靠近输出的变量)
全局变量与对象共存亡,局部变量只存活在一次方法调用中
-
全局变量不仅可以在本类中使用,也可以在其他类中通过对象调用进行使用
-
全局变量可以加修饰符,局部变量不可以
35.构造方法/构造器
构造器并不是为了创建对象,而是初始化对象,在创建对象时系统会自动匹配构造器完成对对象的初始化,如果没有初始化,那么属性的值就会是默认值,其次是在我们不需要默认构造函数时,由于某些变量需要初始化才能使用,使用需要通过显式创建构造函数来完成
(i) 语法:[修饰符]方法名(形参列表){方法体;}
(ii) 构造器修饰符可以默认(也可以是public protected private等)
(iii) 构造器无返回值,也不能写void
(iv) 构造器方法名与类名相同
(v) 参数列表和成员方法一样的规则
(vi) 构造器的调用系统完成,不能主动调用
(vii) 一个类可以定义多个构造器,即构造器重载
(viii) 如果没有在类中定义一个构造器,那么系统会自动给类生成一个默认无参构造器,如:Dog(){},所以当我们没有创建构造器时,我们在创建对象时是:Dog dog1 = new Dog();如果重新定义了一个构造器,那么原先的默认构造器就会被覆盖,如果要重新使用,那么需要通过显式定义:Dog(){}
(ix) Javap是JDK提供的一个命令行工具,能够对给定的class文件提供的字节码进行反编译,通过对照其与源代码和字节码,了解更多编译器内部的工作
常用:javap -c -v,-c为对代码进行反汇编,-v为输出附加信息,还有-p为显示所有类与成员
(x) 构造器复用:(图中第三个构造器的第一行运用到了第二个构造器的参数,从而做到了构造器复用,但是要注意,由于this在构造器中使用时必须放在第一行,所以图中剩余的两个变量只能在构造器的重新初始化,而不能再使用this进行初始化)
36.对象创建的流程分析
以person类为例
(1)加载Person类信息(Person.class),只会加载一次
(2)在堆中分配空间(地址)
(3)完成对象初始化:第一步:默认初始化age = 0 name = null
第二步:显式初始化age = 90 name = null
第三步: 构造器初始化 age = 20;name = 小晴
(4)在对象中的堆中的地址,返回给p(p为对象名,也可以理解为对象的引用)
37.this关键字
(1)主要用于在构造器中将参数初始化,初始化之后底下的方法均不需要再写形参,可以直接写括号,这样就避免了写一个方法就要初始化一遍参数的麻烦
(2)java虚拟机会给每一个对象分配this,代表当前对象
(3)在构造器中,为了使代码看起俩简洁方便,使用this,就无需再在形参中重新命名了,this.**指得就是当前这个对象的属性
-
this可以用来访问本类的属性,方法,构造器。
-
this用于区分当前类的属性和局部变量。
-
访问成员方法语法:this.方法名(参数列表);
-
访问构造器语法:this(参数列表);注意只在构造器中使用。(必须放在构造器的第一条语句)
-
this不能再类定义的外部使用,只能在类定义的方法中使用。
38.匿名对象
语法:new+对象名+();
这是对定义对象的简写形式;
正常对象创建与调用过程为:Car c = new Car(); c.run();
但是匿名对象创建与调用过程则为:new Car().run();
(i)匿名对象只可使用一次,使用后自动销毁,因此当所需要的对象只需要一次调用时,则可以通过匿名对象来简化代码。
(ii)匿名对象可以作为实参进行传递
39.包
本质:创建不同的文件夹保存java文件
-
作用:区分相同名字的类,管理类,控制访问范围
-
语法:pacage com.hspedu
-
引入包时,如果只引入一个包,就直接import即可,但是如果引入两个及以上的包时
-
举例如下:
两个包可以通过写包名来区分,第一个包可以直接new一个对象,第二个包则需要写出包名来区分。
- 命名规范:只能包含数字,字母,下划线,小圆点,但是不能数字开头,不能是关键字和保留字,一般采用的是小写字母加小圆点。
(1) Java中常用的包:java.lang.*;//lang包是基本包,默认引入,不需要再引入。
(2) Java.util.*;//util包,系统提供的工具包,工具类
(3) Java.net.*;//网络包,用于网络开发
(4) Java.awt.*//用于java界面开发,GUI
注意事项:pacage用于声明当前类所在的包,需要放在类的最上面,一个类最多一个pacage
40. 访问修饰符
只有默认和public才可修饰类,同一个包中的文件可以互相访问除private外的其他三种属性或方法,在不同包下只能访问public
(i) 公开级别:public修饰
(ii) 受保护级别:protected,对子类和同一个包中的类公开
(iii) 默认级别:无修饰符,向同一个包的类公开
(iv) 私有级别:用private修饰,只有类本身可以访问,不对外公开
(v) public>protected>默认>private
41.面向对象编程三大特征
A.封装
-
介绍:将抽象出的属性和方法封装在一起,数据保存在内部,程序其他部分只有通过被授权的操作(方法),才能对数据进行操作。
-
优点:隐藏实现细节,只能通过方法调用,也可以对数据进行验证,保证安全合理
-
a. 实现步骤:属性私有化(不能直接改属性);
提供一个公共set方法,用于对属性判断并赋值;
提供一个公共的get方法,用于获取属性的值
注意:按下alt+Fn+insert,找到setter and getter,选中要构造的变量进行自动构造
- 将构造器与set结合:因为使用构造器可能会绕开set内的判断,所以解决方法是将set写入构造器中
B.继承
-
介绍:多个类存在相同的属性变量和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要提高extends来声明父类即可。
-
优点:提高了代码复用性
-
基本语法:
class 子类 extends 父类{}
-
父类又叫超类,基类;子类又叫派生类。
-
子类就会自动拥有父类定义的属性和方法(私有属性和方法不能直接访问,要通过公共的方法去访问,可以在父类中写一个公共的方法A,在公共方法中调用私有的方法B,然后在需要使用到私有的信息时,只需要在子类中调用公共方法A即可)。
-
细节:
(1)父类的属性由父类的构造器初始化,子类的由子类的构造器初始化
(2)子类必须调用父类的构造器,完成父类的初始化
(3)创建子类对象时不管使用子类哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的每一个构造器中使用super去指定父类哪一个构造器(根据参数类型,数量自动判断)完成对父类的初始化工作,否则,编译不会通过。
(4)java中子类最多只能继承一个父类,即java为单继承机制。
-
继承的本质:
子类继承父类时,创建子类对象时,在内存中建立了查找关系) 逐级查找,如果在子类没有所需要的属性,就会跳到上一级查找父类,如果父类没有则会查找父类的父类
-
继承的内存布局
C.多态
(1)作用:为了提高代码的复用性,利于代码维护
(2)前提:两个对象(类)存在继承关系
(3)
(a)方法的多态
(重写,重载中多态的体现)
(b) 对象的多态:
(1) 一个对象的编译类型和运行类型可以不一致(如Animal animal = new Dog();)
(2) 编译类型在定义对象时就已经确定了,不能改变
(3) 运行类型可以改变(animal = new Cat()😉,计算机根据你所写下的运行类型来判断要选取哪一个子类运行。
(4) 编译类型在 = 号的左边,运行类型在 = 号的右边
© 向上转型
(父类的引用指向了子类的对象,即编译类型为父类,运行类型为子类)
(1) 本质:父类的引用指向了子类的对象
(2) 语法:
父类类型 –引用名 = new 子类类型();
如:Animal animal = new Dog();其中dog是animal的子类
(3) 特点:编译类型在左,运行类型在右,可以调用父类的所有成员(需遵守访问权限),不能调用子类的特有成员(因为在编译阶段编译的是父类,因此只能调用父类内的成员,而不能调用子类的特有成员),最终运行效果看子类的具体体现(意思就是说,运行的时候只看等号右边是什么,如上方的Animal animal = new Dog();假设调用animal.eat();则会先找到子类Dog,子类如果没有eat()再找父类,在没有再找父类的父类,如果有,就直接调用,没有就报错)
(d) 向下转型
(使用向下转型,即可强转父类引用,从而调用子类的特有成员)
(1)语法:子类类型 引用名 = (子类类型)父类引用
(示例:Cat cat = (Cat) animal,其中cat是animal的子类)
(2)只能强转父类的引用,不能强转父类的对象(对象在创建出来时就已经确定了)
(3)要求父类的引用必须指向的是当前目标类型的对象(意思就是说在在向下转型前要确保上文中Animal animal = new Dog();即animal指向了dog,这样才能在下文写Dog dog = (Dog)animal,进行向下转型)
(4)可以调用子类类型中的所有成员
(e)细节
(1)属性不能重写(如父类中定义一个属性count,然后又在子类中定义一个同样的count,在调用时先看编译类型是什么,如果编译类型内为父类,则直接调用父类的count,与子类内的count无关)
(f)比较操作符instanceOf
用于判断对象的运行类型是不是某类型或某类型的子类型,返回一个布尔值j
(f)例题:
其中:(访问属性(不带括号),看编译类型;方法调用(带括号),看运行类型,方法调用一般从子类开始,如果子类没有则找父类,以此类推)
(1)s.count,b.count,这是访问属性,看编译类型,只需要找到s和b的编译类型是什么,s的编译类型为sub,就去sub中调用count,b的为base,就去base中调用count。
(2)s.display()和b.display(),方法调用,看运行类型,要找s和b的运行类型
其中s.display()的运行类型自然是sub,输出20,因为上面有一句Sub s = new Sub();但是b的就比较特殊,因为中间有一句Base b = s;所以b的运行类型实际上和s一样指向sub,所以b.display()输出的也是20。
(3)this.count指的就是本类
(g)动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里有声明,就在哪里用(属性没有动态绑定机制!!!)
3.示例:
如图,由于调用了a.sum(),因此先查找运行类型B(子类B),B中有sum()方法,因此直接调用,如果没有则找父类A,然后再在sum()中遇到getI(),由于AB中都有getI,此时发挥动态绑定机制的作用,因为调用时是:A a = new B();因此运行类型是B,所以还是调用B中的getI,得到解20+20 = 40。
42.多态数组
-
介绍:数组的定义类型为父类类型,里面保存的实际元素为子类类型
-
使用过程:首先按照要求,先创建一个父类Person,构造基础信息以及getter和setter,再按照要求写下say方法,然后分别创建子类Student和Techer,在里面补充特有信息,写完构造和getter和setter,重写say方法,在say方法中添加子类的特有信息,然后再新建一个类,进行调用,将父类作为数组,父类的每一个元素都是子类,一个个传入对应的参数,然后循环输出。
-
注意:如果要调用子类的特有方法,可以使用向下转型。写成这样:
通过instanceOf进行判断运行类型是不是你要调用的子类,如果是,就通过向下转型进行强转,然后再调用特有的方法即可。
43.Super关键字
-
Super代表父类的引用,用于访问父类的属性,方法,构造器
-
作用:
(a) 能够访问父类的属性,但是不能访问private属性(super.属性名;)
(b) 访问父类的方法,但是不能访问父类的private方法(super.方法名(参数列表);)//访问时查找的顺序:先找子类有没有,子类没有找父类,父类没有找父类的父类,以此类推,直到找到可以访问的位置,返回一个值,如果没找到,或者是private这种不能访问的类型,就会报错。
(c) 访问父类的构造器(super(参数列表);),注意,访问构造器时只能放在构造器的第一句,且只能出现一句
3.优点:
(a) 调用父类构造器的好处:分工明确,父类初始化父类属性,子类初始化子类属性
(b) 当子类中有成员与父类属性重名时,为了访问父类成员,必须通过super,但是不重名时,super,this,直接访问效果相同
- Super和this的区别:
44.方法重写
重写是为了在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。(在子类中改造父类的方法并覆盖父类的方法)
(1) 含义:子类的一个方法与父类一个方法的名称,返回类型,参数一样,就叫子类方法覆盖父类方法
(2) 细节:子类的返回类型与父类的返回类型一样,或者子类的返回类型是父类返回类型的子类(如子类放回类型为String,父类为object,由于String是object的子类,同样构成重写)
(3) 子类方法不能缩小父类的访问权限,但是可以扩大父类的访问权限(public > protected > 默认 > private)
(4) 方法重写与方法重载的区别:
45.object类详解(超类)
object类方法:
(1)== 和equals的区别
(i) == :可以判断基本类型和引用类型
(ii) == :判断基本类型时判断的是值是否相等
(iii) == :判断引用类型时判断的是地址是否相等(是不是同一个对象)
(iv)== 是一个比较运算符,equals方法只能判断引用类型
注意:
-
基本数据类型的包装类,如string等都是自带重写的,因此系统自带的和自己写的重写过的方法在使用equals时只要判断内容即可,否则是判断对象是不是同一个对象。
-
同为基本数据类型时,用==比较,比的是数值大小,就如int类型的65和float类型的65.0f是相同的
(i)默认判断地址是否相等,子类中往往重写该方法,判断内容是否相等
(ii)小练习:
Integer i1 = new Integer(1000);
Integer i2 = new Integer(1000);
I1 == i2;//False
I1.equals(i2);//true
String s1 = new String(“haha”);
String s2 = new String(“haha”);
S1 == s2;//false
S1.equals(s2);//true
(2)hashCode方法
(1)提高具有哈希结构的容器的效率
(2)两个引用,如果指向同一个对象,则哈希值相同
(3)两个引用,如果指向不同对象,则哈希值不一样
(4)哈希值主要根据地址得来,但不能将其等同于地址,得到的值只是将真实地址经过转化得来的一串数字,并非真实地址
(3)toString()方法
介绍:要重写toString()方法时,只需要输入:Fn+alt+insert,然后找到toString()方法,然后选择要重写的即可,这里重写是为了输出我们需要的属性信息等,没有重写的话就会按照object内的toString方法的格式输出
(1)默认返回:全类名+@+哈希值的十六进制,子类往往会重写toString方法,用于放回对象的属性信息(转十六进制字符串过程:Integer.toHexString(hashCode());)
(2)如果没有在自己写的方法(子类)中重写toString的话,打印对象或拼接对象时,都会自动调用该对象的toString形式:(源码)
(3)直接输出一个对象时,toString方法被默认调用:
(4)finalize方法
(1)当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作
(2)回收条件:某个对象没有任何引用,jvm机认为其为一个垃圾对象,使用垃圾回收机销毁对象,销毁前会调用finalize方法,如果程序员有需要,就可以重写finalize,加入程序员想在销毁对象前干的事情,如数据库连接或者打开文件,没有写的话就会默认处理
注意:重写可以使用快捷键Fn+alt+insert,也可以直接输finalize,等系统自动弹出提示信息
(3)垃圾回收机的调用是由系统决定的,也通过System.gc()主动触发垃圾回收机制(有一定几率成功)
46.断点调试(debug)
(1)断点调试过程中是以对象的运行类型来执行,是运行状态
(2)快捷键:F7:跳入方法,F8:逐行执行代码,shift+F8:跳出方法,F9(resum):执行到下一个断点
(3)工作台按钮:
step over 执行下一行(如果当前行有方法调用会执行完方法再放回数据)
step into 执行下一行,会进入自定义方法,但不会进入官方类库的方法
force step into 在调试的时候能进入任何方法(可以强制进入官方类库查源码)
step out 进入一个方法时如果觉得没问题,就用step out跳出方法,放回到下一行,此时该方法已经执行完毕。
Drop frame 放回当前方法调用处重新执行,中间的数据重置
复习:
创建对象流程:(1)加载类信息(2)初始化:默认初始化,显示初始化,构造器初始化(3)返回对象地址
47.类变量(静态变量)
- static变量是静态共享,用static创建对象时该对象会被类的所有对象示例共享
(静态变量被对象共享,因此不影响对静态变量的使用)
-
概念:类变量也叫静态变量/静态属性,是该类所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样一个该类的对象去修改它时,修改的也是同一个变量。
-
语法:
访问修饰符 static 数据类型 变量名;(推荐使用)
也可以是:
static 访问 修饰符 数据类型 变量名
- 访问方式:
类名.类变量名;(推荐使用) 或者 对象名.类变量名;
- 什么时候使用类变量或静态变量
(i)需要某个类的所有对象共享一个变量时可以使用静态变量,该静态变量的值在所有该类对象内相等
(ii)类变量与实例变量(普通属性)的区别
类变量是所有对象共享的,实例对象是每个变量独享的
(iii)加上static可以称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
(iv)在满足访问修饰符的前提下访问类变量可以通过:类名.变量名;或者对象名.类变量名
(v)实例变量不能通过类名.变量名来访问
(vi)类变量在类加载时就已经初始化了,只要类加载了就可以使用类变量。
(vii)类变量的生命周期与类的生命周期相同
- 类方法使用场景:
(i)方法中不涉及任何和对象相关的成员,可以将该方法设计为静态方法,提高开发效率。
(ii)在不希望创建实例并且调用某个方法时,可以使用静态方法,将其当作工具来使用。
-
类方法和普通方法随着类的加载而加载,将结构信息存在方法区,但是类方法无this参数,普通方法中有this。
-
类方法可以通过类名或对象名调用,普通方法不能通过类名调用
-
非静态方法可以访问静态成员和非静态成员,静态方法只能访问静态成员
48.代码块
-
作用:当您的构造器中有多次重复的内容时,可以通过添加一个代码块,在里面加上重复的内容,然后每创建一个对象都会调用一次代码块的内容,再调用构造器,但是注意,重复的内容只会出现在构造器内容的上方
-
(1)相当于另一种形式的构造器,可以看作对构造器的补充,可以进行初始化操作
(2)如果多个构造器有重复的语句,可以抽到初始化块中,提高代码重用性
(3)语法:
【修饰符】{
代码;
}
(4)注意:代码块的顺序优先于构造器
(5)静态代码块
即static代码块,随着类的加载而执行,而且只会执行一次(类每加载一次static加载一次),普通代码块是每创建一个对象就执行
(6)类什么时候被加载:
(i) 创建对象实例时(new的时候)
(ii) 创建子类对象实例,父类也会被加载
(iii)使用类的静态成员时(静态属性,静态方法)
(7)创建一个对象时在一个类调用顺序:
(i)静态代码块和静态属性初始化调用的优先级一样,如果有多个,则按照他们定义的顺序调用
(ii)调用普通代码块和普通属性的初始化,两者优先级相同,按照定义顺序调用
(iii)调用构造方法,构造器
(8)
代码快执行顺序:(前提是一个子类继承父类,父类和子类中均包含静态代码快,普通代码快,构造器,静态方法等,并且main方法调用了子类的无参构造器)先加载
1.父类静态代码快和静态属性
2.子类的静态代码快和静态属性
3.父类的普通代码快和普通属性
4.父类的构造器
5.子类的普通代码快和普通属性
6.子类的构造器
注意:静态方法没调用的话就不管,有调用就按照顺序进行调用
静态代码快只能调用静态成员,普通代码快可以调用任意成员。
49.单例设计模式
采取一定的方法保证在整个软件系统中对某个类只能存在一个对象实例并且该类只存在一个取得其对象实例的方法
分为饿汉式和懒汉式
(1)饿汉式构造器私有化,在类的内部创建对象,提供一个公共的static方法,返回所创建的对象
(2)懒汉式构造器私有化,定义一个static静态属性对象,提供一个public的static方法,返回static对象。
(3)饿汉式存在资源浪费问题,懒汉式存在线程安全问题
50.final关键字
-
当不希望类被继承时,可以使用final修饰
-
当不希望父类的某个方法被子类覆盖/重写时,可以使用final关键字修饰:访问修饰符 final 返回类型 方法名;
-
当不希望类的某个属性的值被修改时,可以使用final(如:public final double A_B = 0.08;)
-
当不希望某个局部变量被修改时,可以使用final修饰该局部变量
-
细节:
(a)final修饰的属性又叫常量,一般用XX_XX_XX命名
(b)final属性在定义时就得赋值,因为之后不能修改数据了,可以添加数据在1.定义时 2.构造器中 3.在代码块中4.如果final修饰的属性为静态,那么只能在定义时和静态代码块中赋值。
(c)final不能继承,但是可以实例化对象
(d)如果类不是final类,但是含有final方法,则该方法不能被重写,但是可以被继承。
(e)如果一个类已经是final类,就没必要将方法修饰为final方法,因为从别处不可能从final类中调用方法了
(f)final不能修饰构造器
(g)final和static往往搭配使用,效率更高,不会导致类加载
如图,final和static结合使用后,只会输出num的值,而不会运行BBB内的输出语句,因为该输出语句在类中,而前方调用了final,不会导致类加载,因此不会输出”BBB 静态……”
(h)包装类如Integer,Double,Float,Boolean都是final,String也是final类。这些类都是不能被继承的
51.抽象类
在父类由某些方法需要声明但是不确定如何实现时可以将其声明为抽象方法,则该类为抽象类)(在不确定是否需要的方法前端加上abstract修饰,并用abstract修饰该父类即可,一般来说抽象类会被继承,而且由其子类来实现
- 对于父类的语法:
访问修饰符 abstract 类名{
}
对于方法体的语法:
访问修饰符 abstract 返回类型 方法名(参数列表);
(a)细节:抽象类不能实例化
(b)抽象类不一定要包含abstract方法,
(c)一旦类包含abstract方法,则这个类必须声明为abstract
(d)abstract只能修饰类和方法,不能修饰属性和其他的
-
抽象类可以有正常类的任意成员
-
抽象方法不能有主体,即不能在父类中实现(即结构如:abstract void aaa(),后面不能加大括号),可以在子类中实现。(所谓实现方法就是要有方法体,就是大括号,只要加大括号就叫实现了)
-
抽象方法不能使用private(私有的方法不能重写),final(final不能继承),static(static关键字与重写无关)修饰,因为这些关键字与重写相违背。
-
抽象类的模板设计模式
52.接口
给出一些未实现的方法,封装到一起,当某个类要使用的时候,根据具体情况把这些方法写出来)(接口中可以省略abstract关键字,同时接口中所有方法都是public方法
- 接口的语法:
interface 接口名 {
//属性
//方法(方法可以为抽象方法,(JDK8之后也可以使用默认实现方法,静态方法))
}
- 实现的语法:
class 类名 implements 接口{
自己属性;
自己方法;
必须实现所有的接口抽象方法;
}
-
注意:在JDK7之前的接口的所有方法都没有方法体(即都是抽象方法),JDK8后接口可以有静态方法,默认方法,可以有方法的具体实现
-
一个普通类实现接口,必须将接口的所有方法实现
-
一个抽象类实现接口,可以不用实现接口的所有方法
-
一个类可以同时实现多个接口
-
接口的属性只能是final的,并且接口内的属性固定有隐藏修饰符:public static final,是不可改变的。(如果看到接口中有属性,那么该属性为公共属性,但不可改变,且为静态,在将接口实现的方法中可以调用接口中的属性,且由于它是静态,因此可以进行类名调用,如接口A内有属性x,则可以写A.x)
-
接口不能继承其他类,但是能够继承多个别的接口
-
接口的修饰符只能是public或者默认
-
接口和继承类的区别:接口是单继承机制的一种补充,继承中:子类继承了父类时,就自动获得父类的功能,如果子类需要拓展功能,可以通过实现接口的方法拓展,得到其他新的功能。
*继承的价值在于:解决代码复用性和维护性(需要满足is-a)*
*接口的价值在于:设计好各种规范,方法,让其他类去实现这些方法。(只需要满足like-a),接口在一定程度上实现代码解耦(接口规范性+动态绑定机制)*
- 接口的多态特性
(a)接口引用可以指向实现了接口的类的对象
(b)接口类型数组
(c)接口的多态传递
假设有接口A,B,其中接口B继承接口A,而现在有一个类bala实现了接口A,那么该类也实现了接口B。
53. 类的五大成员
属性,方法,构造器,代码块,内部类
54.内部类(按定义位置划分)
一个类的内部嵌套了另一个类,被嵌套的类称为内部类
(1)最大特点:内部类可以直接访问私有属性
(2)基本语法
(3)内部类分类:
A.定义在外部类局部位置上(通常是在方法内)
一.局部内部类
有类名
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为其地位是一个局部变量,不能使用修饰符,但是可以使用final修饰(修饰后在该外部类内的其他内部类无法继承该内部类),因为局部变量也可以使用final
-
作用域:仅仅定义在方法和代码块中
-
局部内部类可以直接访问外部类的成员
-
外部类访问局部内部类的成员时需要创建对象,再访问,而且必须在作用域内
-
注意:局部内部类定义在方法中/代码块中;作用域在方法体/代码块中;本质仍然是一个类;
-
外部其他类不能访问局部内部类(因为局部内部类的地位是一个局部变量)
-
如果外部类和局部内部类重名时,遵循就近原则,如果想访问外部的成员,则可以使用(外部类名.this.成员)去访问
二.匿名内部类
没有类名,为了简化开发,为一次性调用
-
本质是类
-
是内部类
-
该类没有名字,其名字是系统自动分配的
-
匿名内部类是定义在外部类的局部位置,如方法中,并且无类名
-
new 类/接口(参数列表){
类体;
}; (注意:匿名内部类的大括号后方有一个分号;)
-
匿名内部类在运行时先创建一个对象实例,自动分配类名,为外部类类名+$1,并将地址返回给编译类型中的类名。
-
匿名内部类使用一次就不能再使用了
-
匿名内部类就是在创建对象之后在后面再假设一个大括号,如正常创建对象是:Father ff = new Father();匿名内部类则是Father ff = new Father(){ },再在大括号内加入类体即可,这是一次性的,其中,ff的编译类型是Father,而其运行类型则是匿名内部类(由系统自动分配的类名),不是Father。
-
匿名内部类由于是一次性调用,所有我们可以采用更加简便的方法进行调用:(如图所示,可以直接在大括号后方加上.hi();其中.hi();的小括号内可以传入相应的数据)
-
匿名内部类可以直接访问外部类成员(包括外部类私有的成员),外部其他类不能访问匿名内部类(因为匿名内部类是一个局部变量)
-
如果外部类和内部类重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问,并且其遵循动态绑定机制。
-
匿名内部类可以当作实参直接传递,如图,将匿名内部类作为一个形参传入
B.定义外部类的成员位置上:
一.成员内部类
没用static修饰
-
成员内部类是定义在外部类的成员位置,并且没有static修饰
-
成员内部类可以直接访问外部类的所有成员,包含私有的,它是可以添加任意访问修饰符(如public,protected,默认,private),因为其地位就是一个成员
-
作用域和外部类其他成员一样,是整个类体,在外部类的成员方法中创建内部类对象,再调用方法
-
成员内部类可以直接访问外部类成员,外部类需要创建对象才能访问内部类(如创建一个方法,在方法中将成员内部类实例化,调用时只需要调用该方法就行),外部其他类也可以访问成员内部类
方式1:如图 ,Outer01为外部类,Inner01为内部类,编译类型为Outer01.Inner01,相当于将new Inner01()当作outer01的成员
方法2:在外部类中编写一个方法返回成员内部类对象,然后再在外部其他类中调用该方法)
-
如果成员内部类的属性与外部类重名,会遵循就近原则
二.静态内部类
使用static修饰
-
说明:静态内部类是定义在外部类的成员位置,并且有static修饰,可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员,外部类需要创建对象(或者写一个方法返回对象实例)才能访问静态内部类(前提是要满足访问权限)
-
可以添加任意访问修饰符(public,protected,默认,private)因为它的地位是一个成员
-
作用域和其他成员一样,为整个外部类类体
-
如果外部类和静态内部类重名,静态内部类访问时遵循就近原则,如果要访问外部类成员,可以使用:外部类名.成员 来访问
C.总结
-
一共四种内部类:局部内部类,成员内部类,静态内部类,匿名内部类
-
重点:匿名内部类:
语法:
new 类/接口(参数列表){
//…
};
- 成员内部类,静态内部类是放在外部类的成员位置,本质是一个成员。
55.枚举
属于一种特殊的类,里面只包含一组有限的特定的对象
(i)步骤:
-
将构造器私有化,防止直接new
-
本类内部创建一组对象
-
对外暴露对象(通过为对象添加public final static修饰符)
-
可以提供get方法,但是不要提供set
(ii)使用enum实现枚举类
public class Enumeration01 {
public static void main(String[] args) {
Week[] values = Week.values();
for(Week week : values){
System.out.println(week);
}
}
}
enum Week{
MONDAY("星期一"),TUESDAY("星期二"),
WEDNESDAY("星期三"), THURSDAY("星期四"),
FRIDAY("星期五"),SATURDAY("星期六"),
SUNDAY("星期日");
private final String name;//创建变量name
Week(String name) {//构造器接收变量name
this.name = name;
}
@Override
public String toString() {
return name;
}
}
-
使用关键字enum替代class 如enum 类名{}
-
将类似public static final Season SPRING = new Season(“春天”,”温暖”)替换为:SPRING(“春天”,”温暖”);//语法:常量名(实参列表)
-
如果有多个对象,则在对象之间用逗号连接,如:SPRING(“春天”,”温暖”),SUMMER(“夏天”,”温暖”),…………//注意:对象行一定要写在类的第一行
-
使用enum关键字开发一个枚举类时,默认继承enum类
-
如果使用无参构造器创建对象,则可以省略小括号
-
enum类包含的方法
(1)name(),返回类名
(2)ordinal(),返回该枚举对象的编号/次序,从0开始(如上方描述季节的四个对象,中间用逗号连接,编号分别为0,1,2,3)
(3)values(),这是一个隐藏方法,可以使用javap反编译找到,它返回一个数组,其包含所有定义的枚举对象
(4)valueof(),将字符串转化为枚举对象,要求字符串必须为已有的常量名,否则报异常
(5)compareTo(),比较两个枚举常量,比较的是编号之差,如果为0,则表示所比较的两个枚举变量是相等的
(6)toString()方法,enum类将toString方法重写了,返回的是当前的对象名,字了欸可以重写该方法返回属性信息。
-
使用enum关键字后就不能再继承其他类了,因为enum会隐式继承Enum,而java是单继承机制。
-
枚举类和普通类一样,可以实现接口,如:
enum 类名 implement 接口1,接口2{}
1. java Enumeration接口
1. 包含方法:
序号 | 方法描述 |
---|---|
1 | boolean hasMoreElements( ) 测试此枚举是否包含更多的元素。 |
2 | Object nextElement( ) 如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。 |
public static void main(String args[]) {
Enumeration<String> days;
Vector<String> dayNames = new Vector<String>();
dayNames.add("Sunday");
dayNames.add("Monday");
dayNames.add("Tuesday");
dayNames.add("Wednesday");
dayNames.add("Thursday");
dayNames.add("Friday");
dayNames.add("Saturday");
days = dayNames.elements();
while (days.hasMoreElements()){
System.out.println(days.nextElement());
}
}
56.注解
1.使用时要在前面加上@符号,同时将Annotation当做一个修饰符使用。
(1)@Override:限定某个方法,是重写父类方法,该注解只能用于方法(不能修饰其他类,包,属性等),如果在方法上方添加了@Override这一句,编译器就会对该方法进行校验,检查它是不是真的对父类的方法进行了重写,如果没有会报错,如果没写编译器就无所谓,也不会报错
@Target是修饰注解的注解,称为元注解(图中第一行为@Override的注解源码)
(2)@Deperecated(修饰某个元素表示该元素已经过时了,即不再推荐使用,当时仍然可以使用):用于表示某个程序元素(类,方法等,可以修饰:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE)
@Deperecated可以用到新旧版本升级时使用)
被@Deperecated修饰的元素会自动带上中划线。
(3)@SuppressWarnings:抑制编译器警告(当代码中出现警告信息时,如果该信息不影响程序运行,那么我们可以在上方加上@SuppressWarnings({“XXXXXX“}),就可以消除警告信息,其中XXXXXX如果为all,就是消除所有警报)
抑制器大括号中传入的可以为:
注意,该注解类有一个数组String[] value();所有可以在中括号内存入String数组,如{“unchecked”,unused}
57.JDK的元注解(元Annotation)
以Override为例:
-
Retention 指定注解的使用范围,包括三种:SOURCE(只作用在源码层面),CLASS(作用在class层面),RUNTIME(运行时会读取该注解)
-
Target //指定注解可以在哪里使用
-
Documented // 指定该注解是否会在javadoc中体现,用于指定被该元Annotation修饰的Annotation类将该+被javadoc工具提取成文档,即在生成文档时,可以看到该注解。
说明:定义为Documented的注解必须设置Retention值为RUNTIME.
- Inherited // 子类也会继承父类注解,被它修饰的Annotation将具有继承性,如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解
说明:实际应用中,使用较少
58.异常(exception)
1.介绍:
在认为代码可能出现问题的时候,使用try-catch异常处理机制来解决,保证程序的健壮性。(会抛出异常信息,但是会继续执行下方的代码)(开发过程中语法错误和逻辑错误不是异常)
2.try-catch快捷键(Ctrl+alt+t)
将你认为可能出错的代码块选中,使用快捷键:Ctrl+alt+t,然后选中try-catch,可以自动生成。
如果要得到异常信息,可以在catch中输出,就可以得到异常的原因:
System.out.println(e.getMessage());
3.异常分类:
A . Error错误:java虚拟机无法解决的严重问题,如jvm系统内部错误,资源耗尽等情况,还有StackOverflowError【栈溢出】和OOM(out of memory)等,Error时严重错误,会导致程序崩溃
B . Exception:因为编程错误或者偶然的外在因素导致的一般性问题,Exception分为两大类,包括运行时异常,和编译时异常
-
运行时异常:编译器不要求强制处置的异常,一般指编程时的逻辑错误,可以吧做处理,因为比较普遍,如果全处理可能对程序可读性和运行效率产生影响
-
编译时异常:是编译器要求必须处理的异常
4.异常体系图(部分常见异常)
5.常见运行时异常
(1)空指针异常:程序在需要对象的地方使用null。
(2)数学运算异常:如除于0等情况
(3)数组下标越界:数组下标索引为负或大于数组大小
(4)类型转化异常:试图将对象强制转化为不是实例的子类时,抛出异常(即如父类中的两个子类互转化,而该两子类是无关的)
(5)数字格式不正确:程序试图将字符串转化为一种数值类型,但是字符串不能转化为适当格式时抛出异常,如:将一串汉字转化为数字,就会抛出该异常(使用Integer.parseInt等)
6.常见编译异常
7.异常处理方式
代码运行的每一级如果遇到异常,都可以使用t-c-f解决,如果不想解决,可以提供throw抛出,可以交给调用的上一级解决,两种方法二选一
(1)try-catch-finally(这是程序员在代码捕获时发生的异常,自行处理)
处理过程:异常发生时,将异常封装为Exception对象e,传递给catch,最后不管try是否有异常发生,finally代码块始终会执行,finally通常用于释放资源的代码等
(2)throws(将异常抛出交给调用者处理,最顶级的处理者为jvm机)
就是在不想处理异常的情况下,提供throw将异常抛给调用的上一级解决,调用的最高级为jvm机,其处理方式就是输出异常信息,退出程序
8.try-catch基本语法
细节:
-
如果异常发生了,异常后方代码编号执行,直接进入catch块
-
异常没有发生,顺序执行try内代码,不进入catch代码块
-
对于一定要执行的代码,写入finally中
-
如果一段代码可能有多个异常,那么可以单独使用catch进行捕获:
(注意:要求子类异常写在前面,父类异常(Exception)写在后面)
- try-finally组合
相当于没有捕获异常,程序会直接奔溃掉,应用场景:不管是否发生异常,都必须执行某个业务逻辑
9.throws处理机制
方法声明中使用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生过的异常类型,也可以是它的父类
细节:
-
对于编译异常,程序必须处理,处理方法分为try-catch和throws
-
运行异常,程序中如果没有处理,默认为throws处理方式
-
子类重写的方法,抛出异常要么与父类抛出的异常一致,要么抛出的异常是父类异常的子类
-
如果方法B抛出一个编译异常,方法A调用方法B而没有显式的抛出异常,也无try-catch,那么就会报错,因为方法A没有显式处理B中抛出的异常
-
java中对于运行异常不需要显式处理,有默认处理机制。
10.自定义异常
- 自定义一个异常类型继承已有的异常类型,在内补充
59.常用类
包含:包装类,String,StringBuffer,StringBuilder,Math,Date,Calendar,LocalDate,System,Arrays,BigInteger,BigDecimal
60.包装类(wrapper)
针对八种基本数据类型相应的引用类型
1.基本数据类型对应的包装类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gzsL6TLF-1659110652837)(C:\Users\lon\AppData\Roaming\Typora\typora-user-images\image-20220316141020346.png)]
2.关系图
3.包装类和基本数据类型的转化
-
以int和integer的相互转化为例
-
装拆箱
Jdk5以前的手动装箱和拆箱方式,装箱:基本类型-》包装类型,反之,拆箱
jdk5及以后为自动装拆箱方式;自动装箱底层调用的是valueOf方法,如Integer.valueOf()
-
Double d = 100d;//自动装箱,等同于 Double d = Double.valueOf(100d); Float f = 1.5f;//等同于Float f = Float.valueOf(1.5f);
-
元运算符;运算优先级
61.包装类方法
- Integer转String
Integer i = 100;
方法一:String str1 = i+””;
方法二:String str2 = i.toString();
方法三:String str3 = String.valueOf(i);
- String 转Integer:
Integer j = new Integer(s1);//Integer内构造器很多,可以接收String类型。
Integer j2 = Integer.valueOf(s2);//(返回的是int类型,但是进过自动装箱,又由int转化为Integer)
- Integer类和Character类常用方法
62.Integer 创建机制
Integer的数据如果访问在-128~127,那么直接返回数据,如果不在此范围,那么会直接new一个Integer
Integer i1 = new Integer(128);
Integer i2 = new Integer(128);
System.out.println(i1 == i2); //输出false
System.out.println(i1.equals(i2)); //输出true
//i1和i2指向的下一个位置不相同,但是指向的最终位置相同
63.String类
-
String对象用于保存字符串,也就是一组字符序列
-
字符串常量对象是双引号括起的字符序列
-
字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
-
String类常用构造器
-
String类实现了三个接口:
Serializable接口(String对象实现串行化,即可以在网络传输)
Comparable接口,说明String对象可以比较大小
CharSequence接口,字符序列
-
String是final类,不能被其他的类继承
-
String类有属性private final char value[];用于存放字符串内容
-
value是一个final类型,赋值后不可修改(字符可以修改,地址不可修改,即value不能指向新的对象)
-
创建String对象两种方式:
String a = “adq”;
路线:先从常量池查看是否有“adq”对象,如果有则直接指向,如果没有则重写创建,然后指向,s最终指向的是常量池的空间地址
或
String a = new String(“afd”);
路线:先在堆中创建空间,里面维护了value属性,指向常量池afd空间,如果常量池没有afd,重新创建,如果有,直接通过value指向,最终指向的是堆中的空间地址
- intern()方法:
当调用intern方法时,如果常量池中语句包含一个等于此String对象的字符串(通过equals(Object)确定),则返回常量池中的字符串,否则将String对象添加到常量池中,返回此String对象的引用
总之:intern()方法最终返回的是常量池的地址(对象)
- 练习题笔记:
String a = “hd”;
String b = new String(“hd”);
可以得到:(a->常量池中的“hd”,b->堆中的value,再由value->常量池中的“hd”)
(i)a.equals(b)为true,比较的是最终指向的地址(对象/内容),因为最终都指向“hd“这个对象
(ii)a == b为False,==号比较的是他们指向的对象而不是最终指向的对象,所以a指向常量池中的“hd”,b指向堆中的value,不同
(iii)a == b.intern(); true,指向的都是“hd”对象
(iv)b == b.intern(); false,b是指向堆的,b.intern();是指向常量池的,所以不相等
(v)String是final类型,所以字符串是不可变的,内容不可变
如:String s1 = “aaa”; s1 = “bbb”;那么实际上创建了两个对象,s1指向了新的对象,而不是将aaa的内容修改为bbb。
(vi)String s1 = “aaa”+”bbb”;则只创建了一个对象,即编译器通过优化直接将“aaabbb“给了s1.
(vii)String a = “aaa”; String b = “bbb”;String c = =a+b;
- 运行流程:
先创建一个StringBuilder sb = StringBuilder();
执行 sb.append(“aaa”);
sb.append(“bbb”);
String c = sb.toString();
最终c指向了堆中的对象(String)value[] -> 池中“aaabbb”
-
总结:底层是StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b);sb在堆中,并且append是在原来字符串基础上追加的
-
重要规则:String c1 = “ab”+”cd”;//常量相加,看的是池。
String c1 = a+b;//变量相加,看的是堆。
64.String类的常用方法
-
常用方法
-
常用方法解析
-
注意:
toUpperCase()为将字符串中的小写英文字母转化为大写,toLowerCase()是将大写英文字母转为小写。
concat()可以用于拼接字符串
split()方法在对字符串进行分割时,如果有特殊字符,需要用到转义符,如利用\分割字符串,那么需要在split的()内填入\\才可完成分割,否则会报错
-
format方法
(i)占位符:
%s 字符串
%c 字符
%d 整形
%.2f浮点型
(ii)
即在输出时现在前方写文案,在需要填入数据的地方使用占位符,再在文案后面跟上对应的数据,要求数据类型要匹配。
65.StringBuffer 类
-
介绍:java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行删减,很多方法与String相同,但是StringBuffer为可变长度,StringBuffer为一个容器
-
StringBuffer的直接父类是AbstractStringBuilder,
StringBuffer实现了Serrializable,即它可以串行化,
在它的父类中,AbstractStringBuilder有属性char[] value,不是final,可以增加或删除数据,不用像String一样更换地址
该value数组存放字符串内容,引用存放在堆中
StringBuffer是一个final类,不能被继承
-
String和StringBuffer的区别
-
StringBuffer构造器
(i)第一个构造器创建了一个大小为16的char数组,用于存放字符内容
(ii)第二个不常用
(iii)第三个通过构造器指定char数组大小
(iv)第四个是给一个String创建StringBuffer,char数组大小就是字符串的长度加上16;
- String和StirngBuffer的转化
(i)String转化StringBuffer
方法1:使用构造器,直接将String放入StringBuffer:
StringBuffer sb = new StringBuffer(string1);//注意,返回的是StringBuffer对象,对String无影响
方法2:使用append()方法
StringBuffer sb = new StringBuffer();
sb = sb.append(String1)
(ii)StringBuffer转String
方法1:
String s = StringBuffer1.toString();
方法2:
String s1 = new String(StringBuffer1);
- StringBuffer类常用方法
(i)增append//在原先的StringBuffer后添加,可以添加任意类型的数据,最后返回的还是StringBuffer类型(注意,如果传入一个空的字符串,那么底层调用的是AbstractStringBuilder的appendNull方法,将null转化为字符‘n’’u’’l’’l’,此时该StringBuilder的长度为4)
注意(以下情况会抛出空指针异常):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H39ytGxh-1659110652841)(C:\Users\lon\AppData\Roaming\Typora\typora-user-images\image-20220316145600373.png)]
(ii)删delete(start,end)。//前闭后开
(iii)改repalce(start,end,String)//将start-end间的内容替换,不含end
(iv)查indexOf//查找子串在字符串第一次出现的索引,如果找不到返回-1
(v)插入: insert(索引,要用于替换的数据)
(vi)获取长度length
(vii)查找字符最后一次出现的索引:lastIndexOf();
66.StringBuilder类
-
介绍:这是一个可变的字符序列,此类提供一个与StringBuffer兼容的API,但不保证同步(即StringBuilder不是线程安全),此类用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候,建议优先使用该类,它比StringBuffer更快.
-
StringBuilder上主要操作是append和insert方法,可以重载这些方法,接收任意类型数据。
-
StringBuilder继承AbstractStringBuilder,有属性char[] value,不是final,可以增加或删除数据,同时实现了Serializable,可以串行化(可以网络传输,也可保存到文件)
-
比较
注意:
(i) 由于使用String时修改会残留无用的字符串对象在内存中,所以应该有大量修改,不要使用String
(ii) 运行速度上,StringBuilder>StringBuffer>String
67.Math类方法
-
abs绝对值
-
pow 求幂
-
ceil 向上取整//返回大于等于该参数的最小整数(转为double类型)
-
floor 向下取整//返回小于等于该参数的最小整数
-
round 四舍五入//Math.round(该参数+0.5)(转为long类型)
-
sqrt 求开方(如果复数开方,就会返回NaN)
-
random 求随机数
-
max 求两个数的最大值
-
min 求两个数的最小值
68.Arrays类
该类包含了一系列静态方法
-
toString方法,返回数组的字符串形式
-
sort 排序(自然排序和定制排序,自然排序为从小到大,定制排序
-
binarySearch通过二分搜索法进行查找,前提是需要先排序
在括号内填入两个数据,第一个是要查找的以及排序过的数组,第二个是要查找的数据,没有的话返回-1.
-
copyOf(要复制的数组,要复制元素的个数(注意,从第一个元素开始拷贝))
-
fill数组元素的填充fill(数组,要填充的元素),将数组中所以元素都替换掉
-
equls比较两个数组元素内容是否完全一致
-
asList将一组值转化为list
69.System类
-
exit退出当前程序:exit(0)表示正常退出
-
arrayCopy复制数组元素,比较适合底层调用,一般用Arrays.copyOf完成复制数组
意思是从src数组第0位开始拷贝三个数据到dest数组内从第0位开始的。
-
currenTimeMillens:返回当前时间距离1970-1-1的毫秒数
-
gc():运行垃圾回收机制
70.BigInteger和BigDecimal类
- BigInteger适合保存较大的整数
BigInteger b = new BigInteger(“XXXXXX”);
如果要进行加减乘除运算,可以先创建一个BigInteger类
- BigDecimal适合保存精度更高的浮点数
BigDecimal b = new BigDecimal(“XXXXXX.XXXXXX”);
运算时也需要创建对象来完成(除法时可能抛出异常,因为可能是除不尽的,为无限循环小数,遇到此情况需要在调用divide时指定精度:b.divide(b, BigDecimal.ROUND_CEILING))
- 加减乘除:
add加
subtract减
multiply乘
divide除
71. 日期类
A.第一类
(1)Date默认输出格式为国外的一般需要进行格式转化,位置在java.util内,
Date d = new Date();//可以获取当前系统时间,但是是国外的方式
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy年MM月dd日 hh:mm:ss E”);//可以转化日期的格式为国内的形式
(2)也可以通过指定毫秒数获取时间,在Date的()内传入毫秒数即可
B.第二代
- Calendar类(日历)
是一个抽象类,构造器为private
可以通过getInstance()来获取实例//没有经过转化的
- 应用示例:(月中加1是因为返回月时是按照0开始编号)
这里的hour默认为12进制,如果需要24进制,则可以将
Calendar.HOUR改为Calendar.HOUR_OF_DAY即可
- 第三代日期特性:
-
方法:
-
常见方法:
-
DateTimeFormatter格式日期类
格式:
-
Instant时间戳
-
第三代其他方法
72.集合
- 集合相对于数组而言,可以动态任意保存多个对象,并且提供了一些列分别的操作对象的方法:add,remove,set,get
- 体系图:
(1) 单列集合:(List和Set集合内存放单个的对象,一次只能存一个对象)
单列集合(例):
(2) 双列集合:(接口实现的是K-V)
双列集合(例):
73.collection接口和常用方法
Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。
Collection 接口存储一组不唯一,无序的对象。
(1)子类中可以存放多个元素,每个元素都可以是object
(2)一些collection的实现类可以存放重复的元素
(3)collection的实现类一些是有序的(List),有些事无序的(Set)
(4)collection接口没有直接实现子类,而是提供子接口实现
(5)collection内存在的方法:
XXX.add(要传入的元素);
XXX.remove(所要删除数据的索引);
XXX.contains(所要查找的元素);
XXX.size();//得到元素的个数
XXX.isEmpty();//判断是否为空
XXX.size();// 返回集合的长度
74.Iterator接口(迭代器)
Iterator接口为collection接口遍历对象方式之一
(1)Iterator对象称为迭代器,主要用于遍历collection内的元素
(2)所有实现了collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即放回一个迭代器
(3)Iterator迭代器仅用于遍历集合,本身不存放对象
(4)执行原理:
步骤:
-
获取迭代器:
-
输入快捷键itit,得到collection的while循环
注意:在调用iterator.next()方法之前必须要先调用iterator.hasNext()进行检测,如果不调用,则下一条记录无效,直接调用it.next()会抛出异常
while 循环iterator快捷键:
itit,显示所有快捷键:Ctrl+j
- 应用实例:
75.增强for循环(collection接口遍历对象的方式)
-
介绍:增强for循环可以替代iterator迭代器,是其的简化版,底层依旧是迭代器,只能用于遍历集合或数组
-
语法:
for(元素类型 元素名:集合名或数组名){
访问元素;
}
- 案例:增强for和while效果相同
76.List接口和常用方法
List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。
List 接口存储一组不唯一,有序(插入顺序)的对象。
(1)XXX.add(int index,Object ele){ }//在index位置插入ele元素
(2)XXX.addAll(int index,Collection else);//从index位置将els内的所有元素添加进来
(3)Object get(int index);//获取指定index位置的元素
(4)int indexOf(Object ob);//获取ob在集合中首次出现的位置
(5)int lastIndexOf(Object obj);//返回obj在当前集合中末次出现的位置
(6)Object remove(int index);//移除指定index位置的元素,并返回此元素
(7)List subList(int fromIndex,int toIndex);//返回从fromIndex到toIndex位置的子集合(前闭后开,包括fromIndex但不包括toIndex)
77.List的三种遍历方式
ArrayList,LinkedList,Vextor三种都是List接口的实现子类,所以同样的代码使用这三者之一都可以跑除同样的结果,使用的位置在创建List对象时:
(1)方式一:使用iterator迭代器
(2)方式二:使用增强for循环
(3)使用普通for循环
78.集合中元素排序
可以提供编写一个方法,如下,使用冒泡排序:
79.ArrayList注意事项
(1)可以存入空值null
(2)ArrayList是由数组来实现数据储存的
(3)与Vector基本相同,但是ArrayList是线程不安全的(执行效率高),多线程情况下不建议使用ArrayList。(ArrayList没有synchronized修饰,使用线程互斥,多线程建议使用Vector)
80.List三大子类对象:
A. ArrayList底层结构和源码分析
(1)elementData数组
(i)无参数构造器未传入数据时的长度为0,传入一个长度时将空间扩张为10,需要再次扩张时按照乘1.5倍来扩张
(ii)有参构造器可以自定义空间大小(在创建List对象时指定构造器大小),如果需要扩张时,也是按照自定义的空间大小乘1.5倍来扩张
B.Vector底层结构和源码分析
(1)介绍:
(2)Vector和ArrayList的比较
注意:Vector的扩容机制中如果是无参构造器,默认空间为10,如果空间不足需要扩容,会按照两倍的扩容(指定大小的构造器也是),而ArrayList为1.5倍。
C. LinkedList底层结构和源码分析
(1)LinkList实现了双向链表和双端队列特点
(2)可以添加任意元素(元素可以重复),包括null
(3)线程没有实现同步,不安全
(4)构建双向链表
(5)CRUD == 增删改查
(6)ArrayList和LinkedList之间的比较
改查操作多,选择ArrayList(由于程序中查询比较多,所以大多数情况下都使用ArrayList)
增删操作多,选择LinkedList
81.set接口两大子类对象:
Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。
- set介绍:
set是无序的添加和取出的顺序不一致,没有索引
不允许重复的元素存在,最多只包含一个null(
注意:
(1). 常规的add添加时会判断元素内容是否相同来确定是否添加,但是如果是创建了一个对象,然后通过new的方式(除了new String)传入相同的元素,那么就会成功加入
(2).如果是new String(“XXXXX”),那么如果添加了第一个元素后,就不能再添加相同的元素了)
由于set是collection的子接口,所以常用方法和collection相同
- set中存在的方法
set取出的顺序不一定就是存入的顺序,但是取出后的排列是固定的
如add()等boolean方法,如果操作成功会返回一个true,失败返回一个false
- set的遍历
方法一:迭代器 iterator
方法二:增强for
注意:set对象不能通过索引来获取,因为set没有索引,所以不能使用普通for循环
A. HsahSet
-
HashSet实现了set接口
-
HashSet实则为HashMap
-
可以存放null,但是只能存放一个null,因为set内的元素不能重复,如果由多个重复的元素,那么会自动只保留一个元素,并且在添加失败的那行代码处返回一个false
-
HashSet不能保证元素是有序的,去接与hash后,再确定索引的结果
-
hashSet底层为hashMap,hashMap的底层则是数组+链表+红黑树
第一次扩容时大小是16,达到12时为了缓冲,所以在12就开始扩容,其中加载因子为0.75,按照两倍扩容
B.LinkedHashSet
1. 介绍
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层为LinkedHashMap(LinkedHashMap是hashMap的子类),底层维护了一个数组(数组是HashMap N o d e [ ] 存放的元素 / 数据是 L i n k e d H a a s h M a p Node[]存放的元素/数据是LinkedHaashMap Node[]存放的元素/数据是LinkedHaashMapEntry类型)+双向链表(第一次添加时就扩容到16个,存放的节点类型是LinkedHaashMap$Entry)
- LinkedHashSet根据元素hashCode值来确定元素储存位置,同时使用链表维护元素次序,使元素以插入顺序保存
- LinkedHashSet不允许存入重复的元素
- 在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet中有head和tail)
- 每一个节点都有before和after属性,这样可以形成双向链表
- 每添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后再进行判断,如果该位置以及存在该元素,就不添加,否则直接加入双向链表(原则和hashset一样)
- 如此,我们遍历LinkedHashSet能保证插入顺序和遍历顺序一致
添加:SortedSet
82.正则表达式(regular exception)
-
常见匹配符号
-
元字符
-
量化
一. 作用:对字符串进行模式匹配的技术
提取文章中所有的英文单词/数字/数字和英文单词等
二. 代码实现
1.举例
a.基础语法\ \d 表示一个任意的数字,如果要得到一个四位数字,就用
\ \d\ \d\ \d\ \d来表示
b.分组:加一个小括号就是一组
2.步骤:
a.创建一个String类型接收文本AAA
b.按照要求使用String创建需要搜索的文本格式BBB
c.使用Pattern创建模式对象:
如Pattern p = Pattern.compile(BBB);
d.创建一个匹配器
Matcher m = p.m(AAA);
e.通过while循环判断输出:
例:while(m.find()){ };
3.元字符
1)转义符号\ (注意:java的正则表达式中两个[\表示其他语言的一个](file://表示其他语言的一个/))
(2)字符匹配符
(3)语法
83.Map接口和常用方法
Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
-
介绍
map添加数据使用put
提取数据使用get,get内填写要提取的数据的key值
(1)map中存放具有映射关系的双列元素key和value
(2) key不能重复(和hashset相同),value可以重复,key重复时会采用替换机制,后面的value替代前面的value,最后只保留一个key
(3)有相同的value时,使用后面的value替代前面的
-
存放位置
-
特点
- map将key-value存放在HashMap N o d e 中, H a s h M a p Node中,HashMap Node中,HashMapNode = new Node(hash,key,value,null)
- k-v为方便遍历创建了EntrySet集合,存放元素类型Entry,一个Entry对象就有一个k,v,EntrySet<Entry<K,V>>,即,transient Set<Map.Entry<K,V>> entrySet;
-
从HashMap$Node中取出k-v
-
先做一个向下转型
Map.Entry entry = (Map.Entry)obj; System.out.print(entry.getKey()+"-"+entry.getValue());
-
-
1. map内方法
Map map = new hashMMap();
map.put(k,v);//添加数据
map.size();//获取元素个数
map.isEmpty();//判断个数是否为0
map.putAll(Map<? extends k, ?extends V> );
map.clear();//清除数据
map.remove(k);//根据key删除映射关系
2. map接口特点
一:取出所有的key,通过key取出对应的value
-
增强for
Set keyset = map.keyset(); for(Object key : ketset){ System.out.println(map.get(key)); }
-
迭代器
Set keyset = map.keyset(); Iterator iterator = keySet.iterator(); while(iterator.hasNext()){ Object key = iterator.next(); System.out.println(map.get(key)); }
二.取出所有的values
- 增强for循环
Collection values = map.values();
for(Object value : values){
System.out.println(value);
}
-
迭代器
Collection values = map.values(); Iterator iterator = value.iterator(); while(iterator.hasNext()){ Object value = iterator.next(); System.out.println(map.get(value)); }
三. EntrySet取出所有的key和value
- 增强for
//取出key和value
Set entrySet = map.entrySet();//EntrySet<Entry<K,V>>
for(Object entry : entrySet){
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+" "+m.getValue());
}
-
迭代器
//取出key和value Iterator iterator = entrySet.iterator(); while(iterator.hasNext()){ Object entry = iterator.next(); }
四. entry 补充
84.HashMap(Map接口实现类)
树化条件
如果table为null或者大小没到64,暂时不树化,而是进行扩容
Properties
-
properties继承Hashtable
-
可以通过key和value存放数据,key和value不能为null,如果key相同也会替换
-
增加数据:
Properties p = new Properies(); p.put("jk",100); System.out.println(p.get("jk"));
-
增加:put()
删除:remove()
修改:使用替换原理,存入相同的key会替换
查询:get()
-
84 . 如何选择集合实现类
1. 储存类型为一组对象:collection(单列)
1. 允许重复:List
-
增删多:LinkedList底层维护一个双向链表
-
改查多:ArrayList底层维护Object类型的可变数组
2. 不允许重复:Set
-
无序:HashSet底层是HashMap,维护一个哈希表(数组+链表+红黑树)
-
排序:TreeSet
-
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
2 .储存类型为一组键值对:Map(双列)
-
键无序:HashMap(底层为HashMap,维护一个哈希表(数组+链表+红黑树))
-
键排序:treeMap
-
键插入和取出顺序一致:LinkedHashSet
-
读取文件 Properties
85.Collection工具类
Collection是一个操作List,Set,Map等集合的工具类,其内包含一系列静态方法对集合进行排序,查询和修改
1. 排序操作(均为static方法)
-
reverse(List) :反转List内的元素
-
shuffle(List) : 对List集合元素进行随机排序
-
sort(List) : 根据元素的自然顺序对指定的List集合元素进行升序排序
-
sort(List,Comparator) : 根据指定的Comparator产生的顺序对List集合元素进行排序
//假设list内存入String字符串,按照长度大小排序 Collections.sort(list,new Comparator(){ @Override public int compare(Object o1,Object o2){ return ((String)o1).length() - ((String)o2).length(); });
-
swap(List,int,int) : 将指定List集合中的 i 处元素和 j 处元素进行交换
-
Object max(Collection) : 根据元素自然顺序,返回给定集合中的最大元素
-
Object max(Collection,Comparator) : 根据Comparator指定的顺序返回给定集合中的最大元素
-
Object min(Collection)
-
Object min(Collection,Comparator)
-
int frequency(Collection,Object) : 返回指定集合中指定元素的出现次数
-
void copy(List dest,List src) : 将src内的内容复制到dest中
-
boolean replaceAll(List list.Object oldVal,Object newVal) : 使用新值替换List对象的所有旧值
2. 更新map内数据
//以下是将所有员工的工资提高100
Map m = new HashMap();
m.put("肖梓贤",3000);
m.put("钢板日穿",10);
Set keySet = m.keySet();
for(Object key : keySet){
m.put(key,(Integer)m.get(key)+100);
}
System.out.println(m);
3. 迭代器遍历取出所有key和value
Set entrySet = m.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
System.out.println(entry.getKey()+" "+entry.getValue());
}
4. 遍历只取出key或value
Collection values = m.values();
for (Object o : values){
System.out.println(o);
}
5. HashSet和TreeSet去重
1. HashSet去重机制
hashCode()+equals(),底层先通过传入对象,进行运算得到员工哈希值,提高哈希值得到对应的索引,如果该索引位置没有数据,则直接存入数据,有数据就通过equals进行比较,相同就不加入,不同就加入
2. TreeSet去重机制
如果传入一个Compartor匿名对象,就是要实现的compare去重,如果返回0,就是相同元素,不添加,如果不是传入Comparator匿名对象,则以添加到对象实现的Compareable接口的compareTo去重
86 . 泛型(generic)
1. 传统方法的弊端
-
不能对加入到集合ArrayList内的数据类型进行约束(不安全)
-
遍历时需要进行类型转化,如果数据量大就会导致效率低下
-
遍历时可以直接取出指定的类的类型,而不用写object类型
如下图中增强for循环是以Dog类型取出的,而不是Object类型
2 .具体作用
-
作用 : 编译期间指定对象的参数的数据类型
//如下方的Person方法直接指定了要传入String类型数据 Person<String> person = new Person<String>("哈哈哈哈哈"){ }
3. 语法
接口: interface 接口< T >{}
类 : class 类<K,V>{}
T,K,V不代表值,而是代表类型,使用任意字母都可以
4 .注意事项
-
指定泛型的类型时不能指定基本数据类型,如int,double等,需要引用数据类型,如Integer等
-
在给泛型指定具体类型后,可以传入该类型或其子类类型
-
使用形式:
-
传统使用形式:
List<Integer> list = new ArrayList<Integer>();
-
推荐使用形式:
List<Integer> list = new ArrayList<>();
即只写前面的<>,不写后面的<>,编译器会自动进行类型推断,省事
-
下方这种情况默认传入类型为object
List list = new ArrayList();
-
5. 自定义泛型
-
类泛型语法:
class 类名<T,R…>{
}
class AA<String,integer>{ }
接口泛型语法:
Iterface 接口名<T,R…>{
}
Iterface II<U,R>{ } class AA implements II<Integer,Float>{ }
-
普通成员可以使用泛型(属性,方法)
-
使用泛型的数组不能初始化
-
静态方法中不能使用类的泛型(因为静态和类相关,类加载时对象还没创建,所以如果对静态方法和静态属性使用了泛型,JVM无法完成初始化)
-
泛型类的类型是在没有创建对象时创建的(因为创建对象时需要指定确定类型)
-
如果在创建对象时没有是定类型,默认为object
-
接口中静态成员耶不能使用泛型
-
泛型接口的类型在继承接口或者实现接口时确定
6. 泛型方法
-
语法:
修饰符 <T,R>返回类型 方法名(参数列表){}
public <U,M> void eat(U u,M m){ }
-
泛型方法可以定义在普通类中,也可以定义在泛型类中
-
泛型方法被调用时类型会确定
-
public void eat(E e){},修饰符后没有<T,R…> eat方法不是泛型方法,而是使用了泛型
7. 泛型的继承和通配符
-
泛型没有继承性
- <?>表示支持任意泛型类型
public static void hh(List<?> c){}
- <? extend AA> 表示上限为AA,可以接收AA以及AA的子类
- <? super AA> 表示下限为AA,可以接收AA即AA的父类
87.JUNIT测试框架
在要单独调用的方法上方加上一个**@Test**即可
88 . JAVA绘图技术
1. 创建面板
先定义一个MyPanel继承JPanel类,即创建面板
class MyPanel extends JPanel{
@Override
public void paint(Graphics g){
suoper.paint(g);
}
}
2. Graphics绘图方法
在这里插入图片描述
public void paint(Graphics g) {//绘图方法
super.paint(g);//调用父类的方法完成初始化.
System.out.println("paint 方法被调用了~");
//画出一个圆形.
//g.drawOval(10, 10, 100, 100);
//画直线 drawLine(int x1,int y1,int x2,int y2)
g.drawLine(10, 10, 100, 100);
//画矩形边框 drawRect(int x, int y, int width, int height)
g.drawRect(10, 10, 100, 100);
//画椭圆边框 drawOval(int x, int y, int width, int height)
//填充矩形 fillRect(int x, int y, int width, int height)
//设置画笔的颜色
g.setColor(Color.blue);
g.fillRect(10, 10, 100, 100);
//填充椭圆 fillOval(int x, int y, int width, int height)
g.setColor(Color.red);
g.fillOval(10, 10, 100, 100);
//画图片 drawImage(Image img, int x, int y, ..)
//1. 获取图片资源, /bg.png 表示在该项目的根目录去获取 bg.png 图片资源
Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bg.png"));
g.drawImage(image, 10, 10, 175, 221, this);
//画字符串 drawString(String str, int x, int y)//写字
//给画笔设置颜色和字体
g.setColor(Color.red);
g.setFont(new Font("隶书", Font.BOLD, 50));
//这里设置的 100, 100, 是 "北京你好"左下角
g.drawString("北京你好", 100, 100);
//设置画笔的字体 setFont(Font font)
//设置画笔的颜色 setColor(Color c)
}
89.java事件处理机制
//KeyListener类需要重写三个方法来实现功能
public class MyPanel extends HPanel implements KeyListener{
@Override
public void KeyTyped(KeyEvent e){
}
@Override
public void KeyPressed(KeyEvent e){
//假设:判断按下了W键
if(e.getKeyCode() == keyEvent.VK_W){
}
}
@Override
public void KeyReleased(KeyEvent e){
}
}
90 . 多线程基础
- 进程:指的是运行中的程序,是程序的一次性执行过程
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
- 单线程:同一个时刻只允许执行一个线程
- 多线程:同一个时刻,可以执行多个线程
- 多个子线程运行代码案例
package Thread;
/**
* @author #FFFF00_IODINE
*/
@SuppressWarnings("all")
public class Thread02 {
public static void main(String[] args) {
T1 t1 = new T1();//实例化第一个子线程类
Thread thread1 = new Thread(t1);//将该子线程类传入Thread中,通过start方法进行调用
thread1.start();
}
}
@SuppressWarnings("all")
class T1 implements Runnable {//第一个子线程类
int count = 0;
@Override
public void run() {//主要要跑的业务代码写在run内
do {
System.out.println("HalloWorld! " + (++count)+" "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (count != 10);
}
}
1. 并发
同一个时刻,多个任务交替进行,造成一种“貌似同时的错觉,单核CPU实现的多任务就是并发
2. 并行
同一个时刻,多个任务同时执行,多核CPU可以实现并行
3. 线程基本使用
注意: 两种创建线程的方法的区别:*
首先,继承Thread和实现Runnable接口两种方法本质上没有区别,毕竟Thread类本省身也实现类Runnable接口,但是:实现Runnable接口更加适合多个线程共享一个资源的情况,并且避免了单继承的限制(可以两个线程同时运行一个任务),因此一般建议使用实现Runnable接口的方法
-
方法一:继承Thread类,重写run方法
class Cat extends Thread{ public void run(){ while(true){ //让程序休眠一秒 System.out.println("这是一只猫"); try{ Thread.sleep(1000);//休眠1000毫秒 }catch(InterruptedException e){ e.printStackTrace(); } //这边tey-catch能够让程序保持感知响应,防止程序中断响应 } } }
-
方法二:实现Runnable接口,重写run方法
public class Thread01{ public static void main(String []args) throws InterruptedException{ Cat cat = new Cat(); cat.start();//启动线程 //注意,启动线程时只能用start,不能用run(),因为run只是一个普通方法,没有真正启动一个程序,只由start可以启动一个新的线程 //主线程启动应该子线程Thread-0,主线程不会阻塞,会继续执行 //这时主线程和子线程是交替执行 System.out.println("主程序继续执行"+Thread.currentThread().getName());//名字 for(int i = 0;i<10;i++){ System.out.println("主线程 i = "+i); Thread.sleep(1000); } } }
4. JConsole(监控线程执行情况)
运行代码后再终端中输入JConsole,打开线程监控
5. 代理模式
创建一个线程代理类,模拟了一个极简的Thread,重写Thread的run,start方法
//线程代理类,模拟了一个极简的Thread
class ThreadProxy implements Runnable {//线程代理类
private Runnable target = null;
@Override
public void run() {
if (target != null) {
target.run();
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//真正实现多线程的方法,才真正调用run方法
}
public void start0() {
run();
}
}
6. 同时启用两个线程案例演示
package Thread;
/**
* @author #FFFF00_IODINE
*/
@SuppressWarnings("all")
public class Thread02 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
@SuppressWarnings("all")
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
do {
System.out.println("HalloWorld! " + (++count)+" "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (count != 10);
}
}
@SuppressWarnings("all")
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
do {
System.out.println("Hi! " + (++count)+" "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (count != 5);
}
}
7. 线程终止
-
通常情况下线程完成任务后会自动退出
-
如果要主动退出,可以通过控制run方法来停止线程,即通过通知方式。
所谓通知,实际上就是在run方法内写一个退出程序的开关,然后再写一个方法能够改变中国开关的布尔值,最后再在主方法内调用该方法,传入布尔值来控制
8. 线程常用方法
1. setName()
设置线程名称,使之与参数name相同
2. getName()
返回该线程名称
3. start()
使线程开始执行,Java虚拟机底层调用该线程的start0方法,会创建新的线程调用run方法
4. run()
调用线程对象run方法
5. setPriority()
更改线程的优先级
MIN_PRIORITY 表示最低优先级
MAX_PRIORITY 表示最高优先级
6. getPriority()
获取线程的优先级
7. sleep()
线程的静态方法,使线程在指定的毫秒数内休眠
8. interrupt()
中断线程,但是并没有真正的结束线程,所以一般用于中断正在休眠的线程
//在主线程中中断子线程的休眠
T t = new T();
t.setName("HHH");
t.setPriority(Thread.MIN_PRIORITY);//设置优先级
t.start();//启动子线程
//主线程打印5 hi ,然后我就中断一次子线程的休眠,只执行一次,不会影响线程的下一次执行
for(int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());
t.interrupt();//当执行到这里,就会中断 t线程的休眠.
9.yield()
线程的礼让,让出cpu,让其他线程执行,由于礼让的时间不确定,所以不一定可以礼让成功
T t = new T();
t2.start();
for(int i = 1; i <= 20; i++) {
Thread.sleep(1000);
if(i == 5) {
System.out.println("主线程让子线程先运行");
Thread.yield();//礼让,不一定成功..
System.out.println("子线程运行完了主线程 接着运行");
}
}
}
10.join()
线程的插队,一旦插队成功,则肯定先执行完插入线程的所有任务
//join的使用,是一定会成功的
T t = new T();
t.start();
for(int i = 1;i<=20;i++){
Thread.sleep(1000);
System.out.println("主线程让子线程先运行");
t.join();
System.out.println("子线程运行完了主线程接着运行");
}
11 . 用户线程和守护线程
-
用户线程 : 也叫工作线程,当线程的任务执行完成或通知方式结束
-
守护线程 setDaemon(): 一般是为工作线程服务,当所有的用户线程结束,守护线程自动结束
//希望main线程结束之后子线程自动结束 //可以将子线程设置为守护线程 t3.setDaemon(true); t3.start(); //注意setDaemon必须写在start前面
-
常见的守护线程 : 垃圾回收机制
12 . 线程七大状态
说是七个状态是因为可以将Runnable状态分为Ready状态和Running状态
- getState()可以获取当前状态
- 如Thread.State.TERMINATED等返回一个布尔值
91 . 线程同步机制
-
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,保证数据的完整性
-
线程同步,即当有一个线程在对内存进行操作,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
-
同步的具体方法
//一 : 同步代码块 synchronized(对象){//得到对象的锁,才能操作同步代码 //需要被同步代码 } //例 : public void sell(){ //给本代码块加锁 synchronized(this){ } } //二 : 同步方法将synchronized放在方法声明中 public synchronized void m(String name){ //需要被同步的代码 }
92 . 互斥锁
- 互斥锁用于保证共享数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁”的标记,这些标记保证在某一个任意时刻只能有一个线程访问
- 关键字synchroized用于与对象的互斥锁联系,保证该对象任一时刻只能有一个线程访问
- 同步局限性:程序效率降低
- 非静态同步方法的锁可以是this,也可以是其他对象(必须是同一个对象)
- 静态的同步方法的锁为当前类本身
//一 : 非静态同步
//非静态同步方法,代码块加锁
public void sell(){
//给本代码块加锁
synchronized(this){
}
}
//二 : 静态同步
//静态同步,锁为单签本身
public synchronized static void sell(){
}
//静态同步,也可以使用代码块加锁,但是要使用到当前类的类名,不能写this
public static void sell(){
synchronized(类名.class){
}
}
92.上锁步骤
- 分析上锁代码
- 选择使用同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可(不能一个对象一把锁,否则就没意义了)
93.线程的死锁
多个线程都占用了对方的锁资源但由于条件未被满足不释放锁,导致死锁
//模拟线程死锁
class DeadLock extends Thread{
}
94. 什么时候释放锁
- 线程的同步方法或同步代码块执行结束
- 当前线程在同步代码块或同步方法中遇到break或return
- 遇到未处理的Error或Exception,导致异常结束
- 同步代码块或同步方法中执行了线程对象的wait()方法,会暂停当前线程,并释放锁
注意 :
- 线程执行同步代码块或同步方法时,如果程序调用Thread.sleep(),Thread.yield()方法来暂停当前程序的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(尽量避免使用suspend()和resume()方法,二者为过时方法)
95 . IO流
1. 文件流
文件再程序中是以流的形式操作的
- 流 : 数据再数据源(文件)和程序(内存)之间经历的路径
- 输入流 :文件到程序的路径
- 输出流 : 程序到文件的路径
2. 常用文件操作
(File 实现的两个接口表明它可以串行化和进行比较)
1. 根据路径创建一个File对象(需要调用creatNewFile()方法)
创建成功后就会在对应的路径生成一个新的文件
new File(String pathName)
String filePath = "e:\\newS1.txt";
File file = new File(filePath);
try{
file.creatNewFile();
}catch(IOException e){
e.printStackTrance();
}
2. 根据父目录文件+子路径构建
new File(File parent,String child)
File parentFile = new File("e:\\");
String fileName = "newS2.txt";
File file = new File(parentFile,fileName);
try{
file.creatNewFile();
}catch(IOException e){
e.printStackTrance();
}
3. 根据父目录+子路径构建
new File(String parent,String child)
String parentPath = "e:\\";
String fileName = "newS3.txt";
File file = new File(parentPath,fileName);
try{
file.creatNewFile();
}catch(IOException e){
e.printStackTrance();
}
4. 文件常用方法
1. getName()
2. getAbsolutePath()
用于得到文件绝对路径
File file = new File("e:\\newS1.txt");
System.out.println(file. getAbsolutePath());
//会得到 :e:\newS1.txt
3. getParent()
得到文件父级目录
4. lenngth()
得到文件大小(字节)
5. exists()
判断一个文件是否存在,返回布尔值
6 . isFile()
判断是不是一个文件,返回布尔值
7. isDirectory()
判断是不是一个目录,返回布尔值
5. 目录的删除和文件删除
1. mkdir()创建一级目录
String directoryPath = "D:\\demo";
File file = new File(directoryPath);
file.mkdir();//创建一个一级目录,返回一个布尔值,可以用于判断你要创建的目录是否已经存在,判断方法如下
if(file.mkdir){
System.out.println(directoryPath+"创建成功");
}else{
System.out.println(directoryPath+"创建失败");
}
2. mkdirs()创建多级目录
创建多级目录只能使用mkdirs进行创建
String directoryPath = "D:\\demo\\a\\b\\c";
File file = new File(directoryPath);
file.mkdirs();//创建一个多级目录,返回一个布尔值,可以用于判断你要创建的目录是否已经存在,判断方法如下
if(file.mkdirs){
System.out.println(directoryPath+"创建成功");
}else{
System.out.println(directoryPath+"创建失败");
}
3. delete()删除空目录或文件
3. IO流原理
- IO即指Input和Output
- 类和接口是存在Java.IO包下
1. 流的分类
注意 :字节流和字符流的区别在于字节流一次只能读入一个字节,如果文本中含有汉字,那么会出现乱码,字符流读取的是文本,因此不会
1. 字节流
字节流处理单元为一个字节(byte),操作字节和字节数组,存储的是二进制文件,比较适合处理音频文件、图片、歌曲。
2 .字符流
处理的单元为2个字节的Unicode字符(1Unicode = 2字节 = 16位),分别操作字符、字符数组或字符串,比较适合处理含有中文的文本
- 按操作数据单位不同分为:字节流(8 bit)二进制文件,字符流(按字符对应字节)文本文件
- 按数据流的流向不同分为:输入流和输出流
- 按流的角色不同分为:节点流,处理流/包装流
4. 常用的类
1. InputStream字节输入流
InputStream抽象类是所有类字节输入流的超类
常用子类
1. FileInputStream : 文件输入流
注意:在使用完流之后应当使用close()关闭文件流释放资源
-
read()方法可以从输入流中读取一个字节的数据,如果没有数据可用此方法会被阻止,读取完毕返回-1
根据读取完返回-1的特性我们可以将其作为循环读取的条件:
String filePath = "e:\\hello.txt"; int readData = 0; FileInputStream fileInputStream = null;//扩大作用域 try{ fileInputStream = new FileInputStream(filePath); while((readData = fileInputStream.read()) != -1){//返回-1表示读取完毕 System.out.print((char)readData); } }catch (IOException e){ e.printStackTrace(); }finally{ try{ fileInputStream.close();//关闭文件流释放资源,需要再次抛出异常 }catch(IOException e){ e.printStackTrace(); } }
-
由于read方法直接读取效率低下,所以对read方法进行改进,使用byte数组进行传入数据
public int read(byte[] b) throw IOException{ }
完整的演示如下:
String filePath = "e:\\hello.txt"; byte []buf = new byte[8]; int readLength = 0; FileInputStream fileInputStream = null;//扩大作用域 try{ fileInputStream = new FileInputStream(filePath); while((readLength = fileInputStream.read(buf)) != -1){//返回-1表示读取完毕,如果文本中剩余的字节数不足数组长度,则会返回实际读取的字节数 System.out.print(new String(buf,0,readLength)); } }catch (IOException e){ e.printStackTrace(); }finally{ try{ fileInputStream.close();//关闭文件流释放资源,需要再次抛出异常 }catch(IOException e){ e.printStackTrace(); } }
2. BufferedInputStream() : 缓冲字节输入流,其直接父类为FilterInputStream而非InputStream
3. ObjectInputStream : 对象字节输入流
2. OutputStream字节输出流
1. FileOutputStream
父类是OutputStream
public void writeFile(){
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try{
fileOutputStream = new FileOutputStream(filePath);
//写入一个字节
fileOutputStream.write('a');
//写入字符串,String中的getByte()方法可以把字符串转为字节数组
String str = "hello,world";
fileOutputStream.write(str.getBytes());
//写入字符串,控指定长度的字符串转化为字符数组
fileOutputStream.write(str.getBytes(),0,str.length());
}catch (IOException e){
e.printStackTrace();
}finally{
try{
}catch (IOException e){
e.printStackTrace();
}
}
注意:通常情况下使用fileOutputStream = new FileOutputStream(filePath);创建方式当写入内容时会将文本内的内容进行覆盖
如果要改为追加到现有文本后面的创建方式,需要:
fileOutputStream = new FileOutputStream(filePath,true);
即在参数处添加一个true
5. 文件拷贝
原理 : 即将一个文件的数据通过输入流传入java程序中,再由Java程序通过输出流将数据存入另一个文件中,完成文件拷贝,每读取部分的数据就传输一次,而不是全读取完才传输
代码演示 :
String srcFilePath = "d:\\h.txt";
String destFilePath = "d:\\steam\\dhasdk.txt";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = fileInputStream.read(buf)) != -1){
fileOutputStream.write(buf,0,readLen);
}
System.out.println("拷贝成功");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
6.FileReader字符输入流
由于其是按照字符来读取数据,所以在读取到汉字时就不会出现乱码
常用方法
1. new FileReader(File/String)
2. read()
每次读取单个字符并返回,读完文件返回-1
3. read(char[])
批量读取多个字符到数组,返回读取到的字符数,读取完文件返回-1
相关API:
- new String(char[]):将char[]转化为String
- new String(char[],off,len):将char[]的指定部分转化为String
//使用一次读取一个字符的方法
String filePath = "d:\\java代码.txt";
FileReader fileReader = null;
int data = 0;
try {
fileReader = new FileReader(filePath);
while((data = fileReader.read()) != -1){
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileReader != null){
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//使用一次读入一个字符数组
String filePath = "d:\\java代码.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
try {
fileReader = new FileReader(filePath);
while((readLen = fileReader.read(buf)) != -1){
System.out.print(new String(buf,0,readLen));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileReader != null){
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7. FileWriter字符输出流
注意:FileWriter使用完后必须要(close)或刷新(flush),否则写入不到指定的文件,只会将数据保存在内存中
常用方法:
1. new FileWriter(File/String)
覆盖模式,相当于流的指针在首端
2. new FileWriter(File/String,true)
追加模式,流的指针在尾端
3. writr(int)
写入单个字符
4. write(char[])
写入指定数组
5. write(char[],off,len)
写入指定数组的指定部分
6. write(String)
写入整个字符串
7. write(String,off,len)
写入指定字符串的指定部分
相关API:
String类:toCharArray:将String转化为char[]
代码示例:
String filePath = "d:\\java代码.txt";
FileWriter fileWriter = null;
char[] c = {'a','b','c'};
try {
fileWriter = new FileWriter(filePath);
//覆盖文件中的内容并且存入内容
fileWriter.write('H');
fileWriter.write(c);
fileWriter.write("黄典大帅批".toCharArray(),0,3);
fileWriter.write("帅批");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束");
8. 节点流和处理流
1. 节点流和处理流的介绍
-
数据源:即存放数据的地方
-
节点流:从一个特定的数据源读写数据(FileReader,FireWriter),只能从一个数据源读取,灵活性差
-
处理流(包装流):连接在已经存在的流(节点流或处理流)上,为程序提高强大的读写功能(能够对节点流进行包装增强灵活性),BufferReader类中,有属性Reader可以封装一个节点流(BufferReader,BufferWriter)
2. 节点流和处理流的区别
- 节点流是底层流/低级流,可以直接和数据源连接
- 处理流(包装流)可以对节点流进行包装,既可以消除不同节点流的差异,也可以提高更加方便的方法来完成输入输出
- 处理流使用了修饰器设计模式,不会直接与数据源相连
- 处理流的功能:
- 性能的提高:以增加缓冲的方式提高输入输出的效率
- 操作的便捷:处理流可能提高一系列便捷的方法来一次输入输出大量的数据,使用起来更加灵活方便
3. BufferedReader
-
使用BufferedReader封装FileReader来读取文件时,读取完文件后返回的是null,可以用于判断
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); }
-
BufferedReader运行结束后只需要关闭BufferedReader即可,不用再去关闭节点流,节点流会由底层自动关闭
3. BufferWriter
String filePath = "d:\\a.java";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hhh");
bufferedWriter.close();
4. Buffered拷贝1
String srcFilePath = "d:\\a.java";
String destFilePath = "d:\\a1.java";
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
String line;
try {
bufferedReader = new BufferedReader(new FileReader(srcFilePath));
bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
while ((line = bufferedReader.readLine()) != null) {
//每读取一行就写入
bufferedWriter.write(line);
bufferedWriter.newLine();//插入一个换行
}
System.out.println("拷贝成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
5. 处理流-BufferedInputStream和BufferedOutputStream
按照字节来处理,可以处理二进制文件和文本文件
1.BufferedInputStream
BufferedInputStream是字节流,在创建时会创建一个内部缓冲区数组
2. BufferedOutputStream
BufferedOutputStream是字节流,实现缓冲区的输出流,可以将多个字节写入底层输出流中,而不必对每个字节写入调用底层的系统
6. Buffered拷贝2
String srcFilePath = "d:\\瓦达.png";
String destFilePath = "d:\\玛雅.png";
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFilePath));
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
byte bt[] = new byte[1024];
int readLen = 0;
while ((readLen = bufferedInputStream.read(bt)) != -1) {
bufferedOutputStream.write(bt, 0, readLen);
}
System.out.println("拷贝成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7. ObjectInputStream和ObjectOutputStream(对象流)
能够对基本数据类型进行序列化和反序列化操作
1. 序列化和反序列化操作(序列化即为串行化)
-
序列化是在保存数据时保存数据的值和数据类型
-
反序列化是在恢复数据时恢复数据的值和数据类型
-
为了让某个类支持序列化操作,需要实现下列两个接口:
Serializable //标记接口,无方法
Externalizable //该接口有方法需要实现
2. ObjectOutputStream(序列化)
注意 :
在序列化中的方法:
write()方法可以存入一个int类型,
writeBoolean();可以存入一个布尔类型
writeUTF()可以存入一个String类型
writeObject()可以存入一个对象,而且该对象需要实现Serializable接口
package objectIoputstream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author #FFFF00_IODINE
*/
public class ObjectOutputSteam_ {
public static void main(String[] args) throws Exception {
String filePath = "d:\\a.txt";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeInt(100);
objectOutputStream.writeBoolean(true);
objectOutputStream.writeChar('l');
objectOutputStream.writeUTF("Hello");
objectOutputStream.writeObject(new Dog("大狗", 100));
objectOutputStream.close();
System.out.println("数据保存成功");
}
}
class Dog implements Serializable {
private final String name;
private final int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3. ObjectInputStream(反序列化)
package objectIoputstream;
import java.io.*;
public class ObjectInputStream_ {
public static void main(String[] args) throws Exception {
String filePath = "d:\\a.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog
//调用Dog的方法, 需要向下转型
Dog dog2 = (Dog) dog;
System.out.println(dog2.getName());
//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();
}
}
8 . 节点流和处理流注意事项和细节
-
读写顺序要一致
-
要求实现序列化或反序列化对象,需要实现Serializable
-
序列化的类中建议添加serialVersionUID,提高版本的兼容性
//serialVersionUID 是序列化的版本号,可以提高兼容性 private static final long serialVersionUID = 1L;
-
序列化对象时默认序列化所有对象,除了static或transient修饰的成员
-
序列化对象时,要求属性的类型也需要实现序列化接口
-
序列化具备可继承性,也就是如果某类已经实现类序列化,则其所有子类也已经默认实现序列化
9. 二进制文件
二进制文件包括声音,视频,doc,pdf等,不能使用字符流操作二进制文件,否则可能造成文件损坏,对于二进制文件只能使用字节操作流进行操作
10 . 标准输入输出流
- System.in 标准输入流(InputStream)键盘
- System.out 标准输出流(PrintStream)显示器
11 . 转换流InputStreamReader和OutputStreamWriter(将字节流转为字符流)
1. InputStreamReader
是Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
package IOputStreamRW;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
/**
* @author #FFFF00_IODINE
*/
public class InputStreamReader_ {
public static void main(String[] args) throws Exception {
String filePath = "d:\\a.txt";
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
System.out.println("读取到的内容为 : " + bufferedReader.readLine());
bufferedReader.close();
}
}
当文本中有多行要输出时可以如下(添加一个while循环):
String filePath = "d:\\a.txt";
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while((line = bufferedReader.readLine()) != null){
System.out.println("读取到的内容为 : " + line);
}
bufferedReader.close();
2. OutputStreamWriter(其构造器内的CharSet可以指定字符编码)
是Writer的子类,可以将OutputStream(字节流)包装成Writer(字符流)
可以调用OutputStreamWriter构造器内的CharSet来指定字符编码
12.打印流 PrintStream和PrintWriter
注意 :打印流只有输出流没有输入流
1. PrintStream
代码示例:
PrintStream printStream = System.out;
printStream.println("哇哈哈哈哈哈打不过我吧!");
//print底层调用的是write方法,所以我们可以直接调用write方法
printStream.write("呀呼黄典真帅".getBytes());
printStream.close();
//可以修改打印流输出的位置/设备,通过setOut()方法修改路径
System.setOut(new PrintStream("d:\\b.txt"));
System.out.println("亚述飒飒可范文芳输出");
2. PrintWriter
//将数据直接在控制台输出
PrintWriter printWriter = new PrintWriter(System.out);
printWriter.write("哇靠牛逼");
printWriter.close();
//指定路径创建一个文件存入数据
PrintWriter printWriter = new PrintWriter(new FileWriter("d:\\d.txt"));
printWriter.write("哇靠牛逼");
printWriter.close();
13.Properties类(配置文件)
程序在操作数据库时需要用户名和密码等信息,那么就需要读取Properties配置文件内的信息
-
传统方法
//演示传统方法读取Properties内的数据 BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\mysql.properties")); String line = ""; while ((line = bufferedReader.readLine()) != null) { String[] split = line.split("="); System.out.println(split[0] + "值是" + split[1]); } //传统方法存在一个弊端,就是如果要指定得到其中的一个数据,那么需要再加上一个if判断,很不方便
-
便捷的实现方法:使用Properties
1. Properties的介绍
-
专门用于读写配置文件的集合类,配置文件的格式为:
键=值
-
注意:键值不需要有空格,值不需要使用引号包起来,默认为String类型
2. Properties的方法
- load:加载配置文件的键值对到Properties对象
- list:将数据显示到指定设备
- getProperties(key):根据键获取值
- setProperties(key,value):设置键值对到Properties对象
- store:将Properties中的键值对储存到配置文件中,再idea中,保存信息到配置文件中,如果含有中文,会储存为unicode码
3. Properties读取指定元素
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
//全部输出
properties.list(System.out);
//根据键获取值
String user = properties.getProperty("users");
String pwd = properties.getProperty("pwd");
System.out.println(user);
System.out.println(pwd);
4. Properties创建和修改配置文件内的元素
- 创建
Properties properties = new Properties();
properties.setProperty("charset","utf-8");
properties.setProperty("users","Tom");
properties.setProperty("pwd","abc111");
properties.store(new FileOutputStream("src\\mysql2.properties"),null);
//null处是注释,可以写对该配置文件的注释,内容会出现在配置文件最上方
System.out.println("保存成功");
-
修改
就是在FileOutputStream内的地址,如果已经存在一个文件,那么就是对这个文件进行修改
5. 从Properties提取基本数据类型时
-
String:
String name = (String) properties.get(“name”);
-
int
int age = Integer.parseInt((String)properties.get(“age”));
14 . 判断文件是否存在于指定路径
方法:exists()方法,返回一个布尔值
15.创建单级目录和多级目录
- mkdir()创建单级目录
- mkdirs()创建多级目录
96.网络编程
1. 网络通信
- 概念:两台设备进行数据传输
- 网络通信:数据传输
- java.net包下提供一系列的类或接口,用于完成网络通信
2. 网络分类
3. ip地址
- 概念:用于唯一标识网络中的计算机
- 查看ip地址:ipconfig
- ip表示形式:点分十进制 xx.xx.xx.xx
- 每一个十进制数的范围:0~255
- ip地址的组成:网络地址+主机地址
- ilPv6是互联网工程任务组设计的下一代ip协议,号称为世界每一粒啥子编上一个地址,解决了ilPv4资源有限的难题
- ipv4是四个字节32位表示,每个字节都是0~255
- ipv4分类
-
特殊地址:
127.0.0.1表示本机地址
4. 域名
-
为了方便记忆,解决记ip的困难
-
概念:将ip地址映射为域名。这里怎么映射上,HTTP协议
5. 端口号
- 概念:用于标识计算机上某个特定的网络程序
- 表示形式:以整数形式,范围:0~65535
- 0~1024已经被占用了
浏览器搜寻网络服务时需要ip+端口
6. 网络通信协议
1. TCP协议(传输控制协议)
知识点:当客户端连接到服务端之后,实际上客户端也是通过应该端口和服务端进行通讯的,这个端口是TCP/IP来分配的,是随机的而不是固定的
shutdownOutput()的作用
在TCP编程中如果socket在发送完消息后没有加上socket.shutdownOutput(),那么就会导致服务器和客户端将一直处于僵持状态,服务器端一直在等待客户端发来的消息,但是一直不知道客户端什么时候会结束发送消息,而shutdownOutput()就代表了客户端结束输入的标志,这样服务端就可以正常运转。
2. UDP协议(用户数据协议)
1. 基本介绍
2. 基本流程
-
核心两个类/对象 DatagramSocket与DatagramPacket
-
建立发送端和接收端(UDP无明确的服务端和客户端,演变为了数据发送端和接收端)
-
建立数据包
-
调用DatagramSocket进行发送接收(将数据分装到DatagramSocket对象(装包),接收方法时需要对DatagramSocket对象进行拆包)
注意 :DatagramSocket可以指定在哪个端口接收数据
注意 :UDP一个数据包最大只能64k
-
关闭DatagramSocket
7. InetAddress类
1. 获取本机 InetAddress 对象 getLocalHost(主机名称和ip地址)
2. 根据指定主机名/域名获取 ip 地址对象 getByName
3. 获取InetAddress对象的主机名 getHostName
4 .获取InetAddress对象的地址 getHostAddress
public static void main(String[] args) throws Exception {
//输出主机名称和ip地址
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("localHost为 " + localHost);
//根据指定的主机名称获得InetAddress对象
InetAddress host1 = InetAddress.getByName("典狱司");
System.out.println("host1为 " + host1);
//根据域名返回 InetAddress对象,比如 www.baidu.com对应
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2为 " + host2);
//通过 InetAddress 对象,获取对应的地址
String hostAddress = host2.getHostAddress();
System.out.println("host2的ip为 " + hostAddress);
//通过 InetAddress 对象获取对应的主机名称或者域名
String hostName = host2.getHostName();
System.out.println("host2 的主机名称 " + hostName);
}
8. Socket套接字
可以通过socket调用方法
-
主机或客户端发起连接时:
getOutputStream(),getInputStream()
-
主机或服务端接收请求时:
getOutputStream(),getInputStream()
1. 使用socket传输信息
//服务器端
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端,socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取, 使用字符流, 老师使用 InputStreamReader 将 inputStream 转成字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);//输出
//5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 使用字符输出流的方式回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束
bufferedWriter.flush();//注意需要手动的flush
//6.关闭流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//关闭
System.out.println("服务端退出.....");
}
//客户端
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道, 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server 字符流");
bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束, 注意,要求对方使用readLine()!!!!
bufferedWriter.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
//4. 获取和socket关联的输入流. 读取数据(字符),并显示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
//5. 关闭流对象和socket, 必须关闭
bufferedReader.close();//关闭外层流
bufferedWriter.close();
socket.close();
System.out.println("客户端退出.....");
}
2. 使用socket传输文件
一下演示传输图片的过程:
//StreamUtils
public static byte[] streamToByteArray(InputStream is) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
bos.write(b, 0, len);
}
byte[] array = bos.toByteArray();
bos.close();
return array;
}
//服务端
public static void main(String[] args) throws Exception {
//1. 服务端在本机监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在8888端口监听....");
//2. 等待连接
Socket socket = serverSocket.accept();
//3. 读取客户端发送的数据
// 通过Socket得到输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//4. 将得到 bytes 数组,写入到指定的路径,就得到一个文件了
String destFilePath = "src\\abc.mp4";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();
// 向客户端回复 "收到图片"
// 通过socket 获取到输出流(字符)
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("收到视频");
writer.flush();//把内容刷新到数据通道
socket.shutdownOutput();//设置写入结束标记
//关闭其他资源
writer.close();
bis.close();
socket.close();
serverSocket.close();
}
//客户端
public static void main(String[] args) throws Exception {
//客户端连接服务端 8888,得到Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//创建读取磁盘文件的输入流
String filePath = "d:\\abc.mp4";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
//bytes 就是filePath对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);
//通过socket获取到输出流, 将bytes数据发送给服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);//将文件对应的字节数组的内容,写入到数据通道
bis.close();
socket.shutdownOutput();//设置写入数据的结束标记
//=====接收从服务端回复的消息=====
InputStream inputStream = socket.getInputStream();
//使用StreamUtils 的方法,直接将 inputStream 读取到的内容 转成字符串
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
//关闭相关的流
inputStream.close();
bos.close();
socket.close();
}
9. netstat指令
先win+R打开运行面板
- netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络链接情况
- netstat -an | more 可以分页显示
- 要求在dos控制台下执行
- netstat指令主要用于查看现在正在使用的端口,如上方代码运行起来只会,就会占用8888端口
- Listening表示某个端口在监听
- 如果有应该外部程序连接到该端口,就会显示一条连接信息
97.项目流程分析
98.多用户即时通信系统(QQ项目)
99.反射(reflection)
1. 使用反射的原因
Properties p = new Properties();
p.load(new FileInputStream("src/Reflection/ReflectionTest1/Cat.properties"));
String path = p.get("path").toString();
String method = p.get("method").toString();
System.out.println("path = "+path);
System.out.println("method = "+method);
如果我们要调用properties配置文件内的数据,并且根据该数据进行下一步操作如根据其内的路径调用该路径的类的方法等,由于我们提取出来的数据是字符串,不可能用于处理如new一个类等的操作,所以常规方法不能解决该问题。
**由于这种情况在框架中出现的比较多,同时要求在不修改源码的情况下控制程序,也符合设计模式中的ocp原则(开闭原则),所以产生了一种解决方法:反射 : **
//解决方法:
public static void main(String[] args) throws Exception{
Properties p = new Properties();
p.load(new FileInputStream("src/Reflection/ReflectionTest1/Cat.properties"));
String path = p.get("path").toString();
String methodName = p.get("method").toString();
//以上是先从配置文件中取出数据
//加载类,返回Class类型的对象c
Class c = Class.forName(path);
//通过c得到要加载的类Reflection.ReflectionTest1.Cat的对象实例
Object o = c.newInstance();//运行类型实际上就是Cat
//通过c得到已加载的类Reflection.ReflectionTest1.Cat的methodName的方法对象
//即在反射中,可以把方法视为对象(万物皆可对象)
Method method1 = c.getMethod(methodName);
//通过方法的对象调用方法
method1.invoke(o);
//传统方法 : 对象.方法()
//反射机制 : 方法.invoke(对象)
}
注意 :
- 传统方法 : 对象.方法()
- 反射机制 : 方法.invoke(对象)
2. 使用反射的优点
-
可以在不修改源码的情况下调用配置文件中类的方法
-
在要修改数据时只需要修改配置文件内的内容即可,无需修改源码
3. 反射机制
4. 反射在java程序计算机的位置
5. 反射的作用
- 运行时判断一个对象所属的类
- 运行时构造任意一个类的对象
- 运行时得到任意一个类所具有的成员变量和方法
- 运行时调用任意一个对象的成员变量和方法
- 生成动态代理
6. 反射相关类(这些类都在java.lang.Reflection内)
- java.lang.reflect.Field演示
//java.lang.reflect.Field演示
//注意:getField无法获取私有属性,只能获取public的字段
Field nameField = cls.getField("age");//age是
System.out.println(name.Field.get(o));//成员变量.get(对象)
- java.lang.reflect.Constructor代表类的构造方法
Constructor c1 = cls.getConstructor();//()中指定构造器参数类型,返回无参构造器
Constructor c2 = cls.getConstructor(String.class);//指定带String类型参数的构造器
7. 反射不足之处
反射不足之处在于反射是解释执行,会消耗大量时长
以下两个方法分别是传统调用配置文件内的类的方法和采用反射调用的方法
//传统方法
public static void m1() throws Exception{
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法调用时间 :"+(end - start));
}
//反射方法
public static void m2() throws Exception{
Class cls = Class.forName("Reflection.ReflectionTest1.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("用反射机制调用耗时 :"+(end - start));
}
最终结果为:
传统方法调用时间 :5ms
用反射机制调用耗时 :2703ms
1. 优化方法(setAccessible())
Method,Field,Constructor内都存在一个方法:setAccessible();
通过 setAccessible()方法对反射进行优化,setAccessible()方法是用于启动和禁用访问安全检查的开关,参数值为true时为关闭安全检测开关,可以提高方法调用速率,为false是则是开启安全检查开关。
public static void m3() throws Exception{
Class cls = Class.forName("Reflection.ReflectionTest1.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
hi.setAccessible(true);
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("用反射机制并且关闭安全检测开关调用耗时 :"+(end - start));
}
结果:
传统方法调用时间 :6ms
用反射机制调用耗时 :2523ms
用反射机制调用耗时 :1100ms
8. Class类(反射中的)
- Class类也是类,继承了Object类
- Class类不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次(在类加载器ClassLoader中存在一个方法loadClass(),通过该方法实现类加载,生成某个类对应的Class对象)
- 每个类的实例会记得自己是由哪个Class实例生成
- Class类存放在堆中
- 通过Class类可以得到一个类的完整结构,通过一系列API
- 类的字节码二进制数据存放在方法区,有的地方称为类的元数据(包括方法,变量名,方法名,访问权限等)
String classAllPath = "Class类.car";
//获取dog类的Class对象
Class<?> cls = Class.forName(classAllPath);
//显示是哪个类的Class对象
System.out.println(cls);
//运行类型
System.out.println(cls.getClass());
//包名
System.out.println(cls.getPackage().getName());
//通过cls创建car对象实例
car car = (car)cls.newInstance();
System.out.println(car);
//通过反射获取单个属性
Field brand = cls.getField("brand");
System.out.println(brand.get(car)).
//通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
//得到所有的属性
9.获取Class类对象
-
Class类的静态方法forName()方法获取
-
类名.class获取
-
对象.getClass()获取
-
通过类加载器获取类的加载对象
-
得到类加载器
-
通过类加载器得到Class对象
-
按照各个数据类型的特殊方式得到Class对象
-
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int
//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
System.out.println(type1);
System.out.println(integerClass.hashCode());//?
System.out.println(type1.hashCode());//?
//上方两个hashcode值相同
具备Class对象的类型有:
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
Class<Thread.State> cls6 = Thread.State.class;//枚举
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void数据类型
Class<Class> cls9 = Class.class;//Class也可以有自己的类对象
10.类加载
-
静态加载:编译时加载相关的类,如果没有则报错,依赖性强
(如在判断语句中采用相关类时,有可能因为判断结果不同导致类不会被使用,而编译器则强制要编译该类,就会报错)
-
动态加载(延时加载):运行时加载需要的类,如果不使用则不报错,降低依赖性(如在反射中,只有执行到了类对应的代码块时才会开始进行类加载,如:Class cls = Class.forNmae(“Person”);这样就会在运行到此处时才会加载该类,就不会报错)
1.类加载时机
-
创建对象时(new了一个对象)
-
子类被加载时
-
调用类中的静态成员时
-
通过反射
Class.forName("com.test.Cat");
2. 类加载阶段
其中连接和加载是由JVM机控制,初始化则是由程序员控制
1. 加载 Loading
JVM机将字节码从不同的数据源转化为二进制字节流加载到内存中,并且生成一个代表该类的java.lang.Class对象
2. 连接 Linking (将字符引用转为直接引用)
内包含三个过程
-
验证(安全验证)(-Xverify参数缩短类加载时间)
确保Class的字节流符合当前虚拟机的要求,并且不会危害虚拟机安全
包括:文件格式验证(是否以魔数 :oxcafebabe开头),元数据验证,字节码验证和符号引用验证
注意:使用 -Xverify:none 参数可以关闭大部分的安全验证,可以缩短虚拟机类加载的时间,从而缩短运行时间
-
准备
JVM机在该阶段对静态变量进行默认初始化(对于非静态变量不会进行处理,也不会分配内存),初始化值为0,0L,null,false等(在最后的初始化阶段才会给这些静态变量赋值(注意是静态变量非常量,常量是static final,静态变量只有static),准备阶段只是分配一个内存),这些变量所用内存都将在方法区内进行分配
class A{ public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; /* 注意: n1是实例属性而非静态变量,在类加载时不会进行处理,也不会分配内存空间 n2是静态变量,在类加载中的准备阶段会分配内存空间,但是分配的是默认值如0,false等 n3是常量,在类加载中的准备阶段会直接赋值 */ }
-
解析
虚拟机将常量池中内的符号引用转化为直接引用的过程
即为给每一个类等赋上一个内存地址,之后的调用就都是通过内存地址进行调用了
3. 初始化 initialization
-
初始化时才真正开始执行java程序代码,是执行()方法的过程
-
()方法是由编译器按照语句顺序依次收集静态变量的赋值动作和静态代码块中的语句进行合并
-
虚拟机会保证一个类的()方法在多线程中被正确的加锁,同步,如果多个线程同时初始化一个类,那么只会有一个线程执行这个类的()方法,其他线程均为阻塞等待,知道先前哪个线程执行()方法完毕
11.通过反射获取类的结构信息
//获取本类所有构造器,多个构造器可以用数组保存再遍历输出
Constructor<?>[] delcaredConstructors = personCls.getDeclaredConstructors();
for(Constructor<?> delcaredConstructor : delcaredConstructors){
System.out.println("本类所有的构造器"+delcaredConstructor.getName());
}
获取本类所有属性也几乎相同,只是换成用Field数组接收,然后for循环输出。
修饰符返回值(getModifiers())
默认修饰符为0,public是1,private是2,protected是4,static 是8,final是16
如public static,那么修饰符返回值就是1+8 = 9;
12.通过反射创建对象
-
调用类中的public修饰的无参构造器
-
调用类中的指定构造器
-
Class类相关方法
- getDeclaredConstructor().newInstance():调用类中的无参构造器,获取对应的对象
- getConstructor(Class…clazz): 根据参数列表获取对应构造器
- getDecalaredConstroctor(Class…clazz): 根据参数列表,获取对应构造器对象
-
Constroctor 类相关方法
-
setAccessible ; 暴破
-
newInstance(Object…obj): 调用构造器
-
//通过反射机制创建实例
//先获取类的class对象
Class<?> user = Class.forName("Reflection.User");
//通过public的无参构造器创建实例
Object o = user.getDeclaredConstructor().newInstance();
//通过public的有参构造器创建实例
//1.先得到对应构造器
Constructor<?> constructor = user.getConstructor(String.class);
//2.创建实例并且传入实参
Object x = constructor.newInstance("xxx");
System.out.println("xxx " + x);
//3.通过非public的有参构造器创建实例
//3.1得到private的构造器对象
Constructor<?> constructor1 = user.getDeclaredConstructor(String.class,int.class);
//3.2创建实例
constructor1.setAccessible(true);//暴破
Object o1 = constructor1.newInstance( "肖狗",100);
System.out.println(o1);
1.getDeclaredConstructor().newInstance()创建实例
原先的newInstance()方法已被弃用,更改为getDeclaredConstructor().newInstance()
2. 暴破(反射中用于访问非public构造器,如private)
可以通过暴破对私有的属性,构造器,方法进行访问
//将setAccessible()方法设为true即可访问非public构造器
constructor1.setAccessible(true);
13.通过反射访问类中的成员
1.反射操作属性
-
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名)
-
暴破,强行获取:类中的属性,主要用于获取私有属性
f.setAccessible(true);
-
访问
f.set(对象,值);
syso(f.get(对象));
-
如果是静态属性,则set和get内的对象参数可以写成null
2. 反射操作方法
-
根据方法名和参数列表获取Method方法对象:
Method m = clazz.getDeclaredMethod(方法名,XX.class);
-
获取对象:
Object o = clazz.getDeclaredConstructor().newInstance();
-
暴破:m.setAccessible(true);
-
访问:Object returnValue = m.invoke(o,实参列表);
-
注意,如果是静态方法,则invoke的参数o可以写为null
-
私有的方法需要使用到爆破(setAccessible),并且需要通过getDeclaredMethod()获取方法,public的可以通过getMethod获取方法
-
无返回值(void)的方法可以直接通过invoke进行输出,由返回值的方法需要使用System.out.println输出
public static void main(String[] args) throws Exception{
Class<?> bc = Class.forName("Reflection.Boss");
Object o = bc.newInstance();
//Method hi = bc.getMethod("hi",String.class);这种只能调用public的方法
//下方这种可以调用全部,包括私有
Method hi = bc.getDeclaredMethod("hi",String.class);
hi.invoke(o,"肖楚生");
Method say = bc.getDeclaredMethod("say",int.class,String.class, char.class);
say.setAccessible(true);
System.out.println(say.invoke(o,12,"肖狗",'骚'));
//因为say方法为静态方法,所以可以传入null
System.out.println(say.invoke(null,12,"肖狗",'骚'));
//当然,invoke得到的值也可以通过Object接收
//Object reVal = say.invoke(null,12,"肖狗",'骚');
}
100.数据库
1. 命令行连接mysql指令
D:\mysql-5.7.19-winx64\bin
在mysql下载的bin目录下以管理员身份进行运行输入:
mysql -h 127.0.0.1 -P 3306 -u root -phd200308041410
2. MySQL三层结构
数据库的本质就是文件
其中mysqld会一直处于3306端口进行监听
数据库的位置就处于我们下载的数据库的位置下的data目录中,创建的表等数据也会存储在data文件夹内
3. SQL语句分类
grant(授予)revoke(撤回)
4. 数据库基本语句+演示
-
CHARACTER SET :指定数据库采用的字符集,如果没指定默认为utf-8
-
COLLATE : 指定数据库字符集的校对规则(常用的utf8_bin (区分大小写),utf8_general_ci(不区分大小写) 默认为后者)
-
创建数据库,指定字符集,区分校对规则
-
查询,删除数据库
查询表数据:
SELECT * FROM t1
-
备份和恢复数据库
# 数据库操作
# 使用指令操作总数据库
#创建数据库 db03
CREATE DATABASE db03
# 删除数据库指令
DROP DATABASE db03
# 创建指定utf8字符集的db03数据库
CREATE DATABASE db03 CHARACTER SET utf8
# 创建指定utf8字符集并带区分大小写校对规则的db04数据库
CREATE DATABASE db04 CHARACTER SET utf8 COLLATE utf8_bin
# 创建指定utf8字符集并默认不区分大小写校对规则的db04数据库
CREATE DATABASE db05 CHARACTER SET utf8 COLLATE utf8_general_ci
# 校对规则:utf8_bin 区分大小写 utf8_general_ci 为默认类型,不区分大小写
# 查询语句 SELECT * 表示查询所有字段 FROM 表 的 WHERE 名字是‘tom’的
#测试utf8_bin 区分大小写
SELECT * FROM t1 WHERE NAME = 'tom'
#测试utf8_general_ci 不区分大小写
SELECT * FROM t1 WHERE NAME = 'tom'
# 查看当前数据库服务器中的所有数据库
SHOW DATABASES
# 查看已经创建的数据库的定义消息
SHOW CREATE DATABASE db05
# 创建数据库时如果遇到数据库的名称为关键字
#可以给名称添加反引号``
CREATE DATABASE `CREATE`
# 备份,要在DOS下执行mysqldump指令,将数据库备份在指定路径
# 这个备份文件就是对应的sql语句
mysqldump -u root -p -B db03 db04 > d:\\back.sql
DROP DATABASE db03
# 恢复数据库(需要进入Mysql命令行再执行)
# 通过管理员身份打开DOS并且输入 : mysql -u root -p
# 最后输入密码即可使用mysql命令行工具
# 恢复数据库方法一 : source d:\\back.sql
# 恢复数据库方法二,直接新建一个询问页面
# 将已备份好的文件的内容复制粘贴并执行刷新
5. 表的创建,删除与修改指令
1. 创建表
# 指令创建表
# 指定四个属性,并且设置字符类型,校正方式和引擎(引擎默认为innodb)
CREATE TABLE `USER1`(
id INT,
`name` VARCHAR(255),
`passward` VARCHAR(255),
`birthday` DATE)
CHARACTER SET utf8 COLLATE utf8_bin ENGINE INNODB
2. 关于表的例题
CREATE DATABASE 作业;
CREATE TABLE `employer`(
id INT,
`name` VARCHAR(32),
sex CHAR(1),
birthday DATETIME,
job VARCHAR(32),
salary DOUBLE,
`resume` TEXT) CHARSET utf8 COLLATE utf8_bin ENGINE INNODB;
SELECT * FROM employer
-- 在员工表内添加image列,varchar类型,要求在resume后
ALTER TABLE employer ADD image VARCHAR(32) NOT NULL DEFAULT ' '
AFTER RESUME
-- 显示表结构,查看所有列
DESC employer
-- 修改job列,长度为60
ALTER TABLE employer MODIFY job VARCHAR(60) NOT NULL DEFAULT ' '
-- 删除sex列
ALTER TABLE employer DROP sex
-- 修改表名为emp
RENAME TABLE employer TO emp
-- 修改表的字符集为utf8
ALTER TABLE emp CHARACTER SET utf8
-- 列明name修改为user_name
ALTER TABLE emp CHANGE `name` user_name VARCHAR(32) NOT NULL DEFAULT ' '
SELECT * FROM emp
6. Mysql常用数据类型(列类型)
1. 数值类型
- tinyint
# tinyint 无符号范围为0~255,有符号为-128~127
# 如果没有指定 unsinged ,则TINYINT 就是
CREATE TABLE t1(
id TINYINT );
# 如果要定义无符号整数只需要在TINYINT后加上unsigned
# 无符号范围为0~255,有符号为-128~127
CREATE TABLE t2(
id TINYINT UNSIGNED);
#最基础的添加语句
INSERT INTO t1 VALUES (127);
# 查询表数据
SELECT * FROM t1
- bit
# bit存入数据库的数据是以二进制的形式保存的
CREATE TABLE t1(num BIT(8));
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(3);
INSERT INTO t1 VALUES(255);
SELECT * FROM t1;
DROP TABLE t1;
# 查找时可以直接输入值,而不用换算二进制进行查找
SELECT * FROM t1 WHERE num = 1;
SELECT * FROM t1 WHERE num = 3;
SELECT * FROM t1 WHERE num = 255;
-
FLOAT/DOUBLE[UNSIGNED]
FLOAT单精度,DOUBLE双精度
-
DECIMAL[ M,D ] [ UNSIGNED ]
M代表整数可以保存至65位,D表示小数可以保存到30位,可以做到数非常大同时精度非常高
D默认为0,M默认为10
# 创建含float,double,decimal三种数据类型的表
CREATE TABLE t2(
num1 FLOAT,
num2 DOUBLE,
num3 DECIMAL(30,20));
# 添加数据
INSERT INTO t2 VALUES(88.12523645611236,88.12523645611236,88.12523645611236);
2. 文本类型
-
CHAR(size)
固定长度字符串,最大255字节
-
STRING(size)
可变长度字符串,最大65532字节[] [utf8编码最大21844字符,1~3个字节用于记录大小]
-
字符串操作案例
-- 注释快捷键 ctrl+shift+C
-- 固定长度字符串 最大255 字符
CREATE TABLE t3(
`name` CHAR(255));
-- VARCHAR(size)0~65535字节
-- 可变长度字符串,最大65532字节
-- 如果编码为 utf8 varchar(size) size = (65535-3)/3 = 21844
-- [utf8编码最大21844字符,1~3个字节用于记录大小]
CREATE TABLE t5(
`name` VARCHAR(21844)) CHARSET utf8;
-- 如果编码为 gbk varchar(size) size = (65535-3)/2 = 32766
CREATE TABLE t6(
`name` VARCHAR(32766)) CHARSET gbk;
DROP TABLE t4
-
字符串使用细节
注意:char的好处在于在数据长度确定的情况下,查询速度要快于varchar,所以char被保留了下来
1.
-- 字符串类型的使用细节 CREATE TABLE t1( `name` CHAR(4)); -- char 是不区分汉字还是字母的,存入的是字符 -- char 是定长,即使存入的数据长度没超过定长 -- 也会按定长分配空间,因此会导致空间浪费 INSERT INTO t1 VALUES('acfe'); INSERT INTO t1 VALUES('傻逼梓贤'); SELECT * FROM t1; -- varchar 是变长,即使插入的数据没到定长 -- 实际占用的空间会按照插入数据长度加1~3计算 -- 1~3是用于记录存放内容长度的 CREATE TABLE t2( `name` VARCHAR(4)); INSERT INTO t2 VALUES('abcd'); INSERT INTO t2 VALUES('傻逼梓贤'); SELECT * FROM t2; -- 定长的数据要使用char存放,查询速度快 -- 若varchar不够用,可以用mediumtext或longtext -- 或者直接写text,text是按照字节计算长度,一个汉字占三个字节 CREATE TABLE t3 (content TEXT, content2 MEDIUMTEXT,content3 LONGTEXT); INSERT INTO t3 VALUE ('黄典大帅逼','肖梓贤臭傻逼','黄典大帅逼'); SELECT * FROM t3;
2.
3. 二进制数据类型
4 .日期类型
# 创建时间相关的类型
CREATE TABLE t1 (
birthday DATE,-- 出生日期(自己设置)
jobtime DATETIME,-- 工作日期(自己设置)
logintime TIMESTAMP NOT
NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
-- 自动生成当前时间
-- 刷新logintime,时间会自动更新为当前时间
-- (前提是未给logintime设置固定时间)
) ;
INSERT INTO t1(birthday,jobtime) VALUES('2022-11-11','2022-11-11 10:10:10');
SELECT * FROM t1;
DROP TABLE t1
7.修改列
101.SQLyog快捷键
注释:ctrl+shift+C
格式化:shift+Fn+F12
102.数据库CRUD语句
1.Insert语句(添加数据)
# 创建包含三个参数的表
CREATE TABLE shoptable(
id INT,
goods_name VARCHAR(10),
price DOUBLE);
# 给表传入数据
INSERT INTO shoptable(id,goods_name,price) VALUES (20,'呀呼~~~',1234.5);
# 显示表
SELECT * FROM shoptable;
# 删除表
DROP TABLE shoptable
Insert细节
- 细节1中如果添加数据时需要传入int但是传入其他类型,自然会报错,但是如果传入’30’等加引号的数字,虽然是字符串,但是在mysql底层会尝试转为int,因此’30’会成功作为int类型传入,但是’abc’不行
- 给表传数据时在形参内的参数类型可以与创建表时的顺序不一样,但是传入的数据需要与形参内的类型对应
- 一条insert语句可以添加多条记录
INSERT INTO emp(id,user_name,entry_date,job,salary,`resume`,image)
VALUES(3,'肖楚生','2022.5.3','母猪产后护理',2000,'嘿嘿嘿','梓贤傻逼'),(4,'肖栽种','2022.5.4','公猪产后护理',2.001,'咳咳咳','傻逼梓贤');
2.Update语句(更新数据)
-- 将所有员工的薪水修改为5000,就不需要写WHERE
-- update emp set salary = 5000
-- 将员工2的薪水修改为5000
UPDATE emp SET salary = 5000 WHERE id = 2
-- 将员工3的薪水增加1000,job修改
UPDATE emp SET salary = salary+1000,job = '母牛产后护理'WHERE id = 3;
3.Delete语句(删除数据)
delete只会删除表中的记录,但是不会删除表本身,
-- 删除表中id为4的所有记录
DELETE FROM emp WHERE id = 4;
-- 删除表中所有记录
DELETE FROM emp
细节
4.Select语句(查找数据)
1. 简单查询+过滤(distinct)
-- 查询所有学生的消息
SELECT * FROM student;
-- 查询所有学生姓名和对应的英语成绩
SELECT `name`,english FROM student;
-- 过滤重复数据(distinct)将重复数据的去除掉,留下不重复的
SELECT DISTINCT english FROM student;
-- 每个字段都相同才会去重
2. 运算+改名(as)
-- 统计每个学生的总分
SELECT `name`,(chinese+math+english) FROM student;
-- 统计所有学生总分加10的结果
SELECT `name`,(chinese+math+english+10) FROM student;
-- 使用别名表示学生姓名分数
SELECT `name`AS '姓名',chinese+math+english+10 AS '总成绩' FROM student;
3. where子句常用运算符
-- 查询姓名为c的学生成绩
SELECT * FROM student WHERE `name` = 'c';
-- 查询英语成绩大于90的同学
SELECT *FROM student WHERE english>90;
-- 查询总分大于200的同学
SELECT * FROM student WHERE (chinese+math+english)>200;
-- 查询math大于60并且id大于3的学生成绩
SELECT * FROM student WHERE math>60 AND id>3;
-- 查询英语成绩大于语文成绩的学生
SELECT * FROM student WHERE english>chinese;
-- 查询总分大于200并且数学成绩小于语文成绩的并且id在1~6之间并且名字在b到f之间的同学
SELECT * FROM student WHERE (chinese+math+english)>200 AND math<chinese AND id>1 AND id<6 AND `name`>'b' AND `name`<'f';
4. mysql中字符串abcd也可比大小,a<b<c…
CREATE TABLE test(
id INT,
`name` VARCHAR(3));
INSERT INTO test VALUES(1,'abc'),(2,'def');
SELECT * FROM test WHERE `name`<'ghi';
5. 查询以某个字开头的数据(%)(LIKE)
SELECT * FROM student WHERE `name` LIKE '黄%'
-- 其中%表示0~多,可有可无,like表示类似于什么什么
6. 排序(order by)
asc默认升序,desc默认降序
-- 升序(由小到大)
SELECT * FROM student ORDER BY math ASC;
-- 降序(由大到小)
SELECT * FROM student ORDER BY math DESC;
-- 升序(由小到大)
SELECT * FROM student ORDER BY math ASC;
-- 降序(由大到小)
SELECT * FROM student ORDER BY math DESC;
-- 姓名b到f之间总分降序输出
SELECT `name`,(chinese+math+english) AS '总分' FROM student WHERE `name`>'b' AND `name`<'f' ORDER BY '总分';
103.合计/统计函数-count
-- 统计员工班有多少学生
SELECT COUNT(*) FROM student;
-- 统计数学大于90的学生有多少人
SELECT COUNT(*) FROM student WHERE math > 90
-- 统计总分大于250的有多少人
SELECT COUNT(*) FROM student WHERE (math+english+chinese) >250
-- count(*)和count(列)的区别
-- count(列)统计满足条件的列有几个,但是会排除为null的情况
CREATE TABLE test1(
`name` VARCHAR(20));
INSERT INTO test1 VALUES('tom'),('jack'),('mary'),('jacob'),('mary'),(NULL);
SELECT * FROM test1
-- count(列)会排除空的情况
SELECT COUNT(*) FROM test1; -- 这个结果为6
SELECT COUNT(NAME) FROM test1; -- 这个结果为5
-- 统计一个班级数学总成绩
SELECT SUM(math) FROM student;
-- 统计班级语数英各科总成绩
SELECT SUM(chinese),SUM(english),SUM(math) FROM student;
-- 统计一个班语数英总和
SELECT SUM(chinese+math+english) FROM student;
104.平均数
-- 统计一个班语文平均分
SELECT SUM(chinese) / COUNT(*) FROM student;
-- 求一个班级数学平均分
SELECT AVG(math) FROM student;
-- 求一个班级总分平均分
SELECT AVG(chinese+math+english) FROM student;
105.max/min最大数最小数
SELECT MAX(chinese+math+english) ,MIN(chinese+math+english) FROM student;
106.group by+having 分组
在分组中进行过滤的是having而不是where,不要混淆
-- 演示group by + having
-- 按照部分进行分组查询
-- 由于存入的数据只有三个部门有人,所以编号为40的部门不会被查询到
SELECT AVG(sal),MAX(sal),deptno FROM emp GROUP BY deptno;
-- 显示每个部门的平均工资和最低工资
SELECT AVG(sal),MIN(sal),deptno FROM emp GROUP BY deptno;
-- 显示每个部门的每种岗位的平均工资和最低工资
SELECT AVG(sal),MIN(sal),deptno,job FROM emp GROUP BY deptno,job;
-- 显示平均工资低于2000的部门号和它的平均工资
SELECT AVG(sal) AS '平均工资',deptno AS '部门号' FROM emp GROUP BY deptno HAVING AVG(sal)<2000;
107.字符串相关函数
-- 字符串相关函数
-- charset(str) 返回字串字符集,(表中均为utf8)
SELECT CHARSET(ename) FROM emp;
-- concat(String2 [,...]) 连接字串,将多个字串拼接成一列
SELECT CONCAT(ename, ' job is ',job) FROM emp;
-- instr(String,subString) 返回substring在string中的位置
-- 为了方便查询采用了mysql提供的伪表dual
-- dual是亚元表,可以作为测试表使用,是伪表
SELECT INSTR('huangdaindashuaibi','bi') FROM DUAL;
-- 转化大写
SELECT UCASE(ename) FROM emp;
-- 转化小写
SELECT LCASE(ename) FROM emp;
-- 从字符串左边取length个字符
SELECT LEFT(ename,2) FROM emp;
-- 从字符串右边取length个字符
SELECT RIGHT(ename,2) FROM emp;
-- string长度
SELECT LENGTH(ename) FROM emp;
-- 在字符串中用另一个字符串替代其子串
SELECT REPLACE(job,'MANAGER','经理') FROM emp;
SELECT * FROM emp;
-- 逐字符比较两个字串大小,前面大于后面返回1,小于返回-1,等于返回0
SELECT STRCMP (ename,job) FROM emp;
-- 截取字符串中的子串(是从1开始计算的)
SELECT SUBSTRING(job,1,2) FROM emp;
-- ltrim() rtrim() trim()分别为去掉前端,后端,和两边的空格
SELECT LTRIM(' hda ') FROM DUAL;
SELECT RTRIM(' hda ') FROM DUAL;
SELECT TRIM(' hda ') FROM DUAL;
-- 显示员工首字母为小写的姓名
-- 写法一:全用substring
SELECT CONCAT(LCASE(SUBSTRING(ename,1,1)),SUBSTRING(ename,2)) FROM emp;
-- 写法二:使用左边截取和右边截取
SELECT CONCAT(LCASE(LEFT(ename,1)),RIGHT(ename,(LENGTH(ename)-1))) FROM emp;
-- 写法三:左边截取和substring截取
SELECT CONCAT(LCASE(SUBSTRING(ename,1,1)),RIGHT(ename,LENGTH(ename)-1)) FROM emp;
108.数学函数
-- ABS(num) 绝对值
select abs(-10) from dual;
-- BIN(DECIMAL_NUMBER) 十进制转化二进制
select bin(123041) from dual;
-- ceiling(number) 向上取整,得到比number大的最小整数
select ceiling(3124.4322) from dual;
-- conv(number,from_base,to_base) 进制转化,对应的进制用2,6,8,10写
select conv(12413, 10,2) from dual;
-- 向下取整
select floor(3124.123) from dual;
-- 保留指定位数的小数位数并四舍五入
select format(4123.1345,3)from dual;
-- 转化16进制
select hex(124) from dual;
-- 求最小值
select least(123,125,1332,46,123,64,245,82,4,5)from dual;
-- 求模
select mod(10000,7) from dual
-- 每次返回不同的随机数,范围为0~1.0
select rand() from dual
-- 每次返回相同的数,需要在rand内填入一个数值,数值不变返回的值不变
select rand(19) from dual
109.日期函数
-- 查询在十分钟之内发布的新闻
-- 写法一
select * from mes where date_add(sendtime,interval 10 minute) >= now()
-- 写法二
select * from mes where sendtime >= date_sub(now(),interval 10 minute);
细节:
-- 返回1970-1-1到现在的秒数
SELECT UNIX_TIMESTAMP() FROM DUAL;
-- 将一个秒数转化为指定格式的日期(从1970-1-1开始计算)
-- 未指定时会将年月日时分秒(默认)都输出
SELECT FROM_UNIXTIME(1651804202)FROM DUAL;
-- 指定为年月日的格式
SELECT FROM_UNIXTIME(1651804202,'%Y-%m-%d')FROM DUAL;
-- 指定为年月日时分秒的格式
SELECT FROM_UNIXTIME(1651804202,'%Y-%m-%d %H:%i:%s')FROM DUAL;
110.加密函数和系统函数
-- user()查看登录到mysql的有哪些用户,以及登录的ip
SELECT USER() FROM DUAL;
-- database() 数据库名称
SELECT DATABASE() FROM DUAL;
-- md5(str) 为一个字符串算出一个md5 32 的字符串,对用户密码进行加密
SELECT MD5('hd200308041410') FROM DUAL;
-- 数据库中存放的密码就是md5格式的,32位密码
-- 创建表并传入使用MD5进行加密的密码
CREATE TABLE huang(
id INT,
`name` VARCHAR(32) NOT NULL DEFAULT ' ',
pwd CHAR(32) NOT NULL DEFAULT ' '
);
INSERT INTO huang VALUES(100,'黄典',MD5('hd200308041410'));
-- 查询表中数据
SELECT * FROM huang WHERE `name` = '黄典' AND pwd = MD5('hd200308041410');
-- password(str) --- 加密函数,mysql数据库的用户密码就是用passward函数进行加密
SELECT PASSWORD('hd200308041410') FROM DUAL;
-- mysql.user表示数据库.表,可以查看所有user的权限
SELECT * FROM mysql.`user`
111.流程控制函数
SELECT ename,(SELECT CASE
WHEN job = 'CLERK' THEN '职员'
WHEN job = 'MANAGE' THEN '经理'
WHEN job = 'SALESMAN' THEN '销售人员'
ELSE job END)
FROM emp;
112.查询增强
1. 比较日期
-- 使用where子句
-- 获得在指定日期入职的员工信息
-- 1. (日期也可直接比较,注意格式)
SELECT * FROM emp
WHERE hiredate > '1992-01-01';
2.特殊符号(% _)
3. 模糊查询(like语句)
-- 获得首字母为 S 的所有员工姓名和工资
SELECT ename,sal FROM emp
WHERE ename LIKE 'S%';
-- 获得第三个字符为大写O的员工姓名和工资
SELECT ename,sal FROM emp
WHERE ename LIKE '__O%';
4. IS 语句
-- 查询没有上级的员工(需要用is null,而不能用等号)
SELECT * FROM emp
WHERE mgr IS NULL;
5. 查询表结构
-- 查询表的结构(可以看到字段,类型,是否为空等等)
DESC emp;
6. ORDER BY 排序(升序ASC,降序DESC)
-- 使用ORDER BY子句
-- 按照工资的从低到高顺序,显示雇员信息
SELECT * FROM emp
ORDER BY sal ASC
-- 按照部门升序而工资降序排列,显示雇员信息
SELECT * FROM emp
ORDER BY deptno ASC, sal DESC
7.查询表设置(DESC)
通过DESC table_name,即可得到表的配置信息
113.分页查询(limit)
语法:SELECT … LIMIT START,ROWS
(从start + 1开始查询,取出ROW行,start从0开始计算)
-- 分页查询,按照雇员empno号码升序取出,每页含三条记录
select * from emp
order by empno asc limit 0,3 ;
SELECT * FROM emp
ORDER BY empno ASC LIMIT 3,3;
SELECT * FROM emp
ORDER BY empno ASC LIMIT 6,3;
114.group by 增强
-- 增强group by的使用
-- 显示每种岗位的雇员总数和平均工资
SELECT COUNT(*),AVG(sal),job
FROM emp
GROUP BY job;
-- 显示获得补助的雇员数
SELECT COUNT(*),COUNT(comm)
FROM emp;
-- 显示没有获得补助的雇员数
SELECT COUNT(*),COUNT(IF(comm IS NULL,1,NULL))
FROM emp;
-- 显示管理员总人数,使用distinct去重
SELECT COUNT(DISTINCT mgr)
FROM emp;
-- 显示雇员工资最大差额
SELECT MAX(sal) - MIN(sal)
FROM emp;
115.mysql查询语句顺序
select > from > group by > having > order by > limit
-- 统计各个部门的平均工资,并且是大于1000的
-- 按照从高到低排序,取出前两条
SELECT deptno,AVG(sal) AS avgsal FROM emp
GROUP BY deptno
HAVING avgsal > 1000
ORDER BY avgsal DESC
LIMIT 0,2;
116.多表笛卡尔集(多表查询)
1. 多表查询规则
就是第一个表的每一列和第二个表的所有列都匹配一遍,总条数为第一个表的列数 * 第二个表的列数
为了得到正确的信息,需要使用where写出正确的过滤条件
-- 多表查询
-- 查询员工姓名,薪水,部门名称
SELECT ename,sal,dname FROM emp,dept
WHERE emp.`deptno` = dept.`deptno`
-- 不能查询表中重复出现的字段
-- 表中deptno重复出现两次,因此查询会导致报错
-- 为了解决这个问题,可以:
SELECT ename,sal,dname,emp.`deptno` FROM emp,dept
WHERE emp.`deptno` = dept.`deptno`
-- 显示部门号为10的部门名,员工名和工资
SELECT dname,ename,sal,emp.`deptno` FROM emp,dept
WHERE emp.`deptno` = dept.`deptno`
AND dept.`deptno` = 10;
-- 显示所有员工姓名工资以及工资级别
SELECT ename,sal,grade FROM emp,salgrade
WHERE sal BETWEEN losal AND hisal
-- 显示雇员名字,雇员工资以及所在部门的名字,并且部门按照降序排序
SELECT ename,sal,dname,emp.deptno FROM emp,`dept`
WHERE emp.`deptno` = dept.`deptno` ORDER BY emp.`deptno` DESC
117.自连接
指在同一张表的连接查询(将一张表看成两张表)
特点:1. 需要把一张表当作两张来使用 2 . 需要给表取两个别名 3. 列名不明确,可以指定列的别名
-- 将emp分成两张表来看,第一张看作worker表,第二张看作boss表
SELECT worker.ename AS '职员名',boss.ename AS'上级名'
FROM emp worker,emp boss
WHERE worker.`mgr` = boss.`empno`;
118.子查询
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询
1. 单行子查询
单行子查询指只返回一行数据的子查询语句
2. 多行子查询
多行子查询指返回多行数据的子查询,使用关键字in
SELECT worker.ename AS '职员名',boss.ename AS'上级名'
FROM emp worker,emp boss
WHERE worker.`mgr` = boss.`empno`;
-- 得到与smith相同部门的员工
SELECT deptno FROM emp WHERE ename = 'SMITH'
SELECT * FROM emp WHERE deptno = (
SELECT deptno
FROM emp
WHERE ename = 'SMITH'
)
-- 查询和10号部门工作相同的雇员的名字岗位工资部门号
-- 但是不含10号部门自己的员工
SELECT DISTINCT job FROM emp WHERE deptno = 10;
SELECT ename,job,sal,deptno
FROM emp
WHERE job IN(
SELECT DISTINCT job
FROM emp
WHERE deptno = 10
)AND deptno != 10;
-- 注意,这边的不等号也可以是<>
-- 查询各个类别中价格最高的商品
select goods_id,ecs_goods.cat_id,goods_name,shop_price
from(
select cat_id,max(shop_price) as max_price
from ecs_goods
group by cat_id
)temp,ecs_goods -- temp为此临时表的表名
where temp.`cat_id` = ecs_goods.`cat_id`
and temp.max_price = ecs_goods.shop_price
-- 创建了员工临时表获得cat_id和max_price,并且将这个临时表取名为temp
-- 从ecs_goods获得goods_id和goods_name
-- 设置过滤条件为物品类id要相同,同时最大价格要匹配对应的物品价格,以获取最大价格的物品
119.all操作符和any操作符
1. all操作符
SELECT ename,sal,deptno FROM emp
WHERE sal>ALL(
SELECT sal FROM emp WHERE deptno = 30
)
-- 子查询得到的是30部门内所有的工资情况
-- 只要emp内的薪水大于30部门的薪水
-- 就可以得到除30部门外比其所有员工薪水高的员工信息了
-- 也可以直接从子查询获得30的最高工资
SELECT ename,sal,deptno FROM emp
WHERE sal>ALL(
SELECT MAX(sal) FROM emp WHERE deptno = 30
)
2. any操作符
-- 得到除30部门外比30部门其中一个员工薪水高的员工信息
SELECT ename,sal,deptno FROM emp
WHERE sal>ANY(
SELECT sal FROM emp WHERE deptno = 30
)
-- 也可以不用any,直接获得30内最低工资即可
SELECT ename,sal,deptno FROM emp
WHERE sal>(
SELECT MIN(sal) FROM emp WHERE deptno = 30
)
120.mysql表子查询
多列子查询:多列子查序列则是指擦汗寻返回多个列数据的子查询语句
where(A,B) = (SELECT A,B FROM XXX WHERE XXX = XXX)
WHERE内的内容和子查询内得到的内容要完全相同,同时顺序也要匹配
-- 多列子查询
-- 查询与smith部门和岗位完全相同的员工
SELECT * FROM emp WHERE (deptno,job) = (SELECT deptno,job FROM emp WHERE ename = 'ALLEN')AND ename <> 'ALLEN';
-- 查找每个部门工资高于本部门平均工资的人的资料
-- 需要将员工子查询当作员工临时表使用
SELECT deptno,AVG(sal)
FROM emp GROUP BY deptno
SELECT ename,sal,temp.avgsal,emp.`deptno`
FROM emp,(
SELECT deptno,AVG(sal) AS avgsal
FROM emp GROUP BY deptno
) temp
WHERE emp.`deptno` = temp.`deptno`
AND emp.sal >temp.avgsal
-- 查找每个部门工资最高的人的详细资料
SELECT * FROM emp,(
SELECT deptno,MAX(sal) AS maxsal
FROM emp
GROUP BY deptno
)temp
WHERE emp.`deptno` = temp.deptno
AND emp.sal = temp.maxsal
-- 查询每个部门的信息(
-- 部门名,编号,地址
-- )和人员数量
SELECT dname,dept.`deptno`,loc
FROM dept,(
SELECT COUNT(*),deptno
FROM emp
GROUP BY deptno
) tmp
WHERE tmp.deptno = dept.`deptno`
-- 还有一种写法,表 * 列出表内所有元素
SELECT tmp.* , dname,loc
FROM dept,(
SELECT COUNT(*),deptno
FROM emp
GROUP BY deptno
) tmp
WHERE tmp.deptno = dept.`deptno`
121.表复制
1. 自我复制数据(蠕虫复制)
有时候为了对某个sql语句进行效率测试,可以使用最高方法为表创建海量数据(复制速度为2的n次方)
-- 1.将emp表的数据复制到t1内
INSERT INTO t1(
id,`name`,sal,job,deptno)
SELECT empno,
`ename`,sal,job,
deptno FROM emp;
-- 2. 自我复制,将查询自己的记录插入给自己
-- 以2的n次方的速度进行快速复制
INSERT INTO t1 SELECT * FROM t1;
SELECT * FROM t1;
122.表去重
-- 得到一张表的非重复记录(去重)
-- 此方法不会影响原表的数据量,只会筛选出非重复的数据
-- 1. 创建员工表并且复制emp表的结构
CREATE TABLE t2 LIKE emp;
-- 2.将emp表内的数据复制到t2中
-- 添加t2中不重复的数据到t2
INSERT INTO t2 SELECT DISTINCT * FROM t2;
-- 查询t2中不重复的数据,但是并不会删除t2的重复数据
SELECT DISTINCT * FROM t2
123.合并查询
为了合并多个select语句的结果,可以使用集合操作符号
union , union all , union.sql
1. union all
用于取得两个结果集的并集,不会取消重复集
-- union all 用于取得两个结果集的并集,不会去重
SELECT ename,sal,job
FROM emp
WHERE sal>2500 UNION ALL
SELECT ename,sal,job
FROM emp
WHERE job = 'MANAGER';
2. union
用于取得两个结果集的并集,会去重
-- union 用于取得两个结果集的并集,会去重
SELECT ename,sal,job
FROM emp
WHERE sal>2500 UNION
SELECT ename,sal,job
FROM emp
WHERE job = 'MANAGER'
124.mysql表外连接
1. 左外连接
左侧的表完全显示
语法:select … from 表1 left join 表2 on 条件
-- 左外连接
-- 显示所人员的成绩
-- 若无成绩也要显示该人的姓名和id,成绩显示为空
SELECT `name`,stu.`id`,grade
FROM stu LEFT JOIN exam
ON stu.`id` = exam.`id`;
2. 右外连接
右侧的表完全显示
语法:select … from 表1 right join 表2 on 条件
125.mysql约束
1. 主键
约束用于确保数据库数据满足特定商业规则,在mysql中约束包括 not null,unique,primary key,foreign key和check五种
CREATE TABLE t3(
id INT PRIMARY KEY, -- 表示id是主键
NAME VARCHAR(32),
email VARCHAR(32));
-- 主键指定方式:
-- 1.直接在字段后指定:字段名 primary key
-- 2. 在表定义最后写 primary key(列名)
-- 使用desc表名,可以看到primary key的情况
-- 主键的值不能重复,且不能为null
INSERT INTO t3
VALUES(NULL,'hd','2084222411@qq.com');
-- 一张表最多一个主键,但可以是复合主键
-- 复合主键:
CREATE TABLE t4(
id INT,
`name` VARCHAR(32),
emial VARCHAR(32),
PRIMARY KEY (id,`name`)
);
DESC t4;
INSERT INTO t4
VALUES(1,'hd','2084222411@qq.com')
-- 由于现在的主键是id和name
-- 所以只有id和name都相等才不会存入
INSERT INTO t4
VALUES(1,'lxy','2084222411@qq.com')
SELECT * FROM t4
2. not null
语法:字段名 字段类型 not null
3. unique
语法:字段名 字段类型 unique
CREATE TABLE t5
(id INT UNIQUE, -- 表示id是不可重复的
`name` VARCHAR(32),
email VARCHAR(32)
);
-- 注意,让id等于null也是可以存入的,除非设置not null
INSERT INTO t5 VALUES(NULL,'hd','20302@e12e');
SELECT * FROM t5
-- 一张表中可以有多个unique字段
4. foreign key (外键)
语法
注意事项
CREATE TABLE class(
id INT PRIMARY KEY,
`name` VARCHAR(32) NOT NULL DEFAULT ' ');
CREATE TABLE stu(
id INT PRIMARY KEY,
`name` VARCHAR(32) NOT NULL DEFAULT ' ',
class_id INT,FOREIGN KEY(class_id) REFERENCES class(id));
INSERT INTO stu
VALUES(1,'tom',100),(2,'jack',200);
-- 由于主表中只存在100和200的班级
-- 并且通过外键进行约束id
-- 所以无法存入除100和200以外的班级
INSERT INTO stu
VALUES(4,'mis',300);
-- 建立主外键关系后,数据不能随意删除,否则会报错
DELETE FROM class
WHERE id = 100;
5. check
强制行数据要满足的条件
语法
注意:使用mysql5.7版本目前还不支持check,只作为语法校验,并不会生效
sex varchar(32) check(sex in('man','woman')
6. 约束例题
-- 商品表
CREATE TABLE goods(
goods_id INT PRIMARY KEY,
goods_name VARCHAR(64) NOT NULL DEFAULT ' ',
unitprice DECIMAL(10,2) NOT NULL DEFAULT 0
CHECK(unitprice>=1.0 AND unitprice <= 9999.99),
category INT NOT NULL DEFAULT 0,
provider VARCHAR(64) NOT NULL DEFAULT ' ');
-- 客户表
CREATE TABLE customer(
customer_id CHAR(8) PRIMARY KEY,
`name` VARCHAR(64) NOT NULL DEFAULT ' ',
address VARCHAR(64) NOT NULL DEFAULT ' ',
email VARCHAR(64) UNIQUE NOT NULL,
sex ENUM('男','女') NOT NULL ,
card_id CHAR(18) UNIQUE);
-- 购买表
CREATE TABLE purchase (
order_id INT UNSIGNED PRIMARY KEY ,
customer_id CHAR(8) NOT NULL DEFAULT ' ',
goods_id INT NOT NULL DEFAULT 0 ,
nums INT NOT NULL DEFAULT 0,
FOREIGN KEY(customer_id)
REFERENCES customer(customer_id),
FOREIGN KEY(goods_id)
REFERENCES goods(goods_id)
);
126.自增长
语法
格式
细节
自增长默认从目前已有的最大数开始自增长,如果我们存入一个小于目前最大数的值,会自动将该值进行大小排序,将它插入到列的中间
CREATE TABLE t1(
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(32) NOT NULL DEFAULT ' ',
`name` VARCHAR(32) NOT NULL DEFAULT ' ');
-- 将PRIMARY KEY和AUTO_INCREMENT配合使用
-- 不会重复,不会为空
-- 将id设为自增长后
-- 每添加一次数据就会自动在id位置增长一次数据
-- 和java中将id设为static有些类似
-- 会保留上一次得到的值并且自增长
-- 如果要赋值的话可以修改存入的值不为null
INSERT INTO t1
VALUES(NULL,'dasdad@edasd','ja'),(NULL,'dasdad@edasd','dsa');
SELECT * FROM t1
修改自增长的默认值:自增长默认值为1,可以通过AUTO_INCREMENT修改默认值,需要在表创建完成之后修改
alter table t1 auto_increment = 100
127.mysql索引
好处:虽然索引会多占用内存,但是无序修改程序,无序调用sql,查询速度快
语法:
CREATE INDEX 任意索引名称 ON (要加快查询速度的字段名)
给指定的列添加索引之后,查询该列数据的速度会提高非常多,在数据量大的时候速度提升非常明显
1. 索引的原理
索引会形成一个二叉树,能够快速的以除2的方式查找到数据
注意:如果对已经建立索引的表进行修改,删除,添加数据,那么可能会造成表单的混乱
2 .索引的类型
一般开发不会使用mysql自带的索引
3. 索引使用
CREATE TABLE t2(
id INT,
`name` VARCHAR(32));
-- 查询表此时是否有索引
SHOW INDEXES FROM t2
-- 添加唯一索引
CREATE UNIQUE INDEX id_index ON t2(id);
-- 注意:Non_unique中如果为0,就表示该索引是唯一索引
-- 为1表示不是唯一索引
-- 添加普通索引方法1
CREATE INDEX id_index ON t2(id);
-- 如果某列的值是不会重复的,优先考虑唯一索引
-- 添加普通索引方式2
ALTER TABLE t2 ADD INDEX id_index (id);
-- 添加主键索引
-- (在建表的时候没指定某个需要的元素为主键的情况下使用)
ALTER TABLE t2 ADD PRIMARY KEY (id)
-- 删除索引
DROP INDEX id_index ON t2;
-- 修改索引时要先删除才能修改
-- 查询索引
-- 1. 方式1
SHOW INDEX FROM t2
-- 2. 方式2
SHOW INDEXES FROM t2
-- 3. 方式3
SHOW KEYS FROM t2
-- 4.方式4
DESC t2
4.小结
128.mysql事务
事务用于保证数据的一致性,由一组相关的dml语句组成,改组的dml语句要么全部成功,要么全部失败,可以保证数据的一致性
执行事务操作时(dml语句),mysql会在表上加锁,防止其他用户改表的数据。
1.mysql数据库操作事务时的重要操作
-- 事务的重要概念和具体操作
-- 1.创建一张表
CREATE TABLE t3(
id INT,
`name` VARCHAR(32));
-- 2. 开始事务
START TRANSACTION;
-- 3. 设置保存点
SAVEPOINT a;
-- 执行dml操作
INSERT INTO t3
VALUES(100,'tom');
SELECT * FROM t3;
-- 4. 设置保存点
SAVEPOINT b;
INSERT INTO t3
VALUES(200,'jack');
SELECT * FROM t3;
-- 5. 退回到b保存点
ROLLBACK TO b;
SELECT * FROM t3;
-- 6. 退回到a保存点
ROLLBACK TO a;
SELECT * FROM t3;
-- 回退全部事务
ROLLBACK;
-- 结束事务时自动删除定义的所以保存点
-- 所有数据正式生效
COMMIT;
2.事务细节
- 如果不开启事务,那么进行dml操作会自动提交,即使使用rollback也没用
- InnoDB存储引擎支持事务,MyISAM不支持
- 开启事务有两种方式
- start transaction
- set autocommit = off
129.事务的隔离级别
定义了事务和事务之间的隔离程度
介绍
-- 1.开启两个mysql控制台
-- 2.查看当前mysql 的默认隔离级别
SELECT @@tx_isolation;
-- mysql> SELECT @@tx_isolation
-- -> ;
-- +-----------------+
-- | @@tx_isolation |
-- +-----------------+
-- | REPEATABLE-READ |
-- +-----------------+
-- 3.把控制台2隔离级别设置为读未提交
SET SESSION TRANSACTION ISOLATION
LEVEL READ UNCOMMITTED ;
-- 4.查看设置后2的隔离级别
-- mysql> SELECT @@tx_isolation;
-- +------------------+
-- | @@tx_isolation |
-- +------------------+
-- | READ-UNCOMMITTED |
-- +------------------+
-- 5.同时启动事务
-- 6. 创建表
CREATE TABLE account(
id INT,
`name` VARCHAR(32),
money INT);
-- 7. 在表1中存入数据,但是不输入commit
-- 此时查询控制台1和2会发现都可以查询到数据
-- 但是我们只是修改了数据,并没有提交,这是脏读
可串行化是最严密的隔离级别,当有一个控制台在操作数据库时,它会对数据库进行加锁,保证同一时间只能有一个控制台在进行操作,如果此时另一个控制台想操作这个数据库,就会被卡住,无法访问,知道上一个操作数据库的控制台commit之后才可进行操作
1. 隔离级别操作语句
-
当前会话隔离级别
select @@tx_isolation;
-
当前系统隔离级别
select @@global.tx_isolation;
-
设置当前会话隔离级别
set session transaction isolation level repeatable read;
-
设置当前系统隔离级别
set globle transaction isolaton level repeatable read;
-
mysql默认的事务隔离级别是repeatable read,一般情况下,没有特殊要求,没有必要修改(该级别可以满足大部分需求)
2.mysql事务ACID特性
3. mysql表类型和储存引擎
- 注意:只有InnoDB类型支持事务,支持行级锁(锁是在行上的,耗费时间长力度小于表级锁),也支持外键
- mrg myisam是一种集合性质的类型
- memory是基于哈希表且存储在内存中的,读写速度非常快,但是只对临时表有用,只要重启mysql就会自动注销表内数据,但是表的结构还在
- blackhole 任何写入的数据都会被销毁
- myisam批量添加数据的速度非常快,但是不支持事务,支持表级锁
4. 储存引擎/表类型特点(非常重要)
5. 细节
6. 如何选择存储引擎
注意;memory的使用其实非常多,比如一些不需要进行记录并且随时会进行更换的数据,可以使用memory,这样占用的空间小,查询速度快,删除之后也无所谓,只要重新登录的时候再把数据写进去就行
7. 指令修改存储引擎
ALTER TABLE t1 ENGINE = INNODB;
130.视图(限制不同用户可查询范围)
视图是一个虚拟表,内容由查询定义,视图和原始表是映射关系
1. 总结:
2. 视图基本使用
-- 创建一个视图emp_view
-- 只能查询emp表的
-- (empno,ename,job,deptno)信息
CREATE VIEW emp_view
AS
SELECT empno,ename,job,deptno FROM emp;
SELECT * FROM emp_view
-- 创建视图后查看数据库只会看到一个视图结构文件
-- (视图名称.frm)
-- 视图的数据变化会影响到基表
-- 基表数据变化也会影响到视图
-- insert update delete
UPDATE emp_view
SET job = 'MANAGER'
WHERE empno = 7369;
3. 视图优点
131.mysql管理
mysql中的用户数据都是出差你在数据库中的user表中
1. user表重要字段说明
- host 允许登录的位置,localhost表示该用户只允许进行本机登录,也可以指定ip地址进行登录
- user 用户名
- authentication_string 密码,是通过mysql的passward()函数加密之后的密码
2. 管理语句
-
创建用户同时指定密码
create user ‘用户名’ @ ‘允许登录位置’ identified by ‘密码’ ;
-
删除用户
drop user ‘用户名’ @ ’允许登录位置‘ ;
-- mysql用户管理
-- 项目开发中可以根据不同的用户赋予相应的
-- mysql操作权限
-- 1. 创建新用户(存入的数据是加密信息而不是明文存放)
-- @前后不能有空格
CREATE USER 'hd_lxy'@'localhost' IDENTIFIED BY '123456'
SELECT * FROM mysql.`user`;
-- 2. 删除用户
DROP USER 'hd_lxy'@'localhost'
-- 3. 登录用户
-- 直接在sqlyog连接新的mysql并且输入正确密码即可
-- 4.修改自己密码
SET PASSWORD = PASSWORD('密码');
-- 5. 修改他人密码(需要权限)
SET PASSWORD FOR '用户名'@'登录位置' = PASSWORD('密码');
-- root可以修改其他登录用户的密码,其他登录者不能修改root密码
3. mysql权限
4. 给用户授权
语法
说明
5. 回收用户权限
语法
6. 权限生效指令
CREATE USER 'hd'@'localhost' IDENTIFIED
BY '123'
CREATE DATABASE testdb;
CREATE TABLE news(
id INT,
content VARCHAR(32));
INSERT INTO news VALUES(1,'hdsnd'),
(2,'lxysnm');
SELECT * FROM news;
-- 给用户分配查看news表和添加数据的权限
GRANT SELECT,INSERT
ON testdb.`news`
TO 'hd'@'localhost';
-- 回收权限
REVOKE SELECT,INSERT
ON testdb.`news` FROM 'hd'@'localhost';
-- 删除用户
DROP USER 'hd'@'localhost';
通过以上最后三行代码,可以给新创建的用户hd赋予查看数据库和储存数据的权限,从而在新建的用户处即可进行一系列操作
7. 细节
-
创建用户时,如果不指定Host,则为%,%表示所有IP都有连接权限
-
指定ip地址可以连接:
create user ‘xxx’@‘192.168.0.1.%’
即为ip地址为192.168.0.1.*的用户可以登录
-
删除用户时如果host不是%,需要明确指定 ‘用户’@‘host值’
-- 是百分号可以直接删除 DROP USER jack;
132.mysql小细节
- 给一个字段改名时可以使用AS XXX,也可以在字段后加空格再加"XXX",不能直接写XXX
- 判断一个字段是否为空,可以使用IS NOT NULL,但是不能使用 != NULL或者<>NULL
- ORDER BY 后可以跟字段或者字段的别名,但是不能跟数字或者与数据库无关的字母,单词
-- mysql 练习题
-- 找出各月倒数第三天受雇的所有员工的信息
-- 使用LAST_DAY方法
SELECT * FROM emp
WHERE LAST_DAY(hiredate) - 2 = hiredate;
-- 找出十二年前被雇佣的员工
-- 使用日期加减方法DATE_ADD()和查看现在时间NOW()
SELECT * FROM emp
WHERE DATE_ADD(hiredate,INTERVAL 12 YEAR) <NOW();
-- 工作相同按照job降序排序,不同按照sal进行默认排序
SELECT ename,job,sal FROM emp
ORDER BY job DESC,sal
-- 取出三十天内员工的日工资,忽略余数
-- FLOOR函数不会四舍五入,会直接取出整数部分
-- ROUND函数会四舍五入
SELECT ename,ROUND(sal/30) FROM emp;
-- 显示员工加入公司的天数
SELECT ename,DATEDIFF(NOW(),hiredate)
FROM emp;
-- 显示员工加入公司的年月日
SELECT ename,CONCAT((YEAR(NOW())-YEAR(hiredate)),'-',(MONTH(NOW())-MONTH(hiredate)),'-',(DAY(NOW())-DAY(hiredate)))
FROM emp;
-- 得到至少有一个人的部门
SELECT COUNT(*) AS c,deptno
FROM emp
GROUP BY deptno
HAVING c>1;
-- 查出薪水大于SMITH的员工
SELECT * FROM emp
WHERE sal >
(SELECT sal FROM emp
WHERE ename = 'SMITH')
-- 查出受雇日期晚于直接上级的员工
SELECT worker.ename AS '员工名',worker.hiredate AS '下属入职日期',
leader.ename AS '上级名' ,worker.hiredate AS '上级入职日期'
FROM emp worker,emp leader
WHERE worker.hiredate>leader.hiredate
AND worker.mgr = leader.empno
-- 列出部门名称和这些部门的员工信息,同时列出没有员工的部门
-- 要使用左外连接
SELECT dname,emp.* FROM dept
LEFT JOIN emp ON dept.`deptno` = emp.`deptno`
-- 列出所有办事员(CLERK)的名称及其部门名称
SELECT emp.ename,dept.dname FROM emp,dept
WHERE emp.job = 'CLERK' AND emp.`deptno` = dept.`deptno`
-- 列出最低薪金高于公司平均薪金的所有员工
SELECT MIN(sal) AS min_sal,job
FROM emp
GROUP BY job
HAVING min_sal<1500
-- 列出'SALES'(销售部)工作的员工的姓名
SELECT ename,dname
FROM emp,dept
WHERE emp.deptno = dept.`deptno` AND dname = 'SALES';
-- 得到高于三十号部门所有员工工资的员工信息
SELECT ename,sal FROM emp
WHERE sal>(
SELECT MAX(sal) FROM emp WHERE deptno = 30)
-- 得到每个部门员工数量,平均工资和平均服务时间
SELECT COUNT(*) AS '部门员工数量',
AVG(sal) AS '部门平均工资',
AVG(DATEDIFF(NOW(),hiredate))
FROM emp
GROUP BY deptno;
-- 得到所有部门详细信息和部门人数
SELECT dept.*,tmp.c AS '部门人数' FROM dept,(
SELECT COUNT(*) AS c,deptno
FROM emp
GROUP BY deptno
)tmp
WHERE dept.`deptno` = tmp.deptno
-- 列出所以员工的年工资,按年薪从低到高排序
-- 采用IFNULL方法,如果为空返回0,不为空返回comm
SELECT ename,(sal+IFNULL(comm,0))*12 AS '年工资' FROM emp
ORDER BY '年工资';
133.JDBC原理
1. 介绍
JDBC为访问不同的数据库提高统一的接口,为使用者屏蔽了细节问题
java程序员可以使用JDBC连接任何提供JDBC驱动程序的数据库系统,从而完成对数据库的操作
2. JDBC编写步骤
3. 使用JDBC操作数据库
public void connect04() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
// Class.forName()可以自动完成注册驱动
// 因此可以少写一句DriverManager.registerDriver(driver)
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
Connection conn = DriverManager.getConnection(url,user,password);
String sql = "insert into actor values(1,'肖狗')";
Statement statement = conn.createStatement();
for (int i = 0; i < 5; i++) {
int rows = statement.executeUpdate(sql);
System.out.println(rows > 0 ? "成功" : "失败");
}
statement.close();
conn.close();
}
134.java程序获得数据库连接方式
1.静态加载Driver实现类对象
因为是直接new一个Driver,所以灵活性不强
public void connect01() throws Exception{
//静态加载Driver
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/作业";
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hd200308041410"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
2 .使用反射进行动态加载
public void connect02() throws Exception{
//反射加载Driver,动态加载,减少依赖性
Class c = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)c.getDeclaredConstructor().newInstance();
String url = "jdbc:mysql://localhost:3306/作业";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","hd200308041410");
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
3. 使用DriverManager替换Driver
public void connect03() throws Exception{
Class c = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)c.getDeclaredConstructor().newInstance();
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url,user,password);
System.out.println(connection);
}
4. 使用Class.forName自动完成注册驱动(简化代码)(最常用)
实际在mysql5.1.6之后的版本都无需注册驱动,而是自动注册好了,但是还是建议多写一行注册驱动的语句
public void connect04() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
// Class.forName()可以自动完成注册驱动
// 因此可以少写一句DriverManager.registerDriver(driver)
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
5. 使用配置文件,连接数据库更加灵活
public void connect05() throws Exception {
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
135.ResultSet(结果集)
public void connect04() throws Exception{
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
String sql = "select id,name,sex,borndate from actor";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
//会自动向后查看后面还有没有数据,如果没有数据返回false
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String sex = resultSet.getNString(3);
Date borndate = resultSet.getDate(4);
System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate);
}
resultSet.close();
statement.close();
connection.close();
}
配置文件内容
user=root
password=hd200308041410
url=jdbc:mysql://localhost:3306/shop
driver=com.mysql.jdbc.Driver
日期加减方法DATE_ADD()和查看现在时间NOW()
SELECT * FROM emp
WHERE DATE_ADD(hiredate,INTERVAL 12 YEAR) <NOW();
– 工作相同按照job降序排序,不同按照sal进行默认排序
SELECT ename,job,sal FROM emp
ORDER BY job DESC,sal
– 取出三十天内员工的日工资,忽略余数
– FLOOR函数不会四舍五入,会直接取出整数部分
– ROUND函数会四舍五入
SELECT ename,ROUND(sal/30) FROM emp;
– 显示员工加入公司的天数
SELECT ename,DATEDIFF(NOW(),hiredate)
FROM emp;
– 显示员工加入公司的年月日
SELECT ename,CONCAT((YEAR(NOW())-YEAR(hiredate)),‘-’,(MONTH(NOW())-MONTH(hiredate)),‘-’,(DAY(NOW())-DAY(hiredate)))
FROM emp;
– 得到至少有一个人的部门
SELECT COUNT() AS c,deptno
FROM emp
GROUP BY deptno
HAVING c>1;
– 查出薪水大于SMITH的员工
SELECT * FROM emp
WHERE sal >
(SELECT sal FROM emp
WHERE ename = ‘SMITH’)
– 查出受雇日期晚于直接上级的员工
SELECT worker.ename AS ‘员工名’,worker.hiredate AS ‘下属入职日期’,
leader.ename AS ‘上级名’ ,worker.hiredate AS ‘上级入职日期’
FROM emp worker,emp leader
WHERE worker.hiredate>leader.hiredate
AND worker.mgr = leader.empno
– 列出部门名称和这些部门的员工信息,同时列出没有员工的部门
– 要使用左外连接
SELECT dname,emp. FROM dept
LEFT JOIN emp ON dept.deptno
= emp.deptno
– 列出所有办事员(CLERK)的名称及其部门名称
SELECT emp.ename,dept.dname FROM emp,dept
WHERE emp.job = ‘CLERK’ AND emp.deptno
= dept.deptno
– 列出最低薪金高于公司平均薪金的所有员工
SELECT MIN(sal) AS min_sal,job
FROM emp
GROUP BY job
HAVING min_sal<1500
– 列出’SALES’(销售部)工作的员工的姓名
SELECT ename,dname
FROM emp,dept
WHERE emp.deptno = dept.deptno
AND dname = ‘SALES’;
– 得到高于三十号部门所有员工工资的员工信息
SELECT ename,sal FROM emp
WHERE sal>(
SELECT MAX(sal) FROM emp WHERE deptno = 30)
– 得到每个部门员工数量,平均工资和平均服务时间
SELECT COUNT() AS ‘部门员工数量’,
AVG(sal) AS ‘部门平均工资’,
AVG(DATEDIFF(NOW(),hiredate))
FROM emp
GROUP BY deptno;
– 得到所有部门详细信息和部门人数
SELECT dept.,tmp.c AS ‘部门人数’ FROM dept,(
SELECT COUNT(*) AS c,deptno
FROM emp
GROUP BY deptno
)tmp
WHERE dept.deptno
= tmp.deptno
– 列出所以员工的年工资,按年薪从低到高排序
– 采用IFNULL方法,如果为空返回0,不为空返回comm
SELECT ename,(sal+IFNULL(comm,0))*12 AS ‘年工资’ FROM emp
ORDER BY ‘年工资’;
### 133.JDBC原理
#### 1. 介绍
JDBC为访问不同的数据库提高统一的接口,为使用者屏蔽了细节问题
java程序员可以使用JDBC连接任何提供JDBC驱动程序的数据库系统,从而完成对数据库的操作
[外链图片转存中...(img-IYeH2gZ7-1659110652913)]
[外链图片转存中...(img-k09a3HsR-1659110652914)]
#### 2. JDBC编写步骤
[外链图片转存中...(img-VFci1fZW-1659110652914)]
#### 3. 使用JDBC操作数据库
```java
public void connect04() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
// Class.forName()可以自动完成注册驱动
// 因此可以少写一句DriverManager.registerDriver(driver)
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
Connection conn = DriverManager.getConnection(url,user,password);
String sql = "insert into actor values(1,'肖狗')";
Statement statement = conn.createStatement();
for (int i = 0; i < 5; i++) {
int rows = statement.executeUpdate(sql);
System.out.println(rows > 0 ? "成功" : "失败");
}
statement.close();
conn.close();
}
134.java程序获得数据库连接方式
1.静态加载Driver实现类对象
因为是直接new一个Driver,所以灵活性不强
public void connect01() throws Exception{
//静态加载Driver
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/作业";
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hd200308041410"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
2 .使用反射进行动态加载
public void connect02() throws Exception{
//反射加载Driver,动态加载,减少依赖性
Class c = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)c.getDeclaredConstructor().newInstance();
String url = "jdbc:mysql://localhost:3306/作业";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","hd200308041410");
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
3. 使用DriverManager替换Driver
public void connect03() throws Exception{
Class c = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)c.getDeclaredConstructor().newInstance();
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url,user,password);
System.out.println(connection);
}
4. 使用Class.forName自动完成注册驱动(简化代码)(最常用)
实际在mysql5.1.6之后的版本都无需注册驱动,而是自动注册好了,但是还是建议多写一行注册驱动的语句
public void connect04() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
// Class.forName()可以自动完成注册驱动
// 因此可以少写一句DriverManager.registerDriver(driver)
String url = "jdbc:mysql://localhost:3306/作业";
String user = "root";
String password = "hd200308041410";
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
5. 使用配置文件,连接数据库更加灵活
public void connect05() throws Exception {
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
135.ResultSet(结果集)
public void connect04() throws Exception{
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
String sql = "select id,name,sex,borndate from actor";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
//会自动向后查看后面还有没有数据,如果没有数据返回false
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String sex = resultSet.getNString(3);
Date borndate = resultSet.getDate(4);
System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate);
}
resultSet.close();
statement.close();
connection.close();
}
配置文件内容
user=root
password=hd200308041410
url=jdbc:mysql://localhost:3306/shop
driver=com.mysql.jdbc.Driver