JavaStudy

JavaSE

1.1 Scanner 接收用户输入

java.util.Scanner,获取用户输入

//   新建一个Scanner对象,扫描器
Scanner scanner = new Scanner(System.in);

next()和nextLine()

程序通过Scanner类的next()或nextLine()从控制台获取输入

  • next():
    1. 获取到有效字符才会结束
    2. 在有效字符前输入的空格会被next()方法自动去掉
    3. 在有效字符后面接收到空格或回车结束
    4. next()不能获取到含有空格的字符串
  • nextLine():
    1. 输入回车才会结束,能够接收空格
  • next()的空格结束符可以被nextLine作为下一次输入接收到
scanner.next();
scanner.nextLine();
//  关闭scanner对象
scanner.close();

hasNext()和hasNextLine()

hasNext()方法判断输入(文件、字符串、键盘等输入流)是否还有下一个输入项,若有,返回true,反之false。

Scanner sc = new Scanner(new File("test.txt")); 
System.out.println(sc.hasNext());

若test.txt为空(空格、tab、回车),则输出结果为false;若该文件不为空输出true。

当输入为键盘时,该方法出现阻塞(block),也即等待键盘输入。输入源没有结束,又读不到更多输入项。

Scanner sc = ``new` `Scanner(System.in);
System.out.println(sc.hasNext());

运行上面的程序,控制台等待键盘输入,输入信息后输出结果为true。

输入:Hello
true

由上面的知识,则如下代码则不难理解:

Scanner sc = ``new` `Scanner(System.in);
while(sc.hasNext()) {
	System.out.println(``"Input from keyboard:"` `+ sc.next());
}

sc.hasNext()始终等待键盘输入,输入信息后,sc.next()打印出该信息。

Scanner其他方法

基本数据类型都有:如:

  • nextInt():接收一个int类型的数据
  • hasNextInt(): ;

2. Arrays工具类

静态类静态方法

  • toString():输把传入的数组参数进行输出打印
  • sort():把传入的数组按升序进行排序
  • fill():数组填充,参数一:填充数组的元素,参数二:被填充的数组
  • equals():两个数组参数:比较两个数组的元素值是否相等

3. 创建对象的内存解析

image-20211029095711721

方法区是堆的一个特殊区域

4. super

super调用父类的属性或者方法

  1. super调用父类的构造方法时,必须写在构造方法的第一个
  2. super只能出现在子类的方法或构造其中
  3. super()和this()不能同时显示的写在构造方法中,因为这两个都要求必须写在第一行

5. 多态

即同一个方法可以根据发送对象的不同而才去多种不同的行为方式

一个对象的实际类型是确定的,但可以指向对象的引用类型有很多(父类类型,有继承关系的类型)

多态的条件:

  • 有继承关系
  • 子类重写父类的方法
  • 父类的引用指向子类对象

多态是方法的多态,属性没有多态性

6. 内部类

6.1成员内部类

编写在类中的类;

创建成员内部类的实例时,需要通过外面的类的实例进行创建
例如:外面的类是:Animal 成员内部类:Person

Animal animal= new Animal();
Animal.Person person= new animal.Person();

内部类能够直接使用外部类的私有属性和方法

编写在一个类文件中的两个类也算内部类

6.2 局部内部类

编写在类方法中的类

6.3匿名内部类

在创建时,实现接口;如

interface Person{ public void getName(); }
main{
    new Person(){ // 重写接口方法}
}

6.4 静态内部类

编写在类中的类,并且内部有static修饰

7.异常

抛出异常后程序会停止

捕获异常程序不会停止执行

throw:

  • 主动抛出异常:thow new xxx(); // 主动抛出某种类型的异常 // 一般用在方法中处理一些可以预见的错误

throws: 用于方法抛出异常

自定义异常

继承Exception类即可

public class MyException extends Exception{
    int date;

    public MyException(int date) {
        this.date = date;
    }
    @Override
    public String toString() {
        return "MyException{" +
                "除数date=" + date +
                '}';
    }
}

运行测试:

int a=0;
    try {
        if(a==0){
            throw new MyException(a);
        }
    } catch (MyException e) {
        System.out.println(e);;
    }
}

8.包装类

image-20211029151636187

包装了基本数据类型提供了强大的功能

8.1 基本数据类型,包装类,String之间的相互转化

基本数据类型转化为包装类:

  1. 调用包装类的构造器

    • new Integer(666);
      new Integer("666");
      new Float(3.14f);
      new Float("3.14");
      new Boolean("true"); //在值不为空且忽略大小写==”true“则为true,其他为false 
      
    • 存在自动装箱,直接赋值也行

包装类转化为基本数据类型:

  • 调用包装累的xxxValue():
  • 存在自动拆箱,直接赋值也行

String转化为包装类: 利用包装类的构造函数
包装类转化为String:调用包装类的toString()

String转化为基本数据类型:

  1. 调用相应包装类的parseXxx(String)静态方法

    • Integer.parseInt(str)
    • Integer.valueOf(str).intValue()
  2. 利用包装类的构造器和自动拆箱原理

    int i = new Integer("2");
    

基本数据类型转化为String

  • num + ""
  • Integer.toString(num)
  • String.valueOf(num)

8.2 自动装箱拆箱

自动装箱的原理:调用了包装类的 构造方法

自动拆箱原理:调用了包装类的xxxValue();

Integer integer = new Integer(1);
int i = integer.intValue();

9. 常用类

9.1 String、StringBuffer、SringBuider

String

通过字面量的方式给String变量赋值,指向的是常量池中的常量,区别new的堆空间

  1. String声明为final,不可被继承

  2. 实现了Comoarable接口:表示字符串支持序列化

    实现了CharSequence接口:表示字符串可以比较大小

  3. 内部定义:private final char value[];表示字符串是不可变的

String变量的值改变时,是把变量的地址重新指定

字符串的拼接问题:

  • 字符串常量和常量拼接:结果也会是常量,例如"hello"+"word"

  • 字符串变量和常量拼接:结果在堆空间中,例如 str+"word"

  • 如果拼接的结果调用intern()结果就在常量池中,如:

    String result1 = new String("hello") + "word"; 
    String result2 = result.intern(); // 返回常量池中已经有的"helloword"的地址值	
    // result1在堆空间中
    // result2在常量池中
    
