Java 学习笔记
文章目录
- Java 学习笔记
- 1、面向对象编程
- 2、Object类的函数
- 3、断点调试
- 4、枚举和注解
- 5、异常
- 6、常用类
- 7、集合
- 8、泛型
- 9、线程
- 10、IO流
- 11、网络编程
- 12、反射
- 13、数据库(MySQL)
- 14、JDBC和数据库连接池
- 15、正则表达式
- Z、设计模式
- 番外1:@SuppressWarnings参数大全
- 番外2:JUnit
- 番外3:坦克大战
- 番外4、多用户通信系统
- 番外5、mysql权限
1、面向对象编程
1、多态
Animal animal = new Cat();
animal为编译类型,cat为运行类型。
向上转型:此时,你可以调用animal的各种属性和方法,但是不能使用cat的。
Animal animal = new Cat();
Cat cat = (Cat) animal;
向下转型:前提是此时的animal是cat, 才能有第二行。
向下转型是为了调用运行类型的特殊方法。
多态后,不存在属性重写的说法,属性的值看编译类型。
bb instanceof AA;
instanceof用来判断对象的动态类型是哪个类或是哪个类的子类。
2、动态绑定机制
选择方法时,都是优先选择运行类型的方法,找不到才会通过继承规则向父类再寻找。
- 如果方法中涉及方法的时候,重复上面的寻找思路;
- 如果方法中涉及到对象属性的时候,优先选择当前域里面的属性值。
3、static
静态变量\类变量\静态属性
- static 变量是同一个类所有对象共享的;
- static类变量,在类加载的时候就生成了。所以可以通过类,无需实例对象就能调用
静态方法\类方法
- 静态方法里面只能访问静态变量或静态方法;
- 静态方法是跟类有关的,所以静态方法里不能使用this和super;
- 使用方法:通过类或者对象调用;
- 使用场景:工具类像Math类,一些通用方法,比如打印一维数组,冒泡排序等。。
4、代码块
1)static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载1而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
class A{
private int n1 = 10;
static{
System.out.println("A");
}
}
class B extends A{
private int n2 = 20;
public static int totalNum = 100;
static{
System.out.println("B");
}
}
class C{
private int n1 = 100;
public static int total = 200;
{
System.out.println("C的普通代码块。。。");
}
}
2)普通代码块,在创建对象实例时,会被隐式的调用。如果只是使用类的静态成员时,普通代码块不会执行。
3)创建一个对象时,在一个类调用顺序是:
- 调用静态代码块和静态属性初始化;(静态代码块和静态属性优先级一样,会按照定义顺序依次)
- 调用普通代码块和普通属性的初始化;(优先级一样,按照定义顺序依次调用)
- 调用构造方法。
4)创建一个子类对象时,调用顺序:
- 父类的静态代码块和静态属性;
- 子类的静态代码块和静态属性;
- 父类的普通代码块和普通属性初始化;
- 父类的构造方法;
- 子类的普通代码块和普通属性初始化;
- 子类的构造方法。
5)静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。
5、final
1)当你不希望类被继承,可以用final修饰;
2)当不希望父类的某个方法被子类覆盖/重写时,可以用final修饰;
3)当不希望类的某个属性的植被修改,可以用final修饰;
4)当不希望某个局部变量被修改,可以用final修饰。
final使用注意事项和细节:
1)final修饰的属性又叫常量,一般用 XX_XX_XX 来命名;
2)final修饰的属性,必须赋初值,一般在:1、定义时;2、构造器中;3、代码块中
3)如果final修饰的属性是静态的,则初始化的位置只能是:1、定义时;2、静态代码块
4)如果一个类已经被final修饰,那就没必要将方法用final修饰
5)final不能修饰构造器
6)final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
7)包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
6、抽象类
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
abstract class Animal{
//具体咋吃不知道。。。。
//并且无实际意义
public abstract void eat();
}
-
抽象类的介绍:
1)抽象方法是没有方法体的;
2)抽象类的价值更多作用是在于设计;
3)抽象类,在框架和设计模式使用较多。
-
注意点:
1)抽象类不能被实例化;
2)抽象类不一定要有抽象方法;
3)abstract只能修饰类和方法;
4)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类;
5)抽象方法不能使用private、final和static来修饰。
7、接口--------(规范)
public interface UsbInterface{//接口
//规定接口的相关方法.....规范
public void start();
public void stop();
}
public class Camera implements UsbInterface{//实现接口
@Override
public void start(){
System.out.println("相机开始工作...");
}
@Override
public void stop(){
System.out.println("相机停止工作...");
}
}
public class Phone implements UsbInterface{
@Override
public void start(){
System.out.println("手机开始工作...");
}
@Override
public void stop(){
System.out.println("手机停止工作...");
}
}
public class Computer{
//编写一个方法,计算机工作
public void work(UsbInterface usbInterface){
//通过接口,来调用方法,能自动识别类(手机还是相机)
usbInterface.start();
usbInterface.stop();
}
}
public class Interface01{
public static void main(String[] args){
//创建手机,相机对象
Camera camera = new Camera();
Phone phone = new Phone();
//创建电脑
Computer computer = new Computer();
computer.work(phone);//把手机接入电脑
computer.work(camera);//把相机接入电脑
}
}
- 基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要是用的时候,在根据具体情况把这些方法写出来。
小结:
- 在JDK7.0前 接口里的所有方法都没有方法体;
- JDK8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。
public interface AInterface{
//写属性
public int n1 = 10;
//写方法
//在接口中,抽象方法,可以省略abstract关键字
public void hi();
//在jdk8后,可以有默认方法,需要使用default关键字修饰
default public void ok(){
System.out.println("ok...");
}
//在jdk8后,可以有静态方法
public static void cry(){
System.out.println("cry...");
}
}
public class Interface02{
public static void main(String[] args){
}
}
class A implements AInterface{
@Override
public void hi(){
System.out.println("hi().....");
}
}
-
注意点和细节
1)接口不能被实例化;
2)接口中所有的方法是public方法,接口中抽象方法可以不用abstract;
3)一个普通类实现接口,就必须将该接口的所有方法都实现;
4)抽象类实现接口,可以不用实现接口的方法;
5)一个类同时可以实现多个接口;
6)接口中的属性,只能是public static final修饰符;
int a = 1; //实际上是 public static final int a = 1;
7)接口中属性的访问,直接通过接口名;
8)一个接口不能继承其他类,但是可以继承多个别的接口;
interface A extends B,C{}
-
接口VS继承类
-
接口和继承解决的问题不同
继承:解决代码的复用性和可维护性。
接口:设计,设计好各种规范。
-
接口比继承更加灵活
-
接口在一定程度上实现代码解耦(即:接口规范性+动态绑定)
-
-
接口的多态特性
1)多态参数,比如前面的Usb接口案例(接口引用可以指向实现了接口的类的对象);
2)多态数组;
public class Interface03{ public static void main(String[] args){ Usb[] usbs = new Usb[2]; usbs[0] = new Phone(); usbs[1] = new Camera(); for(int i=0;i<usbs.length;i++){ usbs[i].work();//动态绑定 //向下转型 if(usbs[i] instanceof Phone){ ((Phone) usbs[i]).call(); } } } } interface Usb{ void work(); } class Phone implements Usb{ public void call(){} public void work(){} } class Camera implements Usb{ public void work(){} }
3)接口存在多态传递现象。
public class Interface04{ public static void main(String[] args){ IG ig = new Teacher(); //如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口 //那么,实际上就相当于 Teacher 类也实现了 IH接口。 IH ih = new Teacher(); } } interface IH{ void hi(); } interface IG extends IH{} class Teacher implements IG{ public void hi(){} }
8、内部类
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
-
内部类的分类:
定义在外部类局部位置上(比如方法内):
1)局部内部类(有类名)
2)匿名内部类(没有类名⭐️⭐️⭐️)
定义在外部类的成员位置上:
1)成员内部类(没用static修饰)
2)静态内部类(使用static修饰)
1、局部内部类
- 可以直接访问外部类的所有成员,包含私有;
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final;
- 作用域:在定义它的方法或代码块中;
- 局部内部类----访问---->外部类成员【直接访问】
- 外部类—访问—>局部内部类的成员【创建对象,再访问(必须在作用域内)】
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
class Outer01{//外部类
private int n1 = 100;
private void m2(){}//私有方法
public void m1(){//方法
//局部内部类是定义在外部类的局部位置,通常在方法中
final class Inner01{//局部内部类(此处final看上面4、final章节)
//可以直接访问外部类的所有成员,包含私有
private int n1 = 800;
public void f1(){
//同名,想要使用外部类的成员,得(外部类名.this.成员)
System.out.println("n1=" + n1 + "外部类的n1" + Outer01.this.n1);
m2();
}
}
//外部类在方法中,可以创建Inner01对象,然后调用方法即可
Inner01 inner01 = new Inner01();
inner01.f1();
}
{//代码块
class Inner02{
}
}
}
2、匿名内部类
匿名内部类,同时也是个对象。表面没名字,实际底层自动生成名字(例如:外部类名$1)
- 可以直接访问外部类的所有成员,包含私有的;
- 不能添加访问修饰符,因为地位就是个局部变量;
- 作用域:定义它的方法或代码块中;
- 匿名内部类—访问—>外部类成员【直接访问】;
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
class Outer02{//外部类
private int n1 = 10;//属性
public void method(){//方法
//基于接口的匿名内部类
//需求:想要定义一个类,但只想使用一次,后面不再使用
IA tiger = new IA(){
@Override
public void cry(){
System.out.println("老虎叫唤...");
}
};
tiger.cry();
//基于类的匿名内部类
Father father = new Father("jack"){
}
//也可以直接调用
new Father(){
@Override
public void test(){
System.out.println("hihihi方法");
}
@Override
public void ok(String str){
System.out.println(str);
}
}.ok("jack");
}
}
interface IA{//接口
public void cry();
}
class Father{//类
public Father(String name){//构造器
}
public void test(){//方法
}
public void ok(String str){//方法
}
}
-
匿名内部类的最佳实践
当做实参直接传递,简洁高效。
public class InnerClassExercise01{
public static void main(String[] args){
//当做实参直接传递,简洁高效
f1(new IL(){
@Override
public void show(){
System.out.println("只是还爱着你啊");
}
});
}
//静态方法
public static void f1(IL il){
il.show();
}
}
//接口
interface IL{
void show();
}
3、成员内部类
成员内部类是定义在外部类的成员位置,并且没有static修饰。
- 可以直接访问外部类的所有成员,包含私有的;
- 可以添加任意访问修饰符,因为它的地位就是一个成员;
- 作用域:和外部类的其他成员一样;
- 成员内部类—访问—>外部类【直接访问】;
- 外部类—访问—>内部类【创建对象,再访问】;
- 外部其他类—访问—>成员内部类【见代码】;
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
public class InnerClassExercise02{
public static void main(String[] args){
Outer03 outer03 = new Outer03();
//外部其他类,使用成员内部类的三种方式
//第一种方式
Outer03.Inner03 inner03 = outer03.new Inner03();
//第二种方式
Outer03.Inner03 inner03Instance = outer03.getInner03Instance();
//第三种,跟第一种一样
Outer03.Inner03 inner0333 = new Outer03().new Inner03();
}
}
class Outer03{
private int n1 = 10;
public String name = "张三";
class Inner03{
public void say(){
System.out.println("Outer03的n1 = " + n1);
}
}
//方法,返回一个Inner03实例
public Inner03 getInner03Instance(){
return new Inner03();
}
public void t1(){
Inner03 inner03 = new Inner03();
inner03.say();
}
}
4、静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰。
- 可以直接访问外部类的所有静态成员,包含私有,但不能访问非静态成员;
- 可以添加任意修饰符,地位是一个成员;
- 作用域:同其他成员,为整个类体;
- 静态内部类—访问—>外部类【直接访问所有静态成员】;
- 外部类—访问---->静态内部类【创建对象,再访问】;
- 外部其他类—访问—>静态内部类【见代码】。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。【此处不一样】
public class StaticInnerClass01{
public static void main(String[] args){
Outer04 outer04 = new Outer04();
outer04.m1();
//外部其他类,使用静态内部类
//方式1
//静态内部类,是可以直接通过类名直接访问的(前提是满足访问权限)
Outer04.Inner04 inner04 = new Outer04.Inner04();
//方式2
//编写方法,返回对象
Outer04.Inner04 inner041 = new Outer04.getInner04();
//这种不用创建类对象
Outer04.Inner04 inner04_ = Outer04.getInner04_();
}
}
class Outer04{
private int n1 = 10;
private static String name = "张三";
static class Inner04{
public void say(){
System.out.println(name);
}
}
public void m1(){
Inner04 inner04 = new Inner04();
inner04.say();
}
public Inner04 getInner04(){
return new Inner04();
}
public static Inner04 getInner04_(){
return new Inner04();
}
}
2、Object类的函数
1、==和equals的对比
==:
- 可以判断基本类型,判断值是否相等
- 可以判断两个对象,判断的是地址是否相等
equals:
- 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相同(比如String/Integer)
String str1 = new String("hehe");
String str2 = new String("hehe");
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
//源码里Integer重写了Object的equals方法
public boolean equals(Object obj){
if(obj instanceof Integer){
return value == ((Integer)obj).intValue();
}
return false;
}
2、hashCode()
返回对象的哈希码值,可以提升哈希结构容器的效率,必要时也会重写该方法。
3、toString()
//getClass().getName() 返回类的全类名(包名+类名)
public String toString(){
return getClass().getName() + "@" + Integer.toHexString(hashcode());
}
一般输出对象的时候,自动调用 对象.toString()。
4、finalize()
在销毁对象前,会调用该函数。(一般可以在该方法中,写自己的业务逻辑,比如释放资源:数据库连接,打开文件…)
垃圾回收机制的调用,是由系统来决定(即有自己的GC算法),也可以通过System.gc()主动触发垃圾回收机制。
3、断点调试
F7:跳入方法内
F8:逐行执行代码
shift+F8:跳出方法
F9:执行到下一个断点
4、枚举和注解
1、枚举
枚举是一组有限的,特定的对象。
1、自定义枚举类
- 不需要提供setXxx()方法,因为枚举对象值通常为只读;
- 对枚举对象/属性使用final+static共同修饰,实现底层优化;
- 枚举对象名通常使用全部大写,常量的命名规范;
- 枚举对象可以有多个属性。
class Season{
private String name;
private String desc;
public static final Season SPRING = new Season("春天","温暖");
public static final Season SUMMER = new Season("夏天","炎热");
public static final Season AUTUMN = new Season("秋天","凉爽");
public static final Season WINTER = new Season("冬天","寒冷");
//1.将构造器私有化,目的防止直接new
//2.将setXX()去掉, 防止属性被修改
//3.在Season内部,直接创建固定的对象
//4.优化,可以加入final修饰
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
}
2、enum关键字
enum Season{
// public static final Season SPRING = new Season("春天","温暖");
// public static final Season SUMMER = new Season("夏天","炎热");
// public static final Season AUTUMN = new Season("秋天","凉爽");
// public static final Season WINTER = new Season("冬天","寒冷");
//1.使用关键字enum替代class
//2.public static final Season SPRING = new Season("春天","温暖");变成
// SPRING("春天","温暖")
//3.如果有多个常量,使用逗号隔开
//4.常量对象要写在最前面
//5.如果使用的是无参构造器,可以省略()
SPRING("春天","温暖"),WINTER("冬天","寒冷"),AUTUMN("秋天","凉爽"),
SUMMER("夏天","炎热"),WHAT;
private String name;
private String desc;
private Season(){
}
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
}
3、enum常用方法
- toString:Enum类已经重写过了,返回的是当前对象名,子类可以重写该方法,用于返回对象的属性信息;
- name:返回当前对象名(常量名),子类中不能重写;
- ordinal:返回当前对象的位置号,默认从0开始;
- values:返回当前枚举类中所有的常量;
- valueof:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常;
- compareTo:比较两个枚举常量,比较的就是位置号。
public class EnumMethod{
public static void main(String[] args){
Season autumn = Season.AUTUMN;
System.out.println(autumn.name());//AUTUMN
System.out.println(autumn.ordinal());//2
Season[] values = Season.values();
for(Season season: values){
System.out.println(season);
}
//通过名字拿对象
Season autumn1 = Season.valueof("AUTUMN");
System.out.println(autumn1);
System.out.println(Season.AUTUMN.compareTo(Season.SUMMER));
//2-3 = -1
}
}
enum Season{
SPRING("春天","温暖"),WINTER("冬天","寒冷"),AUTUMN("秋天","凉爽"),
SUMMER("夏天","炎热");
private String name;
private String desc;
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
}
4、Enum使用细节
1)使用enum关键字后,就不能在继承其他类了,因为enum会隐式继承Enum,而Java是单继承机制
2)枚举类可以和普通类一样实现接口;
2、注解
1)注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
2)和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息;
3)在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。
在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等。
- 三个基本的Annotation:
1)@Override:限定某个方法,是重写父类方法,该注解只能用于方法;
2)@Deprecated: 用于表示某个程序元素(类、方法等)已过时;
3)@SuppressWarnings: 抑制编译器警告。
class Father{
public void fly(){
...
}
}
class Son extends Father{
//重写父类方法
//该注解,会让编译器去检查该方法是否真的重写了
@Override
public void fly(){
...
}
}
/**
* @Override实际上是
*
* @Target(ElemenType.METHOD)
* @Retention(RetentionPolicy.SOURCE)
* public @interface Override{
* }
*
* @interface表示是一个注解类
* @Target是修饰注解的注解,称为元注解
*/
//@Deprecated 过时的意思:不在推荐使用,但是仍然可以使用
//标志是,使用时名字会有一条横线
//可以修饰方法、类、字段、包、参数等
//@Deprecated 可以做版本升级过渡使用
@Deprecated
class A{
@Deprecated
public int n1 = 100;
@Deprecated
public void hi(){}
}
/**
* @Documented
* @Retention(RetentionPolicy.RUNTIME)
* @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, * PARAMETER, TYPE})
* public @interface Deprecated {
* }
*/
public class Test{
//当我们不希望看到这些警告的时候@SuppressWarnings
//在{""}中添加希望抑制的警告信息
@SuppressWarnings({"rawtypes","unchecked","unused"})
public static void main(String[] args){
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
int i;
System.out.println(list.get(1));
}
}
/*
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
*/
@SuppressWarnings的参数大全请参考番外1
- 元注解🍏
1)Retention //指定注解的作用范围,三种SOURCE,CLASS,RUNTIME
2)Target //指定注解可以在那些地方使用
3)Documented //指定该注解是否会在javadoc体现
4)Inherited //子类会继承父类注解
@Retention的三种值:🚷
1)RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释;
2)RetentionPolicy.CLASS: 编译器将把注解记录在class文件中,当运行Java程序时,JVM不会保留注解。这是默认值;
3)RetentionPolicy.RUNTIME: 编译器将把注解记录在class文件中,当运行Java程序时,JVM会保留注解。程序可以通过反射获取该注解。
5、异常
- 执行过程中所发生的异常事件可分为两大类
1)Error(错误):Java虚拟机无法解决的问题。如:JVM系统内部错误、资源耗尽
等严重情况。比如:StackOverflowError【栈溢出】和OOM(out of memory),Error是严重错误,程序会崩溃。
2)Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断
等等,Exception分为两大类:运行时异常😈和编译时异常😧。
1、常见的运行时异常
3)ArrayIndexOutOfBoundsException数组下标越界异常
5)NumberFormatException数字格式不正确异常
1、NullPointerException空指针异常
当应用程序试图在需要对象的地方使用null时,抛出该异常。
public class NullPointerException_{
public static void main(String[] args){
String name = null;
System.out.println(name.length());
}
}
2、ArithmeticException数学运算异常
当出现异常的运算条件时,抛出异常。
public class ArithmeticException_{
public static void main(String[] args){
int num1 = 100;
int num2 = 0;
System.out.println(num1/num2);
}
}
3、ArrayIndexOutOfBoundsException数组下标越界异常
用非法索引访问数组时抛出异常。
int[] arr = new int[4];
System.out.println(arr[4]);
4、ClassCastException类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出此异常。
public class ClassCastException_{
public static void main(String[] args){
A a = new B(); //向上转型
B b = (B)a; //向下转型
C c = (C)a; //No please
}
}
class A{}
class B extends A{}
class C extends A{}
5、NumberFormatException数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但字符串不能转换为适用格式时,抛出异常。
使用异常我们可以确保输入满足条件数字。
public class NumberFormatException_{
public static void main(String[] args){
String name = "老子真帅";
int num = Integer.parseInt(name);
}
}
2、常见的编译异常
🎠SQLException:操作数据库时,查询表可能发生异常;
🐹IOException:操作文件时,发生的异常;
🐥FileNotFoundException:当操作一个不存在的文件时,发生异常;
🐣ClassNotFoundException:加载类,而该类不存在时,异常;
🙉EOFException:操作文件,到文件末尾,发生异常;
🐲IllegalArguementException:参数异常。
3、throws处理机制
- try-catch-finally 和 throws二选一;
- 如果程序员,没有显式处理异常,默认throws;
- 始终不处理的话,最终抛到 JVM 后,就会导致抛出异常,程序停止。
4、try-catch异常处理
1)如果异常发生了,则异常发生后面的代码不会执行,直接进入catch块;
2)如果异常没有发生,则顺序执行try的代码块,不会进入catch;
3)如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用finally{}。
4)可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在 前,比如( Exception 在后,NullPointerException 在前),如果发生异常,只会匹配一个catch。
5)可以进行 try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉。
应用场景:就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。
public class TryCatchDetail{
public static void main(String[] args){
//crtl + alt + t
try{
//String str = "123";
String str = "爷真帅";
int a = Integer.parseInt(str);
System.out.println("数字:" + a);
} catch (NumberFormatException e){
System.out.println("异常信息=" + e.getMessage());
} finally {
System.out.println("finally代码执行");
}
System.out.println("程序依旧执行");
}
}
public class TryCatchDetail2{
public static void main(String[] args){
try{
Person person = new Person();
person = null;//NullPointerException
int n1 = 10;
int n2 = 0;
int res = n1 / n2;//ArithmeticException
} catch(NullPointerException e) {
System.out.println("空指针异常=" + e.getMessage());
} catch(ArithmeticException e) {
System.out.println("数学运算异常=" + e.getMessage());
} catch (Exception e){
System.out.println("异常信息=" + e.getMessage());
} finally {
}
}
}
class Person{
private String name = "Tom";
public String getName(){
return name;
}
}
5、throws异常处理
- 基本介绍
1)如果一个方法可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
public class Throws01{
public static void main(String[] args){
}
//或者 throws Exception
public void f1() throws FileNotFoundException,NullPointerException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
}
- 注意事项和使用细节
1)对于编译异常,程序中必须处理(显式处理),比如try-catch或者throws;
2)对于运行时异常,程序中如果没有处理,默认就是throws的方式处理(意味着可以不显式处理);
3)子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型。
4)在throws 过程中,如果有方法try-catch,就相当于处理异常,就可以不必throws。
class Father{//父类
public void method() throws RuntimeException{
}
}
class Son extends Father{//子类
public void method() throws NullPointerException{
}
}
6、自定义异常
1)定义类:自定义异常类 ,继承Exception或RuntimeException;
2)如果继承Exception,属于编译异常;
3)如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)。
public class CustomException{
public static void main(String[] args){
int age = 180;
//要求范围在18 - 120之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)){
throw new AgeException("年龄需要在 18-120 之间");
}
}
}
//自定义的异常
class AgeException extends RuntimeException{
public AgeException(String message){
super(message);
}
}
6、常用类
1、包装类 Wrapper
-
针对八种基本数据类型相应的引用类型----包装类
Boolean/Character/Byte/Short/Integer/Long/Float/Double
-
包装类和基本数据的转换
public class Integer01{
public static void main(String[] args){
//jdk5前是手动装箱和拆箱
//手动装箱,int--->Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱
//Integer--->int
int i = integer.intValue();
//jdk5后,就可以自动装箱和拆箱
int n2 = 200;
//装箱
Integer integer2 = n2;//底层依旧是Integer.valueOf(n2)
//拆箱
int n3 = integer2;//底层依旧是intValue()
}
}
- 包装类型和String类型的相互转换
//包装类型----->String类型
Integer i = 10;
//方式1
String s1 = i.toString();
//方式2
String s2 = String.valueOf(i);
//方式3
String s3 = i + "";
//String------------>包装类
//方式1
Integer i1 = new Integer(s1);
//方式2
Integer i2 = new Integer.valueOf(s2);
//方式3
Integer i3 = Integer.parseInt(s3);
- Integer类和Character类的常用方法
System.out.println(Integer.MIN_VALUE);//返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
2、String类
1)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节;
2)String类较常用的构造方法:
- String s1 = new String();
- String s2 = new String(String original);
- String s3 = new String(char[] a);
- String s4 = new String(char[] a, int startIndex, int count);
3)String类实现了接口 Serializable【String 可以串行xing第二声化:可以在网络传输】
接口 Comparable 【String 对象可以比较大小】
4)String有属性 private final char value[]; 用来存放字符串内容
注意:此处的final意味着,value的地址不能修改,但是字符内容是可以修改的。
1、创建String对象的两种方式
1)方式一:直接赋值 String s = “shuaibi”;
2)方式二:调用构造器 String s = new String(“shuaibi”);
- 两种创建的区别:
- 方式一:先从常量池查看是否有“shuaibi”数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址;
- 方式二:先在堆中创建空间,里面维护了value属性,指向常量池的shuaibi空间。如果常量池没有“shuaibi”,重新创建,如果有,直接通过value指向。最终 s 指向的是堆中的空间地址。
2、字符串的特性
String c = "hello" + "abc";
//实际上底层优化是 String c = "helloabc";
//所以是直接指向常量池
String a = "hello";
String b = "abc";
String c = a + b;
//底层是 StringBuilder sb = new StringBuilder();
// sb.append(a);
// sb.append(b);
// c = sb.toString();
//所以 c实际上指向的是堆,堆中的value指向的常量池
3、String类的常见方法
String类是保存字符串常量的。每次更新都需要开辟空间,效率较低,因此java设计者还提供了 StringBuilder 和 StringBuffer 来增强String的功能,并提高效率。
👇👇👇String类的常见方法
- equals //区分大小写,判断内容是否相等
- equalsIgnoreCase //忽略大小写的判断内容是否相等
- length //获取字符的个数,字符串的长度
- indexOf //获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1
- lastIndexOf //获取字符在字符串中最后一次出现的索引,索引从0开始,如找不到,返回-1
- substring //截取指定范围的子串
- trim //去前后空格
- charAt //获取某索引处的字符,注意不能使用Str[index] 这种方式
public class StringMethod01{
public static void main(String[] args){
//equals
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));
//equalsIgnoreCase
String username = "JOHN";
if("john".equalsIgnoreCase(username)){
System.out.println("Success");
}else{
System.out.println("failure");
}
//length
System.out.println("failure".length());
//indexOf
String s1 = "abcde@fgh";
int index = s1.indexOf('@');
System.out.println(index);
//lastIndexOf
String s1 = "abc@de@fg@h";
int index = s1.lastIndexOf('@');
System.out.println(index);
System.out.println(s1.lastIndexOf('de'));
//substring
String name = "hello,张三";
//从索引6开始截取后面的所有内容
System.out.println(name.subString(6));
//从索引0开始,截取到5-1 = 4 位置
System.out.println(name.subString(0,5));
}
}
- toUpperCase //
- toLowerCase //
- concat // 拼接字符串
- replace // 替换字符串中的字符
- split // 分割字符串
- compareTo // 比较两个字符串的大小
- toCharArray // 转换成字符数组
- format // 格式字符串
public class StringMethod01{
public static void main(String[] args){
//toUpperCase
String s = "hello";
System.out.println(s.toUpperCase());
//toLowerCase
System.out.println(s.toLowerCase());
//concat
String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);
//replace
String s1 = "宝玉 and 薛宝钗 薛宝钗";
s1 = s1.replace("薛宝钗","林黛玉");
System.out.println(s1);
//split
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
String[] split = poem.split(",");
String poem = "E:\\aaa\\bbb";
split = poem.split("\\\\"); //这里得用转义字符
for(int i = 0;i<split.length;i++){
System.out.println(split[i]);
}
//toCharArray
s = "happy";
char[] chs = s.toCharArray();
for(int i=0;i<chs.length;i++){
System.out.println(chs[i]);
}
//compareTo
String a = "jchn";
String b = "jack";
System.out.println(a.compareTo(b)); //返回值是 'c' - 'a' = 2
//format
//%s %c %d %.2f
String name = "john";
int age = 10;
double score = 98.3 / 3;
char gender = '男';
String info = String.format("我的姓名是%s年龄是%d,成绩是%.2f性别是%c。在线征 婚",name,age,score,gender);
System.out.println(info);
}
}
3、StringBuffer类
- 基本介绍
1)java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。
2)StringBuffer是可变长度的。
3)StringBuffer是一个容器
- StringBuffer结构解读
1)StringBuffer的直接父类是 AbstractStringBuilder;
2)StringBuffer实现了 Serializable,即StringBuffer的对象可以串行化;
3)在父类中,AbstractStringBuilder有属性 char[] value, 不是final,因此存放在堆中。
1、String VS StringBuffer
1)String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低;
2)StringBuffer保存的是字符串常量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用更新地址,效率较高。
2、StringBuffer构造器
//16
StringBuffer stringBuffer = new StringBuffer();
//100
StringBuffer stringBuffer1 = new StringBuffer(100);
//char[] 的大小是 str.length + 16
StringBuffer stringBuffer2 = new StringBuffer("hello");
3、String 和 StringBuffer 相互转换
public class StringAndStringBuffer{
public static void main(String[] args){
//String--->StringBuffer
String str = "hello";
//方式1:使用构造器
StringBuffer stringBuffer = new StringBuffer(str);
//方式2:使用append方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//StringBuffer--->String
StringBuffer stringBuffer3 = new StringBuffer("shuaibi");
//方式1:toString()
String s = stringBuffer3.toString();
//方式2:使用构造器
String s1 = new String(stringBuffer3);
}
}
4、StringBuffer类常见方法
1)增 append
2)删 delete(start,end)
3)改 replace(start,end,string) //将start—end间的内容替换掉,不含end
4)查 indexOf //查找子串在字符串第一次出现的索引,如果找不到返回-1
5)插 insert
6)获取长度 length
public class StringBufferMethod{
public static void main(String[] args){
StringBUffer s = new StringBuffer("hello");
//增
s.append(',');
s.append("张三丰");
s.append("赵敏").append(100).append(true).append(10.5);
System.out.println(s);
//hello,张三丰赵敏100true10.5
//删
//删除索引 >=start && <end 处的字符
s.delete(11,14);
System.out.println(s);
//hello,张三丰赵敏true10.5
//改
s.replace(9,11,"周芷若");
System.out.println(s);
//hello,张三丰周芷若true10.5
//查找
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);
//6
//插
s.insert(9,"赵敏");
System.out.println(s);
//hello,张三丰赵敏周芷若true10.5
//长度
System.out.println(s.length());
}
}
4、StringBuilder类
- 基本介绍
1)一个可变的字符序列。此类提供一个与StringBuffer 兼容的API,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
2)在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。
1、String、StringBuffer 和 StringBuilder的比较
1)String: 不可变字符序列,效率低,但是复用率高;
StringBuffer:可变字符序列、效率较高(增删)、线程安全;synchronized
StringBuilder:可变字符序列、效率最高、线程不安全。
2)如果我们对String 需要做大量修改,不要使用String。
5、Math类
abs/pow/ceil向上取整/floor向下取整/round四舍五入/sqrt/random/max/min
6、Arrays类
1、Arrays类常见方法
- toString 返回数组的字符串形式;
- sort 排序(自然排序和定制排序);
- binarySearch 通过二分搜索法进行查找,要求必须排好序;没找到返回一个负数
- copyOf 数组元素的复制;
- fill 数组元素的填充;
- equals 比较两个数组元素内容是否完全一致;
- asList 将一组值,转换成list
public class ArraysMethod01{
public static void main(String[] args){
Integer[] integers = {1,20,90};
//toString 返回数组的字符串形式
System.out.println(Arrays.toString(integers));
//[1,20,90]
//sort 排序
Integer arr[] = {1,-1,7,0,89};
Arrays.sort(arr);//默认排序
//[-1,0,1,7,89]
//定制排序
Arrays.sort(arr,new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
//return o1 - o2;
//[-1,0,1,7,89]
return o2 - o1;
//[89,7,1,0,-1]
}
});
}
}
public class ArraysMethod02{
public static void main(String[] args){
Integer[] arr = {1,2,90,123,567};
//binarySearch 通过二分搜索法进行查找,要求必须排好序
int index = Arrays.binarySearch(arr,123);//3
//copyOf 数组元素的复制
//如果拷贝的长度 > arr.length 就在新数组后面 增加null
//如果拷贝的长度 < 0,就抛出异常NegativeArraySizeException
//该方法底层使用的是 System.arraycopy()
Integer[] newArr = Arrays.copyOf(arr,arr.length);
//fill 数组元素的填充
Integer[] num = new Integer[]{9,3,2};
Arrays.fill(num,99);
//[99,99,99]
//equals 比较两个数组元素内容是否完全一致
Integer[] arr2 = {1,2,90,123};
boolean equals = Arrays.equals(arr,arr2);
//asList 将一组值,转换成list
//Arrays.asList(2,3,4,5,6,1) 实际上是 ArrayList类
List<Integer> asList = Arrays.asList(2,3,4,5,6,1);
System.out.println(asList);
}
}
7、System类
1)exit 退出当前程序;
System.exit(0);
//0 表示一个状态,正常的状态
2)arraycopy: 复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组;
int[] src = {1,2,3};
int[] dest = new int[3];
//源数组,起始索引,目标数组,起始索引,拷贝长度
System.arraycopy(src,0,dest,0,3);
3)currentTimeMillens:返回当前时间距离1970-1-1的毫秒数;
4)gc:运行垃圾回收机制。
8、BigInteger和BigDecimal类
应用场景:
1)BigInteger适合保存比较大的整型;
2)BigDecimal适合保存精度更高的浮点型(小数)
1、BigInteger和BigDecimal常见方法
1)add 加;2)subtract减;3)multiply乘;4)divide除
public class BigInteger_{
public static void main(String[] args){
//当我们编程中,需要处理很大的整数,long 不够用
//可以使用BigInteger的类来搞定
BigInteger bigInteger = new BigInteger("237888888999999999999999999999");
BigInteger bigInteger2 = new BigInteger("1009999999999999999999999999");
System.out.println(bigInteger);
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(substract);
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);
}
}
public class BigDecimal_{
public static void main(String[] args){
BigDecimal bigDecimal = new BigDecimal("1999.11111111199999999999999");
BigDecimal bigDecimal2 = new BigDecimal("1999.11111111888899999");
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//可能抛出异常ArithmeticException,因为无限循环除不尽
System.out.println(bigDecimal.divide(bigDecimal2));
//在调用divide 方法时,指定精度即可
System.out.println(bigDecimal.divide(bigDecimal2
,BigDecimal.ROUND_CEILING));
}
}
9、日期类
1、第一代日期类
1)Date:精确到毫秒,代表特定的瞬间;
2)SimpleDateFormat:格式和解析日期的类。它允许进行格式化(日期->文本 或 文本->日期)。
public class Date01{
public static void main(String[] args){
Date d1 = new Date(); //获取当前系统时间
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println(d1.getTime()); //获取某个时间对应的毫秒数
SimpleDateFormat sdf = new SimpleDateFormat(yyyy年MM月dd日 hh:mm:ss E);
String format = sdf.format(d1); //将日期转换成指定格式的字符串
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
}
}
2、第二代日期类
1)第二代日期类,主要就是Calendar类(日历)。
2)Calendar类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法
public class Calendar01{
public static void main(String[] args){
//Calendar是一个抽象类,并且构造器是private
//可以通过 getInstance()来获取实例
//提供大量方法和字段
//小时要变成24小时制, Calendar.HOUR=====>Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance();//创建日历类对象
System.out.println(c);
//获取日历对象的某个日历字段
System.out.println("年:"+c.get(Calendar.YEAR));
//月是从0开始的
System.out.println("月:"+(c.get(Calendar.MONTH)+1));
System.out.println("日:"+c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:"+c.get(Calendar.HOUR));
System.out.println("分钟:"+c.get(Calendar.MINUTE));
System.out.println("秒:"+c.get(Calendar.SECOND));
//Calendar 没有专门的格式化方法,需要程序员自己组合
System.out.println(c.get(Calendar.YEAR)+"年"+
(c.get(Calendar.MONTH)+1)+"月"+
c.get(Calendar.DAY_OF_MONTH)+"日");
}
}
3、第三代日期类
💩前两代日期类的不足:
1)可变性:日期和时间这样的类应该是不可变的;
2)偏移性:Date中的年份是从1900开始的,月份是从0开始的;
3)格式化:Calendar不能格式化;
4)它们不是线程安全的,不能处理闰秒等(每隔2天,多出1s)。
- 第三代日期类常见方法
1、LocateDateTime
1)LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间) JDK8
public class LocalDate_{
public static void main(String[] args){
//使用 now()获取当前日期时间对象
LocalDateTime ldt = LocalDateTime.now();
System.out.println("年="+ldt.getYear());
System.out.println("月="+ldt.getMonth());//返回英文月份
System.out.println("月="+ldt.getMonthValue());//返回数字
System.out.println("日="+ldt.getDayOfMonth());
System.out.println("时="+ldt.getHour());
System.out.println("分="+ldt.getMinute());
System.out.println("秒="+ldt.getSecond());
}
}
2、DateTimeFormatter格式日期类
public class DateTimeFormatter_{
public static void main(String[] args){
LocalDateTime ldt = LocalDateTime.now();
//参数查文档
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
String strDate = dtf.format(ldt);
System.out.println(strDate);
}
}
3、Instant时间戳
public class Instant_{
public static void main(String[] args){
//获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//通过from 可以把 Instant 转成 Date
Date date = Date.from(now);
//通过toInstant() 可以把 date 转成 Instant对象
Instant instant = date.toInstant();
}
}
4、更多方法
- MonthDay类:检查重复事件;
- 是否是闰年;
- 增加日期的某个部分;
- 使用plus方法测试增加时间的某个部分,(比如:890天后是啥日期);
- 使用minus方法测试查看一年前的日期,(比如:3456分钟前的日期)。
//将字符串指定部分进行反转
public static String reverse(String str,int start,int end){
//对输入数据进行校验
//先写出正确的情况,然后取反。 想错误的情况是难想的,想正确的容易
if(!(str != null && start >=0 && end > start && end < str.length())){
throw new RuntimeException("参数异常");
}
//把String 转成char[]
char[] chars = str.toCharArray();
char temp = '';
for(int i=start,j=end;i<j;i++,j--){
temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
return new String(chars);
}
7、集合
1)可以动态保存任意多个对象,使用比较方便;
2)提供了一系列方便地操作对象的方法:add、remove、set、get等;
1、集合框架体系
2、Collection
A、Collection接口常用方法
Collection接口常用方法,以实现子类ArrayList来演示。
1)add:添加单个元素;
2)remove:删除指定元素;
3)contains:查找元素是否存在;
4)size:获取元素个数;
5)isEmpty:判断是否为空;
6)clear:清空;
7)addAll:添加多个元素;
8)containsAll:查找多个元素是否都存在;
9)removeAll:删除多个元素。
public class CollectionMethod{
public static void main(String[] args){
List list = new ArrayList();
//add:添加单个元素
list.add("jack");
list.add(10); //list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
//remove:删除指定元素
list.remove(0); //删除第一个元素
list.remove("jack");//删除指定元素
System.out.println("list=" + list);
//contains:查找元素是否存在
System.out.println(list.contains("jack"));
//size:获取元素个数
System.out.println(list.size());
//isEmpty:判断是否为空
System.out.println(list.isEmpty());
//clear:清空
list.clear();
System.out.println("list=" + list);
//addAll:添加多个元素
List list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
//containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));
//removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);
}
}
B、Collection接口遍历元素
1、使用Iterator(迭代器)
1)所有实现Collection接口的集合类都有一个iterator()方法,用以返回一个迭代器。
2)Iterator仅用于遍历集合,Iterator本身并不存放对象。
Iterator iterator = coll.iterator(); //得到一个集合的迭代器
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next()作用:1、下移 2、将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
在调用 it.next() 方法之前必须调用 it.hasNext() 进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
2、for循环增强
增强for就是简化版的iterator,只能用于遍历集合或数组。
1、List
A、List接口基本介绍
1)List集合类中元素有序(即添加顺序和取出顺序一致),且可重复;
2)支持索引;
3)List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
public class List_{
public static void main(String[] args){
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("zwj");
list.add("tom");
System.out.println("list=" + list);
System.out.println(list.get(3));//zwj
}
}
B、List接口的常用方法
List集合里添加了一些根据索引来操作集合元素的方法。
1)void add(int index, Object ele): 在index位置插入ele元素;
2)boolean addAll(int index, Collection eles): 从index位置开始将eles中的所有元素添加进来;
3)Object get(int index): 获取指定index位置的元素;
4)int indexOf(Object obj): 返回obj在集合中首次出现的位置;
5)int lastIndexOf(Object obj): 返回obj在集合中最后一次出现的位置;
6)Object remove(int index): 移除指定index位置的元素,并返回此元素;
7)Object set(int index, Object ele): 设置指定index位置的元素为ele,相当于替换;
8)List subList(int fromIndex, int toIndex): 返回从fromIndex到toIndex位置的子集合;
public class ListMethod{
public static void main(String[] args){
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
//void add(int index, Object ele): 在index位置插入ele元素
list.add(1,"伟小杰");
System.out.println("list=" + list);
//[张三丰,伟小杰,贾宝玉]
//boolean addAll(int index, Collection eles):
//从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList();
list.add("jack");
list.add("tom");
list.addAll(1,lisy2);
System.out.println("list=" + list);
//[张三丰,jack,tom,伟小杰,贾宝玉]
//好多方法其实是重复的,没意思
//List subList(int fromIndex, int toIndex): 返回子集合
List returnlist = list.subList(0,2);//2不包含
System.out.println("returnlist=" + returnlist);
}
}
C、List的三种遍历方法
1)方式一:使用iterator;
2)方式二:使用增强for;
3)方式三:使用普遍for。
for(int i=0;i<list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
1、ArrayList
1、ArrayList的注意事项
1)ArrayList 可以加入null,并且可以多个;
2)ArrayList 是由数组来实现数据存储的;
3)ArrayList 基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrayList。
2、ArrayList扩容机制
1)ArrayList中维护了一个Object类型的数组。
transient Object[] elementData;//
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍;
3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
2、LinkedList
1、LinkedList的全面说明
1)LinkedList底层实现了双向链表和双端队列特点;
2)可以添加任意元素(元素可以重复),包括null;
3)线程不安全,没有实现同步。
2、LinkedList的底层操作机制
1)LinkedList底层维护了一个双向链表;
2)LinkedList中维护了两个属性first和last分别指向首结点和尾结点;
3)每个结点(Node对象),里面又维护了prev、next、item三个属性,最终实现双向链表;
4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
3、ArrayList和LinkedList的对比
1)如果我们改查的操作多,选择ArrayList;
2)如果我们增删的操作多,选择LinkedList;
3)一般,在程序中,基本都是查询,因此大部分情况下会选择ArrayList;
4)在一个项目中,根据项目灵活选择,ArrayList和LinkedList互相使用。
3、Vector
1、Vector的基本介绍
1)Vector类的定义说明:
2)Vector底层也是一个对象数组,protected Object[] elementData;
3)Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized;
4)在开发中,需要线程同步安全时,考虑使用Vector。
2、Vector和ArrayList的对比
2、Set
A、Set接口基本介绍
1)无序(添加和取出顺序不一致),没有索引;
2)不允许重复元素,所以最多包含一个null
B、Set接口的常用方法
和Collection接口一样。
C、Set接口的遍历方式
1、可以使用迭代器;2、增强for;3、不能使用索引。
1、HashSet
1、HashSet的全面说明
1)HashSet实际上是HashMap;
2)可以存放null,但是只能有一个null;
3)HashSet不保证元素是有序的,取决于hash后,再确定索引的结果;
4)不能有重复元素/对象。😈这里有点离谱的
public class HashSet01{
public static void main(String[] args){
HashSet set = new HashSet();
set.add("lucy"); //添加成功
set.add("lucy"); //添加失败
set.add(new Dog("tom"));//可以添加
set.add(new Dog("tom"));//可以添加
System.out.println("set=" + set);
//面试题
//得看底层了
set.add(new String("shuaibi"));//OK
set.add(new String("shuaibi"));//加入不了
}
}
class Dog{
private String name;
public Dog(String name){
this.name = name;
}
}
2、HashSet底层机制说明
HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
HashSet的添加元素底层是如何实现(hash()+equals())
- HashSet 底层是HashMap
- 添加一个元素时,先得到hash值,然后转成索引值
- 找到存储数据表table,看这个索引位置是否已经存放元素
- 如果没有,直接加入
- 如果有,调用equals 比较,如果相同,就放弃添加;如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认是8),并且table的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树,即平衡二叉树)
能否添加,关键在于equals()的设置。
分析HashSet的扩容和转成红黑树机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,
临界值(threshold)是 16 * 加载因子(loadFactor)是0.75 = 12;
如果table 数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推;
在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。
public class HashSetExercise{
public static void main(String[] args){
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan",18));
hashSet.add(new Employee("smith",28));
hashSet.add(new Employee("milan",18));
System.out.println("hashSet=" + hashSet);
}
}
class Employee{
private String name;
private int age;
public Employee(String name,int age){
this.name = name;
this.age = age;
}
//如果name和age值相同,则返回相同的hash值
@Override
public boolean equals(Object o){
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name,employee.name);
}
@Override
public int hashCode(){
return Objects.hash(name,age);
}
}
2、LinkedHashSet
1、LinkedHashSet的全面说明
1)LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组+双向链表;
2)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的;
3)LinkedHashSet不允许添加重复元素。
2、LinkedHashSet源码解读
-
LinkedHashSet 加入顺序和取出元素的顺序一致;
-
LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类);
-
LinkedHashSet 底层结构(数组table+双向链表);
-
添加第一次时,直接将数组table扩容到16,存放的结点类型LinkedHashMap$Entry
-
数组是 HashMap$Node[] 存放的元素,
数据是LinkedHashMap$Entry类型;
-
Entry 继承了 HashMap.Node(Node是HashMap的静态内部类,Entry也是静态内部类)
3、TreeSet
public class TreeSet_{
public static void main(String[] args){
//1.当我们使用无参构造器,创建TreeSet时,仍然是无序的
//2.使用TreeSet提供的一个构造器,可以传入一个比较器
// 来指定排序规则
TreeSet treeSet = new TreeSet(new Comparator(){
@Override
public int compare(Object o1,Object o2){
return ((String)o1).compareTo((String)o2);
}
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
System.out.println(treeSet);
//treeSet=[a,jack,sp,tom]
}
}
3、Map
A、Map接口实现类的特点【很实用】
1)Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value;
2)Map中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中;
3)Map中的key不允许重复;
4)Map中的value可以重复;
5)Map的key可以为null,value也可以为null,但是key只能有一个null,value可以多个null;
6)常用String作为Map的key;
7)key和value是一对一关系,通过指定的key总能找到对应的value。
public class Map_{
public static void main(String[] args){
Map map = new HashMap();
map.put("no1","帅哥");
map.put("no2","美女");
map.put("no1","张三丰");//当有相同的k,就等价于替换
map.put("no3","张三丰");
map.put(null,null);
map.put(1,"赵敏");
map.put(new Object(),"金毛狮王");
System.out.println("map=" + map);
}
}
8)一对k-v是放在一个Node中的,又因为Node实现了Entry接口,也会说一对k-v就是一个Entry。
public class Map2_{
public static void main(String[] args){
Map map = new HashMap();
map.put("no1","帅哥");
map.put("no2","美女");
//1.k-v 最后是 HashMap$Node node = newNode(hash,key,value,null);
//2.k-v 为了方便程序员遍历,还会创建EntrySet集合,该集合存放的元素类型是Entry,
//而一个Entry对象就有k,v EntrySet<Entry<K,V>>,
// 即transient Set<Map.Entry<K,V>> entrySet;
// 但是并没有另外储存,只是引用,有点像是一个抽象的上层建筑;
//3.entrySet 中,定义的类型是Map.Entry,但是实际上存放的还是 HashMap$Node
// 这是因为 HashMap$Node implements Map.Entry<K,V>
//4.当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历,
// 因为Map.Entry提供了重要的方法getKey()\getValue()
//entrySet
Set set = map.entrySet();
System.out.println(set.getClass()); // HashMap$EntrySet
for(Object obj : set){
System.out.println(obj.getClass());// HashMap$Node
//向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue());
}
//keySet/values
Set set1 = map.keySet();
Collection values = map.values();
}
}
B、Map接口常用方法
1)put:添加;
2)remove:根据键删除映射关系;
3)get:根据键获取值;
4)size:获取元素个数;
5)isEmpty:判断个数是否为0;
6)clear:清除;
7)containsKey:查找键是否存在。
public class MapMethod{
public static void main(String[] args){
Map map = new HashMap();
map.put("邓超",new Book("",100));//OK
map.put("邓超","孙俪");//替换
map.put("王宝强","马蓉");//OK
map.put("宋喆","马蓉");//OK
map.put("苏东坡",null);//OK
map.put(null,"刘亦菲");//OK
map.put("鹿晗","关晓彤");//OK
System.out.println("map=" + map);
//remove:根据键删除映射关系;
map.remove(null);
System.out.println("map=" + map);
//get:根据键获取值;
Object val = map.get("鹿晗");
System.out.println("val=" + val);
//size:获取元素个数;
System.out.println("个数=" + map.size());
//isEmpty:判断个数是否为0;
System.out.println("isEmpty=" + map.isEmpty());
//clear:清除;
map.clear();
System.out.println("map=" + map);
//containsKey:查找键是否存在。
System.out.println(map.containsKey("shuaibi"));//F
}
}
class Book{
private String name;
private int price;
public Book(String name,int price){
this.name = name;
this.price = price;
}
}
C、Map接口遍历方法
1)containsKey:查找键是否存在;
2)keySet:获取所有的键;
3)entrySet:获取所有的关系;
4)values:获取所有的值。
public class MapMethod{
public static void main(String[] args){
Map map = new HashMap();
map.put("邓超",new Book("",100));//OK
map.put("邓超","孙俪");//替换
map.put("王宝强","马蓉");//OK
map.put("宋喆","马蓉");//OK
map.put("苏东坡",null);//OK
map.put(null,"刘亦菲");//OK
map.put("鹿晗","关晓彤");//OK
System.out.println("map=" + map);
//第一组:先取出所有的key,通过key取出对应的Value
Set keyset = map.keySet();
//1.增强for
for(Object key:keyset){
System.out.println(key + "-" + map.get(key));
}
//2.迭代器
Iterator iterator = keyset.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组:把所有的values取出
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//1.增强for
for(Object value:values){
System.out.println(value);
}
//2.迭代器
Iterator iterator2 = values.iterator();
while(iterator2.hasNext()){
Object value = iterator2.next();
System.out.println(value);
}
//第三组:通过EntrySet 来获取
Set entryset = map.entrySet();
//1.增强for
for(Object entry:entrySet){
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//2.迭代器
Iterator iterator3 = entryset.iterator();
while(iterator3.hasNext()){
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node-->Map.Entry
//向下转型 HashMap$Node,因为实现了Map.Entry有getKey()/getValue()
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
class Book{
private String name;
private int price;
public Book(String name,int price){
this.name = name;
this.price = price;
}
}
1、HashMap
1、HashMap小结
1)HashMap是Map接口使用频率最高的实现类;
2)如果添加相同的key,则会覆盖原来的key-val,等同于修改;
3)底层是以hash表的方式来存储的(数组+链表+红黑树);
4)HashMap没有实现同步,因此是线程不安全的。
2、HashMap底层机制及源码剖析
1)(k,v)是一个Node实现了Map.Entry<K,V>;
2)jdk7.0的hashmap 底层实现【数组+链表】, jdk8.0底层【数组+链表+红黑树】;
扩容机制 和HashSet相同
2、Hashtable
1、HashTable的基本介绍
1)存放的元素是键值对:即K-V;
2)hashTable的键和值都不能为null;
3)hashTable使用方法基本上和HashMap一样;
4)hashTable是线程安全的,HashMap是线程不安全的;
public class HashTable_{
public static void main(String[] args){
Hashtable table = new Hashtable();
table.put("luck",100);
table.put("john",100);
table.put("lic",100);
table.put("lic",88);//替换
System.out.println(table);
//Hashtable 的底层
//1.底层有数组 Hashtable$Entry[] 初始化大小为11
//2.临界值 threshold 8 = 11 * 0.75
//3.扩容
//4.执行方法 addEntry(hash,key,value,index); 添加K-V 封装到Entry
//5.当 if(count >= threshold) 满足时,就进行扩容
//6.按照 int newCapacity = (OldCapacity << 1) + 1; 的大小扩容
}
}
3、LinkedHashMap
4、TreeMap
public class TreeMap_{
public static void main(String[] args){
//使用默认构造器,是无序的
//TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator(){
@Override
public int compare(Object o1,Object o2){
return ((String)o1).compareTo((String)o2);
}
});
//如果是自定义规则的话,两个对象相不相同,要看比较规则的值
//如果比较规则判定是相同的,那就会key不变,替换值value
//具体看源码
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kristina","克瑞斯提诺");
treeMap.put("smith","史密斯");
System.out.println(treeSet);
}
}
5、Properties
1、基本介绍
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据;
- 他的使用特点和Hashtable类似;
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改;
- 说明:工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO流举例。
4、Collections
1、Collections工具类介绍
1)Collections是一个操作Set、List和Map等集合的工具类;
2)Collections 中提供了一系列的静态方法对集合进行排序、查询和修改等操作。
2、排序操作:
均为(static)方法
1)reverse(List):反转 List 中元素的顺序;
2)shuffle(List):对 List 集合元素进行随机排序;
3)sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序;
4)sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序;
5)swap(List, int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换。
3、查找、替换
1)Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素;
2)Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
3)Object min(Collection);
4)Object min(Collection, Comparator);
5)int frequency(Collection, Object):返回指定集合中指定元素的出现次数;
6)void copy(List dest, List src):将src中的内容复制到dest中;
7)boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换 List对象的所有旧值
public class Collections_{
public static void main(String[] args){
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
//reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
//shuffle(List):对 List 集合元素进行随机排序
Collections.shuffle(list);
System.out.println("list=" + list);
//sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println("list=" + list);
//sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
Collections.sort(list,new Comparator(){
@Override
public int compare(Object o1,Object o2){
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("list=" + list);
//swap(List, int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换。
Collections.swap(list,0,1);
System.out.println("list=" + list);
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素;
System.out.println("自然顺序最大元素=" + Collections.max(list));
//Object max(Collection, Comparator):根据Comparator指定的顺序,返回最大元素
Collections.max(list, new Comparator(){
...
});
//int frequency(Collection, Object):返回指定集合中指定元素的出现次数;
System.out.println("tom出现的次数=" + Collections.frequency(list,"tom"));
//void copy(List dest, List src):将src中的内容复制到dest中;
ArrayList dest = new ArrayList();
//为了能拷贝,我们需要一个等大小的容器
for(int i=0;i<list.size();i++){
dest.add("");
}
Collections.copy(dest, src);
System.out.println("dest=" + dest);
//boolean replaceAll(List list, Object oldVal, Object newVal):
//使用新值替换 List对象的所有旧值
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("替换后的list=" + list);
}
}
Z、总结-开发中如何选择集合实现类(记住)
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
-
先判断存储的类型(一组对象或一组键值对);
-
一组对象:Collection接口
-
允许重复:List
增删多:LinkedList [底层维护了一个双向链表]
改查多:ArrayList [底层维护 Object 类型的可变数组]
-
不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
-
-
一组键值对:Map
键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
8、泛型
- 使用传统方法的问题分析
1)不能对加入到集合 ArrayList 中的数据类型进行约束(不安全);
2)遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响。
- 泛型的好处
1)编译时,检查添加元素的类型,提高了安全性;
2)减少了类型转换的次数,提高效率。
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财",10));
arrayList.add(new Dog("旺仔",12));
arrayList.add(new Dog("旺米",8));
arrayList.add(new Cat("mimi",6));//没用泛型,就不限数据类型
for(Object o:arrayList){//得用Object 接收,然后向下转型
Dog dog = (Dog)o;
System.out.println(dog);
}
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财",10));
arrayList.add(new Dog("旺仔",12));
arrayList.add(new Dog("旺米",8));
//使用泛型后,Cat添加会报错
arrayList.add(new Cat("mimi",6));
//不止Object,可以直接使用Dog,就不用向下转型了,提高了效率
for(Dog dog:arrayList){
System.out.println(dog);
}
1、泛型介绍
1)泛型又称参数化类型,是Jdk5.0 出现的新特性,解决数据类型的安全性问题;
2)在类声明或实例化时只要指定好需要的具体类型即可;
3)Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮;
4)泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。
public class Generic01{
public static void main(String[] args){
Person<String> person = new Person<String>("123");
Person<Integer> person2 = new Person<Integer>(100);
}
}
class Person<E>{
E s; //数据类型
public Person(E s){ //参数类型
this.s = s;
}
public E f(){ //返回值类型
return s;
}
}
interface jiekou<T>{}
- 泛型的声明
interface 接口{} 和 class 类<K,V>{}
1)其中,T,K,V不代表值,而是表示类型;
2)任意字母都可以。常用T表示,是Type缩写。
- 泛型的实例化
1)List strList = new ArrayList() ;
2)Iterator iterator = customers.iterator();
public class GenericExercise{
public static void main(String[] args){
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("jack",18));
students.add(new Student("tom",28));
students.add(new Student("marry",19));
for(Student student : students){
System.out.println(student);
}
HashMap<String, Student> hm = new HashMap<String, Student>();
hm.put("milan",new Student("milan",48));
hm.put("smith",new Student("smith",38));
hm.put("zwj",new Student("zwj",23));
Set<Map.Entry<String, Student>> entries = hm.entrySet();
Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
}
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2、泛型语法和使用
- 泛型使用的注意事项和细节
-
interface List{}, public class HashSet{}…等等
T, E 只能是引用类型 【要Integer,不要int】
-
在指定泛型具体类型后,可以传入该类型或者其子类型;
-
泛型使用形式:
List<Integer> list1 = new ArrayList<Integer>(); //实际开发中,我们往往简写 //编译器会进行类型推断,(推荐) List<Integer> list2 = new ArrayList<>();
如果我们这样写 List list3 = new ArrayList(); 默认给它的泛型是【 E就是 Object】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKSd6lmV-1676708128810)(D:\Users\WJ_Zheng\Desktop\文档里的图片\泛型题目.jpg)]
public class GenericExercise02{
public static void main(String[] args){
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("tom",20000,new MyDate(2000,11,11)));
employees.add(new Employee("jack",12000,new MyDate(2001,12,12)));
employees.add(new Employee("zwj",50000,new MyDate(2001,3,24)));
System.out.println("employees=" + employees);
System.out.println("======排序======");
employees.sort(new Comparator<Employee>(){
@Override
public int compare(Employee emp1,Employee emp2){
//先检查参数
if(!(emp1 instanceof Employee && emp2 instanceof Employee)){
System.out.println("类型不正确...");
return 0;
}
//比较name
int i = emp1.getName().compareTo(emp2.getName());
if(i != 0){
return i;
}
//如果name相同,就比较birthday
//下面是对birthday的比较,因此,最好把这个比较放在MyDate类完成
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println("Sorted,,employees=" + employees);
}
}
public class MyDate implements Comparable<MyDate>{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public int compareTo(MyDate o) {//把年月日比较放在这里
int yearMinus = this.year - o.getYear();
if(yearMinus != 0){
return yearMinus;
}
int monthMinus = month - o.getMonth();
if(monthMinus != 0){
return monthMinus;
}
return day - o.getDay();
}
}
public class Employee{
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
3、自定义泛型
1、自定义泛型类
- 注意细节
1)普通成员可以使用泛型(属性、方法);
2)使用泛型的数组,不能初始化;【还没确定数据类型,无法开辟内存空间】
3)静态方法中不能使用类的泛型;【静态是在类创建执行,泛型是在对象创建才确定】
4)泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型);
5)如果在创建对象时,没有指定类型,默认为Object。
2、自定义泛型接口
- 注意细节
1)接口中,静态成员也不能使用泛型;【接口的属性默认是静态成员哦】
2)泛型接口的类型,在继承接口或者实现接口时确定;
3)没有指定类型,默认为Object。
interface Usb<U,R>{
//U name; 不能这样使用
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1,R r2, U u1,U u2);
//默认方法,也是可以使用泛型
default R method(U u){
return null;
}
}
//泛型接口的类型,在继承接口或者实现接口时确定;
interface IA extends Usb<String,Double>{
}
//当我们去实现IA接口时,因为IA在继承接口时,指定了泛型类型,
//所以实现方法的时候,自动替换U,R
class AA implements IA{
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
class BB implements Usb<Integer,Float>{
}
3、自定义泛型方法
-
基本语法
修饰符 <T,R…>返回类型 方法名(参数列表){
}
-
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中;
- 当泛型方法被调用时,类型会确定;
- public void eat(E e){},修饰符后没有<T,R,…> eat方法不是泛型方法,而是使用了泛型。
public class CustomMethodGeneric{
public static void main(String[] args){
Car car = new Car();
car.fly("宝马",100);//当调用方法时,传入参数,编译器就会确定类型
}
}
//泛型方法,可以定义在普通类中,也可以定义在泛型类中;
class Car{//普通类
public void run(){//普通方法
}
public <T,R> void fly(T t,R r){//泛型方法
}
}
class Fish<T,R>{//泛型类
public void run(){//普通方法
}
public<U,M> void eat(U u,M m){//泛型方法
}
}
4、泛型的继承和通配符
1)泛型不具备继承性
List<Object> list = new ArrayList<String>(); //不能这么写
2)<?>:支持任意泛型类型
3)<? extends A>:支持A类以及A类的子类,规定了泛型的上限;
4)<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限。
public static void print(List<? extends AA> c){
for(Object object: c){
System.out.println(object);
}
}
9、线程
1、线程介绍
1)进程即程序的一次执行过程,有生命周期;
2)线程:线程是由进程创建的,一个进程可以拥有多个线程。
3)单线程:同一时刻,只允许执行一个线程;
多线程:同一时刻,可以执行多个线程。
4)并发:同一时刻,多个任务交替进行,造成一种“貌似同时”的错觉;【单核cpu实现多任务】
并行:同一时刻,多个任务同时执行。【多核cpu可以实现并行】
public class CpuNum{
public static void main(String[] args){
Runtime runtime = Runtime.getRuntime();
//获取当前电脑的cpu数量
int cpuNums = runtime.availableProcessors();
}
}
2、线程创建
在java中线程的创建方式:
- 继承Thread 类,重写 run 方法;
- 实现Runnable 接口,重写 run 方法。【建议】
//继承Thread 类,重写 run 方法
public class Thread01{
public static void main(String[] args){
//创建Cat,当做线程使用
Cat cat = new Cat();
/*
(1)
public synchronized void start(){
start0();
}
(2)
//start0()是本地方法,是JVM调用,底层是c/c++实现
//真正实现多线程的是start0(),而不是 run()
private native void start0();
*/
cat.start();//启动线程 ---> 最终调用的是cat的 run方法
//cat.run();//run就是个普通方法,没有真正启动一个线程,执行完才会执行下面的语句
//这时,主线程和子线程是交替执行..
System.out.println("主线程继续执行"+ Thread.currentThread().getName());
for(int i=0;i<10;i++){
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//1.当一个类继承了 Thread 类,该类就可以当做线程使用
//2.我们会重写 run 方法,写上自己的业务代码
//3.run Thread 类 实现了 Runnable 接口的run方法
class Cat extends Thread{
int times = 0;
@Override
public void run(){//重写 run 方法
while(true){
System.out.println("喵喵,我是小猫咪" + (++times)
+"线程名="+Thread.currentThread().getName());
//让该线程休眠1秒
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
if(times == 80){
break;//这时退出了循环,线程也结束了
}
}
}
}
//实现Runnable接口
//1.java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法不可能
//2.所以就有了实现Runnable接口来创建线程
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用start,因为Runnable接口只有run()函数
Thread thread = new Thread(dog);//【代理模式】
thread.start();
}
}
class Dog implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("小狗汪汪.." + (++count) + Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
继承Thread VS 实现Runnable的区别:
-
本质没区别;
-
实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。
3、线程终止
- 基本说明
- 当线程完成任务后,会自动退出;
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//在main线程,去控制t1线程【通知方式】
Thread.sleep(1000 * 10);
t1.setLoop(false);
}
}
class T extends Thread {
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中。。。");
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
4、线程常用方法
常用方法第一组
1.setName; //设置线程名称,使之与参数 name 相同
2.getName; //返回该线程的名称
3.start; //使该线程开始执行
4.run; //调用线程对象的run 方法
5.setPriority;//更改线程的优先级
6.getPriority;//获取线程的优先级
7.sleep; //让线程休眠
8.interrupt;//中断线程
- 注意事项和细节
- start 底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程;
- 线程优先级的范围【1,5,10】
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,是当前线程休眠。
public class ThreadMethod {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("帅逼");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi");
}
t.interrupt();//中断子线程的休眠
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "吃包子" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中");
Thread.sleep(20000);//20秒
} catch (InterruptedException e) {
//当该线程执行到一个interrupt 方法时,就会catch一个异常,可以加入自己的业务代码
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
常用方法第二组
- yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功;
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务。
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程吃包子 " + i);
if (i == 5) {
System.out.println("主线程让 老大(子线程)先吃");
//join()插队,是一定成功的
//t.join();//让子线程插队,一直到执行完毕
Thread.yield();//礼让,不一定成功【当资源很充足的时候,礼让不成功】
System.out.println("子线程吃完");
}
}
}
}
class T extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T子线程吃包子 " + i);
}
}
}
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完成或通知方式结束;
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
- 常见的守护线程:垃圾回收机制。
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果想要线程,随着main线程结束,子线程也结束
//可以设置为守护线程
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for(int i=1;i<=10;i++){
System.out.println("工作。。。。");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {
for(;;){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("地球在自转?????");
}
}
}
线程的生命周期
- JDK中用 Thread.State 枚举表示了线程的几种状态
5、Synchronized
- 线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这么理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
- 同步具体方法-Synchronized
-
同步代码块
synchronized (对象) { //得到对象的锁,才能操作同步代码
//需要被同步代码;
}
-
synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){ 【同一时刻,该方法只能被一个人调用】
//需要被同步的代码
}
-
如何理解:
就好像上厕所,要上锁,完事后出来(解锁),别人才能使用厕所。
class ... {
private Object ob = new Object();
//同步方法
public synchronized void sell(){
....
}
//同步代码块写法
public void sell(){
synchronized(this){//互斥锁在this对象上
...
}
}
//同步方法(非静态的)的锁也可以是其他对象(要求是同一个对象);
public void sell(){
synchronized(ob){
...
}
}
}
6、互斥锁
- 基本介绍
- Java中,引入了对象互斥锁的概念,来保证共享数据操作的完整性;
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记可以保证在任一时刻,只能有一个线程访问该对象;
- 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问;
- 同步的局限性:导致程序的执行效率要降低;
- 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一个对象);
- 同步方法(静态的)的锁为当前类本身。
class SellTicket implements Runnable {
//同步方法(静态)
//锁为当前类本身
public synchronized static void sell(){//此时锁加在SellTicket.class上
....
}
public static void sell(){
synchronized(SellTicket.class){//这里写this就是错的
....
}
}
}
- 注意事项和细节
- 同步方法如果没有使用static修饰:默认锁对象为this;
- 如果方法使用static 修饰,默认锁对象:当前类.class。
- 实现的落地步骤:
- 需要先分析上锁的代码;
- 选择同步代码块【推荐】或同步方法;
- 要求多个线程的锁对象为同一个即可。【不然你丫的锁不住的】
7、死锁
-
基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程上是一定要避免死锁的发生。
释放锁
-
当前线程的同步方法、同步代码块执行结束。
案例:上厕所,完事出来。
-
当前线程的同步方法、同步代码块遇到break、return。
案例:没正常的完事,经理叫他修改bug,不得已出来。
-
当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致异常结束。
案例:没有正常的完事,发现忘带纸,不得已出来。
-
当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要再酝酿下,所以出来等会再进去。
不会释放锁的情况
-
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。
案例:上厕所,太困了,在坑位上眯一会。
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用。
10、IO流
1、文件
文件流【以程序为视角,进来的是输入,出去的是输出】
- 输入流:数据从数据源(文件)到程序(内存)的路径;
- 输出流:数据从程序(内存)到数据源(文件)的路径。
1、常用的文件操作
//创建文件对象相关构造器和方法
new File(String pathname);//根据路径构建一个File对象
new File(File parent,String child);//根据父目录文件+子路径构建
new File(String parent,String child);//根据父目录+子路径构建
createNewFile //创建新文件
public class FileCreate {
public static void main(String[] args) {
}
//方式一:
@Test
public void create01(){
String filePath = "d:\\news1.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式二:
@Test
public void create02(){
File parentFile = new File("d:\\");
String filename = "new02.txt";
File file = new File(parentFile, filename);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式三:
@Test
public void create03(){
String parentPath = "d:\\";
String fileName = "new03.txt";
File file = new File(parentPath,fileName);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、常用的文件操作
getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory.
public class FileInformation {
public static void main(String[] args) {
}
//获取文件信息
@Test
public void info(){
//先创建文件对象
File file = new File("d:\\new1.txt");
//调用相应的方法,获得对应信息
System.out.println("文件名="+ file.getName());
System.out.println("绝对路径="+ file.getAbsolutePath());
System.out.println("文件父级目录="+ file.getParent());
System.out.println("文件大小(字节)="+ file.length());
System.out.println("文件是否存在="+ file.exists());
System.out.println("是不是一个文件="+ file.isFile());
System.out.println("是不是一个目录="+ file.isDirectory());
}
}
3、目录的操作和文件删除
mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件
public class Directory_ {
public static void main(String[] args) {
}
@Test
public void m1(){
String filePath = "d:\\new1.txt";
//String filePath = "d:\\demo2";//在Java中目录也被当做文件
File file = new File(filePath);
if(file.exists()){
if(file.delete()){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}else{
System.out.println("该文件不存在");
}
}
//创建多级目录
@Test
public void m2(){
String directoryPath = "D:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if(file.exists()){
System.out.println(directoryPath + "存在..");
}else{
if(file.mkdirs()){
System.out.println("创建成功");
}else{
System.out.println("创建失败");
}
}
}
}
2、IO流原理及流的分类
1、Java IO流原理
- I/O是input/output的缩写,用于处理数据传输。如读写文件、网络通讯等;
- Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行;
- java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据。
2、流的分类
(抽象基类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
1)Java的IO流共涉及40多个类,但都是从如上4个抽象基类派生的;
2)由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
3、文件拷贝
//文件拷贝
public class FileCopy {
public static void main(String[] args) {
//完成文件拷贝,将d:\\Koala.jpg 拷贝到 c:\\
//1.创建文件的输入流,将文件读入程序
//2.创建文件的输出流,将读取的文件数据,写入到指定的文件
String srcFilePath = "d:\\Koala.jpg";
String destFilePath = "d:\\Koala2.jpg";
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);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileInputStream!=null){
fileInputStream.close();
}
if(fileOutputStream!=null){
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//缓冲拷贝(字符流)
public class BufferedCopy_ {
public static void main(String[] args) {
String scrFilePath = "d:\\a.txt";
String destFilePath = "d:\\a2.txt";
BufferedReader br = null;
BufferedWriter bw = null;
String line;
try {
br = new BufferedReader(new FileReader(scrFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
while ((line = br.readLine()) != null) {
//每读取一行,就写入
bw.write(line);//没带换行符
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//缓冲拷贝(字节流)
public class BufferedCopy02 {
public static void main(String[] args) {
String scrFilePath = "d:\\a.jpg";
String destFilePath = "d:\\a2.jpg";
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 的子类
bis = new BufferedInputStream(new FileInputStream(scrFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环读取文件,并写入
byte[] buff = new byte[1024];
int readLen = 0;
while((readLen = bis.read(buff))!=-1){
bos.write(buff,0,readLen);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis!=null) {
bis.close();
}
if (bos!=null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4、FileReader 和 FileWriter 介绍
FileReader和 FileWriter是字符流,即按照字符来操作io。
5、处理流-BufferedReader和BufferedWriter
- BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的;
- 关闭时,只需要关闭外层流即可。
- 字符流,不要去操作二进制文件[声音、视频、word、pdf等],可能会造成文件损坏。
6、处理流-BufferedInputStream和BufferedOutputStream
- BufferedInputStream是字节流,在创建BufferedInputStream时,会创建一个内部缓冲区数组。
- BufferedOutputStream是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统。
7、对象流-ObjectInputStream和ObjectOutputStream
需求:要求能够把 基本数据类型 或者 对象 进行 序列化 和 反序列化操作。
1、序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型;ObjectOutputStream
- 反序列化就是在恢复数据时,恢复数据的值和数据类型;ObjectInputStream
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable //这是一个标记接口,没有方法【推荐】
- Externalizable
2、序列化的注意事项和细节说明
- 读写顺序一致;
- 要求实现序列化或反序列化对象,需要实现 Serializable;
- 序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性;
private static final long serialVersionUID = 1L;
-
序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员;
-
序列化对象时,要求里面属性的类型也需要实现序列化接口;
-
序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化。
8、标准输入输出流
类型 | 默认设备 | |
---|---|---|
System.in 标准输入 | InputStream | 键盘 |
System.out 标准输出 | PrintStream | 显示器 |
public class InputAndOutput{
public static void main(String[] args){
//System.in 编译类型 InputStream
//System.in 运行类型 BufferedInputStream
//System.out 编译类型 PrintStream
//System.out 运行类型 PrintStream
}
}
9、转换流-InputStreamReader和OutputStreamWriter
- 介绍
- InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流);
- OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流);
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流;
- 可以在使用时指定编码格式(比如 utf-8,gbk,gb2312,ISO8859-1等)。
10、打印流-PrintStream 和 PrintWriter
打印流只有输出流,没有输入流。
public class PrintStream_ {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream输出数据的位置是标准输出,即显示器
out.print("hello");
//因为print底层使用的是write,所以我们可以直接调用write进行打印/输出
out.write("你好,李焕英".getBytes());
out.close();
//我们可以去修改打印流输出的位置/设备
System.setOut(new PrintStream("d:\\f1.txt"));
System.out.println("你好,帅逼");
}
}
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("d:\\f2.txt"));
printWriter.print("哈哈哈哈");
printWriter.close();
}
}
3、节点流和处理流
1、基本介绍
- 节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter;
- 处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter。
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | |
---|---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer | |
节点流 | 访问文件 访问数组 访问管道 访问字符串 | FileInputStream ByteArrayInputStream PipedInputStream | FileOutputStream ByteArrayOutputStream PipedOutputStream | FileReader CharArrayReader PipedReader StringReader | FileWriter CharArrayWriter PipedWriter StringWriter |
处 | 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
处 | 转换流 | InputStreamReader | OutputStreamWriter | ||
处 | 对象流 | ObjectInputStream | ObjectOutputStream | ||
处 | 抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
处 | 打印流 | PrintStream | PrintWriter | ||
处 | 推回输入流 | PushbackInputStream | PushbackReader | ||
处 | 特殊流 | DataInputStream | DataOutputStream |
包装流的真相(是里面有个基类参数,也就是说只要是其子类都可以操作,所以就灵活多了)
2、节点流和处理流的区别和联系
- 节点流是底层流/低级流,直接跟数据源相接;
- 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便地方法来完成输入输出
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连。
3、处理流的功能
- 性能的提高:主要以增肌缓冲的方式来提高输入输出的效率;
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活。
4、输入流
1、InputStream
1、FileInputStream
public class FileInputStream_ {
public static void main(String[] args) {
}
/**
* 单个字节的读取,效率比较低
*/
@Test
public void readFile01() {
String filePath = "d:\\hello.txt";
FileInputStream fileInputStream = null;
int readData = 0;
try {
fileInputStream = new FileInputStream(filePath);
//如果返回-1,表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 方式,提高效率
*/
@Test
public void readFile02() {
String filePath = "d:\\hello.txt";
FileInputStream fileInputStream = null;
int readLen = 0;
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节
try {
fileInputStream = new FileInputStream(filePath);
//每次读取8个字节到字节数组里,返回值是读取的实际字节大小
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf,0,readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、BufferedInputStream
3、ObjectInputStream
public class ObjectInputStream_ {
public static void main(String[] args) throws Exception {
String filePath = "d:\\kexvliehua.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//1.读取(反序列化)的顺序需要和保存(序列化)的顺序一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object ----> Dog
//如果我们想要调用Dog的方法,需要向下转型
//需要把类的定义移到能够引用的地方
//不是单纯再拷贝
//因为序列化的时候会添加Dog类所在包的地址信息
//Dog dog2 = (Dog) dog;
ois.close();
}
}
2、Reader
1、FileReader
- FileReader相关方法:
1) new FileReader(File/String);
2) read;//每次读取单个字符,返回该字符,如果到文件末尾返回-1
3) read(char[]);//批量读取多个字符到数组,返回读取到的字符数,到文件末尾返回-1
//相关API
1) new String(char[]);
2) new String(char[],off,len);
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 单个字符读取
*/
@Test
public void readFile01() {
String filePath = "d:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1.创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 一次多个字符读取
*/
@Test
public void readFile02() {
String filePath = "d:\\story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
//1.创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、BufferedReader
public class BufferedReader_ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\haha.txt";
//创建BufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line;//按行读取
//按行读取,
while((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
//关闭流,只需要关闭BufferedReader
bufferedReader.close();
}
}
3、InputStreamReader
public class InputStreamReader_ {
public static void main(String[] args) throws Exception {
String filePath = "d:\\a.txt";
//指定编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"utf-8");
// 把 InputStreamReader 传入 BufferedReader
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println("读取到的内容:"+ s);
br.close();
}
}
5、输出流
1、OutputStream
1、FileOutputStream
public class FileOutputStream01 {
public static void main(String[] args) {
}
@Test
public void writeFile() {
//创建 FileOutputStream
String filePath = "D:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//1.new FileOutputStream(filePath) 当写入内容时,是会覆盖原来的内容的
//2.new FileOutputStream(filePath,true) 当写入内容时,是追加到文件末尾
fileOutputStream = new FileOutputStream(filePath);
//写入一个字节
//fileOutputStream.write('a');
//写入几个字符串
String str = "hello,world!";
//str.getBytes() 可以把 字符串---->字节数组
//fileOutputStream.write(str.getBytes());
//write(byte[] b,int off,int len) 将len字节从位于偏移量off的指定字节数组
fileOutputStream.write(str.getBytes(), 0, 3);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、BufferedOutputStream
3、ObjectOutputStream
public class ObjectOutputStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是文本,而是按照它的格式来保存
String filePath = "d:\\kexvliehua.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//保存
oos.writeInt(100);// int ---> Integer (实现了 Serializable)
oos.writeBoolean(true);//boolean ---->Boolean(实现了 Serializable)
oos.writeChar('a');//char ----> Character(实现了 Serializable)
oos.writeDouble(9.5);//double--->Double(实现了 Serializable)
oos.writeUTF("帅逼");//String(实现了 Serializable)
//保存一个对象
oos.writeObject(new Dog("旺财",18));
oos.close();
System.out.println("序列化保存结束");
}
}
class Dog implements Serializable{
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
....
}
2、Writer
1、FileWriter
- FileWriter常用方法
1) new FileWriter(File/String); //覆盖模式,相当于流的指针在首端
2) new FileWriter(File/String,true);//追加模式,相当于流的指针在尾端
3) write(int);//写入单个字符
4) write(char[]);//写入指定数组
5) write(char[],off,len);//写入指定数组的指定部分
6) write(String);//写入整个字符串
7) write(String,off,len);//写入字符串的指定部分
//相关API: String类,toCharArray:将String 转成char[]
注意:
FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件。
public class FileWriter_ {
public static void main(String[] args) {
String fileName = "d:\\haha.txt";
FileWriter fileWriter = null;
char[] chars = {'a','b','c'};
try {
fileWriter = new FileWriter(fileName);
// 3) write(int);//写入单个字符
fileWriter.write('H');
// 4) write(char[]);//写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len);//写入指定数组的指定部分
fileWriter.write("我真牛逼".toCharArray(),0,4);
// 6) write(String);//写入整个字符串
fileWriter.write("你好bro");
// 7) write(String,off,len);//写入字符串的指定部分
fileWriter.write("shift",0,5);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、BufferedWriter
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\copy1.txt";
//想要追加模式的话,FileWriter有true那个构造器,BufferedWriter没有
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));
bufferedWriter.write("hello,China");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello,China2");
bufferedWriter.newLine();
bufferedWriter.write("hello,China3");
bufferedWriter.newLine();
//关闭
bufferedWriter.close();
}
}
3、OutputStreamWriter
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\caiji.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), "utf-8");
osw.write("你是真的马飞");
osw.close();
}
}
6、Properties类
1、基本介绍
1)专门用于读写配置文件的集合类;
配置文件格式:
键=值
键=值
2)注意:键值对不需要有空格,值不需要用引号框起来,默认类型是String。
3)Properties的常见方法
1. load; //加载配置文件的键值对到Properties对象
2. list; //将数据显示到指定设备
3. getProperty(key); //根据键获取值
4. setProperty(key,value);//设置键值对到Properties对象
5. store; //将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码
public class Properties01 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件
//创建Properties对象
Properties properties = new Properties();
//加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//把k-v显示在控制台
properties.list(System.out);
//根据key获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码=" + pwd);
//
}
}
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件,修改配置文件内容
Properties properties = new Properties();
//创建
//有key ,是修改
//没有key,是创建
properties.setProperty("charset","utf-8");
properties.setProperty("user","汤姆");//有中文,是Unicode编码
properties.setProperty("pwd","abc1111");
//将k-v存储文件中,第二个参数是注释,会写在配置文件最上面
properties.store(new FileOutputStream("src:\\mysql2.properties"),null);
System.out.println("保存配置文件成功~");
}
}
11、网络编程
1、网络的相关概念
- 网络通信
- 将数据通过网络从一台设备传输到另一台设备;
- java.net包下提供了一系列的类或接口。
-
网络
- 局域网
- 城域网
- 广域网
-
ip地址
- 查看IP地址:ipconfig;
- ip地址的表现形式:点分十进制 xx.xx.xx.xx;
- 每一个十进制数的范围:0~255;
- ip地址的组成 = 网络地址 + 主机地址;
- 下一代IP协议:IPv6;【128位,16字节,是IPv4的4倍】
- IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍
- 域名
- www.baidu.com;
- 好处:为了方便记忆,解决记ip的困难;
- 概念:将ip地址映射成域名。
- 端口号
- 概念:用于标识计算机上某个特定的网络程序;
- 表示形式:以整数形式,范围0~65535;
- 0~1024已经被占用,比如 ssh 22, ftp 21, smtp 25, http 80;
- 常见的网络程序端口号:
- tomcat:8080
- mysql:3306
- oracle:1521
- sqlserver:1433
-
网络通信协议
- 协议(TCP/IP)
TCP/IP (Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。
- TCP和UDP
- TCP协议
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道;
- 传输前,采用“三次握手”方式,是可靠的;
- TCP协议进行通信的两个应用进程:客户端、服务端;
- 在连接中可进行大数据量的传输;
- 传输完毕,需释放已建立的连接,效率低。
-
- UDP协议
- 将数据、源、目的封装成数据包,不需要建立连接;
- 每个数据报的大小限制在64K内,不适合传输大量数据;
- 因为无需连接,所以是不可靠的;
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快;
2、InetAddress类
- 相关方法
- 获取本机InetAddress对象 getLocalHost;
- 根据指定主机名/域名获取ip地址对象 getByName;
- 获取InetAddress对象的主机名 getHostName;
- 获取InetAddress对象的地址 getHostAddress。
public class API_ {
public static void main(String[] args) throws UnknownHostException {
//获取本机的InetAddress对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-SKVBTC8/192.168.2.14
//根据主机名,获取InetAddress对象
InetAddress host1 = InetAddress.getByName("DESKTOP-SKVBTC8");
System.out.println(host1);//DESKTOP-SKVBTC8/192.168.2.14
//根据域名返回InetAddress对象,比如百度
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2);//www.baidu.com/180.101.50.188
//通过InetAddress对象,获取对应的地址
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress);//180.101.50.188
//通过InetAddress对象,获取对应的主机名或者域名
String hostName = host2.getHostName();
System.out.println(hostName);//www.baidu.com
}
}
3、Socket
- 基本介绍
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准;
- 通信的两端都要有Socket,是两台机器间通信的端点;
- 网络通信其实就是Socket间的通信;
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输;
- 一般主动发起通信的应用程序属客户端,等待通信请求的位服务端。
4、TCP网络通信编程
- 基本介绍
- 基于客户端—服务器的网络通信;
- 底层使用的是TCP/IP协议;
- 应用场景举例:客户端发送数据,服务端接受并显示控制台;
- 基于Socket的TCP编程。
应用案例1(使用字节流【单方】)
客户端单方面发送数据给服务端,服务端获取打印数据。
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//在本机的9999端口监听,等待连接
//前提是9999没有其他服务占用/在监听9999端口
//ServerSocket 可以通过 accept()返回多个socket【多个客户端连接服务器,多并发】
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在9999端口监听,等待连接。。。");
//当没有客户端连接9999端口时,程序会阻塞,等待连接
//如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("socket=" + socket.getClass());
//通过socket.getInputStream() 读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen =0;
while((readLen=inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
//关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();//多一个serverSocket关闭
System.out.println("服务器关闭");
}
}
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//连接服务器(ip,端口)
//如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("socket="+ socket.getClass());
//连接上后,生成Socket,得到socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//通过输出流,写入数据到数据通道
outputStream.write("hello,server".getBytes());
//关闭流对象和socket
outputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
应用案例2(使用字节流【双方】)
在上一个案例的基础上改变:
客户端和服务端互相发送数据,并且获取和打印数据。
注意:双方发送的时候,需要在发送的数据后添加结束标记,不然会卡住。
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//在本机的9999端口监听,等待连接
//前提是9999没有其他服务占用/在监听9999端口
//ServerSocket 可以通过 accept()返回多个socket【多个客户端连接服务器,多并发】
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在9999端口监听,等待连接。。。");
//当没有客户端连接9999端口时,程序会阻塞,等待连接
//如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("socket=" + socket.getClass());
//通过socket.getInputStream() 读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen =0;
while((readLen=inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
}
//获取socket的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,client".getBytes());
//设置结束标记
socket.shutdownOutput();
//关闭流和socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("服务器关闭");
}
}
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//连接服务器(ip,端口)
//如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("socket="+ socket.getClass());
//连接上后,生成Socket,得到socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//通过输出流,写入数据到数据通道
outputStream.write("hello,server".getBytes());
//设置结束标记
socket.shutdownOutput();
//获取socket的输入流,读取数据,并打印
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf))!=-1){
System.out.println(new String(buf,0, readLen));
}
//关闭流对象和socket
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
应用案例3(使用字符流)
跟案例2一样的效果。
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
//在本机的9999端口监听,等待连接
//前提是9999没有其他服务占用/在监听9999端口
//ServerSocket 可以通过 accept()返回多个socket【多个客户端连接服务器,多并发】
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在9999端口监听,等待连接。。。");
//当没有客户端连接9999端口时,程序会阻塞,等待连接
//如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("socket=" + socket.getClass());
//通过socket.getInputStream() 读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//IO读取,使用字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = bufferedReader.readLine();
System.out.println(line);
//获取socket的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello,client");
bufferedWriter.newLine();
bufferedWriter.flush();
//关闭外部流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();
System.out.println("服务器关闭");
}
}
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//连接服务器(ip,端口)
//如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("socket="+ socket.getClass());
//连接上后,生成Socket,得到socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//通过输出流,写入数据到数据通道,使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello,server");
bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束,但是对方得使用readLine()
bufferedWriter.flush();//如果使用的是字符流,需要手动刷新,否则数据写不进去
//获取socket的输入流,读取数据,并打印
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = bufferedReader.readLine();
System.out.println(line);
//关闭外层流对象和socket
bufferedReader.close();
bufferedWriter.close();
socket.close();
System.out.println("客户端退出");
}
}
应用案例4(上传文件)
public class StreamUtils {
/**
* 功能:将输入流转换成byte[],既可以把文件的内容读入到byte[]
*
* @param is
* @return
* @throws Exception
*/
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;
}
/**
* 功能:将InputStream转换成String
* @param is
* @return
*/
public static String streamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
String line;
while((line=reader.readLine())!=null){
builder.append(line+"\r\n");
}
return builder.toString();
}
}
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {
//服务端在本机监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器在8888端口进行监听。。。。");
//等待连接
Socket socket = serverSocket.accept();
//读取客户端发送的数据
//通过socket获得一个输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//将得到的bytes,写入到指定的路径,就得到一个文件了
String destFilePath = "src\\qie2.png";
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 class TCPFileUploadClient {
public static void main(String[] args) throws Exception {
//客户端连接服务器,得到Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//创建读取磁盘文件的输入流
String filePath = "d:\\qie.png";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
//bytes就是文件对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);
//通过socket获取到输出流,将bytes数据发送到服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);//将文件字节数组写入到数据通道里
bis.close();
socket.shutdownOutput();//设置写入数据的结束标志
//接收服务端回复的信息
InputStream inputStream = socket.getInputStream();
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
//关闭相关的流
inputStream.close();
bos.close();
socket.close();
}
}
文件下载
- 编写客户端程序和服务端程序;
- 客户端可以输入一个音乐文件名,比如高山流水,服务端收到音乐名后,可以给客户端返回这个音乐文件,如果服务器没有这个文件,返回一个默认的音乐即可;
- 客户端收到文件后,保存到本地 D:\\
- 有使用之前的工具类StreamUtils
public class Server {
public static void main(String[] args) throws Exception {
//监听9999端口
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端连接
Socket socket = serverSocket.accept();
//读取,客户端发送的下载文件名称
//此处是为了防止文件名太长,才采用循环
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
String downLoadFileName = "";
while ((len = inputStream.read(b)) != -1) {
downLoadFileName += new String(b, 0, len);
}
System.out.println("客户端希望下载文件名:" + downLoadFileName);
//如果客户下载的是 高山流水,我们就返回该文件,否则一律 默认文件
String resFileName = "";
if ("高山流水".equals(downLoadFileName)) {
resFileName = "src\\高山流水.mp3";
} else {
resFileName = "src\\默认.mp3";
}
//创建一个输入流,读取文件
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(resFileName));
//使用工具类,读取文件到一个字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);
//得到socket的输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//写入数据通道
bos.write(bytes);
//设置结束标志
socket.shutdownOutput();
bos.close();
//关闭资源
bis.close();
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("服务端退出");
}
}
public class Client {
public static void main(String[] args) throws Exception {
//接收用户输入,指定下载文件名
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要下载的文件名:");
String downloadFileName = scanner.next();
//客户端连接服务端,获取socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
//获取socket输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write(downloadFileName.getBytes());
//设置结束标志
socket.shutdownOutput();
//读取服务端返回的文件(字节数组)
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//得到一个输出流,准备把bytes写入磁盘
String filePath = "d:\\"+downloadFileName+".mp3";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
bos.write(bytes);
//关闭资源
bos.close();
bis.close();
outputStream.close();
socket.close();
System.out.println("客户端下载完毕");
}
}
netstat 指令
- netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况;
- netstat -an|more 可以分页显示;
- 要求在dos控制台下执行。
- netstat -anb可以在原来的基础上,看到是哪个程序在连接端口。【需要以管理员打开dos】
说明:
- Listening 表示某个端口在监听;
- 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息;
- 可以输入crtl +c ,把more全部展开。
TCP网络通讯不为人知的秘密
- 当客户端连接到服务端后,实际上客户端也是通过一个端口和服务器进行通讯的,这个端口时TCP/IP来分配的,是不确定的,是随机的。
5、UDP网络通信编程【了解】
1、基本介绍
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
- UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
- UDP协议中每个数据宝都给出了完整的地址信息,因此无须建立发送方和接收方的连接。
2、基本流程
- 核心的两个类/对象 DatagramSocket 和 DatagramPacket;
- 建立发送端、接收端;
- 建立数据包;
- 调用DatagramSocket的发送、接收方法;
- 关闭DatagramSocket。
3、应用案例
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//创建一个DatagramSocket 对象,准备在9999接收数据
DatagramSocket socket = new DatagramSocket(9999);
//构建一个DatagramPacket对象,准备接收数据
//UDP最大的包包是64K
//byte[] buf = new byte[64 * 1024];
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//调用接收方法,把网络传播的DatagramPacket对象填充到我们的packet对象里面
System.out.println("接收端 等待接收数据。。。");
socket.receive(packet);
//可以把packet进行拆包,取出数据,并显示
int length = packet.getLength();//获取实际接收数据的长度
byte[] data = packet.getData();//获取数据
String s = new String(data, 0, length);
System.out.println(s);
//发送回复信息给B端
//将需要发送的数据,封装到DatagramPacket对象中
data = "好的,明天见".getBytes();
//参数:字节数组,长度,主机(IP),端口
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);
socket.send(packet);
//关闭资源
socket.close();
System.out.println("A端退出");
}
}
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,准备发送和接收数据
DatagramSocket socket = new DatagramSocket(9998);
//将需要发送的数据,封装到DatagramPacket对象中
byte[] data = "hello 明天吃火锅~".getBytes();
//参数:字节数组,长度,主机(IP),端口
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);
socket.send(packet);
//接收信息
//构建一个DatagramPacket对象,准备接收数据
//UDP最大的包包是64K
//byte[] buf = new byte[64 * 1024];
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//调用接收方法,把网络传播的DatagramPacket对象填充到我们的packet对象里面
System.out.println("接收端 等待接收数据。。。");
socket.receive(packet);
//可以把packet进行拆包,取出数据,并显示
int length = packet.getLength();//获取实际接收数据的长度
data = packet.getData();//获取数据
String s = new String(data, 0, length);
System.out.println(s);
//关闭资源
socket.close();
System.out.println("B端退出");
}
}
12、反射
需求:通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则
【ocp原则(开闭原则:不修改源码,扩容功能)】
classfullpath=com.zwj.Cat
method=hi
//使用Properties类,读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
//使用反射机制
//(1)加载类,返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2)通过 cls 得到你加载的类 com.zwj.Cat 的对象实例
Object o = cls.newInstance();
System.out.println(o.getClass());//运行类型为 Cat
//(3)通过 cls 得到你加载的类 com.zwj.Cat 的 methodName 的方法对象
Method method1 = cls.getMethod(methodName);
//(4)通过method1 调用方法,即通过方法对象来实现调用方法
method1.invoke(o); //传统方法 对象.方法(), 反射机制 方法.invoke(对象)
1、反射机制
1、基本介绍
- 反射机制允许程序在执行期间借助与Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到;
- 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。ClassLoader
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kptbUoi-1676708128816)(D:\Users\WJ_Zheng\Desktop\文档里的图片\反射原理图.jpg)]
2、Java反射机制的作用
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时得到任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的成员变量和方法;
- 生成动态代理。
3、反射优点和缺点
- 优点:可以动态的创建和适用对象(也是框架底层核心),使用灵活。
- 缺点:使用反射基本是解释执行,对执行速度有影响。
2、Class类
1、基本介绍
- Class也是类,因此也继承Object类;
- Class类对象不是new出来的,而是系统创建的;ClassLoader
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次;
- 每个类的实例都会记得自己是由哪个Class实例所生成的;
- 通过Class可以完整地得到一个类的完整结构,通过一系列API;
- Class对象是存放在堆的;
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)。
2、获取Class类对象
-
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例
Class cls1 = Class.forName("java.lang.Cat");
应用场景:多用于配置文件,读取类全路径,加载类。Class.forName(全类名)
-
前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。实例:
Class cls = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象。类.class
-
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:
Class cls = 对象.getClass();
应用场景:通过创建好的对象,获取Class对象。 实例.getClass()
-
其他方式:
//先得到类加载器,再通过类加载器得到Class对象 类加载器.loadClass(全类名)
ClassLoader cl = 对象.getClass().getClassLoader();
Class cl4 = cl.loadClass("类的全类名");
-
基本数据类型得到Class: 基本数据类型.class
-
基本数据类型的包装类得到Class:包装类.TYPE
3、有Class对象的类型
- 外部类,内部类;
- 接口;
- 数组;
- 枚举;
- 注解;
- 基本数据类型;
- void
3、类加载
1、基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强;
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。
2、类加载的时机
- 当创建对象时(new);
- 当子类被加载时;
- 调用类中的静态成员时;
- 通过反射。
3、具体说明
1、加载阶段
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至是网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象。
2、连接阶段-验证
- 目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;
- 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证;
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
3、连接阶段-准备
JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false 等)。这些变量所使用的的内存都将在方法区中进行分配。
4、连接阶段-解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
5、初始化
- 到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行() 方法的过程
- () 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
- 虚拟机会保证一个类的() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的() 方法,其他线程都需要阻塞等待,直到活动线程执行() 方法完毕。
4、反射获取类的结构信息
1、java.lang.Class
1.getName;//获取全类名
2.getSimpleName;//获取简单类名
3.getFields;//获取所有public修饰的属性,包含本类以及父类的
4.getDeclaredFields;//获取本类中所有属性
5.getMethods;//获取所有public修饰的方法,包含本类以及父类
6.getDeclaredMethods;//获取本类中所有的方法
7.getConstructors;//获取本类中所有public修饰的构造器
8.getDeclaredConstructors;//获取本类中所有的构造器
9.getPackage;//以Package形式返回 包信息
10.getSuperClass;//以Class形式返回父类信息
11.getInterfaces;//以Class[] 形式返回父类信息
12.getAnnotations;//以Annotation[] 形式返回注解信息
2、java.lang.reflect.Field
1.getModifiers;//以int形式返回修饰符
//【默认修饰符是0,public是1,private是2,Protected是4,static是8,final是16】
2.getType;//以Class形式返回类型
3.getName;//返回属性名
3、java.lang.reflect.Method
1.getModifiers;//以int形式返回修饰符
//【默认修饰符是0,public是1,private是2,Protected是4,static是8,final是16】
2.getReturnType;//以Class形式获取返回类型
3.getName;//返回方法名
4.getParameterTypes;//以Class[]返回参数类型数组
4、java.lang.reflect.Constructor
1.getModifiers;//以int形式返回修饰符
2.getName;//返回构造器名(全类名)
3.getParameterTypes;//以Class[]返回参数类型数组
5、通过反射创建对象
- 方式一:调用类中的public修饰的无参构造器;
- 方式二:调用类中的指定构造器;
- Class类相关方法:
- newInstance: 调用类中的无参构造器,获取对应类的对象;public
- getConstructor(Class…clazz): 根据参数列表,获取对应的构造器对象;public
- getDecalaredConstructor(Class…clazz): 根据参数列表,获取对应的构造器对象;all
- Constructor类相关方法:
- setAccessible: 暴破
- newInstance(Object…obj): 调用构造器
public class ReflecCreateInstance {
public static void main(String[] args) throws Exception {
//先获取到User类的Class对象
Class<?> userClass = Class.forName("com.zwj.User");
//通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//通过public的有参构造器创建实例
//1.先获得相应的构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//2.创建实例,并传入实参
Object user1 = constructor.newInstance("shuaibi");
System.out.println(user1);
//通过非public的有参构造器创建实例
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
constructor1.setAccessible(true);//暴破,使用反射可以访问private构造器/方法/属性
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println(user2);
}
}
class User{
private int age;
private String name;
public User(){//无参 public
}
public User(String name){//有参 pubic
this.name = name;
}
private User(int age,String name){//有参 private
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
6、访问属性
-
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名);
-
暴破:
f.setAccessible(true); //f 是 Field
-
访问:
f.set(o,值);
syso(f.get(o));
-
如果是静态属性,则set和get中的参数o,可以写成null。
public class ReflectAccessProperty {
public static void main(String[] args) throws Exception {
//得到Class对象
Class<?> stuClass = Class.forName("com.zwj.Student");
Object o = stuClass.newInstance();
//使用反射得到属性age
Field age = stuClass.getField("age");
age.set(o,88);//通过反射设置属性
System.out.println(o);
System.out.println(age.get(o));
//使用反射操作name属性
Field name = stuClass.getDeclaredField("name");
//暴破
name.setAccessible(true);//可以操作私有了
name.set(o,"老帅比");
name.set(null,"捞仔"); //对于静态属性也能这么设置,无需对象
System.out.println(o);
System.out.println(name.get(o));
System.out.println(name.get(null));
}
}
class Student {
public int age;
private static String name;
public Student() {
}
public String toString() {
return "Student [age=" + age + ",name=" + name + "]";
}
}
7、访问方法
- 根据方法名和参数列表获取Method方法对象:
Method m = clazz.getDeclaredMethod(方法名,XX.class);
- 获取对象:
Object o = clazz.newInstance();
- 暴破:
m.setAccessible(true);
- 访问:
Object returnValue = m.invoke(o,实参列表);
- 注意:如果是静态方法,则invoke的参数o,可以写成null。
public class ReflectAccessMethod {
public static void main(String[] args) throws Exception {
Class<?> bossClass = Class.forName("com.zwj.Boss");
Object o = bossClass.newInstance();
Method hi = bossClass.getMethod("hi", String.class);
hi.invoke(o, "hello");
//调用私有方法
Method say = bossClass.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
System.out.println(say.invoke(o,100,"张三",'男'));
//say还是个静态方法
System.out.println(say.invoke(null,200,"张四",'中'));
}
}
class Boss {
public int age;
private static String name;
public Boss() {
}
private static String say(int n, String s, char c) {
return n + " " + s + " " + c;
}
public void hi(String s) {
System.out.println(s);
}
}
5、反射相关类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象;
- java.lang.reflect.Method:代表类的方法;
- java.lang.reflect.Field:代表类的成员变量;
- java.lang.reflect.Constructor:代表类的构造函数。
这些类在java.lang.reflect包中。
//getField不能得到私有变量
Field nameField = cls.getField("age");
System.out.println(nameField.get(o));
//Constructor
Constructor constructor = cls.getConstructor();//()中可以指定构造器参数类型
System.out.println(constructor);//当前返回的是无参构造器
cls.getConstructor(String.class); //这里是Cat(String name){}构造器
6、反射调用性能优化
1、关闭访问检查
- Method和Field、Constructor对象都有==setAccessible()==方法;
- setAccessible作用是启动和禁用访问安全检查的开关;
- 参数值为true表示 反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则执行访问检查。
7、Class类常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名name的Class对象 |
Object newInstance() | 调用无参构造器,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
String classAllPath = "com.zwj.Car";
//获得Class对象
Class<?> cls = Class.forName(classAllPath);
//输出cls
System.out.println(cls);//显示cls对象,是哪个类的Class对象 com.zwj.Car
System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
//得到包名
System.out.println(cls.getPackage().getName());//包名 com.zwj
//得到全类名
System.out.println(cls.getName());//com.zwj.Car
//通过cls创建对象实例
Car car = (Car)cls.newInstance();
System.out.println(car);//car.toString()
//通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));//奔驰
//得到所有的属性
Field[] fields = cls.getFields();
for(Field f:fields){
System.out.println(f.getName());//属性名称
}
13、数据库(MySQL)
1、MySQL安装和配置
-
mysql常用版本:mysql5.5 | mysql5.6 |mysql5.7(稳定)|mysql8 更高版本
-
使用命令行窗口连接MySQL数据库
- mysql -h 主机名 -P 端口 -u 用户名 -p密码
- 登录前,保证服务启动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FPGxml2c-1676708128817)(D:\Users\WJ_Zheng\Desktop\文档里的图片\启动mysql数据库[Dos命令].jpg)]
注意:
(1)-p密码不要有空格;
(2)-p后面没有写密码,回车会要求输入密码;
(3)如果没有写-h 主机,默认就是本机;
(4)如果没有写-P 端口,默认就是3306;
(5)在实际工作中,3306一般修改。
0731_韩顺平Java_Mysql5.7安装配置_哔哩哔哩_bilibili
0733_韩顺平Java_Navicat安装和使用_哔哩哔哩_bilibili
0734_韩顺平Java_SQLyog安装和使用_哔哩哔哩_bilibili
2、数据库
- SQL语句分类
- DDL:数据定义语句【create 表,库…】
- DML:数据操作语句【增加insert,修改update,删除delete】
- DQL:数据查询语句【select】
- DCL:数据控制语句【管理数据库的用户权限grant revoke】
1、创建数据库
- CHARACTER SET:指定数据库采用的字符集,如果不指定字符集,默认utf8
- COLLATE:指定数据库字符集的校对规则(常用的 utf8_bin【区分大小写】、utf8_general_ci【不区分大小写】 注意默认是utf8_general_ci)
# 创建数据库
CREATE DATABASE db02
# 删除数据库
DROP DATABASE db02
# 创建一个使用utf字符集的数据库
CREATE DATABASE db02 CHARACTER SET utf8
# 创建一个使用utf8字符集,并带校对规则utf8_bin的数据库
CREATE DATABASE db02 CHARACTER SET utf8 COLLATE utf8_bin
2、查看、删除数据库
# 查看当前数据库服务器中的所有数据库
SHOW DATABASES
# 查看前面创建的数据库的定义信息
SHOW CREATE DATABASE `db01`
# 在创建数据库,表的时候,为了规避关键字,可以使用反引号解决
CREATE DATABASE `CREATE`
# 删除数据库
DROP DATABASE db01
3、备份恢复数据库
-
备份数据库(注意:在DOS执行)
mysqldump -u 用户名 -p -B 数据库1 数据库2 数据库n > 文件名.sql
-
恢复数据库(注意:进入SQLyog再执行)
Source 文件名.sql
-
备份库的表
mysqldump -u 用户名 -p密码 数据库 表1 表2 表n > d:\\文件名.sql
3、表
1、创建
CREATE TABLE `user` (
id INT,
`name` VARCHAR ( 255 ),
`password` VARCHAR ( 255 ),
`birthday` DATE )
CHARACTER SET utf8 COLLATE utf8_bin ENGINE INNODB
2、删除
DROP TABLE `user`
3、修改
修改表名:Rename table 表名 to 新表名
修改表字符集:alter table 表名 character set 字符集
-- 员工表emp上增加一个image列,varchar 类型(要求在resume后面)
ALTER TABLE emp
ADD image VARCHAR(32) NOT NULL DEFAULT ''
AFTER RESUME
-- 修改job列,使其长度为60
ALTER TABLE emp
MODIFY job VARCHAR(60) NOT NULL DEFAULT ''
-- 删除sex列
ALTER TABLE emp
DROP sex
-- 表名改为employee
RENAME TABLE emp TO employee
-- 修改表的字符集为utf8
ALTER TABLE employee CHARACTER SET utf8
-- 列名name修改为user_name
ALTER TABLE employee
CHANGE `name` `user_name` VARCHAR(64) NOT NULL DEFAULT ''
-- 查看表结构
DESC employee
4、MySQL数据类型
-
bit[] :位,当数据只有0和1的时候,可以考虑用这种数据类型;
-
decimal[M,D]:
-
M是小数位数的总数,D是小数点后面的位数
-
如果D是0,则没有小数点部分。M最大65,D最大是30。
如果D被省略,默认是0。如果M被省略,默认是10。
-
-
char(size) 固定长度字符串 最大255 字符
-
varchar(size) 可变长度字符串,最大65532 字节【utf8编码最大21844字符,utf8除以3,gbk除以2】,【1-3个字节记录该字段大小,所以不是65535】
-
什么时候使用 char ,什么时候使用 varchar
- 如果数据是定长,推荐使用char ,比如md5密码,邮编,手机号,身份证号码;
- 如果一个字段的长度不定,我们使用varchar,不如留言,文章;
- 查询速度 char > varchar
-
在存放文本时varchar不够用,也可以使用Text数据类型。
-
时间戳timestamp:
CREATE TABLE t(
login_time TIMESTAMP
NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP);-- 如果希望login_time列自动更新,需要配置
5、CRUD
1、Insert
CREATE TABLE `goods` (
id INT,
goods_name VARCHAR ( 10 ),
price DOUBLE );
INSERT INTO `goods` ( id, goods_name, price )
VALUES( 10, '华为手机', 2000 );
INSERT INTO `goods` ( id, goods_name, price )
VALUES( 20, 'vivo', 1888 );
SELECT * FROM goods;
注意事项:
- 字符和日期数据应包含在单引号中;
- 列可以插入空值【前提是该字段允许为空】;
insert into tab_name (列名...) values(), (), ()
形式添加多条记录;- 如果是给表中的所有字段添加数据,可以不写前面的字段名称;
- 当不给某个字段值时,如果有默认值就会添加。
2、Update
-- 1.将所有员工薪水修改为5000元
UPDATE employee SET salary = 5000
-- 2.将姓名为 小妖怪 的员工薪水修改为3000元
UPDATE employee
SET salary = 3000
WHERE user_name = '小妖怪'
-- 3.将 老妖怪 的薪水在原有基础上增加1000元
INSERT INTO employee
VALUES(200,'老妖怪','1990-11-11','2000-11-11 10:10:10','捶背的',5000,'给大王捶背');
UPDATE employee
SET salary = salary + 1000
WHERE user_name = '老妖怪'
SELECT * FROM employee
使用细节:1. 如果没有WHERE ,则更新所有行;
2. 如果需要修改多个字段,可以通过set 字段1=值1,字段2=值2…
3、Delete
DELETE FROM employee
WHERE user_name = '老妖怪'
-- 删除所有记录
DELETE FROM employee
注意细节:
- Delete语句不能删除某一列的值(可使用update将某一列的值设为
null
或者''
) - delete语句只能删除记录,不删除表本身【删除表得用drop】
4、Select
1、单表
DISTINCT可选,指显示结果时,是否去掉重复数据。
-- 查询表中所有学生的信息
SELECT * FROM student
-- 查询表中所有学生的姓名和对应的英语成绩
SELECT `name`,english FROM student
-- 过滤表中重复数据 distinct
-- 要查询的记录,每个字段都相同,才会去重
SELECT DISTINCT * FROM student
- 使用表达式对查询的列进行运算
- 在select语句中可使用as语句
SELECT `name`,( chinese + english + math + 10 ) AS total FROM student
- 在where子句中经常使用的运算符
-- 查询姓名为赵云的学生成绩
SELECT * FROM student
WHERE `name` = '赵云'
-- 查询英语成绩大于90的学生
SELECT * FROM student
WHERE english > 90
-- 查询总分大于200分的所有同学
SELECT * FROM student
WHERE (chinese + english + math) > 200
-- 查询math大于60 并且id大于4的学生成绩
SELECT * FROM student
WHERE math > 60 AND id > 4
-- 查询英语成绩大于语文成绩的同学
SELECT * FROM student
WHERE english > chinese
-- 查询总分大于200分 并且数学成绩小于语文成绩的性王的学生
SELECT * FROM student
WHERE (chinese + english + math) > 200 AND
math < chinese AND `name` LIKE '王%'
- 使用order by 子句排序查询结果
- Order by 指定排序的列,可以是表中的,也可以是select语句后指定的列名;
- Asc 升序[默认]、Desc 降序;
- ORDER BY子句应位于SELECT语句结尾。
-- 对数学成绩排序后输出【升序】
SELECT * FROM student
ORDER BY math
-- 对总分按从高到低的顺序输出【降序】
SELECT `name`,(chinese + english + math) AS total_score FROM student
ORDER BY total_score DESC
-- 对姓李的学生成绩排序输出(升序)
SELECT * FROM student
WHERE `name` LIKE '李%'
ORDER BY math
2、查询加强
1、增强
-- 在mysql中,日期类型可以直接比较
SELECT * FROM emp
WHERE hiredate < '1992-01-01'
-- 模糊查询
-- %: 表示0到多个字符, _:表示单个字符
-- 如何显示首字母为s的员工姓名和工资?
SELECT ename,sal FROM emp
WHERE ename LIKE 'S%'
-- 如何显示第三个字符为大写O的所有员工的姓名和工资?
SELECT ename,sal FROM emp
WHERE ename LIKE '__O%'
-- 如何显示没有上级的雇员的情况
SELECT * FROM emp
WHERE mgr IS NULL
-- 如何按照工资的从低到高的顺序,显示雇员的信息
SELECT * FROM emp
ORDER BY sal
-- 按照部门号升序而雇员的工资降序排序,显示雇员信息
SELECT * FROM emp
ORDER BY deptno ASC,wages DESC
2、分页查询
-- 分页查询
-- 第一页
SELECT * FROM emp
ORDER BY empno
LIMIT 0,3
-- 第二页
SELECT * FROM emp
ORDER BY empno
LIMIT 3,3
-- 公式
SELECT * FROM emp
ORDER BY empno
LIMIT 每页显示记录数*(第几页-1),每页显示记录数
3、分组增强
-- 显示每个岗位的雇员总数,以及平均工资
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
-- 显示管理者的总人数
SELECT COUNT(DISTINCT mgr)
FROM emp
-- 显示雇员工资的最大差额
SELECT MAX(sal)-MIN(sal)
FROM emp
4、总结
group by、having、order by、limit使用时候的顺序:
-- 请统计各个部门group by 的平均工资avg,
-- 并且是大于1000的having, 并且按照平均工资从高到低排序,order by
-- 取出前两行记录 Limit
SELECT deptno,AVG(sal) AS avg_sal
FROM emp
GROUP BY deptno
HAVING avg_sal > 1000
ORDER BY avg_sal DESC
LIMIT 0,2
3、多表
多表查询的条件至少不能少于【 表数-1 】
-- 显示部门号为10的部门名、员工名和工资
SELECT ename,sal,dname,emp.deptno
FROM emp,dept
WHERE emp.deptno = dept.deptno AND emp.deptno = 10
1、自连接
自连接是指在同一张表的连接查询。
-- 显示公司员工名字和他的上级的名字
SELECT worker.ename AS '职员名',boss.ename AS '上级名'
FROM emp worker,emp boss -- 同一张表需要取别名
WHERE worker.mgr = boss.empno -- mgr是上级编号,empno是职员编号
2、子查询
-
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询;
-
单行子查询是指只返回一行数据的子查询语句;
-
多行子查询指返回多行数据的子查询,使用关键字 in。
-- 显示与SMITH同一部门的所有员工
SELECT *
FROM emp
WHERE deptno = (
SELECT deptno
FROM emp
WHERE ename = 'SMITH'
)
-- 查询和部门10的工作相同的雇员的名字、岗位、工资、部门号,但是不含10自己
SELECT ename,job,sal,deptno
FROM emp
WHERE job IN(
SELECT DISTINCT job
FROM emp
WHERE deptno = 10
) AND deptno <> 10
- 子查询当做临时表使用
-- 查询ecshop中各个类别中,价格最高的商品
SELECT goods_id,temp.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
WHERE temp.cat_id = ecs_goods.cat_id
AND temp.max_price = ecs_goods.shop_price
- 在多行子查询中使用all和any操作符
-- 显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
SELECT ename,sal,deptno
FROM emp
WHERE sal > ALL(
SELECT sal
FROM emp
WHERE deptno = 30
)
-- 显示工资比其中一个员工工资高的信息
SELECT ename,sal,deptno
FROM emp
WHERE sal > ANY(
SELECT sal
FROM emp
WHERE deptno = 30
)
- 多列子查询是指查询返回多个列数据的子查询语句
-- 查询与smith的部门和岗位完全相同的所有雇员(并且不含smith本人)
-- (字段1,字段2,...) = (select 字段1,字段2 from ...)
SELECT *
FROM emp
WHERE (deptno,job) = (
SELECT deptno,job
FROM emp
WHERE ename = 'SMITH'
) AND ename != 'SMITH'
3、合并查询
有时为了合并多个select语句的结果,可以使用集合操作符号。union,union all
-
union all
该操作符用于取得两个结果集的并集,但不会取消重复行。
-
union
会自动去掉结果集中的重复行。
-- 不会去重
SELECT ename,sal,job FROM emp WHERE sal>2500 -- 5
UNION ALL
SELECT ename,sal,job FROM emp WHERE job='MANAGER' -- 3
-- 会去重
SELECT ename,sal,job FROM emp WHERE sal>2500 -- 5
UNION
SELECT ename,sal,job FROM emp WHERE job='MANAGER' -- 3
4、表复制和表去重
- 自我复制数据(蠕虫复制)
有时,为了对某个sql语句进行效率测试,我们需要海量数据时,可以使用此法为表创建海量数据。
-- 先把emp表的记录复制到my_tab01
INSERT INTO my_tab01
(id,`name`,sal,job,deptno)
SELECT empno,ename,sal,job,deptno FROM emp
-- 自我复制(多次使用下面的语句)
INSERT INTO my_tab01
SELECT * FROM my_tab01
-- 如何删除掉一张表的重复记录
/*
(1)先创建一张临时表my_tmp,该表的结构和my_tab02一样
(2)把my_tmp 的记录通过distinct关键字 处理后 把记录复制到 my_tmp
(3)清除掉 my_tab02记录
(4)把 my_tmp 表的记录复制到 my_tab02
(5)drop 掉 临时表 my_tmp
*/
-- (1)先创建一张临时表my_tmp,该表的结构和my_tab02一样
CREATE TABLE my_tmp LIKE my_tab02
-- (2)把my_tmp 的记录通过distinct关键字 处理后 把记录复制到 my_tmp
INSERT INTO my_tmp
SELECT DISTINCT * FROM my_tab02
-- (3)清除掉 my_tab02记录
DELETE FROM my_tab02
-- (4)把 my_tmp 表的记录复制到 my_tab02
INSERT INTO my_tab02
SELECT * FROM my_tmp
-- (5)drop 掉 临时表 my_tmp
DROP TABLE my_tmp
6、函数
1、统计函数
1、Count
count返回行的总数
-- 统计数学成绩大于90的学生有多少个
SELECT COUNT(*) FROM student
WHERE math > 90
count(*) 和 count(列) 的区别:
- count(*) 返回满足条件的记录的行数;
- count(列) 统计某列里满足条件的,但是会排除值为null的。
2、Sum
Sum函数返回满足where条件的行的和,一般是用在数值列上。
-- 统计一个班级语文、英语、数学各科的总成绩
SELECT SUM(math) AS math_total,SUM(english),SUM(chinese) FROM student
3、avg
AVG函数返回满足where条件的一列的平均值。
-- 求一个班级数学平均分
SELECT AVG(math) FROM student
4、max/min
SELECT MAX(math + english + chinese),MIN(math + english + chinese) FROM student
5、group by
- 使用group by 子句对列进行分组
- 使用having 子句对分组后的结果进行过滤
-- 显示每个部门的平均工资和最高工资
SELECT AVG(sal),MAX(sal),deptno
FROM emp GROUP BY deptno
-- 显示每个部门的每种岗位的平均工资和最低工资
SELECT AVG(sal),MAX(sal),deptno,job
FROM emp GROUP BY deptno,job
-- 显示平均工资低于2000的部门号和它的平均工资
SELECT AVG(sal) AS avg_sal,deptno
FROM emp GROUP BY deptno
HAVING avg_sal < 2000
2、时间日期
-- 当前日期
SELECT CURRENT_DATE() FROM DUAL
-- 当前时间
SELECT CURRENT_TIME() FROM DUAL
-- 当前时间戳
SELECT CURRENT_TIMESTAMP() FROM DUAL
SELECT NOW() FROM DUAL
INSERT INTO mes VALUES(3,'广州新闻',NOW())
-- 显示所有新闻信息,发布日期只显示日期,不用显示时间
SELECT id,content,DATE(send_time)
FROM mes
-- 请查询在10分钟内发布的新闻
SELECT *
FROM mes
WHERE DATE_ADD(send_time,INTERVAL 10 MINUTE) >= NOW()
-- 或者
SELECT *
FROM mes
WHERE send_time >= DATE_SUB(NOW(),INTERVAL 10 MINUTE)
-- 请在mysql 的sql 语句中求出 2011-11-11 和 1990-1-1 相差多少天
SELECT DATEDIFF('2011-11-11','1990-01-01') FROM DUAL
-- 请用mysql 的SQL语句求出你活了多少年?
SELECT DATEDIFF(NOW(),'2001-01-01')/365 FROM DUAL
-- 如果你能活80岁,求出你还能活多少天
SELECT DATEDIFF(DATE_ADD('2001-01-01',INTERVAL 80 YEAR),NOW())
FROM DUAL
细节:
- DATE_ADD() 中的 interval 后面可以是 year minute second day等【DATE_SUB一样】
- DATEDIFF(date1,date2)得到的是天数,而且是date1-date2 的天数,因此可以取负数
- 这四个函数的参数日期类型可以是date,datetime,timestamp
-- YEAR|MONTH|DAY|DATE(datetime)
SELECT YEAR(NOW()) FROM DUAL
SELECT MONTH(NOW()) FROM DUAL
SELECT DAY(NOW()) FROM DUAL
-- unix timestamp():返回的是1970-1-1到现在的秒数
SELECT UNIX_TIMESTAMP()/(24*3600*365) FROM DUAL
-- FROM_UNIXTIME() 可以把一个unix_timestamp秒数,转成指定格式的日期
-- 意义:在开发中,可以存放一个整数,然后表示时间,通过FROM_UNIXTIME
SELECT FROM_UNIXTIME(1618483484,'%Y-%m-%d') FROM DUAL
SELECT FROM_UNIXTIME(1618483100,'%Y-%m-%d %H:%i:%s') FROM DUAL
3、字符串函数
-- CHARSET(str) 返回字符串字符集
SELECT CHARSET(ename) FROM emp
-- CONCAT (String2 [,...]) 连接字符串,将多个列拼接成一列
SELECT CONCAT(ename,' job is ',job) FROM emp
-- INSTR(string,substring) 返回substring在string中出现的位置,没有返回0
SELECT INSTR('hanshunping','ping') FROM DUAL
-- UCASE(string2) 转换成大写
SELECT UCASE(ename) FROM emp
-- LCASE(string2) 转换成小写
SELECT LCASE(ename) FROM emp
-- LEFT(string2,length) 从string2中的左边起取length个字符
-- RIGHT(string2,length) 从string2中的右边起取length个字符
SELECT LEFT(ename ,2) FROM emp
-- LENGTH(string) string长度【按照字节】
SELECT LENGTH(ename) FROM emp
-- REPLACE(string1,search_str,replace_str)
-- 在str中用replace_str替换search_str
SELECT ename,REPLACE(job,'MANAGER','经理') FROM emp
-- STRCMP(string1,string2) 逐字符比较两字符串大小
SELECT STRCMP('zzz','iii') FROM DUAL
-- SUBSTRING(str,position,[,length]) 从str的position开始【从1开始计算】
SELECT SUBSTRING(ename,1,2) FROM emp
-- LTRIM(string2) RTRIM(string2) trim 去除前端空格或后端空格
SELECT LTRIM(' 一个人的夜') FROM DUAL
SELECT RTRIM('一个人的夜 ') FROM DUAL
SELECT TRIM(' 一个人的夜 ') FROM DUAL
-- 以首字母小写的方式显示所有员工emp表的姓名
SELECT CONCAT(LCASE(SUBSTRING(ename,1,1)),SUBSTRING(ename,2)) AS new_name
FROM emp
4、数学函数
-- ABS(num) 绝对值
SELECT ABS(-10) FROM DUAL
-- BIN(decimal_number) 十进制转二进制
SELECT BIN(10) FROM DUAL
-- CEILING(number2) 向上取整,得到比num2 大的最小整数
SELECT CEILING(1.1) FROM DUAL
-- CONV(number2,from_base,to_base) 进制转换
SELECT CONV(8,10,2) FROM DUAL
-- FLOOR(number2) 向下取整,得到比 num2 小的最大整数
SELECT FLOOR(-1.1) FROM DUAL
-- FORMAT(number,decimal_places) 保留小数位置
SELECT FORMAT(78.123458,2) FROM DUAL
-- HEX(DecimalNumber) 转十六进制
-- LEAST(number,number2,[,..]) 求最小值
SELECT LEAST(0,1,-10,4) FROM DUAL
-- MOD(numerator, denominator) 求余
SELECT MOD(10,3) FROM DUAL
-- RAND([seed]) 其范围为 0 <= v <= 1.0
SELECT RAND() FROM DUAL
5、流程控制
-- 如果 expr1 为true,则返回expr2 否则返回 expr3
SELECT IF(FALSE,'北京','上海') FROM DUAL
-- 如果 expr1 不为空NULL, 则返回expr1 ,否则返回expr2
SELECT IFNULL(NULL,'牛逼')
-- 如果expr1 为 TRUE,则返回expr2,如果 expr2为t,返回expr4,否则返回expr5
SELECT CASE
WHEN TRUE THEN 'jack'
WHEN FALSE THEN 'tom'
ELSE 'mary' END;
-- 如果comm 是null,则显示0.0
SELECT ename,IF(comm IS NULL,0.0,comm)
FROM emp
SELECT ename,IFNULL(comm,0.0)
FROM emp
-- 多判断
SELECT ename,(SELECT CASE
WHEN job = 'CLERK' THEN '职员'
WHEN job = 'MANAGER' THEN '经理'
WHEN job = 'SALESMAN' THEN '销售人员'
ELSE job END) AS 'job'
FROM emp;
6、加密函数
-- USER() 查询用户
-- 可以查看登录到mysql的有哪些用户,以及登录的IP
SELECT USER() FROM DUAL; -- 用户@IP地址
-- DATEBASE() 查询当前使用数据库名称
SELECT DATABASE();
-- MD5(str) 为字符串算出一个 MD5 32 的字符串,常用(用户密码)加密
-- root 密码是 zzz->加密->在数据库中存放的是加密后的密码
SELECT MD5('zzz') FROM DUAL;
-- 存密码一定要加密
INSERT INTO `user`
VALUES(100,'嘿嘿',MD5('heihei'));
SELECT * FROM `user`
WHERE `name`='嘿嘿' AND pwd = MD5('heihei')
-- PASSWORD(str) -- 加密函数 -- Mysql数据库用户的密码加密就是用的这个函数
SELECT PASSWORD('str') FROM DUAL
7、内连接
8、外连接
- 左外连接(如果左侧的表完全显示我们就说是左外连接)
- 右外连接(如果右侧的表完全显示我们就说是右外连接)
-- 使用左外连接,
-- 显示所有人的成绩,如果没有成绩,也要显示该人的姓名和id号,成绩显示为空
SELECT `name`,stu.id,grade
FROM stu LEFT JOIN exams
ON stu.id = exam.id
-- 右外连接,显示所有成绩,如果没有名字匹配,显示空
SELECT `name`,stu.id,grade
FROM stu RIGHT JOIN exams
ON stu.id = exam.id
9、约束
约束用于确保数据库数据满足特定的商业规则。
1、not null(非空)
2、primary key(主键)
用于唯一的标示表行的数据,当定义主键约束后,该列不能重复。
CREATE TABLE t(
id INT PRIMARY KEY, -- 表示id列是主键
`name` VARCHAR(32),
email VARCHAR(32));
细节说明:
- primary key 不能重复而且不能为null;
- 一张表最多只能由一个主键,但可以是复合主键;
- 主键的指定方式有两种:
- 直接在字段名后指定:字段名 primary key
- 在表定义最后写 primary key(列名)
- 使用
desc 表名
,可以看到primary key的情况。
-- 复合主键
CREATE TABLE t(
id INT,
`name` VARCHAR(32),
email VARCHAR(32),
PRIMARY KEY(id,`name`) -- 这里就是复合主键
);
DESC t -- 有key列,可以看出哪个是主键
3、unique(唯一)
当定义了唯一约束后,该列值就不能重复。
细节:
1.如果没有指定not null,则 unique 字段可以有多个 null
2.一张表可以有多个unique字段
-- unique
CREATE TABLE t(
id INT UNIQUE,
`name` VARCHAR(32),
email VARCHAR(32),
);
4、foreign key(外键)
用于定义主表和从表之间的关系:外键约束要定义在从表上,主表则必须具有主键约束或是unique约束,当定义外键约束后,要求外键列数据必须在主表的主键列存在或是为null。
-- 外键
-- 创建 主表 my_class
CREATE TABLE my_class(
id INT PRIMARY KEY, -- 班级编号
`name` VARCHAR(32) NOT NULL DEFAULT '')
-- 创建从表 my_stu
CREATE TABLE my_stu(
id INT PRIMARY KEY, -- 学生编号
`name` VARCHAR(32) NOT NULL DEFAULT '',
class_id INT, -- 学生所在班级编号
-- 下面指定外键关系
FOREIGN KEY (class_id) REFERENCES my_class(id))
-- 测试数据
INSERT INTO my_class
VALUES(100,'java'),(200,'web');
INSERT INTO my_stu
VALUES(1,'tom',100); -- 成功
INSERT INTO my_stu
VALUES(2,'jack',200); -- 成功
INSERT INTO my_stu
VALUES(3,'peter',300); -- 失败,因为300班级编号不存在
INSERT INTO my_stu
VALUES(5,'king',NULL); -- 成功,外键没有写 not null
细节:
- 外键指向的标的字段,要求是primary key 或者是 unique;
- 表的类型是innodb,这样的表才支持外键;
- 外键字段的类型要和主键字段的类型一致(长度可以不同)
- 外键字段的值,必须在主键字段中出现过,或者为null【前提是外键字段允许为null】
- 一旦建立主外键的关系,数据不能随意删除。
5、check
用于强制行数据必须满足的条件,假定在sal列上定义了check约束,并要求sal列值在1000~20000之间如果不再这区间里就会提示出错。
🌸oracle 和 sql server 均支持check,但是mysql5.7 目前还不支持check,只做语法校验,但不会生效
注: 在mysql中实现check的功能,一般是在程序中控制或者通过触发器完成。
CREATE TABLE t(
id INT PRIMARY KEY,
`name` VARCHAR(32),
sex VARCHAR(6) CHECK(sex IN('man','woman')),
sal DOUBLE CHECK (sal > 1000 AND sal < 2000)
);
6、自增长
在某张表中,存在一个id表(整数),我们希望在添加记录的时候,该列从1开始,自动的增长。
CREATE TABLE t(
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(32) NOT NULL DEFAULT '',
`name` VARCHAR(32) NOT NULL DEFAULT ''
);
INSERT INTO t
VALUES(NULL,'...','...');
INSERT INTO t
(email,`name`) VALUES('.1.','.2.');
细节:
- 一般来说自增长是和primary key 配合使用的;
- 自增长也可以单独使用(但是需要配合一个unique)
- 自增长修饰的字段为整数型的(虽然小数也可以)
- 自增长默认从 1 开始,可以通过如下命令修改
alter table 表名 auto_increment = xxx
。
Z、总结
-- 商品goods
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 ''
);
-- 客户custmoer
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)
);
-- 购买purchase
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)
);
10、索引
说起提高数据库性能,索引是最物美价廉的东西了。不用加内存,不用改程序,不用调sql,查询速度就可能提高百倍千倍。
-- 索引本身也会占用空间的
-- empno_index 索引名称
-- ON emp (empno) : 表示在emp表的 empno列创建索引
CREATE INDEX empno_index ON emp (empno)
索引实际上是个数据结构,比如二叉树,所以可以提高查询效率,但是添加、删除、修改时,还需要修改索引,所以执行这些语句的效率会有影响。
1、主键索引
主键自动的为主索引(类型Primary Key)
2、唯一索引(UNIQUE)
unique约束的,同时也是索引,称为unique索引。
3、普通索引(INDEX)
4、全文索引(FULLTEXT)[适用于MyISAM]
一般开发中,不使用musql自带的全文索引,而是使用:全文搜索 Solr 和 ElasticSearch (ES)。
Z、索引的各种方法
-- 创建索引
CREATE TABLE t(
id INT,
`name` VARCHAR(32)
);
-- 查询表是否有索引
SHOW INDEXES FROM t2;
-- 添加索引
-- 添加唯一索引
CREATE UNIQUE INDEX id_index ON t(id);
-- 添加普通索引
CREATE INDEX id_index ON t(id);
-- 1.如果某列的值,是不会重复的,则优先考虑使用unique索引,否则使用普通索引
-- 添加普通索引方式2
ALTER TABLE t ADD INDEX id_index (id)
-- 添加主键索引
CREATE TABLE t2(
id INT,
`name` VARCHAR(32)
);
ALTER TABLE t2 ADD PRIMARY KEY (id)
-- 删除索引
DROP INDEX id_index ON t
-- 删除主键索引
ALTER TABLE t2 DROP PRIMARY KEY
-- 修改索引,先删除,在添加新的索引
-- 查询索引
-- 1.
SHOW INDEX FROM t
-- 2.
SHOW INDEXES FROM t
-- 3.
SHOW KEYS FROM t
- 小结:哪些列上适合使用索引
- 较频繁的作为查询条件字段应该创建索引;
- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件;
- 更新非常频繁的字段不适合创建索引;
- 不会出现在WHERE子句中字段不该创建索引。
ZZ、索引失效的情况
explain select * from emp where sal = 800;
使用explain加语句可以查看有没有使用索引。【看type列】
- type 反应查询语句的性能
type
结果从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,得保证查询至少达到 range
级别,最好达到 ref
级别,否则会出现性能问题。
possible_keys
:SQL查询时用到的索引key
:显示SQL实际决定查询结果使用的键(索引)。如果没有使用索引,值为NULLrows
:显示MySQL认为它执行查询时必须检查的行数
- 失效的第1种情况
select * from emp where ename like '%T'
ename上即使添加了索引,也不会走索引,因为模糊匹配当中以%
开头了!
尽量避免模糊查询的时候以
%
开始,这是一种优化的手段。
- 失效的第2种情况
使用or的时候会失效,如果使用or那么要求or两边的条件字段都要有索引,才会走索引,如果其中一边有一个字段没有索引,那么另外一个字段上的索引也会失效。所以这就是为什么不建议使用or的原因。
可以考虑union
- 失效的第3种情况
使用复合索引的时候,没有使用左侧的列查找,索引失效。
假如有复合索引(job,sal):使用job查找的时候,会使用索引;使用sal查找的时候,索引失效。
- 失效的第4种情况
在where当中索引列参加了运算,索引失效。
select * from emp where sal+1 = 800
- 失效的第5种情况
在where当中索引列使用了函数
select * from emp where lower(ename) = 'smith'
- 还有很多。。。。
11、事务
1、事务管理
- 什么是事务
事务是用于保证数据的一致性,它由一组相关的dml语句组成,该组的dml语句要么全部成功,要么全部失败。比如:转账。。
- 事务和锁
当执行事务操作时,mysql会在表中加锁,防止其他用户改表的数据。
CREATE TABLE t(
id INT,
`name` VARCHAR(32)
);
-- 开始事务
START TRANSACTION
-- 设置保存点
SAVEPOINT a
-- 执行dml操作
INSERT INTO t VALUES(100,'tom');
SELECT * FROM t
SAVEPOINT b
INSERT INTO t VALUES(200,'jack');
-- 回退到b
ROLLBACK TO b
-- 继续回退
ROLLBACK TO a
COMMIT
2、savepoint
保存点(savepoint)。保存点是事务中的点,用于取消部分事务,当结束事务时(commit),会自动的删除该事务所定义的所有保存点。
3、rollback
当执行回退事务时,通过指定保存点可以回退到指定的点。
4、commit
使用commit语句可以提交事务。当执行了commit语句子后,会确认事务的变化、结束事务、删除保存点、释放锁,数据生效。当使用commit语句结束事务子后,其他会话将可以查看到事务变化后的新数据
Z、事务细节讨论
- 如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚;
- 如果开始一个事务,你没有创建保存点,你可以执行rollback,默认就是回退到你事务开始的状态;
- 你也可以在这个事务中(还没有提交时),创建多个保存点。
- 你可以在事务没有提交前,选择回退到哪个保存点;
- mysql的事务机制需要innodb的存储引擎还可以使用,myisam不好使。
- 开始一个事务的两种方式:
start transaction
或者set autocommit=off
5、隔离级别
- 事务隔离级别介绍
- 多个连接开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个连接在获取数据时的准确性。
- 如果不考虑隔离性,可能会引发如下问题:
- 脏读
- 不可重复读
- 幻读
脏读(dirty read): 当一个事务读取另一个事务尚未提交的修改时,产生脏读;
不可重复读(nonrepeatable read): 同一查询在同一事务中多次进行,由于其他事务所做的修改或删除,每次返回不同的结果集,此时发生不可重复读;
幻读(phantom read): 同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时产生幻读。
- 事务隔离级别
-- 查看当前mysql的隔离级别
SELECT @@tx_isolation;
-- 查看系统当前隔离级别
SELECT @@global.tx_isolation
-- 设置隔离级别,这里设置为Read uncommitted
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
-- 设置系统当前隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- mysql 默认的事务隔离级别是 repeatable read,一般情况下,没有特殊要求,没有必要修改
- 全局修改,修改my.ini配置文件,在最后加上
#可选参数有:READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ,SERIALIZABLE
[mysqld]
transaction-isolation = REPEATABLE-READ
6、ACID
-
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生;
-
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态;
-
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离;
-
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
12、mysql表类型和存储引擎
1、基本介绍
- MySQL的表类型由存储引擎(Storage Engines)决定,主要包括MyISAM、innoDB、Memory等
- MySQL数据表主要支持六种类型,分别还是:CSV、Memory、ARCHIVE、MRG_MYISAM、MYISAM、InnoBDB。
- 这六种又分为两类,一类是“事务安全型”(transaction-safe),比如:InnoDB; 其余都属于第二类,称为“非事务安全型”(non-transaction-safe)[mysiam和memory]。
2、主要的存储引擎/表类型特点
3、细节说明
这里重点介绍三种:MyISAM、InnoDB、MEMORY
- MyISAM不支持事务、也不支持外键,但其访问速度快,对事务完整性没有要求;
- InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全,但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引;
- MEMORY存储引擎使用存在内存中的内容来创建表。每个MEMORY表只实际对应一个磁盘文件。MEMORY类型的表访问非常得快,因为它的数据是放在内存中的,并且默认使用HASH索引。但是一旦服务关闭,表中的数据就会丢失掉,表的结构还在。
-- innodb 存储引擎
-- 1.支持事务 2.支持外键 3.支持行级锁
-- myisam 存储引擎
CREATE TABLE t(
id INT,
`name` VARCHAR(32)
) ENGINE MYISAM
-- 1.添加速度快 2.不支持外键和事务 3.支持表级锁
-- memory 存储引擎
-- 1.数据存储在内存中[关闭了MySQL服务,数据丢失,但是表结构还在]
-- 2.执行速度很快(没有IO读写) 3.默认支持索引(hash表)
CREATE TABLE t2(
id INT,
`name` VARCHAR(32)
) ENGINE MEMORY
4、如何选择表的存储引擎
- 如果你的应用不需要事务,处理的只是基本的CRUD操作,那么MyISAM是不二选择,速度快。
- 如果需要支持事务,选择InnoDB;
- Memory存储引擎就是将数据存储在内存中,由于没有磁盘I/O的等待,速度极快。但由于是内存存储引擎,所做的任何修改在服务器重启后都将消失。(经典用法 用户的在线状态())
-- 指令修改存储引擎
ALTER TABLE t ENGINE = INNODB
13、视图
1、对视图的总结
- 视图是根据基表(可以是多个基表)来创建的,视图是虚拟的表
- 视图也有列,数据来自基表
- 通过视图可以修改基表的数据
- 基表的改变,也会影响视图的数据
2、视图的基本使用
create view 视图名 as select语句
创建alter view 视图名 as select语句
更新SHOW CREATE VIEW 视图名
查看drop view 视图名1,视图名2
删除
CREATE VIEW emp_view01
AS
SELECT empno,ename,job,deptno FROM emp;
3、视图细节
- 创建完视图后,实际上只产生了一个视图结构文件【并没有重新存数据】【形式: 视图名.frm】
- 不管是视图,还是基表的数据变化,都会对对方产生影响
- 视图中可以再使用视图
4、视图最佳实践
- 安全。一些数据表有重要的信息,有些字段是保密的,这时就需要视图,保留一部分字段。这样,用户就可以查询自己需要的字段,并且不能查看保密字段。
- 性能。关系数据库的数据常常会分表存储,使用外键建立表的关系。这时数据库查询通常会使用连接(JOIN)。这方式麻烦而且效率比较低。如果建立一个视图,将相关表和字段组合在一起,就可以避免使用JOIN查询数据。
- 灵活。如果系统中有一张旧表,需要做出升级。但是因为表牵扯极大,为了不影响应用,这时可以建立一张视图,视图中的数据直接映射到新建的表。就可以以极小的改动,达到升级数据表的目的。
14、MySQL管理
1、Mysql用户管理
- Mysql用户
mysql中的用户,都存储在系统数据库mysql中 user表中。
其中user表的重要字段说明:
- host:允许登录的“位置”,localhost表示该用户只允许本机登录,也可指定ip地址
- user:用户名
- authentication_string:密码,是通过mysql的password()函数加密之后的密码。
- 创建用户
create user '用户名'@'允许登录位置' identified by '密码'
说明:创建用户,同时指定密码
- 删除用户
drop user '用户名'@'允许登录位置'
-- 1.创建新的用户
-- 'zzz' 是用户名,'localhost' 是登录的IP
CREATE USER 'zzz'@'localhost' IDENTIFIED BY '123456'
SELECT * FROM mysql.user
-- 2.删除用户
DROP USER 'zzz'@'localhost'
- 用户修改密码
-- 修改自己的密码,没问题
SET PASSWORD = PASSWORD('abcdef')
-- 修改其他人的密码,需要权限
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('密码')
2、Mysql权限管理
1、给用户授权
2、回收用户授权
基本语法:
revoke 权限列表 on 库.对象名 from '用户名'@'登录位置'
3、权限生效指令
如果权限没有生效,可以执行下面命令。
FLUSH PRIVILEGES
-- 创建用户 pppp 密码 123,从本地登录
CREATE USER 'pppp'@'localhost' IDENTIFIED BY '123'
-- 使用root用户创建 testdb,表news
CREATE DATABASE testdb
CREATE TABLE news(
id INT,
content VARCHAR(32)
);
-- 添加一条测试数据
INSERT INTO news VALUES(100,'北京新闻')
SELECT * FROM news
-- 给 pppp 分配查看 news 表和 添加news的权限
GRANT SELECT,INSERT
ON testdb.news
TO 'pppp'@'localhost'
-- 可以增加update权限
GRANT UPDATE
ON testdb.news
TO 'pppp'@'localhost'
-- 修改 pppp 的密码为abc
SET PASSWORD FOR 'pppp'@'localhost' = PASSWORD('abc');
-- 回收 pppp 用户在 testdb.news 表的所有权限
REVOKE SELECT,UPDATE,INSERT ON testdb.news FROM 'pppp'@'localhost'
-- 或者
REVOKE ALL ON testdb.news FROM 'pppp'@'localhost'
Z、细节说明
-
在创建用户的时候,如果不指定Host,则为%,%表示所有IP都有连接权限
create user xxx
-
你也可以这样指定
create user 'xxx'@'192.168.1.%'
表示xxx用户在 192.168.1.*的ip可以登录mysql -
在删除用户的时候,如果host不是 %,需要明确指定 ‘用户’@‘host值’
15、数据库的设计
1、三范式
- 第一范式:要求任何一张表必须有主键,每一个字段原子性不可再分;
- 第二范式:在第一范式的基础上,要求所有非主键字段完全依赖主键,不要产生部分依赖;
- 第三范式:在第二范式的基础上,要求所有非主键字段直接依赖主键,不要产生传递依赖。
2、总结
多对多,三张表,关系表两个外键。
一对多,两张表,多的表加外键。
一对一,外键唯一。
数据库设计三范式是理论上的。
实践和理论有的时候有偏差。
最终的目的都是为了满足客户的需求,有的时候会拿冗余换执行速度。
因为在sql当中,表和表之间连接次数越多,效率越低。(笛卡尔积)
有的时候可能会存在冗余,但是为了减少表的连接次数,这样做也是合理的,并且对于开发人员来说,sql语句的编写难度也会降低。
14、JDBC和数据库连接池
1、JDBC概述
- 基本介绍
- JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题;
- Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
- JDBC的基本原理图
- JDBC API:规范了应用程序与数据库的连接、执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中。
2、JDBC快速入门
- JDBC程序编写步骤
- 注册驱动 - 加载Driver类
- 获取连接 - 得到Connection
- 执行增删改查 - 发送SQL 给mysql执行
- 释放资源 - 关闭连接
public class JDBC01 {
public static void main(String[] args) throws SQLException {
//在项目下创建一个文件夹比如 libs
//将mysql-connector-java-5.1.37.jar 拷贝到该目录下,点击add as project加入到项目中
//注册驱动
Driver driver = new Driver();
//得到连接
String url = "jdbc:mysql://localhost:3306/db01";
//将用户名和密码放入Properties对象中
Properties properties = new Properties();
properties.setProperty("user", "root");
properties.setProperty("password", "123456");
Connection connect = driver.connect(url, properties);
//执行sql
String sql = "insert into actor values(null,'刘德华','男','1970-11-11','110')";
// 用于执行静态sql语句
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);//如果是dml语句,返回的就是影响行数
System.out.println(rows > 0 ? "成功" : "失败");
//关闭连接
statement.close();
connect.close();
}
}
1、数据库连接方式(5种)
- 方式1
//获取Driver
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/jdbc_db";
Properties info = new Properties();
info.setProperties("user","root");
info.setProperties("password","123456");
Connection conn = driver.connect(url,info);
System.out.println(conn);
- 方式2
//方式1是静态加载,灵活性差
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/jdbc_db";
Properties info = new Properties();
info.setProperties("user","root");
info.setProperties("password","123456");
Connection conn = driver.connect(url,info);
System.out.println(conn);
- 方式3
//使用DriverManager替换Driver
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "123456";
DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
- 方式4
//使用Class.forName自动完成注册驱动,简化代码
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
- 方式5
//使用配置文件,
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 conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
user=root
password=123456
url=jdbc:mysql://localhost:3306/testdb
driver=com.mysql.jdbc.Driver
3、JDBC API
1、PreparedStatement
- 基本介绍
- PreparedStatement 执行的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement 对象的 setXxx() 方法来设置这些参数。setXxx() 方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值。
- 调用 executeQuery(),返回 ResultSet 对象;
- 调用 executeUpdate():执行更新,包括增、删、改。
- 预处理的好处
- 不再使用 + 拼接sql语句,减少语法错误;
- 有效的解决了sql注入问题;
- 大大减少了编译次数,效率较高。
String sql = "select name,pwd from admin where name=? and pwd=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给 ? 赋值
preparedStatement.setString(1,admin_name);
preparedStatement.setString(2,admin_pwd);
//执行的时候,不用再填入sql语句
preparedStatement.executeQuery();
2、DriverManager
3、Statement
- 基本介绍
- Statement对象 用于执行静态SQL语句并返回其生成的结果的对象;
- 在连接建立后,需要对数据库进行访问,执行 命名或是SQL语句,可以通过
- Statement【存在SQL注入】【一般不用】
- PrepareStatement【预处理】
- CallableStatement【存储过程】
- Statement对象执行SQL语句,存在SQL注入风险;
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
- 要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
-- SQL注入模拟
-- 用户名 为 1' or
-- 密码 为 or '1'='1
-- 效果
SELECT *
FROM admin
WHERE NAME='1' or' AND pwd='or '1'='1'
-- 把AND语句变成OR语句,条件一定成立,危险!!!!
4、ResultSet
- 基本介绍
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前。
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。
//得到statement
Statement statement = conn.createStatement();
//组织Sql
String sql = "select id,name,sex,borndate from actor";
//执行sql语句,该语句返回ResultSet对象
ResultSet resultSet = statement.executeQuery(sql);
//使用while取出数据
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//关闭连接
resultSet.close();
statement.close();
conn.close();
4、JDBCUtils
public class JDBCUtils {
private static String user;//用户名
private static String password;//密码
private static String url;//url
private static String driver;//驱动名
//在static代码块去初始化
static{
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
} catch (IOException e) {
// 将编译异常转成运行异常
// 这时调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
}
}
//连接数据库,返回Connection
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭相关资源
public static void close(ResultSet set, Statement statement,Connection connection){
try {
if(set!=null){
set.close();
}
if(statement!=null){
statement.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
5、事务
- 基本介绍
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚;
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务;
- 调用 Connection 的 setAutoCommit(false) 可以取消自动提交事务;
- 在所有的 SQL 语句都成功执行后,调用 commit() 方法提交事务;
- 在其中某个操作失败或出现异常时,调用 rollback() 方法回滚事务。
try{
connection = JDBCUtils.getConnection(); //默认情况下,是自动提交的
//将connection 设置为不自动提交
connection.setAutoCommit(false); //此处相当于开启了事务
preparedStatement = connection.prepareStatement(sql);
prepareStatement.executeUpdate(); //执行第一条sql
int i = 1/0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
prepareStatement.executeUpdate(); //执行第2条sql
//这里提交事务
connection.commit();
} catch (Exception e) {
//这里我们可以进行回滚,即撤销执行的SQL
connection.rollback(); //没有填写保存点,就默认回滚到事务最开始的地方
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null,preparedStatement,connection);
}
6、批处理
- 基本介绍
-
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
-
JDBC的批量处理语句包括下面方法:
addBatch();//添加需要批量处理的SQL语句或参数 executeBatch();//执行批量处理语句 clearBatch();//清空批处理包的语句
-
JDBC连接MySQL时,如果要是用批处理功能,请再url中加参数
?rewriteBatchedStatements=true
-
批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。
//使用批量方式添加数据
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i=0; i<5000; i++){
preparedStatement.setString(1,"jack" + i);
preparedStatement.setString(2,"666");
// 将sql 语句加入到批处理包中
preparedStatement.addBatch();
//当有1000条记录时,再批量执行
if((i + 1)%1000 == 0){
preparedStatement.executeBatch();
//清空
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println((end - start));
//关闭连接
JDBCUtils.close(null,preparedStatement,connection);
- 批处理源码分析
- 第一步就是创建了 ArrayList — elementData => Object[]
- elementData => Object[] 就会存放我们预处理的sql语句
- 当elementData满了后,就按照1.5倍扩容
- 当添加到指定的值后,就executeBatch
- 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高。
7、连接池
1、传统获取Connection问题分析
- 传统的JDBC数据库连接使用DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证IP地址,用户名和密码(0.05~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作间占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。
2、数据库连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
3、数据库连接池种类
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现【提供 .jar包】;
- C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate,spring);
- DBCP数据库连接池,速度相对c3p0较快,但不稳定;
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点;
- BoneCP 数据库连接池,速度快;
- Druid(德鲁伊) 是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池。
4、DataSource
5、DBCP
6、C3P0
public class C3P0_ {
//方式1:相关参数,在程序中指定user,url,password
public void testC3P0_01() throws Exception {
//创建一个数据源对象
ComboPooledDataSource comboPooledDataSource =
new ComboPooledDataSource();
//通过配置文件mysql.properties 获取相关连接的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接的管理是由comboPooledDataSource 来管理的
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
Connection connection = comboPooledDataSource.getConnection();
System.out.println("连接OK");
connection.close();
}
//第二种方式 使用配置文件模板来完成
//1.将c3p0提供的 c3p0-config.xml 拷贝到 src 目录下
//2.该文件指定了连接数据库和连接池的相关参数
public void testC3P0_02() throws SQLException {
ComboPooledDataSource comboPooledDataSource =
new ComboPooledDataSource("shuaibi");
Connection connection = comboPooledDataSource.getConnection();
System.out.println("连接成功");
connection.close();
}
}
<c3p0-config>
<!-- 数据源名称 -->
<named-config name="shuaibi">
<!--加载驱动-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!--数据库url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db01</property>
<!--数据库用户名-->
<property name="user">root</property>
<!--数据库密码-->
<property name="password">123456</property>
<!--每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!--初始的连接数-->
<property name="initialPoolSize">10</property>
<!--最小连接数-->
<property name="minPoolSize">5</property>
<!--最大连接数-->
<property name="maxPoolSize">50</property>
<!--可连接的最多的命令对象数-->
<property name="maxStatements">50</property>
<!--每个连接对象可连接的最多命令对象数-->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
7、Proxool
8、BoneCP
9、Druid
1、测试德鲁伊
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/dbo1?rewriteBatchedStatements=true
username=root
password=root
#initial connection Size
initialSize=10
#min idle connection size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000
public class Druid_ {
public void testDruid() throws Exception {
//加入 Druid的 jar包
//加入配置文件
//读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
// 创建一个指定参数的数据库连接池,Druid连接池
DataSource dataSource =
DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println("连接成功");
connection.close();
}
}
2、将JDBCUtils工具类改成Druid实现
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static{
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//编写getConnection方法
public static Connection getConnection() throws SQLException{
return ds.getConnection();
}
//关闭连接,但是这里只是把连接对象放回连接池
public static void close(ResultSet resultSet, Statement statement,Connection connection){
try {
if (resultSet !=null){
resultSet.close();
}
if(statement!=null){
statement.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
8、Apache-DBUtils
- 解决的问题就是resultSet会因为关闭连接,而无法使用;并且使用起来也不方便。
1、基本介绍
- commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量;
- DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
- 使用QueryRunner类实现查询;
- ResultSetHandler接口:该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式
ArrayHandler;//把结果集中的第一行数据转成对象数组。
ArrayListHandler;//把结果集中的每一行数据都转成一个数组,再存放到List中。
BeanHandler;//将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler;//将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
ColumnListHandler;//将结果集中某一列的数据存放到List中。
KeyedHandler(name);//将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,
//其key为指定的key。
MapHandler;//将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler;//将结果集中的每一行数据都封装到一个Map里,然后再存放到List。
2、ApDBUtils查询和更新
public class DBUtils_USE {
//使用apache-DBUtils 工具类+德鲁伊完成对表的crud操作
public void testQueryMany() throws SQLException {//返回结果是多行的情况
//得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//使用 DBUtils 类和接口,添加jar包
//创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//就可以执行相关方法,返回ArrayList结果集
//也可以选择特定列查询,不用查询的列,默认置为null
String sql = "select * from actor where id >= ?";
// query就是把resultset封装到 ArrayList 集合中
// new BeanListHandler<>(Actor.class):通过反射把resultset-->Actor对象
// 底层需要使用反射机制,去获取Actor 类的属性,然后进行封装
// 1 就是给 SQL 语句中的 ? 赋值的 ,可以有多个值,因为是可变参数。。。
//底层调用的resultset,会在query中关闭,还会关闭PreparedStatement
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
for (Actor actor : list) {
System.out.println(actor);
}
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//apache-DBUtils 工具类+德鲁伊 完成 返回的结果时单行记录(单个对象)
public void testQuerySingle() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
// 执行返回单个对象的语句
String sql = "select * from actor where id = ?";
//因为返回单个对象,所以使用 BeanHandler
Actor actor =
queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 4);
System.out.println(actor);
//如果不存在,就会返回null
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//apache-DBUtils 工具类+德鲁伊 完成 查询结果是单行单列的---返回的就是object
public void testScalar() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
// 执行返回单行单列的语句
String sql = "select name from actor where id = ?";
//因为返回单行单列,所以使用 ScalarHandler
Object object =
queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println(object);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//apache-DBUtils 工具类+德鲁伊 完成 dml(update,insert,delete)
public void testDML() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
// 组织sql语句,完成update,insert ,delete
String sql = "update actor set name = ? where id = ?";
//执行dml操作是使用的 queryRunner.update
//返回值是影响的行数
int affectedRow = queryRunner.update(connection,sql,"张三丰",4);
System.out.println(affectedRow);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
}
3、表和JavaBean 的类型映射关系
因为mysql中的所有类型都可能是 NULL,而Java只有引用数据类型才有 NULL值。 所以创建映射类的时候得使用包装类。
9、DAO增删改查-BasicDao
1、基本说明
-
DAO:data access object 数据访问对象
-
这样的通用类,称为BasicDAO,是专门和数据库交互的,即完成对数据库(表)的crud操作。
-
在BasicDao的基础上,实现一张表 对应一个DAO,更好的完成功能,比如
Customer表 - Customer.java类(javabean) - CustomerDao.java
2、BasicDAO 应用实例
- com.zwj.dao_.utils //工具类
- com.zwj.dao_.domain //javabean
- com.zwj.dao_.dao // 存放XxxDAO 和 BasicDAO
- com.zwj.dao_.test // 写测试类
/**
* 2023/02/16/21:41
* 开发BasicDAO,是其他DAO的父类
*/
public class BasicDAO<T> {//泛型指定具体类型
private QueryRunner qr = new QueryRunner();
//开发通用的dml方法,针对任意的表
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 返回多个对象(即查询的结果是多行),针对任意表
*
* @param sql sql语句,可以有?
* @param clazz 传入一个类的Class对象
* @param parameters 传入?的多个值
* @return 根据Class对象,返回的ArrayList集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanListHandler<>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行结果的通用方法
public T querySingle(String sql,Class<T> clazz,Object...parameters){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行单列的方法,即返回单值的方法
public Object queryScalar(String sql,Object... parameters){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new ScalarHandler(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
}
/**
* 2023/02/17/10:59
*/
public class TestDAO {
public void testActorDAO() {
ActorDAO actorDAO = new ActorDAO();
//查询
List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
for (Actor actor : actors) {
System.out.println(actor);
}
//查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
System.out.println(actor);
//查询单行单列值
Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
System.out.println(o);
//dml操作
int update = actorDAO.update("insert into actor values(null,?,?,?,?)", "张无忌", "男", "2000-11-11", "999");
System.out.println(update > 0 ? "执行成功" : "执行没有影响数据库");
}
}
15、正则表达式
正则表达式是处理文本的利器。
1、基本介绍
- 正则表达式是对字符串执行模式匹配的技术;
- 正则表达式:regular expression => RegExp
2、正则表达式基本语法
//目标:匹配所有四个数字
//1.\\d 表示一个任意的数字
String regStr = "\\d\\d\\d\\d";
//2.创建模式对象【即正则表达式对象】
Pattern pattern = Pattern.compile(regStr);
//3.创建匹配器
//创建匹配器,按照正则表达式的规则去匹配content字符串
Matcher matcher = pattern.matcher(content);
//4.开始匹配
// matcher.find()
// 1.根据规则,定位指定规则的子字符串
// 2.把开始的索引,记录到 matcher对象的属性 int[] groups,groups[0]
// 3.把(结束的索引+1)记录到 groups[1]中
// 4.同时记录oldlast的值,即上一次的(结束索引+1),为了下一次执行寻找做准备
// matcher.group(0)
// 1.根据 groups[0]=0 和 groups[1]=4 的记录的位置,截取子字符串返回
while(matcher.find()){
System.out.println("找到:" + matcher.group(0));
}
// 关于分组,比如 (\\d\\d)(\\d\\d),正则表达式的()表示分组,第一个()表示第1组。。
// groups[0] = 0,把(结束的索引+1)记录到 groups[1]中
// 记录第1组()匹配的字符串,在groups[2]和groups[3]中
// 记录第2组()匹配的字符串,在groups[4]和groups[5]中
// matcher.group(1) 表示匹配到的第一组字符串
// matcher.group(2) 表示匹配到的第二组字符串
3、三个常用类
java.util.regex 包主要包括以下三个类 Pattern 类、Matcher 类和 PatternSyntaxException
1、Pattern
pattern 对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,调用其公共静态方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);
//演示matches方法,用于整体匹配,在验证输入的字符串是否满足条件使用
//就是当你只想知道字符串是否符合你的要求,不需要知道字符串实际的值,使用matches超级方便
String content = "hello abc hello,一二三四五";
String regStr = "hello.*";
boolean matches = Pattern.matches(regStr, content);//底层还是Matcher的matches方法
System.out.println("整体匹配= " + matches);
2、Matcher
Matcher 对象是对输入字符串进行解释和匹配的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
3、PatternSyntaxException
PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
4、分组、捕获、反向引用
1、介绍
-
分组
我们可以用圆括号组成一个比较复杂的匹配模式,name一个圆括号的部分我们可以看作是一个子表达式/一个分组。
-
捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,依此类推。组0代表的是整个正则式。
-
反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用
\\
分组号,外部反向引用$
分组号。
Z、小案例
- 要匹配两个连续相同的数字:
(\\d)\\1
- 要匹配五个连续的相同数字:
(\\d)\\1{4}
- 要匹配个位与千位相同,十位与百位相同的数:
(\\d)(\\d)\\2\\1
5、元字符
1、限定符
用于指定其前面的字符和组合项连续出现多少次。
细节:
- java的匹配默认贪婪匹配,即尽可能匹配多的;【a{3,4}如果aaaaa1111,他会尽量匹配aaaa】
- 非贪婪匹配:
?
可以紧跟其他限定符*、+、?、{n}、{n,}、{n,m}
之后时,匹配模式是非贪心的。搜寻尽可能短的字符串。例如 :"oooo"用o+?
和o+
区别很大。
2、选择匹配符
在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这时你需要用到选择匹配符号。
String regStr = "han|韩|寒"
3、分组组合和反向引用符
1、常用分组
String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("第一组内容:" + matcher.group(1));
System.out.println("第一组内容[通过组名]:" + matcher.group("g1"));
}
2、特别分组
-
String regStr = "小茗同学|小茗老师|小茗弟弟"
等价为
String regStr = "小茗(?:同学|老师|弟弟)"
-
(?=pattern) 像是后置定语,修饰前面的字符串,加强筛选。
-
(?!pattern) 是2的效果取反,满足条件的不要,其他都要。
4、特殊字符
- 转义符
\\
在我们使用正则表达式去检索某些特殊字符的时候,需要用到转义符号。
在Java 的正则表达式中,两个 \\
代表其他语言中的一个 \
。
需要用到转义符号的字符有以下:. * + ( ) $ / \ ? [ ] ^ { }
5、字符匹配符
\\w
代表字母数字和下划线!!!
细节
- java正则表达式默认是区分字母大小写的,如何实现不区分大小写
(?i)abc
表示abc都不区分大小写a(?i)bc
表示bc不区分大小写a((?i)b)c
表示只有b不区分大小写Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE)
-
\\s
匹配任何空白字符(空格,制表符等)\\S
匹配任何非空字符,和\\s
相反。 -
.
匹配除\n
之外的所有字符,如果要匹配.
本身则需要使用\\.
6、定位符
定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置。
6、应用实例
1、简单实例
//识别汉字
String regStr = "^[\u0391-\uffe5]+$"
//邮政编码
//要求:是1-9开头的一个六位数,比如:123890
String regStr = "^[1-9]\\d{5}$"
//QQ号码
//要求:是1-9开头的一个(5位数-10位数)
String regStr = "^[1-9]\\d{4,9}$"
//手机号码
//要求:必须以13,14,15,18开头的11位数
String regStr = "^1[3|4|5|8]\\d{9}$"
2、正则验证复杂URL
String content = "https://www.bilibili.com/video/BV1fh411y7R8/?p=894&spm_id_from=pageDriver&vd_source=e7a2787876633e4c8896d2ea713f37bb";
//1.先确定url的开始部分 https:// | http://
//2.然后通过 ([\\w-]+\\.)+[\\w-]+ 匹配 www.bilibili.com
//3.(\\/[\\w-?=&/%.#]*)?
String regStr = "^((http|https)://)([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$";
3、结巴去重案例
String content = "我....我要....学学学学....编程java!";
1.去掉所有的.
//Pattern pattern = Pattern.compile("\\.");
//Matcher matcher = pattern.matcher(content);
//content = matcher.replaceAll("");
//
2.去掉重复的字
(1)使用 (.)\\1+
(2)使用 反向引用$1 来替换匹配到的内容
// pattern = Pattern.compile("(.)\\1+");//分组的捕获内容记录到$1
// matcher = pattern.matcher(content);
// while(matcher.find()){
// System.out.println("找到=" + matcher.groups(0));
// }
//
使用反向引用$1 来替换匹配到的内容
// content = matcher.replaceAll("$1");
// System.out.println(content);
//3.使用一条语句 去掉重复汉字
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
4、String类中使用正则表达式
- 替换功能
要求:把 JDK1.3 和 JDK1.4 替换成 JDK。
content.replaceAll("JDK1\\.3|JDK1\\.4","JDK");
- 判断功能
要求:验证一个手机号,必须是以138 139开头的
content.matches("1(38|39)\\d{8}")
- 分割功能
要求:按照 # 或者 - 或者 ~ 或者 数字来分割
String[] split = content.split("#|-|~|\\d+")
Z、设计模式
1、单例设计模式
-
单例模式:就是采取一定方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
-
单例模式有两种方式:1)饿汉式 2)懒汉式
-
步骤如下:
1)构造器私有化
2)类的内部创建对象
3)向外暴露一个静态的公共方法。 getInstance
//饿汉式(一般是重量级对象,所以有造成资源浪费的可能)
//即使你还没使用对象,他就已经随着类创建了。
class SingleTon01{
//构造器私有化,防止直接new
private SingleTon01(){}
//提供一个静态属性就是SingleTon01
private static SingleTon01 instance = new SingleTon01();
//提供一个public的静态方法,可以返回instance
public static SingleTon01 getInstance(){
return instance;
}
}
//懒汉式
//在你使用对象实例的时候,才会创建
class SingleTon02{
//构造器私有化,防止直接new
private SingleTon02(){}
//提供一个静态属性就是SingleTon01
private static SingleTon01 instance;
//提供一个public的静态方法,可以返回instance
public static SingleTon01 getInstance(){
if(instance == null){
instance = new SingleTon02();
}
return instance;
}
}
- 饿汉式VS懒汉式
- 创建时机不同;
- 懒汉式存在线程安全问题;
- 饿汉式存在浪费资源的可能;
- java.lang.Runtime就是经典的单例模式。
2、抽象类最佳实践-模板设计模式
-
最佳实践
需求:
1)有多个类,完成不同的任务job
2)要求能够得到各自完成任务的时间
感情的自然流露
- 先用最容易想到的方法
- 分析问题,提出使用模板设计模式
abstract class Template{//抽象类-模板设计模式
public abstract void job();//抽象方法
public void calculateTime(){
//得到开始的时间
long start = System.currentTimeMillis();
job();//动态绑定机制
//得到结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间 " + (end - start));
}
}
class AA extends Template{
//计算任务
//1+...+10000
@Override
public void job(){ //实现Template的抽象方法job()
long num = 0;
for(long i=1;i<=10000;i++){
num +=i;
}
}
}
class BB extends Template{
public void job(){
long num = 0;
for(long i=1;i<=80000;i++){
num *=i;
}
}
}
public class TestTemplate{
public static void main(String[] args){
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
番外1:@SuppressWarnings参数大全
番外2:JUnit
- 为什么需要JUnit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中;
- 如果有很多个功能代码测试,就需要来回注销,切换很麻烦;
- 如果可以直接运行一个方法,就方便多了,并且可以给出相关信息,就好了。
- 基本介绍
- JUnit是一个Java语言的单元测试框架;
- 多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
-
使用方法
在要测试的方法前面写,@Test,然后导入Junit。
番外3:坦克大战
番外4、多用户通信系统
番外5、mysql权限
1、创建对象实例时(new);2、创建子类对象实例,父类也会被加载;3、使用类的静态成员时(静态属性、静态方法)。 ↩︎