常用方法
  • length() : 返回字符串的长度
  • charAt(int ):返回指定数组下标字符
  • toLowerCase(): 把字符串中的所有字符转换为小写
  • toUpperCase():把字符串中的所有字符串转换为大 写
  • isEmpty():判断字符串的长度是否为0
  • trim():去除字符串前后空格
  • equals()
  • equalsIgnoreCase(): 忽略大小写比较字符串是否相等,与equals相似
  • concat(String):将传入的字符串拼接到此字符串结尾。等价于" + "
  • compareTo(String): 比较字符串的大小
  • subString(int beginIndex):取出这个字符串角标在这个参数开始后面的子串
  • subString(int beginIndex, int endIndex): 前闭后开
  • endsWith(String suffix):测试字符串是否以指定的后缀结束
  • startsWith(String prefix):测试字符串是否以指定的前缀开始
  • startsWith(String prefix,int toffset):测试此字符串从指定索引开始的子字符串是否以指定的前缀开始
  • indexOf(String str):返回指定字符串在当前字符串中第一次出现的索引位置
  • indexOf(String str,int fromIndes):返回指定字符串在当前字符串指定索引后面的子串开始中第一次出现的索引位置
  • lastIndexOf(String str):返回指定字符串在当前字符串中,最后出现的索引位置
  • 没有找到对应的子串就返回-1
  • replace(char oldChar,char newChar):
  • image-20211030093350038
String和char[]之间的转换

String--》char[]

​ toCharArray(): String变量调用就可以了

char[]-->String

​ new String( new char[]):String构造器

String和byte[]之间的转换

String-->byte[]

  • getBytes():调用String的成员方法 // 使用的默认的字符集进行转换
  • getBytes("gbk"):可以指定字符集编码格式

byte[]-->String

  • new String( new byte[],"gbk");String构造器,可以指定字符集编码格式
StringBuider

区别:

String:不可变的字符序列,底层的char[],final修饰

StringBuider:可变的字符序列,线程不安全,效率高

StringBuffer:可变的字符序列,线程安全,但效率低,具有synchronized(同步锁)

StringBuffer类的常用方法
  • appernd(xxx):提供了很多,用于字符串拼接
  • delete(int start,int end):删除指定位置上的内容
  • replace(int start,int end,String str):把[ start,end)位置替换为str
  • insert( int offset,xxx):在指定位置上插入xxx
  • reverse():把当前字符串逆序
  • setCharAt(int index,char ch):把指定位置的char替换ch

9.2 时间

第一种通过new 一个对象来操作对象,占用内存大;而且SimpleDateFormat 是线程不安全的,在JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中。

// 返回当前时间到1970.1.1.0.0.0以毫秒为单位的时间差
System.currentTimeMillis();
Date类
  1. java.util.Date类,两个构造器
    • toString():显示当前年月日
    • 获取当前date对象对应的毫秒数,时间戳( 1970.1.1 0:0:0)
  2. java.sql.Date:与数据库对应时间类型
  3. java.util.Date,java.sql.Date相互转换:利用毫秒数,用构造器转换
SimpleDateFormat类

设置格式化格式或解析格式:构造器

SimpleDateFormat simple= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

格式解释:
hh:为12小时制
HH:为24小时制

a: 代表显示上下午

时间格式化:将日期---》字符串

  • SimpleDateFormat simple= new SimpleDateFormat();
    String format = simple.format(new Date());
    

解析: 字符串---》日期

  • String date = "21-10-30 下午4:41";
    Date parse = simple.parse(date);
    
Calender日历类(抽象类)

image-20211030172945994

JDK8引入的时间类

对象的获取方式:调用静态方法now()

LocalDate now =  LocalDate.now();
LocalTime now1 = LocalTime.now();
LocalDateTime now2 = LocalDateTime.now();

可以指定时间 //年月日,时分秒 // 静态方法: of方法

LocalDate of = LocalDate.of(2021, 11, 1);
LocalTime of1 = LocalTime.of(15, 48, 23);
LocalDateTime of2 = LocalDateTime.of(2021, 11, 1, 15, 48, 23);

getXxx(): 获取这个月的第几天,获取这个周的第几天

withXxx(): 修改日期时间的某个属性,返回一个时间或日期:提现了对象的不可变性

plusXxx():在原有的时间基础上加上给的参数时间,返回一个对象

minusXxx():在原有的时间基础上减去给的参数时间,返回一个对象

LocalDate
// 获取日期
LocalTime

// 获取时间

LocalDateTime

// 获取日期和时间 // 使用较多

瞬时: instant

image-20211101160448999

DateTimeFormatter:格式化时间日期

和simpleDateFormat类似

格式化

// 创建对象,类名调用预先定义号的格式

DateTimeFormatter  duixiang=DateTimeFormatter.ISO_LOCAL_TIME

// 获取对象方式二:DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL); // 参数是预先定义的样式常量

// 方式三:自定义格式:

DateTimeFormatter  duixiang = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
解析 parse()

9.3 Java比较器

自然排序Comparable

针对对象进行比较大小: 实现了Comparable接口,重写了comparaTo()方法,给出了比较两个对象大小的方式

comparaTo()的重写规则:

如果当前对象this大于形参对象obj,则返回正整数
如果当前对象this小于形参对象obj,则返回负整数
如果当前对象this等于形参对象obj,则返回零

利用工具类Arrays.sort();使用这个排序

定制排序Compartor

运用在类没有实现comparable接口,或者comparable接口的排序方式不符合我们的期望时,使用comparator接口

//  Comparator实现从大到小排
Arrays.sort(goods, new Comparator<Goods2>() {
    @Override
    public int compare(Goods2 o1, Goods2 o2) {
        if(o1.getPrice() > o2.getPrice()) return -1;
        if(o1.getPrice() < o2.getPrice()) return 1;
        return 0;
    }
});

9.4 System类

image-20211101175706721

9.5 Math

image-20211101180327712

9.6 BigInteger、BigDecimal

BigInteger范围更大的整型数,支持任意长度

BigDecimal精度更高的浮点型,支持任意精度

10. 枚举类

enum , Enum

类的对象zhi只有有限个,确定的

当需要定义一组常量时,强烈建议使用枚举

10.1 定义枚举类

方式一:jdk5.0之前,自定义枚举类
方式二:jdk5.0之前,使用enum关键字 ,默认是继承了Enum类

enum Season{
    // 1.给出枚举类提供的对象,省略 public static final    = new Season
    SPRING("春天","spring"),
    SUMMER("夏天","summer"),
    AUTUMN("秋天","autumn"),
    WINTER("冬天","winter");
    //  2.属性
    private final String seasonName;
    private final String seasonDesc;

    // 3.私有构造器
     Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    // 4. 给出访问属性的get方法

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
    // 4.其他诉求,例如toString()
}

10.2 Enum常用方法

  1. 静态方法:values():返回枚举类里面定义了哪些对象
  2. 静态方法:valueOf(String ): 返回和参数名相同名字的枚举类对象,未找到抛异常
  3. toString(): 未重写就返回名字

枚举类实现接口:

普通实现:

诉求每个枚举类对象执行内容不同,在创建每个对象后面{}实现

image-20211101230029633

11.注解

自我理解:
自定义注解如果不加上(反射)获取注解,对注解的信息进行逻辑处理,不使用反射的话就毫无意义,就只能起到注释的作用,进行一个描述。 反射获取注解,需要在自定义注解中加入元注解Retention且声明自定义的注解在JVM虚拟机中存在,这样反射才获取得到。

image-20211102092102508

image-20211102092334506

自定义注解

  1. 注解声明为:@interface
  2. n内部定义成员,只有一个成员变量的时候通常使用value
  3. 可以指定成员的默认值: default
  4. 如果自定义的注解没有成员,表明这个注解是标识作用
public @interface MyAnnotation {
    String value() default "lang";
}

元注解

对现有的注解进行解释说明的注解

  • Retention:指定所修饰的Annotation的生命周期:SOURCE/CLASS(默认)/ RUNTIME 只有声明为RUNTIME生命周期的注解,才能通过反射获取

    image-20211102123829447

  • Target:用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素

  • Documented:表示所修饰的注解在被javadoc解析时,保留下来

  • Inherited:被它修饰的Annotation将具有继承性,如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解

    比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类级别中的注解

  • jdk8新特性,可重复注解:@Repeatable

  • jdk8新特性,类型注解

自定义注解时一般都会只用,Retention,Target元注解

Target注解的参数:

public` `enum` `ElementType {
  ``/**用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration */
  ``TYPE,
  ``/** 用于描述域 Field declaration (includes enum constants) */
  ``FIELD,
  ``/**用于描述方法 Method declaration */
  ``METHOD,
  ``/**用于描述参数 Formal parameter declaration */
  ``PARAMETER,
  ``/**用于描述构造器 Constructor declaration */
  ``CONSTRUCTOR,
  ``/**用于描述局部变量 Local variable declaration */
  ``LOCAL_VARIABLE,
  ``/** Annotation type declaration */
  ``ANNOTATION_TYPE,
  ``/**用于描述包 Package declaration */
  ``PACKAGE,
  ``/**
   ``* 用来标注类型参数 Type parameter declaration
   ``* @since 1.8
   ``*/
  ``TYPE_PARAMETER,
  ``/**
   ``*能标注任何类型名称 Use of a type
   ``* @since 1.8
   ``*/
  ``TYPE_USE

12.集合

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

  1. Collection接口:单列数据,定义了一组对象的方法的集合
    • List:元素有序、可重复的集合;"动态数组"
      • ArrayList、LinkedList、Vector
    • Set
      • HashSet、LinkedHashSet、TreeSet
  2. Map接口:双列数据,保存具有映射关系"key-value"键值对的集合
    • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

数组信息:

image-20211102145439238

12.1 集合中的API

Collection接口:提取List和Set的公共方法,这里只写了我理解的常用的
  1. size():集合大小

  2. addAll( ): 参数是另外一个集合,把参数集合里面的元素添加进当前集合

  3. isEmpty(): 判断size是否为0,即判断有没有数据

  4. clear(): 清空集合里面的数据

  5. contains(Object o) :如果集合包含指定元素,返回 true;调用obj的equals方法,自定义类需要重写

  6. containsAll(Collection<?> c) 如果这个集合包含指定集合的所有元素 返回 true

  7. image-20211102212358665

    image-20211102212439960

  8. iterator():返回迭代器对象 Iterator (指针,一开始指向第一个数据前面) // 每次调用这个方法都会返回一个全新的迭代器指向头部前面

Iterator类的方法:

  1. next():先迭代器下移,返回迭代器指向的对象

  2. hasNext():判断迭代器的指向是否有元素

  3. remove(): 移除迭代器指向的容器中的元素

    //  遍历操作
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    

forEach增强for循环

12.2 Collection中的List和Set接口

List

image-20211103150004956

元素有序,可重复

ArrayList、LinkedList、Vector

同:都实现了List接口,元素有序,可重复

ArrayList:线程不安全,效率高,底层用数组存储

LinkedList:底层用双向链表存储

Vector:线程安全,效率低,底层用数组存储

ArrayList

jdk7前:无参构造起默认创建长度为10的数组,扩容为原来的1.5倍

jdk8时:无参构造,一开始不会创建空间,指定等于一个常量,在添加时在判断是否需要创建空间,扩容。延长了数组的创建时间节省内存

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Set

存储无序,不重复的数据: 理解:以HashSet为例:存储时创建长度为16的数组,数据太多会扩容
要求Set存储的类一定要重写hashCode()和equals();

  1. 无序:不等于随机。存储的数据并不是按照数组的索引顺序存储,而是根据数据的hashCode值计算得出的存储位置
  2. 不可重复:equals(),比较数据是否一样,不能存相同的数据
  3. add添加元素的过程:对象会调用类中计算哈希值的方法的到哈希值,哈希值通过某种算法计算在数组中存储的位置,判断位置里是否已经存储了数据,如果有就判断两个的哈希值是否一样,不一样就调用equals比较两个元素是否一样,一样则不用在存了,不一样把元素通过链表的形式存储下来,jdk7是后来的元素作为链表头存在数组中,jdk8是先来的元素作为链表头存在数组中

hashCode(): HashSet存储自定义类类的时候需要重写一下hashCode()方法,按某种计算方式返回哈希值,不重写就调用Object的hashCode()近似于随机数,当属性值相同时计算的哈希值大相径庭
image-20211103162328287

HashSet

线程不安全,可以存储null值

LinkedHashSet
  • HashSet的子类,遍历器其内部元素时,可以按照添加的顺序来遍历
  • 在原有的HashSet的基础上,在添加的数据还维护了两个指针,指向上一个元素和下一个元素的地址,优点:存在频繁的遍历操作时,效率高于HashSet
TreeSet
  • 可以按照添加的对象的指定属性进行排序,Comparable和Comparator的体现

  • 向TreeSet中添加的数据我们要求是同一个类型的数据,这样才能指定通过某个属性进行排序

  • 存放自定义类时需要指定排序方式:Comparable(自然排序)和Comparator(定制排序)

    new TreeSet( new Comparator(){ ...} );  // 利用有参构造器给出定制排序
    
  • 底层是二叉树(红黑树)的存放方式,不支持存放相同的元素,比较是否是相同的元素也是通过Comparable(自然排序)或Comparator(定制排序)来判断的,所以不要求重写equals()

image-20211103152150344

12.3 Map

  • HashMap:map的主要实现类,线程不安全,效率高,能存储null的key或value

    • LinkedHashMap
      • 在HashMap的基础上引入了一对指针,指向前一个和后一个元素。
      • 对于需要频繁的遍历操作,效率大于HashMap
  • TreeMap:保证按照添加的key-value进行排序,实现排序遍历。考虑对key进行自然排序或者定制排序

    ​ 底层红黑树

  • Hashtable:老版本的实现类,线程安全,效率低,不能存储null的key或value

    • Properties:常用来处理配置文件,key , value都是String类型的数据
HashMap

底层: jdk7 之前: 数组 + 链表

​ jdk8之后:数组 + 链表 + 红黑树

image-20211103212536321

源代码执行流程:

  1. HashMap的初始化是在插入第一个元素时调用resize完成的(源码629行)

  2. 不指定容量,默认容量为16(源码694)

  3. 指定容量不一定按照你的值来,会经过tableSizeFor转成不小于输入值的2的n次幂,源码378
    这里有个小问题,tableSizeFor转换成2的n次幂不是直接赋值给capacity,而是先将值暂时保存在threshold,见源码457,然后在put第一个元素resize时,婉转的传递给newCap

  4. put元素时,元素的位置取决于数组的长度和key的hash值按位与的结果i = (n - 1) & hash源码630
    4.1 如果这里没有元素,直接放这
    4.2 如果有,判断是不是键冲突(源码634),直接新值覆盖旧值*(源码657)
    4.3 如果有且不是键冲突,则将其放在原元素的next位置(源码642)

  5. 只有当size大于了扩容阈值size > threshold,才会触发扩容,源码662,扩容前,当前元素已经放好了

  6. 扩容时,容量和扩容阈值都翻番(源码687),但要小于MAXIMUM_CAPACITY

  7. 扩容时,元素在新表中的位置分情况
    7.1 当元素只是孤家寡人即元素的next==null(源码)711时,位置为e.hash & (newCap - 1)(源码712)
    7.2 当元素有next节点时,该链表上的元素分两类
    7.21 e.hash & oldCap = 0的,在新表中与旧表中的位置一样(源码738)

    7.22 e.hash & oldCap != 0的,位置为旧表位置+旧表容量,源码742

常用方法

image-20211104112237656

  • clear(): 分配的数组空间任然存在,只是把数组中的元素设置为null
TreeMap

向TreeMap中添加key-value时要求,key是同一种类型,因为要按照key进行排序:自然排序或定制排序

要求需要指定排序方式:具体就是存储类实现Comparable接口 或者new TreeMap时给出Comparator的实现对象

Properties

Properties是HashTable的子类,经常用来处理属性文件 key - value 类型都是String类型

//   
Properties pro = new Properties();
// 获取 properties文件,变成流
FileInputStream file = new FileInputStream("dataResource.properties");
//   加载流
pro.load(file);
System.out.println(pro.getProperty("user"));

12.4 Collections工具类

ArrayList() 和 HashMap都是线程不安全的,当有这个需求时可以调用Collections的静态方法,会返回线程安全的集合

Collections.synchronizedMap(map);
Collections.synchronizedList(list);

操作List , Set , Map 的工具类

image-20211104124555888

image-20211104124629435

13.泛型<>

jdk1.5新特性

所谓的泛型:就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时( 例如: 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入的实际的类型参数,也称为类型参数)

如果使用泛型时没有指明类型,默认就是Object类型

集合就大量使用了泛型,指明数据类型,保证数据的安全
Comparable 和 Comparator也可以使用泛型,实现的方法参数就不再是Object类型

自定义泛型
public class DemoTest<T> {}  // 在类的内部就可以使用T了,在实例化的时候才会明确类型

子类在继承具有泛型的父类时:子类也是泛型类; 但是如果明确了父类泛型的类型子类就不在是泛型了

public class Main<T> extends DemoTest<T> {}            // 泛型
public class Main extends DemoTest<Integer> {}         // 不是泛型
注意事项
  • 在静态方法中不能使用泛型的类型

  • 异常类不能声明为泛型

  • 泛型类中不能够new泛型变量,因为类型不是确定的

  • 子类可以保留,不保留,部分保留父类的泛型:指明父类的某个泛型,就叫不保留这个泛型;父类的所有泛型都被指明则称:不保留

  • 继承方面的赋值问题:

    类A是类B的父类,类G: G 和 G 不具有子父类关系 即不能够: G = new G;

    通配符 ?

泛型方法
public class DemoTest  {
    public  <T> T[] test(T[]){}    //不确定返回的类型是什么类型使用 
}
  • 泛型方法和所处的类是不是泛型没有任何关系
  • 泛型方法可以是静态方法:泛型方法的类型是在调用的时候确定的,并不是实例化的时候确定的,所以可以为static;
通配符 ? 的使用
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Object>();
// 以List使用了通配符后,就不能向对象中添加数据了,除了null; 允许读取
// 错 list.add("str"); // 报错,类型不一致,需要的类型是?
  • 以List使用了通配符后,就不能向对象中添加数据了,除了null; 允许读取
  • 使用有限制条件的通配符中的super 则可以添加数据,父类?= new 子类

有限制条件的通配符:

image-20211104181949510

14.网络编程

14.1 IP

IP地址:在java中用InetAddress类来封装,代表IP

InetAddress
  1. 获取实例:调用静态方法

     InetAddress.getByName("127.0.0.1"); // 参数:域名或者IP
    InetAddress ip = InetAddress.getByName("www.baidu.com");
    System.out.println(ip);// 可以获取到域名和i
    

IP记忆太抽象,引入了域名的概念,会在DNS(域名解析服务器)中寻找对应的ID

方法:

  • getHostAddress(): 获取ip
  • getHostName(): 获取域名

14.2 端口号

不同的进程需要有不同的端口号

Socket

端口号和IP地址组合得到一个网络套字节:Socket

网络通信需要通过Socket进行

网络协议

传输层: TCP ,UDP

TCP:三握手四挥手

image-20211105155737114

TCP的网络编程:

@Test
public void test3() throws IOException {
    // 客户端
    //  1. 定义 ip 和端口号
    InetAddress ip = InetAddress.getByName("127.0.0.1");
    int port = 8888;

    //  2. 创建  Socket 连接
    Socket socket = new Socket(ip, port);

    //  3. 发送数据
    OutputStream out = socket.getOutputStream();

    out.write("exid".getBytes());

    //  4. 关闭连接
    out.close();
    socket.close();
}
@Test
public void test2() throws IOException {
    // 服务器端
    // 1. 创建ServerSocket
    ServerSocket server = new ServerSocket(8888);

    while (true) {
        // 2. 获取Socket
        Socket socket = server.accept();

        // 3. 拿到输入流

        InputStream in = socket.getInputStream();

        byte[] buffer = new byte[1024];
        int len;
        ByteArrayOutputStream byt = new ByteArrayOutputStream();
        while ((len = in.read(buffer)) != -1){
            byt.write(buffer,0,len);
        }

        String data = byt.toString();
        System.out.println(data);

        byt.close();
        in.close();
        socket.close();
        if ("exid".equalsIgnoreCase(data)) break;
    }
    server.close();
}

TCP案例2:

// 出现的问题 : 客户端发送数据到服务器端,服务器通过read()阻塞式接收,服务器不知道数据有没有传输完毕就不会退出接受循环,需要客户端socket.shutdownOutput(),表明数据传输结束。

//  -----------------------tcp 实现客户向服务器发送文件,服务器存到本地中并返回信息断开

@Test
public void test4() throws IOException {
    // 服务器端
    // 1. 创建ServerSocket
    ServerSocket server = new ServerSocket(8888);

    while (true) {
        // 2. 获取Socket
        Socket socket = server.accept();

        // 3. 拿到输入流, 接收文件

        InputStream in = socket.getInputStream();


        byte[] buffer = new byte[1024];
        int len;
        // 把文件存入本地
        FileOutputStream fos = new FileOutputStream(new File("E:\\初音\\test.jpg"));
        while ((len = in.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }

        // 发送接收成功信息
        OutputStream out = socket.getOutputStream();
        out.write("接收完成".getBytes());
        socket.shutdownOutput();

        out.close();
        fos.close();
        in.close();
        socket.close();
        if ("exid".equalsIgnoreCase("")) break;
    }
    server.close();
}

@Test
public void test5() throws IOException {
    // 客户端
    //  1. 定义 ip 和端口号
    InetAddress ip = InetAddress.getByName("127.0.0.1");
    int port = 8888;

    //  2. 创建  Socket 连接
    Socket socket = new Socket(ip, port);

    //  3. 发送数据
    OutputStream out = socket.getOutputStream();
    
    // 发送文件
    File file = new File("E:\\初音\\test2.jpg");
    FileInputStream fis = new FileInputStream(file);
    byte[] buffer = new byte[1024];
    int len;
    while ((len = fis.read(buffer)) != -1){
        out.write(buffer,0,len);
    }
    socket.shutdownOutput();

    // 获取输入流接收服务器发送过来的接收成功的信息
    InputStream in = socket.getInputStream();

    ByteArrayOutputStream byt = new ByteArrayOutputStream();
    while ((len = in.read(buffer)) != -1){
        byt.write(buffer,0,len);
    }

    String data = byt.toString();
    System.out.println(data);

    //  4. 关闭连接
    byt.close();
    fis.close();
    out.close();
    socket.close();
}

UDP

image-20211105173428958

@Test
public void test1() throws SocketException,UnknownHostException,IOException{
    //   发送端
    //  1. 获取Socket
    DatagramSocket socket = new DatagramSocket();

    //   2. DatagramPacket,数据封装在DatagramPacket中
    String str = "udp 发送的数据";
    byte[] data = str.getBytes();
    InetAddress ip = InetAddress.getLocalHost();
    DatagramPacket packet = new DatagramPacket(data, 0, data.length, ip, 8888);

    //   3. DatagramSocket发送数据包
    socket.send(packet);

    // 4.  关闭资源
    socket.close();
}

@Test
public void test2() throws IOException {
    // 接收端
    DatagramSocket socket = new DatagramSocket(8888);

    byte[] buffer = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
    socket.receive(packet);

    String data = new String(packet.getData(), 0, packet.getLength());
    System.out.println(data);

    socket.close();

}

15.URL编程

URL编程:通过地址栏的输入访问到服务器上的资源,不需要向网络编程一样繁琐

image-20211105184755602

下载网络上的资源案例:

public static void main(String[] args) throws IOException {
    //   实现访问服务器的图片资源进行下载
    //   1. 创建资源url
    URL url = new URL("https://img1.baidu.com/it/u=3078928490,3807879590&fm=26&fmt=auto");
    //   2. 获取到服务器的url连接
    URLConnection urlConnection = url.openConnection();
    //   3. 连接上服务器
    urlConnection.connect();
    //   4. 获取到输入流
    InputStream in = urlConnection.getInputStream();
    File file = new File("JavaSE_demo4\\tiqi.webp");
    FileOutputStream fos = new FileOutputStream(file);
    byte[] buffer = new byte[1024];
    int len;
    while((len = in.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    fos.close();
    in.close();
}

16. Java反射机制

类加载过程:

image-20211106174542114

反射机制

image-20211106094238595

image-20211106124947314

反射操作类的结构

反射调用类私有结构:

@Test
public void test1() throws Exception {
    // 运用反射创建类的对象,这里需要构造器是public的,只测试成功了全参数的构造器
    //  用
    // 1. 获取类
    Class cla = Person.class;
    // 2. 获取到构造器
    Constructor con = cla.getConstructor(int.class,String.class);
    //  3.创建实例
    Person lang = (Person) con.newInstance(10,"clang");
    System.out.println(lang.toString());
}
@Test
public void test2()throws Exception{
    //  调用私有的构造器创建实例,
    Class<Person> cla = Person.class;
    Constructor<Person> con = cla.getDeclaredConstructor(String.class);
    //  设置为能访问
    con.setAccessible(true);
    Person test = con.newInstance("test");
    System.out.println(test);

    //  调用私有的属性
    Field name = cla.getDeclaredField("name");
    name.setAccessible(true);

    //
    name.set(test,"修改后");
    System.out.println(name.get(test));
    System.out.println(test);

    // 调用私有方法
    Method show = cla.getDeclaredMethod("show", String.class);
    show.setAccessible(true);
    String data =(String) show.invoke(test, "chenlang");
    System.out.println(data);
}

Class实例

获取Class的实例方式:以类Person为例

  1. 调用运行时类的属性 .class

    Class  cla = Person.class;
    
  2. 通过运行是类的对象获取

    Person p = new Person();
    Class aClass = p.getClass();
    
  3. 调用Class的静态方法:forName(String classPath)

    Class.forName("com.lang.pojo.Person");
    
  4. 使用类加载器:ClassLoader

    ClassLoader loader = ReflectionTest.class.getClassLoader();
    Class aClass = loader.loadClass("com.lang.pojo.Person");
    

image-20211106125411197

创建对象

newInstance(): 调用此方法的时候,创建对应的运行时类的对象。内部调用了运行时类的空参构造器。对类的结构有以下要求:

  1. 运行时类必须提供空参的构造器
  2. 空参的构造器的访问权限通常设置为public。

反射的动态性: 运行时才能确定需要创建什么对象

Class.for

反射代码

属性和方法,修饰符,数据类型,名字

// 反射调用获取类的多种复杂结构
@Test
public void test6()throws Exception{

    Class<Person> cla = Person.class;
    // 获取有参构造器
    Constructor<Person> con = cla.getDeclaredConstructor(int.class, String.class);
    con.setAccessible(true);
    Person lang = con.newInstance(20, "lang");
    System.out.println(lang);

    //  获取类的属性
    Field age = cla.getDeclaredField("age");
    age.setAccessible(true);
    age.set(lang,18);
    System.out.println(lang);

    //       获取声明的属性
    Field[] declaredFields = cla.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        // 获取权限修饰符号,返回 int 0 1 2
        int modifiers = declaredField.getModifiers();
        System.out.print(Modifier.toString(modifiers)+"\t");
        // 获取数据类型
        System.out.print(declaredField.getType()+"\t");

        System.out.println(declaredField.getName());
    }
    System.out.println("----------------");

    //  获取类中的方法
    Method[] declaredMethods = cla.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        //    获取方法上的注解
        System.out.println(declaredMethod.getName());
    }
    // 可以获取方法中的形参列表
}

获取注解信息:

//    获取方法上的注解信息
Class<Person> cla = Person.class;
Method show = cla.getMethod("showPerson");
MyAnnotation annotation = show.getAnnotation(MyAnnotation.class);
String value = annotation.value();

获取抛出的异常类型

getExceptionTypes();

获取运行时的父类

Class<? super FileInputStream> superclass = FileInputStream.class.getSuperclass();

获取运行时带泛型的父类

获取运行时类的接口,所在的包,注解

二、MySQL

B/S: Browser / Server 浏览器- 服务器端

C/S:Client / Server 客户端-服务器端

image-20211107144115189

1. JDBC

1.1 Driver:

Java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现

在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现

操作步骤:

1.2 四种链接方式

方式一:以下生成的Driver是固定mysql的,方式二:可以使用反射根据传入的动态生成各种数据库驱动

@Test
public void test1() throws SQLException {
    //   JDBC的连接测试
    // 1. driver  // 每个数据库驱动程序需要对Driver接口进行实现
    Driver driver = new com.mysql.jdbc.Driver();

    // 2. url:  jdbc:mysql://127.0.0.1:3306/mybatis
    String url = "jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true";

    // 3. 封装属性
    Properties pro = new Properties();
    pro.setProperty("user","root");
    pro.setProperty("password","password");

    //  driver获取数据库的连接
    Connection connect = driver.connect(url, pro);
    System.out.println(connect);
    connection.close();
}

DriverManager 静态

方式三:利用DriverManager替换Driver

@Test
public void test2()throws Exception{
    // 使用DriverManager替换driver
    Class<?> cla = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver) cla.newInstance();

    String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true";
    String user = "root";
    String password = "password";
    //  注册Driver
    DriverManager.registerDriver(driver);

    Connection connection = DriverManager.getConnection(url, user, password);
    System.out.println(connection);
    connection.close();
}

方式四: // 在方式三的基础上,多部分代码默认已经完成了

@Test
public void test2()throws Exception{
    // 使用DriverManager替换driver
    /*Class<?> cla = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver) cla.newInstance();*/

    String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true";
    String user = "root";
    String password = "password";
    //  注册Driver
    //DriverManager.registerDriver(driver);

    Connection connection = DriverManager.getConnection(url, user, password);
    System.out.println(connection);
    connection.close();
}

1.3 连接数据库最终版

方式五: 配置信息放在配置文件中,代码读取

 @Test
    public void test3()throws Exception{
        // 读取配置文件,加载配置信息

        Properties pro = new Properties();
        // 获取配置文件流,FileInputStream默认在当前项目下, 类加载器默认在src下或者resource资源目录下
        //FileInputStream fis = new FileInputStream("src\\properties.properties"); 不行,找不到resource目录下的资源
        // 类加载器加载
        ClassLoader loader = JdbcTest.class.getClassLoader();
        InputStream fis = loader.getResourceAsStream("properties.properties");

        pro.load(fis);
        String url = pro.getProperty("url");
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");
        String driv = pro.getProperty("driver");

        Class<?> cla = Class.forName(driv);
        Driver driver = (Driver) cla.newInstance();

        //  注册Driver
        DriverManager.registerDriver(driver);

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
        connection.close();
        fis.close();
    }

1.4 Statement

弊端:SQL注入问题:由于Statement的sql语句是拼接在,在输入数据时,输入数据有可能拼接成sql的关键字,导致出现sql语句执行出现异常

如何避免SQL注入问题:使用PreparedStatement替换Statement

@Test
public void test4() throws Exception {
    // statement 执行sql语句
    //  自定义的JDBC工具类获取数据库连接
    Connection connection = JDBCUtils.getConnection();
    System.out.println(connection);
    // 4.创建sql语句的执行对象Statement
    Statement statement = connection.createStatement();
    // 5.用statement对象执行sql语句,可能有返回的结果集(链表的形式)
    //    执行查询语句用executeQuery(),执行插入、删除、修改用executeUpdate()
    //    execute(),增删改查的sql都能执行,有判断效率会低点
    ResultSet resultSet = statement.executeQuery("select *from mybatis.books");
    while (resultSet.next()){
        System.out.print(resultSet.getInt("bookID")+"\t");    //在不知道类型的情况下可以用getObject()获取
        System.out.print(resultSet.getObject("bookName")+"\t");// 里面的参数要和数据库一一对应
        System.out.println(resultSet.getObject("detail")+"\t");
    }
    // 6.释放连接,关闭资源
    resultSet.close();
    statement.close();
    connection.close();
}

1.5 PreparedStatement

因为sql语句是预编译的,而且语句中使用了占位符,规定了sql语句的结构。用户可以设置"?"的值,但是不能改变sql语句的结构,因此想在sql语句后面加上如“or 1=1”实现sql注入是行不通的。

connection.prepareStatement(sql); // 预编译,得到后结构就固定了

优点:

  • 可以实更高效的批量操作

    • sql语句在预编译后就会缓存下来,继续执行同样的sql语句时,就不会有sql语句的校验

      for(int i=0;i < 100;i++){
          pre.setObject("");
          pre.execute();
      }
      
    • 以上优化:sql只有值在变化,在多存储一些值,后在提交执行插入操作

      1. addBatch( )、executeBatch( )、clearBatch( )
      2. mysq1服务器默认是关闭批处理的,我们需要通过一个参数, 让mysq1开启批处理的支持。? rewriteBatchedstatements=true写在配置文件的ur1后面
      3. 3.使用更新的mysql 驱动: mysql-connector-java-5.1.37-bin.jar
      for(int i=0;i < 100;i++){
          pre.setObject("");
          // 1.攒sql
          pre.addBatch();
          if(i <=  10){
              // 执行bathch
              pre.executeBatch();
              //清空batch
              pre.clearBatch();
          }
      }
      

插入:

@Test
public void test5() throws SQLException {
   //  使用PreparedStatement避免Statement的sql注入问题
    //  使用了占位符,解决sql注入问题

    //  自定义的JDBC工具类获取数据库连接
    Connection connection = JDBCUtils.getConnection();

    String sql = "INSERT into mybatis.books values(?,?,?,?)";
    PreparedStatement pre = connection.prepareStatement(sql);
    pre.setInt(1,31);
    pre.setString(2,"java神书");
    pre.setInt(3,30);
    pre.setString(4,"从入门到入狱");
    pre.execute();

    pre.close();
    connection.close();
}

修改:

@Test
public void test6()throws Exception{
    // 1. 获取数据库连接,此处是自定义的工具类
    Connection connection = JDBCUtils.getConnection();

    // 2. 预编译sql语句,返回PrepareStatement
    String sql = "update books set bookID=? where bookID=?";
    PreparedStatement pre = connection.prepareStatement(sql);
    // 3. 填充占位符
    pre.setInt(1,34);
    pre.setInt(2,31);
    // 3. 执行
    pre.execute();
    // 4. 资源的关闭
    pre.close();
    connection.close();
}

通用的执行sql的方法:利用可变形参,实现占位符的填充

public void test7(String sql,Object ...data)throws Exception{
    // 1. 获取数据库连接
    Connection connection = JDBCUtils.getConnection();

    // 2. 预编译sql语句,返回PrepareStatement
    PreparedStatement pre = connection.prepareStatement(sql);
    // 3. 填充占位符
    for (int i = 0; i < data.length; i++) {
        pre.setObject(i+1,data[i]);
    }
    // 3. 执行
    pre.execute();
    // 4. 资源的关闭
    pre.close();
    connection.close();

}

查询: 遍历返回的结果集

getObject(int ) : 取出第几个返回值的结果 (从1开始)

getObject(String ) :返回变量名是参数值得 结果

@Test
public void test9()throws Exception{
    //     查询,返回结果集
    // 1. 获取数据库连接
    Connection connection = JDBCUtils.getConnection();

    // 2. 预编译sql语句,返回PrepareStatement
    PreparedStatement pre = connection.prepareStatement("select * from mybatis.books");

    // 3. 执行
    ResultSet resultSet = pre.executeQuery();

    // 4. 遍历返回的结果
        //方式一:
    while (resultSet.next()){        // 结果集中的next: 判断指针的下一个位置是否有元素,返回boolean型数据,为true指针下移
        System.out.print(resultSet.getInt("bookID")+"\t\t");    //在不知道类型的情况下可以用getObject()获取
        System.out.print(resultSet.getObject("bookName")+"\t\t");// 里面的参数要和数据库一一对应
        System.out.println(resultSet.getObject("detail")+"\t");
    }
    // 5. 资源的关闭
    pre.close();
    connection.close();

}

获取返回的结果集中数据的列数,可以写一个通用的查询:

// 获取返回数据,有多少列
        // 获取返回值的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        // 获取有多少列
        int columnCount = metaData.getColumnCount();
		// 获取第一列的列名
		String name = metaData.getColumnName(1);  
		// 获取列的别名 , 没有别名就返回列名
        String columnLabel = metaData.getColumnLabel(1);

1.6 别名解决字段问题

针对数据库字段名,和java实体类的属性名不一致的问题:

  • sql取别名: as

查询: 结果映射,利用反射,通用查询

public <T> List<T> test1(Class<T> clazz,String sql,Object ...args)throws Exception{
        /** 查询:
         * 利用返回的结果集的元数据
         * 获取返回的列数,列的别名,列名
         * 编写一个通用(想查哪几列数据就只显示哪几列数据)的查询
         * 解决:数据库字段和java实体类属性不一致问题
         * */
        // 1. 获取数据库连接
        Connection connection = JDBCUtils.getConnection();
        // 2. 获取PrepareStatement 防止Statement的sql注入问题
        PreparedStatement pre = connection.prepareStatement(sql);
        // 3. 填充占位符
        for (int i = 0; i < args.length; i++) {
            pre.setObject(i+1,args[i]);
        }
        // 4. 执行查询,返回结果集
        ResultSet resultSet = pre.executeQuery();
        // 5. 获取元数据 , 获取列数
        ResultSetMetaData metaData = resultSet.getMetaData();// 元数据
        int columnCount = metaData.getColumnCount();

        // 遍历结果集
        List list = new ArrayList<T>();
        while (resultSet.next()){
            T book = clazz.newInstance();
            // 因为只知道返回的属性名,对属性名的set方法不好指定,利用反射进行修改
            for (int i = 0; i < columnCount; i++) {
                String columnLabel = metaData.getColumnLabel(i + 1);  // 别名
                Object object = resultSet.getObject(columnLabel);   // 值
                Field declaredField = clazz.getDeclaredField(columnLabel);   //
                declaredField.setAccessible(true);
                declaredField.set(book,object);
            }
            list.add(book);
        }

        list.forEach(System.out::println);
        pre.close();
        connection.close();
        if (list.size()==0) return null;
        return list;
    }

1.7 PreparedStatement对数据库中的Blob数据操作

Blob数据类型能够存储较大的数据

只能使用PreparedStatement进行操作,Blob不支持StatementSql语句的拼接

image-20211107170206467

存图片

把图片变成一个文件流传过去

@Test
public void test3()throws Exception{
    // 获取连接
    Connection connection = JDBCUtils.getConnection();
    String sql = "update books set photo = ? where bookID = ?";
    PreparedStatement pre = connection.prepareStatement(sql);
    FileInputStream fis = new FileInputStream("E:\\初音\\test3.png");
    int id = 3;
    //  Blob值注入,占位符
    pre.setBlob(1,fis);
    pre.setObject(2,id);

    // 提交执行
    pre.executeUpdate();

    // 关闭资源
    fis.close();
    connection.close();
}

取图片:

getBinaryStream(String ):获取到Blob字段返回的一个流

@Test
public void test4()throws Exception{
    // 获取连接
    Connection connection = JDBCUtils.getConnection();
    String sql = "select books.photo from books where bookID=?";
    PreparedStatement pre = connection.prepareStatement(sql);
    int id = 3;
    //  Blob值注入,占位符
    pre.setObject(1,id);

    // 提交执行
    ResultSet resultSet = pre.executeQuery();
    // 把Blob类型的字段下载下来,以文件的形式保存在本地中
    InputStream photo = null;
    if (resultSet.next()){
         photo = resultSet.getBinaryStream("photo");
    }

    byte[] buffer = new byte[1024];
    int len;
    FileOutputStream fos = new FileOutputStream(new File("E:\\初音\\test3.png"));
    while ((len = photo.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    // 关闭资源
    if (photo == null) photo.close();
    fos.close();
    connection.close();
}

2 事务

事务问题:需要保证多条sql语句都能执行成功。例如转账问题:需要扣款和 收款必须都能成功才能修改数据库数据。

当一个事务中执行多个操作时,要么所有的事务都被提交执行(commit),那么这些修改就被永久地保存下来;要么数据库管理系统放弃所做的所有修改,整个事务回滚(rollback)到最初状态

提交

数据一旦提交,就不可回滚

导致自动提交因素:

  1. DDL操作一旦执行都会自动提交
    • set autoCommit = false 对DDL操作无效
  2. DML默认情况下,一旦执行,就会自动提交
    • 我们可以通过连接 setAutoCommit(false) 取消自动提交,使用.commit()手动提交
  3. 默认在关闭连接时,会自动提交数据
    • 一个连接内把一个事务的所有操作全部完成后在关闭
//设置不允许自动提交数据
connection.setAutoCommit(false);
// 提交
connection.commit();
//事务,疯狂插入数据
@Test
public void test5()throws Exception{

    // 获取连接
    Connection connection = JDBCUtils.getConnection();

    //  获取sql的执行对象
    String sql = "insert into books(bookID,bookName,bookCounts,detail) values(?,'java神书',11,'从入门到入狱')";
    PreparedStatement pre = connection.prepareStatement(sql);
    // 关闭连接的自动提交
    connection.setAutoCommit(false);
    // 填充sql的占位符
    for (int i = 40; i <= 2000; i++) {
        pre.setInt(1,i);
        // 储存sql
        pre.addBatch();
        if ((i % 200) == 0){
            // 执行sql
            pre.executeBatch();
            // 清空缓存的sql
            pre.clearBatch();
        }
    }
    // 提交事务  ,真正把数据插入数据库
    connection.commit();

    // 关闭资源
    pre.close();
    connection.close();
}

事务回滚:

@Test
public void test1() throws SQLException {
    //  事务考虑自动提交的DML操作
   Connection connection = null;
   try {
       connection = JDBCUtils.getConnection();
       // 取消自动提交
       connection.setAutoCommit(false);
       String sql1 = "delete from books where bookID =1998";
       String sql2 = "delete from books where bookID =1997";
       PreparedStatement pre1 = connection.prepareStatement(sql1);
       // 执行
       pre1.executeUpdate();
       // 异常
       System.out.println(10/0);
       PreparedStatement pre2 = connection.prepareStatement(sql2);
       pre2.executeUpdate();

       // 提交
       connection.commit();
       if(pre1!=null) pre1.close();
       if(pre2!=null) pre2.close();
   }catch (Exception e){
       // 事务回滚
       connection.rollback();
   }finally{
       // 关闭资源
       connection.close();
    }
}

事务特征

ACID原则(数据库事务):事务的四大特征(这个很重要)

  1. 原子性:事务是不可分割的最小操作单元,要么同时成功,要么同时失败。
  2. 持久性:事务一旦回滚/提交后会持久的改变(无论是电脑关机等……都不会改变)
  3. 隔离性:多个事务之间,互相独立,减少影响。(脏读,不可重复读,幻读)mysql默认分离级别能解决,脏读,不可重复读
  4. 一致性:事务执行前后,数据总量不变。eg:上面转账的例子,不管怎么转账,二者的钱数总量是不变的。

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
    数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

设置隔离级别

解决(脏读,不可重复读,幻读)问题:

connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // 读未提交

mysql数据库默认是解决可脏读问题,如果修改过设置,在程序中一定要避免脏读问题

3 数据库连接池

JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口

3.1 c3p0

  • 获取连接方式一:

    //使用C3P0数据库连接池的方式,获取数据库的连接:不推荐
    public static Connection getConnection1() throws Exception{
    	ComboPooledDataSource cpds = new ComboPooledDataSource();
    	cpds.setDriverClass("com.mysql.jdbc.Driver"); 
    	cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    	cpds.setUser("root");
    	cpds.setPassword("abc123");
    		
    //	cpds.setMaxPoolSize(100);
    	
    	Connection conn = cpds.getConnection();
    	return conn;
    }
    
  • 方式二:

    //使用C3P0数据库连接池的配置文件方式,获取数据库的连接:推荐
    private static DataSource cpds = new ComboPooledDataSource("helloc3p0");
    public static Connection getConnection2() throws SQLException{
    	Connection conn = cpds.getConnection();
    	return conn;
    }
    

    配置文件:c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
    	<named-config name="helloc3p0">
    		<!-- 获取连接的4个基本信息 -->
    		<property name="user">root</property>
    		<property name="password">abc123</property>
    		<property name="jdbcUrl">jdbc:mysql:///test</property>
    		<property name="driverClass">com.mysql.jdbc.Driver</property>
    		
    		<!-- 涉及到数据库连接池的管理的相关属性的设置 -->
    		<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
    		<property name="acquireIncrement">5</property>
    		<!-- 初始化数据库连接池时连接的数量 -->
    		<property name="initialPoolSize">5</property>
    		<!-- 数据库连接池中的最小的数据库连接数 -->
    		<property name="minPoolSize">5</property>
    		<!-- 数据库连接池中的最大的数据库连接数 -->
    		<property name="maxPoolSize">10</property>
    		<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
    		<property name="maxStatements">20</property>
    		<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
    		<property name="maxStatementsPerConnection">5</property>
    
    	</named-config>
    </c3p0-config>
    

3.2 DBCP

需要导入两个jar包

  1. commons-dbcp-1.4.jar
  2. commons-pool-1.5.5.jar

方式一

public static Connection getConnection3() throws Exception {
	BasicDataSource source = new BasicDataSource();
		
	source.setDriverClassName("com.mysql.jdbc.Driver");
	source.setUrl("jdbc:mysql:///test");
	source.setUsername("root");
	source.setPassword("abc123");
		
	//
	source.setInitialSize(10);
		
	Connection conn = source.getConnection();
	return conn;
}

方式二:

//使用dbcp数据库连接池的配置文件方式,获取数据库的连接:推荐
private static DataSource source = null;
static{
	try {
		Properties pros = new Properties();
		
		InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
			
		pros.load(is);
		//根据提供的BasicDataSourceFactory创建对应的DataSource对象
		source = BasicDataSourceFactory.createDataSource(pros);
	} catch (Exception e) {
		e.printStackTrace();
	}
		
}
public static Connection getConnection4() throws Exception {
		
	Connection conn = source.getConnection();
	
	return conn;
}

资源文件:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=abc123

3.3 Druid(德鲁伊)

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

添加驱动:druid-1.1.10.jar

方式一:

@Test
public void test1() throws SQLException {
    // 德鲁伊数据库连接池
    // 方式一: 设置属性的形式
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useSSL=true");
    dataSource.setUsername("root");
    dataSource.setPassword("password");

    DruidPooledConnection connection = dataSource.getConnection();

    connection.close();
    dataSource.close();
}

方式二:配置文件方式获取连接

@Test
    public void test2() throws Exception {
        // 方式二:配置文件的形式
        Properties pro = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dataSource.properties");
        pro.load(is);
        // 利用工厂提供的方法创建
        DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);
        Connection connection = dataSource.getConnection();
        
        connection.close();
        dataSource.getClass();
    }

配置文件:

url=jdbc:mysql://localhost:3306/mybatis?useSSL=true
username=root
password=password
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=20
maxWait=1000
filters=wall

详细参数:

image-20211108174536466

4 QueryRunner类

QueryRunner类
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

QueryRunner类提供了两个构造器:

默认的构造器
需要一个 javax.sql.DataSource 来作参数的构造器
QueryRunner类的主要方法:

  • 更新
    public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
  • 插入
    public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
  • 批处理
    public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
    public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
  • 查询
    public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

测试:

// 测试添加
@Test
public void testInsert() throws Exception {
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "insert into customers(name,email,birth)values(?,?,?)";
	int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08");

	System.out.println("添加了" + count + "条记录");
		
	JDBCUtils.closeResource(conn, null);

}
// 测试删除
@Test
public void testDelete() throws Exception {
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "delete from customers where id < ?";
	int count = runner.update(conn, sql,3);

	System.out.println("删除了" + count + "条记录");
		
	JDBCUtils.closeResource(conn, null);

}

三、前端

CSS选择器

1. ID选择器:

(先在head中添加

posted @ 2021-12-27 21:08  恸的池塘  阅读(118)  评论(0编辑  收藏  举报