Java工具书

系列文章目录

文章目录


前言

java工具书 方便以后查阅(环境是JDK8)


一丶基础

(一)语法基础

1.编程基础(通用)

(1)JDK下载以及安装说明

以Win10为例

1)JDK下载

访问Oracle官网
(由于官网排版可能会更新 也不一定是下面的样子)
在首页点击Downloads,进入oracle软件下载页。
在这里插入图片描述
在下载页面,点击Java。
在这里插入图片描述
在这里插入图片描述
在 Java SE Downloads 页面,点击中间的DOWNLOAD按钮。
在这里插入图片描述
在JDK下载页,首先勾选Accept License Agreement,同意Oracle Java SE的Oracle技术网许可协议。
在这里插入图片描述
最后,根据操作系统选择合适的版本下载,以课程为例,我们选择Windows系统64位版本,exe是安装程序,点击下载即可。

2)JDK安装

Windows版安装JDK基本都是傻瓜式安装,但是JDK安装的路径和JRE最好一致,方便统一管理(路径不允许有中文或者特殊字符)
首先双击打开安装程序,点击下一步。
在这里插入图片描述
默认安装目录为C盘,点击更改,修改安装路径。
在这里插入图片描述
将目录更改至E:\develop,要注意不要修改后面的Java\jdk-11\目录结构。点击确定,进入下一步。
在这里插入图片描述
点击下一步,开始安装。
在这里插入图片描述
看到安装成功界面,点击关闭,完成安装。

在这里插入图片描述

(3)Java环境变量配置

以windows10为例
1)右键点击“此电脑”,选择“属性”项。
在这里插入图片描述
2)点击“高级系统设置”,在弹出的系统属性框中,选择“高级”选项卡(默认即显示该选项卡),点击“环境变量”。
在这里插入图片描述
3)在弹出的“环境变量”框,中选择下方的系统变量,点击新建。
在这里插入图片描述
4)在弹出的“新建系统变量”框中,输入变量名和变量值,点击确定。
变量名为:JAVA_HOME
变量值为JDK的安装路径,到bin目录的上一层即可。比如E:\develop\Java\jdk-11
注意:为防止路径输入错误,可以打开文件夹,拷贝路径。
在这里插入图片描述
点击确定后,系统变量中会出现一条新的记录。
在这里插入图片描述
5)然后选中“系统变量”中的“Path”变量,点击“编辑”按钮,将刚才创建的JAVA_HOME变量添加到“Path”变量中。
在这里插入图片描述
在弹出的“编辑系统变量”框中,点击“新建”,输入%JAVA_HOME%\bin。
在这里插入图片描述
输入完毕,点击“上移”按钮,将该值移动到第一行。点击确定。
在这里插入图片描述
至此,java环境变量配置完毕,打开命令行窗口,验证配置是否成功。
如果之前已经打开命令行窗口(Win+R 输入cmd),需要关闭重新启动才可。在非JDK安装的bin目录下,输入java或者javac命令,查看效果。
在这里插入图片描述

(4) JRE和JDK

JVM(Java Virtual Machine),Java虚拟机
JRE(Java Runtime Environment),Java运行环境,包含了JVM和Java的核心类库(Java API)
JDK(Java Development Kit)称为Java开发工具,包含了JRE和开发工具
总结:我们只需安装JDK即可,它包含了java的运行环境和虚拟机。

(3)基础语法
3.1 注释

(1)注释是对代码的解释和说明文字,可以提高程序的可读性,因此在程序中添加必要的注释文字十分重要。Java中的
注释分为三种:
(2)单行注释。单行注释的格式是使用//,从//开始至本行结尾的文字将作为注释文字。
(3)doc注释。文档注释以 /** 开始,以 */ 结束(阿里巴巴规范插件会有要求)

3.2 关键字(理解)

关键字是指被java语言赋予了特殊含义的单词。
键字的特点:
关键字的字母全部小写。
常用的代码编辑器对关键字都有高亮显示,比如现在我们能看到的public、class、static等

3.3 常量

常量:在程序运行过程中,其值不可以发生改变的量。
Java中的常量分类:
字符串常量 用双引号括起来的多个字符(可以包含0个、一个或多个),例如"a"、“abc”、"中国"等
整数常量 整数,例如:-10、0、88等
小数常量 小数,例如:-5.5、1.0、88.88等
字符常量 用单引号括起来的一个字符,例如:‘a’、‘5’、‘B’、'中’等
布尔常量 布尔值,表示真假,只有两个值true和false
空常量 一个特殊的值,空值,值为null
除空常量外,其他常量均可使用输出语句直接输出

package com.wh;

/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/10 11:58
 **/
public class MyTest {
    public static void main(String[] args) {
        test1();

    }
    public static void test1(){
        //输出一个整数
        System.out.println(2022);
        //输出一个小数
        System.out.println(11.3);
        //输出一个字符
        //双引号和单引号的区别 String定义的数据用双引号,而char类型定义的数据用单引号
        //简单的理解就是 单引号引入的数据只有一个字母 一个汉字,一个数字,而双引号引入的数据可以是一个或者多个;前者是字符,后者是字符串
        System.out.println('Y');
        System.out.println('嗨');
        System.out.println('1');
        //输出字符串
        System.out.println("Welcome to Wuhan");
        //输出Boolean值(true&false)
        System.out.println(true);
        System.out.println("true");
        //↑ 这两者是区别的,一个输出的是boolean值  一个输出的是String类型的字符串 虽然在控制台上出来的都是true 但是二者不一样
    }
}

控制台输出
在这里插入图片描述

3.4 数据类型

3.4.1 计算机存储单元
我们知道计算机是可以用来存储数据的,但是无论是内存还是硬盘,计算机存储设备的最小信息单元叫“位
(bit)”,我们又称之为“比特位”,通常用小写的字母”b”表示。而计算机中最基本的存储单元叫“字节(byte)”,
通常用大写字母”B”表示,字节是由连续的8个位组成。
除了字节外还有一些常用的存储单位,其换算单位如下:
1B(字节) = 8bit
1KB = 1024B
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
所以 2^10=1024 故10月24号是程序员日
3.4.2 java中的数据类型
Java是一个强类型语言,Java中的数据必须明确数据类型。在Java中的数据类型包括基本数据类型和引用数据类型两种。
Java基本数据类型

数据类型关键字内存占用取值范围
整数类型byte1-128~127
short2-32768~32767
int(默认)4-2的31次方到2的31次方-1
long8-2的63次方到2的63次方-1
浮点类型float4负数: 3.402823E+38
double(默认)8负数:-1.797693E+308到-4.9000000E-324 正数:4.9000000E-324 到
1.797693E+308
字符类型char20-65535
布尔类型boolean1true,false
说明:
e+38表示是乘以10的38次方,同样,e-45表示乘以10的负45次方。
在java中整数默认是int类型,浮点数默认是double类型。
3.5 变量

3.5.1 变量的定义
变量:在程序运行过程中,其值可以发生改变的量。
从本质上讲,变量是内存中的一小块区域,其值可以在一定范围内变化。
变量的定义格式:

public class MyTest {
    public static void main(String[] args) {
        test2();
    }
 public static void test2(){
        //格式: 数据类型 变量名 =初始化值
        //声明一个int类型的变量num并赋值为0
        int num=0;
        //打印输出
        System.out.println(num);
 
      //另一种声明方式:先声明 后赋值
        //格式 数据类型 变量名;
        //            变量名=初始化值  
        int sum;
        sum=0;
        System.out.println(sum);
           //假如有多个变量需要声明 且数据类型一样
        int a=0,b=1,c=2;//以此类推 用英文的逗号隔开 
        //↑不建议这么写  降低了可读性
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);

        //或者 采用第二种的声明方式
        int d,e,f;
        d=0;
        e=1;
        f=2;
        System.out.println(d);
        System.out.println(e);
        System.out.println(f);
      }

变量的使用:通过变量名访问即可。
3.5.2 使用变量时的注意事项
(1)在同一对花括号中,变量名不能重复。
(2) 变量在使用之前,必须初始化(赋值)。
(3) 定义long类型的变量时,需要在整数的后面加L(大小写均可,建议大写)。因为整数默认是int类型,整数太
大可能超出int范围。
(4) 定义float类型的变量时,需要在小数的后面加F(大小写均可,建议大写)。因为浮点数的默认类型是
double, double的取值范围是大于float的,类型不兼容。

3.6 标识符

标识符是用户编程时使用的名字,用于给类、方法、变量、常量等命名。
Java中标识符的组成规则:
由字母、数字、下划线“_”、美元符号“$”组成,第一个字符不能是数字。
不能使用java中的关键字作为标识符。
标识符对大小写敏感(区分大小写)。
Java中标识符的命名约定:
小驼峰式命名:变量名、方法名
首字母小写,从第二个单词开始每个单词的首字母大写。
大驼峰式命名:类名
每个单词的首字母都大写。
另外,标识符的命名最好可以做到见名知意
比如 StudentName 等

3.7类型转换
public class MyTest {
    public static void main(String[] args) {
       
        test3();
    }
/**
     * java类型转换 分两种情况 一种是自动转换 一种是强制类型转换(简称强转)
     * 自动类型转换:把一个表示数据范围小的数值或者赋值给另一个表示数据范围大的变量
     * 表示数据范围从小到大图
     * byte--->short--->int
     *                     ---->long--->float--->double
     *          char--->int  char类型转int类型是按照ASCII码表中对应的int值来进行计算的 比如'A'对应65 'a'对应97
     * 强制类型转换:把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量
     * 格式:目标数据类型 变量名=(目标数据类型)值或者变量
     * 注意:boolean是不能跟其他类型转换的
     */
    public static void test3(){
        //int转double
        double num1=10;
        //↓ double转int 不符合自动转换规则 所以报错
        //int num2=10.2;
        //所以修改成下面的格式 请注意控制台打印出来的结果
        int num2=(int) 10.2;
        System.out.println(num2);//10
        //↑ 整数类型默认是int byte short char类型数据参与运算均分自动转换为int类型 所以 10.2会直接变成10 因为int没有小数位
    }
3.8 运算符

3.8.1 算术运算符
运算符:对常量或者变量进行操作的符号
表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式。
不同运算符连接的表达式体现的是不同类型的表达式

//+ :是运算符,并且是算术运算符
//a+b 是表达式,由于+是算术运算符,所以这个表达式叫算术表达式
//其他运算符: - *(乘) /(除) %(取余)
// 注意:/和%的区别:两个数据做除法,/取结果的商,%取结果的余数
//整数操作只能得到整数,要想得到小数,必须有浮点数参与运算。
int a = 10;
int b = 20;
int c = a + b;

3.8.1.1 字符的“+”操作

char类型参与算术运算,使用的是计算机底层对应的十进制数值。需要我们记住三个字符对应的数值:
'a' -- 97 a-z是连续的,所以'b'对应的数值是98,'c'是99,依次递加
'A' -- 65 A-Z是连续的,所以'B'对应的数值是66,'C'是67,依次递加
'0' -- 48 0-9是连续的,所以'1'对应的数值是49,'2'是50,依次递加
算术表达式中包含不同的基本数据类型的值的时候,整个算术表达式的类型会自动进行提升。
提升规则:
byte类型,short类型和char类型将被提升到int类型,不管是否有其他类型参与运算。
整个表达式的类型自动提升到与表达式中最高等级的操作数相同的类型
等级顺序:byte,short,char --> int --> long --> float --> double
byte b1 = 10;
byte b2 = 20;
// byte b3 = b1 + b2; // 该行报错,因为byte类型参与算术运算会自动提示为int,int赋值给byte可能损失
精度
int i3 = b1 + b2; // 应该使用int接收
byte b3 = (byte) (b1 + b2); // 或者将结果强制转换为byte类型
-------------------------------
int num1 = 10;
double num2 = 20.0;
double num3 = num1 + num2; // 使用double接收,因为num1会自动提升为double类型

3.8.1.2 字符串的“+”操作
当“+”操作中出现字符串时,这个”+”是字符串连接符,而不是算术运算。

System.out.println("wuhan"+ 666); // 输出:wuhan666

在”+”操作中,如果出现了字符串,就是连接运算符,否则就是算术运算。当连续进行“+”操作时,从左到右逐个执行

System.out.println(1 + 99 + "年"); // 输出:199年
System.out.println(1 + 2 + "wuhan" + 3 + 4); // 输出:3wuhan34
// 可以使用小括号改变运算的优先级
System.out.println(1 + 2 + "wuhan" + (3 + 4)); // 输出:3wuhan7

3.8.2 赋值运算符
赋值运算符的作用是将一个表达式的值赋给左边,左边必须是可修改的,不能是常量。

符号作用说明
=赋值a=10,将10赋值给变量a
+=加后赋值a+=b,将a+b的值给a
-=减后赋值a-=b,将a-b的值给a
*=乘后赋值a*=b,将a×b的值给a
/=除后赋值a/=b,将a÷b的商给a
%=取余后赋值a%=b,将a÷b的余数给a
注意:
扩展的赋值运算符隐含了强制类型转换。
short s = 10;
s = s + 10; // 此行代码报出,因为运算中s提升为int类型,运算结果int赋值给short可能损失精度
s += 10; // 此行代码没有问题,隐含了强制类型转换,相当于 s = (short) (s + 10);

3.8.3 自增自减运算符

符号作用说明
++自增变量的值加1
自减变量的值减1
注意事项:
++和-- 既可以放在变量的后边,也可以放在变量的前边。
单独使用的时候, ++和-- 无论是放在变量的前边还是后边,结果是一样的。
参与操作的时候,如果放在变量的后边,先拿变量参与操作,后拿变量做++或者--。
参与操作的时候,如果放在变量的前边,先拿变量做++或者--,后拿变量参与操作。

2.面向对象

(1)方法
(2)重载
面试题:
面试题:
overload(重载)和override(重写)的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态
性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
方法重载的规则:
1.方法名一致,参数列表中参数的顺序,类型,个数不同。
2.重载与方法的返回值无关,存在于父类和子类, 同类中。
3.可以抛出不同的异常,可以有不同修饰符。
方法重写的规则:
1.参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。
2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次声明。
3.访问权限不能比父类中被重写的方法的访问权限更低。
4.重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
(3)封装
(4)继承
2.4.1 继承的实现

(1)继承的概念:
继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法
(2)实现继承的格式:
继承通过extends实现
格式:class 子类 extends 父类 { }
举例:class Dog extends Animal { }
(3)继承带来的好处:
继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。
示例代码:

//父类方法
public class FatherWay {
    public void show(){
        System.out.println("父类被调用");
    }
}
//子类方法
public class SonWay extends FatherWay{
    public void method(){
        System.out.println("子类方法被调用");
    }
}
//测试方法
public class Test {
    public static void main(String[] args) {
        //创建方法 调用方法
        FatherWay fw = new FatherWay();
        fw.show();
        SonWay sw = new SonWay();
        sw.method();
        sw.method();
    }
}
2.4.2 继承的好处和弊端

(1)继承好处
提高了代码的复用性(多个类相同的成员可以放到同一个类中)
提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
(2)继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
(3)继承的应用场景:
使用继承,需要考虑类与类之间是否存在is…a的关系,不能盲目使用继承
is…a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类

2.4.2 继承中变量的访问特点

(1)在子类方法中访问一个变量,采用的是就近原则。
a.子类局部范围找
b.子类成员范围找
c.父类成员范围找
d.如果都没有就报错(不考虑父亲的父亲…)
示例代码(后期补)

2.4.3 继承中构造方法的访问特点

注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,
原因在于,每一个子类构造方法的第一条语句默认都是:super()

问题:如果父类中没有无参构造方法煤制油带餐构造方法,该如何解决呢
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 在父类中自己提供一个无参构造方法
(5)多态

3.抽象类

4.接口

5. 枚举

6.注解

7.异常处理

8.多线程(基本功)

2.8.1 实现多线程

1.进程和线程
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
2.实现多线程的方式
(1)继承Tread类
(2)实现Runnable接口
(3)使用callable 和Future框架
(4)使用线程池

2.8.2 线程同步
面试题拓展:
1.解释下什么是多线程
首先,什么是线程,线程是程序的执行路径,或者可以说是程序的控制单元。
一个进程可能包含一个或多个进程,当一个进程存在多条执行路径时,就可以将该执行方式称为多线程。
线程的执行方式大致可分为就绪(wait),执行(run),阻塞(block)三个状态,而三个状态的转换实质上是在抢夺 cpu资源过程中造成的,正常情况下 cpu 资源不会被线程独自占用,因此多个线程在运行中相互抢夺资源,造成线程在上述的三个状态之间不断的相互转换。而这也是多线程的执行方式。
2.创建线程的几种方式
(1)继承 Thread 类,重写父类 run()方法
(2)实现 runnable 接口
(3)使用 ExecutorService、Callable、Future 实现有返回结果的多线程(JDK5.0 以后)
(4)使用线程池
(前两者会比较常用)
3.为什么要用线程池,有哪几种线程池
第一,降低资源消耗,通过重复利用已经创建的线程较低线程创建和销毁造成的消耗
第二,提高响应速度,当任务达到时,任务可以不需要等到线程创建就能立即执行
第三,提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。
Executors 详解:
Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个
执行线程的工具。真正的线程池接口是 ExecutorService。ThreadPoolExecutor 是 Executors 类的底层实现。我们先介绍下 Executors。
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执
行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反
复创建线程对象所带来的性能开销,节省了系统的资源。
ThreadPoolExecutor 构造方法
Executors 中创建线程池的快捷方法,实际上是调用了 ThreadPoolExecutor 的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:
// Java 线程池的完整构造函数
public ThreadPoolExecutor(
int corePoolSize, // 线程池长期维持的线程数,即使线程处于 Idle 状态,也不会回收。
int maximumPoolSize, // 线程数的上限
long keepAliveTime,TimeUnit unit,//超过 corePoolSize 的线程的 idle 时长,超过这个时间,多余的线程会被
回收。
BlockingQueue<Runnable> workQueue, //任务的排队队列 ThreadFactory threadFactory,//新线程的产生方
式 RejectedExecutionHandler handler) // 拒绝策略
竟然有 7 个参数,很无奈,构造一个线程池确实需要这么多参数。这些参数中,比较容易引起问题的有
corePoolSize, maximumPoolSize, workQueue 以及 handler:
corePoolSize 和 maximumPoolSize 设置不当会影响效率,甚至耗尽线程;
workQueue 设置不当容易导致 OOM;
handler 设置不当会导致提交任务时抛出异常。
Java 通过 Executors 提供四种线程池,分别为:
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回
收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
4.多线程的同步机制
在需要同步的方法中加入synchronized关键字
使用 synchronized 块对需要进行同步的代码段进行同步`在这里插入代码片`。
使用 JDK 5 中提供的 java.util.concurrent.lock 包中的 Lock 对象。
一段 synchronized 的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java 里边就是拿到某个同
步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只
能等了(线程阻塞在锁池 等待队列中)。 取到锁后,他就开始执行同步代码(被 synchronized 修饰的代码);
线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中 等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。
5.线程的集中可用状态(生命周期)
线程在执行过程中,可以处于下面几种状态:
就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O 阻塞(Blocked on I/O):等待 I/O 操作完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。
6.线程锁对象详解
7.同步方法的实现方式

9.IO流

10.反射

(二)集合

1.Collection

(1)Collection

2.List

(1)List
a.概述和特点
b.常用api介绍
c.遍历(常用)
package com.wh;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @version 1.0
 * @author: swy
 * @date: 2022-03-07 23:17
 * @description: list常用的四种遍历方式
 */
public class MyList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(
                Arrays.asList("EDG", "WBG", "V5", "RNG")
        );
        System.out.println(">--------------------List---------------------<");
        FirstWay(list);
        SecondWay(list);
        ThirdWay(list);
        FourthWay(list);
    }
    /**
     * 利用迭代器遍历
     *
     * @param FirstList
     */
    public static void FirstWay(List<String> FirstList) {
        Iterator<String> iterator = FirstList.iterator();
        System.out.println("FirstWay Result:");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    /**
     * 普通for循环 利用for循环拿到list的索引 然后根据索引获取到值
     *
     * @param SecondList
     */
    public static void SecondWay(List<String> SecondList) {
        System.out.println("SecondWay Result:");
        for (int i = 0; i < SecondList.size(); i++) {
            System.out.println(SecondList.get(i));
        }
    }
    /**
     * 增强for循环
     * 格式:
     * for(元素数据类型 变量名 : 数组/集合对象名) {
     * 循环体;
     * }
     * 快捷键 集合.for
     *
     * @param ThirdList
     */
    public static void ThirdWay(List<String> ThirdList) {
        System.out.println("ThirdWay Result:");

        for (String value : ThirdList) {
            System.out.println(value);
        }
    }
    /**
     * 利用java 8新特性 Stream流遍历
     *
     * @param FourthList
     */
    public static void FourthWay(List<String> FourthList) {
        System.out.println("FourthWay:");
        FourthList.stream().forEach(value -> System.out.println(value));
        System.out.println("--------------------------------------------");
        FourthList.stream().forEach(System.out::println);
    }
}
(2)ArrayList
(3)Linklist

3.Set

(1) Set
a.概述和特点
b.常用api介绍
package com.wh;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @version 1.0
 * @author: swy
 * @date: 2022-03-08 12:18
 * @description: Set 遍历常用方法
 */
public class MySet {
    public static void main(String[] args) {
        Set<String> set=new HashSet<>();
        set.add("EDG");
        set.add("RNG");
        set.add("WBG");
        set.add("V5");
        FirstWay(set);
        SecondWay(set);
        ThirdWay(set);
    }

    /**
     * 增强for循环
     *
     * @param FirstSet
     */
    public static void FirstWay(Set<String> FirstSet){
        System.out.println(">--------------FirstWay-----------------<");
        for (String value : FirstSet) {
            System.out.println(value);
        }
    }

    /**
     * 迭代器 获取到set的迭代器  然后遍历
     * @param SecondSet
     */
    public static void SecondWay(Set<String> SecondSet){
        System.out.println(">--------------SecondWay---------------<");
       Iterator<String> SecondSetIterator= SecondSet.iterator();
       while(SecondSetIterator.hasNext()){
           System.out.println(SecondSetIterator.next());
       }
    }

    /**
     * 利用java 8新特性 Stream
     * @param SecondSet
     */
    public static void ThirdWay(Set<String> SecondSet){
        System.out.println(">--------------ThirdWay---------------<");
        SecondSet.stream().forEach(value -> System.out.println(value));
    }
}
c.遍历(常用)
(2)TreeSet
(3)HashSet

4.Map

(1)Map
a.概述和特点
b.常用api介绍
c.遍历(常用)
package com.wh;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @version 1.0
 * @author: swy
 * @date: 2022-03-07 23:24
 * @description: Map遍历常规的三种方式
 */
public class MyMap {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("WBG", "the shy");
        map.put("RNG", "Uzi");
        map.put("V5", "Rookie");
        map.put("aa", "bb");
        FirstWay(map);
        SecondWay(map);
        ThirdWay(map);

    }

    /**
     * 步骤分析
     * (1)利用 foreach获取所有键:Set<String> keySet=FirstMap.keySet();
     * 用keySet()方法实现
     * (2)根据键去找值,用get(Object key)方法实现
     *
     * @param FirstMap
     */
    public static void FirstWay(Map<String, String> FirstMap) {
        //获取所有键的集合。用keySet()方法实现
        //Set<String> keySet=FirstMap.keySet();
        System.out.println(">------------------FirstWay Result--------------------------<");
        for (String key : FirstMap.keySet()) {
            System.out.println("key:" + key + " " + "value:" + FirstMap.get(key));
            System.out.println(FirstMap.get(key));
        }
    }

    /**
     * 步骤分析:
     * (1)获取所有键值对对象的集合: Set<Map.Entry<String, String>> entrySet = SecondMap.entrySet();
     * (2)遍历键值对对象的集合,得到每一个键值对对象:用增强for实现得到每一个Map.Entry
     * (3)根据键值对对象获取键和值: String key=SecondMapEntry.getKey();
     *                       String value=SecondMapEntry.getValue();
     *
     * @param SecondMap
     */

    public static void SecondWay(Map<String, String> SecondMap) {
        //获取所有键值对对象的集合
        //Set<Map.Entry<String, String>> entrySet = SecondMap.entrySet();
        //根据键值对获取对对象获取键和值
        System.out.println(">------------------SecondWay Result--------------------------<");
        for (Map.Entry<String, String> SecondMapEntry : SecondMap.entrySet()) {
            String key = SecondMapEntry.getKey();
            String value = SecondMapEntry.getValue();
            System.out.println("key:" + key + " " + "value:" + value);
        }


    }

    /**
     * 利用迭代器遍历
     * 步骤分析:
     * (1)相较于第二种方法的区别  先获取到map的迭代器
     * (2)然后迭代器遍历即可
     * @param ThirdMap
     */
    public static void ThirdWay(Map<String, String> ThirdMap) {
        System.out.println(">------------------ThirdWay Result--------------------------<");
        Iterator<Map.Entry<String, String>> ThirdMapEntries=ThirdMap.entrySet().iterator();
        while (ThirdMapEntries.hasNext()){
            Map.Entry<String,String> ThirdMapEntry=ThirdMapEntries.next();
            String key=ThirdMapEntry.getKey();
            String value=ThirdMapEntry.getValue();
            System.out.println("key:" + key + " " + "value:" + value);
        }
    }
}
(2)HashMap
HashMap 基于哈希表的Map 接口实现, 是以key . value存储形式存在, 即主要用来存放撻值对。HashMap 的实现不是同步的, 这意味看它不是线程安全的。它的key 、value 都可以为null. 此外, HashMap 中的映射不是有序的。
JDK8 之前HashMap 由数+ 链表组成的, 数组是HashMap 的主体, 链表则是主要为了解决晗希冲突(两个对象调用的hashcode方法计算的哈希码值一致导致计算的数组索引值相同) 而存在的(" 拉链法" 解决冲突) .JDK8以后在解决希冲突时有了较大的变化, 当链表长度大于阈值( 或者红黑树的边界默认为8 ) 并且当前数组的长度大于64 旺此时此案引位置上的所有数据改为使用红黑树存储。
补充: 将链表转成红黑树前会判断, 即便值大于釓但是数组长度小于64 , 此时并不会将链表变为红黑树。而是选择进行数组扩容。
这样做的目的是因为数组比较小, 尽量避开红黑树结构, 这种情况下变为红黑树结构, 反而会降低戏率, 因为红黑树需要迸行左旋, 右旋, 变色这些操作来保持平衡· 同时数组长度小于64 时, 搜索时间相对要快些。所以综上所述为了提高性和减少搜索时间, 底层在值大于8 并且数组长度大于64 时, 链表才转涣为红黑树。具体可以考treeifYBin 方法.
当然虽然增了红黑树作为底层数据结构, 结构变得复杂了。但是值大于8 并且数组长度大于64 时, 链表转为红黑树时, 效率也变的更高效。
哈希表底层采用何种算法计算hash值,还有那些算法可以计算出hash值?
底层采用的key的hashCode方法的值结合数组长度进行无符号右移(>>>),按位异或(^),按位与(&)计算出索引
还可以采用:平方取中法,取余数,伪随机数法
当两个对象的hashCode相等时会怎么样
会发生哈希碰撞,若key值内容则替换旧的value,不然连接到链表后面,链表长度超过阈值8就转换为红黑树存储
何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞
只要两个元素的key计算的哈希码值相同就会发生哈希碰撞,jdk前使用链表解决哈希碰撞,jdk8之后使用链表+红黑树解决哈希碰撞
如果两个键的hashCode相同,如何存储键值对
hashCode相同,通过equals比较内容是否相同
相同的话,新的value覆盖之前的value,不同就将新的键值对添加到哈希表中
传统hashmap的缺点,1.8为什么引入红黑树,这样结构的话不是更麻烦了吗,为何阈值大于8换成红黑树
JDK8以前hashmap的实现是数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布,当hashmap中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候hashmap就相当于一个单链表,假如单链表有n个元素,遍历的时间复杂度就是O(n),完全失去了它的优势,针对这种情况,jdk1.8中引入了红黑树(查找时间复杂度为O(log n)来优化这个问题,当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对产讯性能有一定影响,所以才会转换成树

在这里插入图片描述

HashMap的负载因子为什么默认是0.75?
HashMap的底层是哈希表,是存储键值对的结构类型,它需要通过一定的计算才可以确定数据在哈希表中的存储位置;一般的数据结构,不是查询快就是插入快,HashMap就是一个插入慢、查询快的数据结构。但这种数据结构容易产生两种问题:① 如果空间利用率高,那么经过的哈希算法计算存储位置的时候,会发现很多存储位置已经有数据了(哈希冲突);② 如果为了避免发生哈希冲突,增大数组容量,就会导致空间利用率不高。
而加载因子就是表示Hash表中元素的填满程度。加载因子=填入表中的元素个数/散列表的长度
加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;
加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容rehash操作的次数。
负载因子太小了浪费空间并且会发生更多次数的resize,太大了哈希冲突增加会导致性能不好,所以0.75只是一个折中的选择
为什么要用红黑树,为何一上来不树化,树化阈值为何是8,何时会树化,何时会退化为链表?
(1)红黑树用来避免DOS攻击,防止链表超长时性能下降,树化应当是偶然情况
①hash表的查找,更新的时间复杂度是O(1),而红黑树的查找,更新的时间复杂度是O(log2n),TreeNode占用空间也比普通Node的大,如非必要,尽量使用链表
②hash值如果足够随机,则在hash表内按泊松,在负载因子0.75,长度超过8的链表出现概率是亿分之6,选择8就是为了让树化几率足够小
(2)树化两个条件:链表长度超过树化阈值;数组容量>=64
(3)退化情况1:在扩容时如果拆分树时,树元素个数<=6 则会退化链表
(4)退化情况2:remove树节点是,若root,root.left,root.right
root.left.left有一个null,也会退化链表
索引如何计算?hashCode都有了,为何还要提供hash()数组容量为何是2的n次幂
①计算对象的hashcode(),在进行调用hashmap的hash()方法进行二次哈希,最后&(capacity-1)得到索引
②二次hash()是为了综合高位数据,让哈希分布更为均匀
③计算索引时,如果是2的n次幂可以使用位与运算代替取模,效率更高;扩容时hash&oldCap==0的元素留在原来位置,否则新位置=旧位置+oldCap
④但①②③都是为了配合容量为2的n次幂是的优化手段,例如HashTable的容量就不是2的n次幂,并不能说哪种涉及更优,应该是设计者综合了各种因素,最终选择了使用2的n次幂作为容量
(3)ConcurrentHashMap
(4)TreeMap
(5)HashTable

5.其他集合

(1)MultiMap

6.并发集合和普通集合

(三)版本特性

1.Java6

2.java 8

Stream和ParallelStream

3.java 11

4.java12

5.Java 13

6.Java 14

7.java 15

8.java 16

9.java 17

(四)设计模式

(五)数据结构与算法

1.数据结构

(1)数组
(2)字符串
(3)队列
(4)栈
(5) 链表
(6)集合
(7)哈希表
(8)二叉树

2.算法

排序
简单排序
import lombok.*;

/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/16 18:38
 **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
//Comparable接口:此接口强行对实现它的每个类的对象进行整体排序
public class Student implements Comparable<Student>{
    public  String name;
    public  Integer age;


    @Override
    public int compareTo(Student s) {
        return s.getAge();
    }
}

/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/16 19:29
 **/
public class StudentTest {
    public static void main(String[] args) {
        Student stu1=Student.builder()
                .name("Wh")
                .age(18)
                .build();

        Student stu2=Student.builder()
                .name("Sh")
                .age(22)
                .build();
        Comparable max=getMax(stu1,stu2);
        System.out.println(max);

    }

    /**
     * 测试方法,获取两个元素中的较大值
     * @param c1
     * @param c2
     * @return
     */
    public static Comparable getMax(Comparable c1,Comparable c2) {
        int cmp = c1.compareTo(c2);
        if (cmp >= 0) {
            return c1;
        } else {
            return c2;
        }
    }
}

冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
排序原理:
(1)比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
(2)对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值。

冒泡排序API排序
类名Bubble
构造方法Bubble():创建Bubble对象
成员方法1.public static void sort(Comparable[] a):对数组内的元素进行排序;
2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w;
3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值
冒泡排序的代码实现
/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/16 22:03
 **/


public class Bubble {
    /**
     * 排序代码实现
     *
     * @param a
     */
    public static void sort(Comparable[] a) {
        for (int i = a.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (ComparisonOfElement(a[j], a[j + 1])) {
                    ChangeOfPosition(a, j, j + 1);
                }
            }
        }
    }

    /**
     * 比较元素m是否比n大
     *
     * @param m
     * @param n
     * @return
     */
    private static boolean ComparisonOfElement(Comparable m, Comparable n) {
        return m.compareTo(n) > 0;
    }
 /**
     * 数组元素 i和j交换位置
     * @param arr
     * @param i
     * @param j
     */
    private static void ChangeOfPosition(Comparable[] arr, int i, int j) {
        Comparable t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
------------------------测试类----------------------------------
import java.util.Arrays;

/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/17 10:05
 **/
public class BubbleTest {

    public static void main(String[] args) {
        Integer[] a = {4, 5, 6, 3, 2, 1};
        Bubble.sort(a);
        System.out.println(Arrays.toString(a));
    }
}
冒泡排序的时间复杂度分析:
冒泡排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以,
我们分析冒泡排序的时间复杂度,主要分析一下内层循环体的执行次数即可。
在最坏情况下,也就是假如要排序的元素为{6,5,4,3,2,1}逆序,那么:
元素比较的次数为:
(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
元素交换的次数为:
(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
总执行次数为:
(N^2/2-N/2)+(N^2/2-N/2)=N^2-N;
按照大O推导法则,保留函数中的最高阶项那么最终冒泡排序的时间复杂度为O(N^2).
选择排序

概念:选择排序是一种更加简单直观的排序方法
排序原理:
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引。
2.交换第一个索引处和最小值所在的索引处的值。
选择排序API设计:

类名Selection
构造方法Selection():创建Selection对象
成员方法1.public static void sort(Comparable[] a):对数组内的元素进行排序
2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w
3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值
选择排序选择排序代码实现
/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/18 9:40
 **/
public class Selection {

    public static void sort(Comparable[] a) {
        for (int i = 0; i <= a.length - 2; i++) {
            //假定本次遍历,最小值所在的索引是i
            int minIndex = i;
            for (int j = i + 1; j < a.length; j++) {
                if (ComparisonOfElement(a[minIndex], a[j])) {
                    //跟换最小值所在的索引
                    minIndex = j;
                }
            }
            //交换i索引处和minIndex索引处的值
            ChangeOfPosition(a, i, minIndex);
        }
    }

    /**
     * 比较元素m是否比n大
     *
     * @param m
     * @param n
     * @return
     */
    private static boolean ComparisonOfElement(Comparable m, Comparable n) {
        return m.compareTo(n) > 0;
    }

    /**
     * 数组元素 i和j交换位置
     *
     * @param arr
     * @param i
     * @param j
     */
    private static void ChangeOfPosition(Comparable[] arr, int i, int j) {
        Comparable t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
-------------------------测试类----------------------------------
import java.util.Arrays;
/**
 * @Version 1.0
 * @Author: swy
 * @Date 2022/3/18 9:41
 **/
public class SelectionTest {

    public static void main(String[] args) {
        Integer[] a = {4, 5, 6, 3, 2, 1};
        Selection.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

选择排序的时间复杂度分析:
选择排序使用了双层for循环,其中外层循环完成了数据交换,内层循环完成了数据比较,所以我们分别统计数据交换次数和数据比较次数:
数据比较次数:
(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
数据交换次数:
N-1
时间复杂度:N^2/2-N/2+(N-1)=N^2/2+N/2-1;
根据大O推导法则,保留最高阶项,去除常数因子,时间复杂度为O(N^2);
插入排序

概念:插入排序(Insertion sort)是一种简单直观且稳定的排序算法。
插入排序的工作方式非常像人们排序一手扑克牌一样。开始时,我们的左手为空并且桌子上的牌面朝下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。
排序原理:
1.把所有的元素分为两组,已经排序的和未排序的;
2.找到未排序的组中的第一个元素,向已经排序的组中进行插入;
3.倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待
插入元素放到这个位置,其他的元素向后移动一位;
插入排序API设计:

类名Insertion
构造方法Insertion():创建Insertion对象
成员方法1.public static void sort(Comparable[] a):对数组内的元素进行排序
2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w
3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值
(2)双指针
(3)查找
(4)分冶
(5)动态规则
(6)递归
(7)回溯
(8)贪心
(9)位运算
(10)DFS
(11)BFS
(12)图

(六)前端基础

1.HTML

2.CSS

3.JavaScript

4.Vue

5.BootStrap

6.Ajax

二丶进阶

(一)JVM

1.JVM内存区域与内存溢出异常

(1)概述
(2)运行时数据区域
a.程序计数器
b. Java虚拟机栈
c.本地方法栈
d.Java堆
e.方法区
f.运行时常量池
g.直接内存
(3)HOtSpot 虚拟机对象探秘
a. 对象的创建
b.对象得到内存布局
c.对象的访问定位
(4) OutOfMemoryError异常

2.垃圾回收机制与内存分配

3.虚拟机性能监控,故障处理工具

4.调优案例分析与实战

5.类文件结构

6.虚拟机类加载机制

7.虚拟机字节码执行引擎

8.前端编译与优化

9.后端编译与优化

10.Java内存模型与线程

11.线程安全与锁优化

(二)多线程

1.多线程基础知识

(1) 线程和进程
(2)线程状态
(3)并行和并发
(4)同步和异步
(5)死锁
(6)可重入锁
(7)线程安全
(8)AQS
(9) Fork Join
(10) CAS

2.常见关键字

(1)synchronized
(2)volatile

3.多线程锁机制

(1) lock对象的使用

4.线程池

5.单例模式与多线程

6.并发工具类(CountDownLatch)

(三)框架

1.SSH(假如有必要的话)

(1) struts
(2)struts2
(3)hibernate

2.SSM

(1)Spring
1.DI
2.IOC
3.AOP

AOP日志

(2) Springmvc
(3) Mybatis

3.SpringBoot

Springboot访问数据库
1.首先有一个Springboot的项目
2.使用JDBC
在SpringBoot的配置文件中配置好数据源,我们就可以查看默认的数据库连接:

spring.datasource.url = jdbc:mysql://127.0.0.1:3306/tale?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver

在测试类中测试连接

    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

如果控制台打印输出,说明连接成功
3.整合Druid数据源
(1)引入pom依赖
(2)在application.yml或者properties文件中配置数据源
使用type属性来指定我们使用的数据源

spring:
  datasource:
    # 根据url自动配置
    driver-class-name: com.mysql.jdbc.Driver
    # 用户名
    username: root
    # 连接数据库的密码
    password: 123456
    # 连接数据库的url
    url: jdbc:mysql://127.0.0.1:3306/tale?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    type: com.alibaba.druid.pool.DruidDataSource
    # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
    initialSize: 5
    # 最小连接池数量
    minIdle: 5
    # 最大连接池数量
    maxActive: 20
    # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    maxWait: 60000
    # Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
    # testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    poolPreparedStatements: true
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

当然,如果直接在配置文件中引入这些是不行的,因为默认的属性不会绑定这些参数,我们需要自定义一个DataSource来绑定参数,可如下操作:

/**
 * 配置Driud数据源
 */
@Configuration
public class DruidConfig {

    @Bean
    //下面的注解表示将属性文件中前缀是spring.datasource的属性绑定到当前数据源
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druid() {
        return new DruidDataSource();
    }

    /**
     * druid的强大在于有一套完整的监控配置,我们可以在这里配置一下,配置druid的后台监控需要配置
     * 一个servlet,我们可以直接使用servletRegistrationBean来配置,配置的servlet的名称
     * 是statViewServlet,
     */
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean
                = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //可以在这个servlet中设置参数来定义后台的一些参数
        Map<String, String> initParms = new HashMap<>();
        //配置登录用户名
        initParms.put("loginUsername", "admin");
        //配置密码
        initParms.put("loginPassword", "123456");
        //配置访问权限,默认是所有都能访问
        initParms.put("allow", "");
        //配置拒绝访问的ip
        initParms.put("deny", "");
        bean.setInitParameters(initParms);
        return bean;
    }

    /**
     * 要使用druid的后台监控功能,还可以配置一个filter,它的名称是webStatFilter
     *
     */
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String, String> initParms = new HashMap<>();
        //不拦截的资源
        initParms.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParms);
        //要拦截的请求
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }

}

4.整合mybatis
(1)首先需要引入mybatis的maven依赖
(2)创建对应的javaBean和Mapper

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
Public class Student{
	private int id;
	private String name;


}
/**
 * 用户表对应的mapper
 */
@Mapper
//mapper注解指定这个接口是mybatis的mapper,且将此接口加入容器
public interface StudentMapper {

    /**
     *  通过id查询user对象
     * @param id 主键
     * @return user对象
     */
    @Select("select * from user where id=#{id}")
    Student queryUserById(int id);
	
    
    @Options(useGeneratedKeys = true, keyProperty = "id")
  	//options用来定义主键返回,keyProperty指定主键对应的属性
    @Insert("insert into user (name) values (#{name})")
    int insert(Student student );

}

在controller层定义好对应的映射:

    @GetMapping("/student/{id}")
    @ResponseBody
    public Student queryUser(@PathVariable("id") int id) {
        Student student = StudentMapper.queryUserById(id);
        return student ;
    }

    @GetMapping("/student")
    @ResponseBody
    public Student insertUser(Student student ) {
        StudentMapper.insert(student );
        return student;
    }

如上做完以后就可以访问页面了,如果需要配置数据库映射的时候使用驼峰命名的规则,可以自定义一个配置规则:

@org.springframework.context.annotation.Configuration
public class MyBatisConfig {
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return new ConfigurationCustomizer(){
            @Override
            public void customize(Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

如果我们的Mapper文件太多,不想一个个写@Mapper注解,也可以直接在启动类上使用以下方式来扫描包路径下的所有mapper:

@SpringBootApplication
@MapperScan(value = "mapper文件所在的包路径, ex:com.xiaojian.mapper")
public class SpringbootJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootJdbcApplication.class, args);
    }
}

使用配置文件整合Mybatis
使用配置文件来整合Mybatis也很简单,只需要将sql定义在xml文件中即可,再写Mybatis的核心配置文件就可以,我们可以在SpringBoot的配置文件中指定Mybatis的配置文件和Mapper映射文件的位置:

mybatis:
  config‐location: classpath:mybatis/mybatis‐config.xml 指定全局配置文件的位置
  mapper‐locations: classpath:mybatis/mapper/*.xml  指定sql映射文件的位置

5.整合JPA
引入jpa相关的pom依赖
编写一个实体类和数据库中的表对应

//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User {
    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private Integer id;
    @Column(name = "last_name",length = 50) //这是和数据表对应的一个列
    private String lastName;
    @Column //省略默认列名就是属性名
    private String email;

编写一个xxxRepository接口来操作对应的表

//继承JpaRepository来完成对数据库的操作,JpaRepository继承了CrudRepository和Page类的功能,既可以进行正常的增删改查,也可以进行分页
public interface UserRepository extends JpaRepository<User,Integer> {
}

还需要在配置文件中配置:

spring: 
 	jpa:
    	hibernate:
#     更新或者创建数据表结构
      	ddl‐auto: update
#    控制台显示SQL
   	    show‐sql: true

完成以上配置以后就可以在controller层写对象的方法来操作数据了

4.SpringCloud

4.1微服务基础知识
单体结构和分布式架构
单体结构:将业务的所有功能集中在一个项目中开发,打包成一个部署,优点是架构简单,部署成本低,缺点是耦合度高;适合小型项目。
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个项目,优点是降低服务耦合,有利于服务升级拓展;适合大型项目。

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
面向服务:微服务对外暴露业务接口
自治:团队独立,技术独立,数据独立,部署独立
隔离性强:服务调用做好隔离,容错,降级,避免出现级联问题

微服务技术对比
DubboSpringcloudSpringcloudAlibaba
注册中心Zookeper,RedisEureka,ConsulNacos,Eureka
服务远程调用Dubbo协议Fegin(http协议)Dubbo,Fegin
配置中心SpringCloudConfigSpringCloudConfig,Zuul
服务网关SpringCloudGateway,ZuulSpringCloudGateway,Zuul
服务监控和保护dubbo-admin,功能弱HystrixSentinel

企业需求大概如下
在这里插入图片描述

4.2 SpringCloud概述
4.2.1 微服务中的相关概念

1.服务注册与发现
服务注册:服务实例将自身服务信息注册到注册中心。这部分服务信息包括服务所在主机IP和提供服务
的Port,以及暴露服务自身状态以及访问协议等信息。
服务发现:服务实例请求注册中心获取所依赖服务信息。服务实例通过注册中心,获取到注册到其中的
服务实例的信息,通过这些信息去请求它们提供的服务。
2.负载均衡
负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应
用、数据库或其他服务的性能和可靠性。
3.熔断
熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压
力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这
种牺牲局部,保全整体的措施就叫做熔断。
4.链路追踪
随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应
用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言
来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要对一次请求涉及的多个
服务链路进行日志记录,性能监控即链路追踪
5.API网关
随着微服务的不断增多,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务
的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信可能出现:
(1)客户端需要调用不同的url地址,增加难度
(2)再一定的场景下,存在跨域请求的问题
(3)每个微服务都需要进行单独的身份认证
针对这些问题,API网关顺势而生。
API网关直面意思是将所有API调用统一接入到API网关层,由网关层统一接入和输出。一个网关的基本
功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个
API服务提供团队可以专注于自己的的业务逻辑处理,而API网关更专注于安全、流量、路由等问题。

4.2.2 SpringCloud的介绍

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基
础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用
Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家
公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉
了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具
包。

4.2.3 SpringCloud的架构

1.SpringCloud中的核心组件
Spring Cloud的本质是在 Spring Boot 的基础上,增加了一堆微服务相关的规范,并对应用上下文
(Application Context)进行了功能增强。既然 Spring Cloud 是规范,那么就需要去实现,目前
Spring Cloud 规范已有 Spring官方,Spring Cloud Netflix,Spring Cloud Alibaba等实现。通过组件
化的方式,Spring Cloud将这些实现整合到一起构成全家桶式的微服务技术栈。
Spring Cloud Netflix组件

组件名称作用
Eureka服务注册中心
Ribbon客户端负载均衡
Feign声明式服务调用
Hystrix客户端容错保护
ZuulAPI服务网关
Spring Cloud Alibaba 组件
组件名称作用
:–:–
Nacos服务注册中心
Sentinel客户端容错保护
SpringCloud原生及其他组件
组件作用
:–:–
Consul服务注册中心
Config分布式配置中心
GatewayAPI服务网关
Sleuth/Zipkin分布式链路追踪

2.SpringCloud的体系结构

在这里插入图片描述
在这里插入图片描述
从上图可以看出Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。
注册中心负责服务的注册与发现,很好将各服务连接起来
断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。
API网关负责转发所有对外的请求和服务
配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
链路追踪技术可以将所有的请求数据记录下来,方便我们进行后续分析
各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况

4.2.3 案例搭建

使用微服务架构的分布式系统,微服务之间通过网络通信。我们通过服务提供者与服务消费者来描述微服
务间的调用关系。

服务提供者:服务的被调用方,提供调用接口的一方
服务消费者:服务的调用方,依赖于其他服务的一方

我们以电商系统中常见的用户下单为例,用户向订单微服务发起一个购买的请求。在进行保存订单之前
需要调用商品微服务查询当前商品库存,单价等信息。在这种场景下,订单微服务就是一个服务消费
者,商品微服务就是一个服务提供者
在这里插入图片描述
1.数据库表
用户表

CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL COMMENT '用户名',
`password` varchar(40) DEFAULT NULL COMMENT '密码',
`age` int(3) DEFAULT NULL COMMENT '年龄',
`balance` decimal(10,2) DEFAULT NULL COMMENT '余额',
`address` varchar(80) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

商品表

CREATE TABLE `tb_product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(40) DEFAULT NULL COMMENT '名称',
`status` int(2) DEFAULT NULL COMMENT '状态',
`price` decimal(10,2) DEFAULT NULL COMMENT '单价',
`product_desc` varchar(255) DEFAULT NULL COMMENT '描述',
`caption` varchar(255) DEFAULT NULL COMMENT '标题',
`inventory` int(11) DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

订单表

CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`product_id` int(11) DEFAULT NULL COMMENT '商品id',
`number` int(11) DEFAULT NULL COMMENT '数量',
`price` decimal(10,2) DEFAULT NULL COMMENT '单价',
`amount` decimal(10,2) DEFAULT NULL COMMENT '总额',
`product_name` varchar(40) DEFAULT NULL COMMENT '商品名',
`username` varchar(40) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

5.RPC

6.dubbo

7.netty

8.Zookeper

(三)中间件

1.redis

2.消息队列

(1)Rabbitmq
(2)Activemq
(3)Rockmq

3.nginx

4.mysql

(四)互联网技术

1.容器

(1)Docker

Docker 安装
Docker官网

下面只是部分
Docker架构
(1)镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
(2)容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
(3)仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
Docker 容器通过 Docker 镜像来创建。
容器与镜像的关系类似于面向对象编程中的对象与类。
Docker面向对象
容器对象
镜像
docker架构图
概念说明
:–:–
Docker 镜像(Images)Docker 镜像是用于创建 Docker 容器的模板,比如 Ubuntu 系统。
Docker 容器(Container)容器是独立运行的一个或一组应用,是镜像运行时的实体。
Docker 客户端(Client)Docker 客户端通过命令行或者其他工具使用 Docker SDK (https://docs.docker.com/develop/sdk/) 与 Docker 的守护进程通信。
Docker 主机(Host)一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker RegistryDocker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。
Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
Docker MachineDocker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。
Docker概念:
(1)是一个开源的应用容器引擎,基于go语言实现
(2)Docker可以让开发者打包他们的应用以及依赖包到一个轻量级,可移植的容器中,然后发布到任何流行的linux机器上 
(3)容器是完全使用沙箱机制,相互隔离
(4)容器性能开销极地
(5)Docker从17.03版本之后分为社区版(免费)和企业版
一句话概括:Docker是一种容器技术,解决软件跨环境迁移的问题 
# 1.yum 包更新到最新
yum update
# 2.安装需要的软件包,yum -util 提供 yum-config-manger 功能,另外两个是deviceMapper驱动依赖的
yum install -y -yum-utils device-mapper-persistent-data lvm2
# 3.设置yum源
yum-config-manger --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4.安装docker,出现输入的界面都按y(可能存在因为网络问题 而下载失败的情况 只需要重新安装即可)
yum install y docker-ce
# 5.查看docker版本,验证是否验证成功(出现版本号 即为成功)
docker -v
Docker服务相关命令
# 1.启动docker服务
systemctl start docker
# 2.停止docker服务
systemctl stop docker
# 3.重启docker 服务
systemctl restart docker
# 4.查看docker服务状态
systemctl status docker 
# 5.设置开机启动docker服务
systemctl enable docker
Docker镜像相关命令
# 1.查看镜像:查看本地所有镜像
docker images
docker images -q # 查看所有镜像的id
# 2.搜索镜像:从网络中查找需要的镜像
docker search 镜像名称
# 3.拉取镜像:从docker仓库下载镜像到本地,镜像名称格式为 名称:版本号,如果版本号不指定则是最新的版本。如果不知道镜像版本,可以去docker hub 搜索对应镜像查看
docker pull 镜像名称
# 4.删除镜像:删除本地镜像
docker rmi 镜像id # 删除指定本地镜像
docker rmi docker images -q # 删除所有本地镜像
Docker容器相关命令
# 1.查看容器
docker ps # 查看正在运行的容器
docker ps -a # 查看所有容器
# 2.创建并启动容器
docker run 参数
# 3.进入容器
docker exec 参数 #退出容器,容器不会关闭
# 4.停止容器
docker stop 容器名称
# 5.启动容器
docker start 容器名称
# 6.删除容器:如果容器是运行状态则删除失败,需要停止容器才能删除
docker rm 容器名称
# 7.查看容器信息
docker inspect 容器名称
参数说明
-i:保持容器运行。通常与-t同时使用。加入it这个两个参数后,容器创建后自动进入容器中,退出容器后,容器自动关闭。
-t:为容器重新分配一个伪输入终端,通常与-i同时使用
-d:以守护(后台)模式运行容器。创建一个容器在后台运行,需要使用docker exec进入容器。退出后,容器不会关闭
-it:创建的容器一般称为交互式容器,-id创建容器一般称为守护式容器
-name:为创建的容器命名
(2)KBS

2.大数据

3.搜索引擎

(1)Lucene
(2)ElasticSearch
(3)Solr
(4)Canal
(5)Kibana
(6)Logstash

4.分布式系统

(1)分布式锁

(五)数据库

数据库的概念:数据库就是一个文件系统,通过标准的SQL语句获取数据

在这里插入图片描述

1.Mysql

什么是MySQL数据库

在这里插入图片描述

什么是关系型数据库

关系型数据库存放的是实体之间的关系
在这里插入图片描述

mysql安装(略)
1.4 服务器

什么是服务器
服务器要从硬件和软件两个方面来说:
硬件:指的就是一台计算机。
软件:需要在这台电脑上安装数据库服务器。

MySQL存储方式
SQL简单了解

SQL语句是不区分大小写的,很多sql开发人员喜欢对所有的sql关键字使用大写,但是对所有表和列名使用小写,方便阅读和调试,多条sql语句必须以英文的分号分隔,虽然mysql是不需要在单条sql语句后面添加分号的,但是加上肯定是没坏处的,所以养成好习惯建议加上,如果你是用mysql命令行的话,那么必须要用分号来结束sql语句

基本概念:
表(table)
模式(shema)
列(column)
行(row)
主键(primary key):唯一标识表中每行的这个列(或这组列)称为主键
使用MySQL

1.连接

为了连接到mysql需要一下信息
主机名(计算机名)——如果连接到本地MySQL服务器, 为localhost;
端口(如果使用默认端口3306之外的端口);
一个合法的用户名;
 用户口令(如果需要)。

2.选择数据库
比如你的数据库的名字叫mydatabase

输入:USE mydatabase;
输出:database changed
分析:USE语句并不返回任何结果。依赖于使用的客户机,显示某种
形式的通知。例如,这里显示出的Database changed消息是
mysql命令行实用程序在数据库选择成功后显示的。
注意:必须先要用use指令选择所需要打开的数据库,才能读取其中的数据

3.了解数据库和表
如果你不知道数据名怎么办

输入:SHOWN DATABASE;
输出:Database
	 name1
	 name2 ……
分析:SHOW DATABASES;返回可用数据库的一个列表。

为了获得一个数据库内的表

输入:SHOW TABLES:
输出:Tables_in_mydatabase
		tablename1
		tablename2 ……
SHOW TABLES;返回当前选择的数据库内可用表的列表
SHOW COLUMNS FROM TABLENAME(表名) 显示表列
SHOW STATUS:用于显示广泛的服务器状态信息
SHOW CREATE DATABASESHOW CREATE TABLE:分别用来显示创建特定数据库或表的MySQL语句
SHOW GRANTS:用来显示授予用户(所有用户或特定用户)的安全权限;
SHOW ERRORSSHOW WARNINGS: 用来显示服务器错误或警告消息
检索数据

1.SELECT语句
2.检索单个列

SELECT 列名 FORM 表名;

3.检索多个列
要想从一个表中检索多个列,使用相同的SELECT语句。唯一的不同
是必须在SELECT关键字后给出多个列名,列名之间必须以逗号分隔。

SELECT 列名1,列名2,列名3,……
SQL语句一般返回原始的、无格式的数据。数据的格式化是一个表示问题,而不是一个检索问题。

4.检索所有列

SELECT * FROM 表名
注意:除非你确定需要表中的每个列,否则最好别使用通配符*,因为虽然使用通配符不需要列出所有列的列名,对于你自己是省事了,但是检索不需要的列通常会降低检索和应用程序的性能;当然使用通配符有一个大优点。由于不明确指定列
名(因为星号检索每个列),所以能检索出名字未知的列。

5.检索不同的行
SELECT返回所有匹配的行。但是,如果你不想要每个值每次都出现,怎么办?例如,假如你想得出products表中产品的所有供应商ID:

SELECT 字段名 FROM 表名;

如果你只是想要不同的值

SELECT DISTINCT 字段名FROM 表名;
注意:不能部分使用DISTINCT DISTINCT关键字应用于所有列而不仅是前置它的列。如果给出SELECT DISTINCT vend_id,
prod_price,除非指定的两个列都不同,否则所有行都将被检索出来。

6.限制结果
SELECT语句返回所有匹配的行,它们可能是指定表中的每个行。为
了返回第一行或前几行,可使用LIMIT子句。下面举一个例子:
limit后面接的是index(索引值)

SELECT 字段名 FROM 表名 LIMIT n; :从第1行开始限制n行
=> SELECT 字段名 FROM 表名 LIMIT 0,n; 其中的0表示第一行,在写法中可以省略
SELECT 字段名 FROM 表名 LIMIT m,n; m和n均表示索引值 行m开始的n行 ->第m+1行开始的n行
所以, 带一个值的LIMIT总是从第一行开始,给出的数为返回的行数。带两个值的LIMIT可以指定从行号为第一个值的位置开始
行0:检索出来的第一行为行0而不是行1。因此, LIMIT 1, 1将检索出第二行而不是第一行
在行数不够时 LIMIT中指定要检索的行数为检索的最大行数。如果没有足够的行(例如,给出LIMIT 10, 5,但只有13
行), MySQL将只返回它能返回的那么多行。
MySQL 5的LIMIT语法 LIMIT 3, 4的含义是从行4开始的3行还是从行3开始的4行?如前所述,它的意思是从行3开始的4
行,这容易把人搞糊涂。
由于这个原因, MySQL 5支持LIMIT的另一种替代语法。 LIMIT 4 OFFSET 3意为从行3开始取4行,就像LIMIT 3, 4一样。

7.使用完全限定的表名

SELECT 表名.列名 FROM 表名;
或者
SELECT 表名.列名 FROM 数据库名.表名;
排序检索数据

1.排序数据

SELECT 字段名 FROM 表名 ORDER BY 字段名;ORDER BY默认按照升序排序)
通过非选择列进行排序 通常, ORDER BY子句中使用的列将是为显示所选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。

2.按多个列排序

SELECT 字段1,字段2,…… FROM 表名 ORDER BY 需要排序的字段名;

3.指定排序方向

SELECT 字段1,字段2,…… FROM 表名 ORDER BY 需要排序的字段名 DESC;(降序排列)
SELECT 字段1,字段2,…… FROM 表名 ORDER BY 字段名1 DESC,字段名2……;(降序排列)
DESC关键字只应用到直接位于其前面的列名,字段1列指定DESC,对字段2列仍然按标准的升序排序。也就是说如果想要针对多个不同的列进行降序排序,就需要在每个字段名得到后面加上DESC。另外与DESC相反的关键字是ASC,由于默认就是升序,所以ASC没有多大用处
区分大小写和排序顺序 
在对文本性的数据进行排序时, A与a相同吗? a位于B之前还是位于Z之后?这些问题不是理论问
题,其答案取决于数据库如何设置。
在字典( dictionary)排序顺序中,A被视为与a相同,这是MySQL(和大多数数据库管理系统)的默认行为。但是,许多数据库管理员能够在需要时改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)。
这里,关键的问题是,如果确实需要改变这种排序顺序,用简单的ORDER BY子句做不到
使用ORDER BY和LIMIT的组合,能够找出一个列中最高或最低的值。比如:
SELECT 字段1 FROM 表名 ORDER BY 字段1 DESC LIMIT1;(出来的值就是最大值)
SELECT 字段1 FROM 表名 ORDER BY 字段1  LIMIT1;(出来的就是最小值)
ORDER BY子句的位置 在给出ORDER BY子句时,应该保证它位于FROM子句之后。如果使用LIMIT,它必须位于ORDER BY之后。使用子句的次序不对将产生错误消息。
过滤数据

1.使用WHERE

SELECT 字段1,字段2 FROM表名 WHERE 筛选条件;
比如
SELECT prod_name,prod_price FROM products WHERE prod_price=3.00;
WHERE子句的位置 在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后

2.WHERE子句操作符

操作符说明
=等于
<>不等于
!=不等于
<小于
<=小于等于
>大于
>=大于等于
BETWEEN在指定的两个值之间
检查单个值
SELECT 字段1,字段2 FROM 表名 WHERE 字段1='某某'
检查字段1='某某'语句。它返回的值为某某的一行。mysql在执行匹配时默认不区分大小写,所以比如某某字符串是Abcd则和abcd是直接相匹配的

现在再举几个别的例子

SELECT prod_name,prod_price
FROM products
WHERE prod_price<20;
SELECT prod_name,prod_price
FROM products
WHERE prod_price<=20;
SELECT vend_id,prod_name
FROM products
WHERE vend_id <>100;
SELECT vend_id,prod_name
FROM products
WHERE vend_id !=100
SELECT prod_name,prod_price
FROM products
WHERE prod_price BETWEEN 5 AND 10;
使用BETWEEN 时 必须指定两个值 低端值 AND 高端值。就像这样两个值必须要用AND关键字隔开,BETWEEN匹配范围内所有的值,包括开始值和结束值
SELECT prod_name 
FROM products 
WHERE prod_prices IS NULL;
返回所有prod_price 是空字段的所有产品(空字段不是价格为0 ,null和0是两个不同的概念),如果表中不存在这样的行便没有返回的数据。
NULL与不匹配 在通过过滤选择出不具有特定值的行时,你可能希望返回具有NULL值的行。但是,不行。因为未知具有特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤或不匹配过滤时不返回它们。
因此,在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL的行。
数据过滤

1.组合WHERE子句
AND操作符

SELECT prod_id,prod_price,prod_name
FROM products
WHERE vend_id =100 AND prod_price <=10
此语句检索是供应商为100并且价格小于等于10元的所有产品的名称和价格,AND连接了两个筛选条件,

OR操作符

OR操作符与AND操作符不同,它指示MySQL检索匹配任一条件的行。
SELECT prod_name,prod_price
FROM products
WHERE vend_id=100 or vend_id=101;
只要筛选条件满足任意一个,都会被返回出来,反之则没有数据返回。

计算次序

SELECT prod_name,prood_price
FROM products
where vend_id=100 OR vend_id=101 AND prod_price>=10;
SQL 在处理OR操作符前,优先处理AND操作符,在上个sql语句中由于AND的执行优先级更好,所以最后的结果是错误的。上句话可以理解为由供应商100制造的任何价格为10美元(含)以上的产品,或者由供应商101制造的任何产品
SELECT prod_name,prood_price
FROM products
where (vend_id=100 OR vend_id=101) AND prod_price>=10;
这个SQL语句理解为由供应商100或者101制造的且都在10美元以上的任何产品
在WHERE子句中使用圆括号 任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。

2.IN操作符

IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。 IN取合法值的由逗号分隔的清单,全都括在圆括号中。
SELECT prod_name,prod_price
FROM products
WHERE vend_id IN (100,101)
ORDER BY prod_name;
此SELECT语句检索供应商1002和1003制造的所有产品。 IN操作符后跟由逗号分隔的合法值清单,整个清单必须括在圆括号中。IN操作符和OR具有完全相同的功能。
SELECT prod_name,prod_price
FROM products
WHERE vend_id =100 OR vend_id=101
ORDER BY prod_name;
为什么要使用IN操作符?其优点具体如下。
(1)在使用长的合法选项清单时, IN操作符的语法更清楚且更直观。
(2)在使用IN时,计算的次序更容易管理(因为使用的操作符更少)。
(3)IN操作符一般比OR操作符清单执行更快。
(4)IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。第14章将对此进行详细介绍。
IN WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。

3.NOT操作符

WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。
NOT WHERE子句中用来否定后跟条件的关键字。
SELECT prod_name,prod_price
FROM products
WHERE vend_id NOT IN (100,101)
ORDER BY prod_name;
这里的NOT否定跟在它之后的条件,因此, MySQL不是匹配100和 101 的 vend_id , 而 是 匹 配 100101 之 外 供 应 商 的vend_id。
为什么使用NOT?对于简单的WHERE子句,使用NOT确实没有什么优势。但在更复杂的子句中, NOT是非常有用的。例如,在与IN操作符联合使用时, NOT使找出与条件列表不匹配的行非常简单。
MySQL中的NOT MySQL 支 持 使 用 NOT 对 IN 、 BETWEEN 和EXISTS子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别。
用通配符进行过滤

1.LIKE操作符

通配符(wildcard) 用来匹配值的一部分的特殊字符。
搜索模式(search pattern)① 由字面值、通配符或两者组合构成的搜索条件
谓词 操作符何时不是操作符?答案是在它作为谓词( predicate)时。从技术上说, LIKE是谓词而不是操作符。虽然最终的结果是相同的,但应该对此术语有所了解,以免在SQL文档中遇到此术语时不知道。

百分号( %)通配符

最常使用的通配符是百分号(%)。在搜索串中, %表示任何字符出现任意次数。例如,为了找出所有以词jet起头的产品,可使用以下SELECT语句:
SELECT prod_id,prod_name
FROM products
WHERE prod_name LIKE 'jet%'
此例子使用了搜索模式'jet%'。在执行这条子句时,将检索任意以jet起头的词。 %告诉MySQL接受jet之后的任意字符,不管它有多少字符
区分大小写 根据MySQL的配置方式,搜索可以是区分大小写的。如果区分大小写, 'jet%'与JetPack 1000将不匹配。
通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。
下面的例子使用两个通配符,它们位于模式的两端:
SELECT prod_name,prod_name
FROM products
WHERE prod_name LIKE '%anvil%';
搜索模式'%anvil%'表示匹配任何位置包含文本anvil的值,而不论它之前或之后出现什么字符。
通配符也可以出现在搜索模式的中间,虽然这样做不太有用。下面的例子找出以s起头以e结尾的所有产品:
SELECT prod_name
FROM products
WHERE prod_name LIKE 's%e';
%代表搜索模式中给定位置的0个、 1个或多个字符。
注意尾空格 尾空格可能会干扰通配符匹配。例如,在保存词anvil 时 , 如 果 它 后 面 有 一 个 或 多 个 空 格 , 则 子 句 WHERE prod_name LIKE '%anvil'将不会匹配它们,因为在最后的l后有多余的字符。解决这个问题的一个简单的办法是在搜索模式最后附加一个%。一个更好的办法是使用函数
去掉首尾空格。
注意NULL 虽然似乎%通配符可以匹配任何东西,但有一个例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作为产品名的行。

下划线(_)通配符

另一个有用的通配符是下划线(_)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
SELECT prod_id,prod_name
FROM products
WHERE prod_name LIKE '_ton anvil';
此WHERE子句中的搜索模式给出了后面跟有文本的两个通配符。结果只显示匹配搜索模式的行:第一行中下划线匹配1,第二行中匹配2。 .5 ton anvil产品没有匹配,因为搜索模式要求匹配两个通配符而不是一个。对照一下,下面的SELECT语句使用%通配符,返回三行产品:
SELECT prod_id,prod_name
FROM products
WHERE prod_name LIKE '%ton anvil';
与%能匹配0个字符不一样, _总是匹配一个字符,不能多也不能少。

2.使用通配符的技巧

通配符记住小技巧:
(1)不要过度使用通配符。如果其他操作符能达到相同的目的,应该
使用其他操作符。
(2)在确实需要使用通配符时,除非绝对有必要,否则不要把它们用
在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起
来是最慢的。
(3)仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
总之,通配符是一种极重要和有用的搜索工具,以后我们经常会用到它。
用正则表达式进行搜索

1.正则表达式介绍
(略)
2.使用mysql正则表达式
基本字符匹配

检索列prod_name 包含文本100的所有行
SELECT prod_name
FROM products
WHERE prod_name REGEXP '100'
ORDER BY prod_name;
除关键字LIKE被REGEXP替代外,这条语句看上去非常像使用LIKE的语句。它告诉MySQL: REGEXP后所跟的东西作为正则表达式(与文字正文1000匹配的一个正则表达式)处理。正则表达式缺失没有带来太多的好处,并且可能还会降低性能。
SELECT prod_name
FROM products
WHERE prod_name REGEXP '.00'
ORDER BY prod_name;
这里使用了正则表达式.00。 .是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符,因此, 100和200都匹配且返回。
LIKE与REGEXP 在LIKE和REGEXP之间有一个重要的差别。请看以下两条语句:
SELECT prod_name
FROM products
WHERE prod_name LIKE '1000'
ORDER BY prod_name;
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1000'
ORDER BY prod_name;
如果执行上述两条语句,会发现第一条语句不返回数据,而第二条语句返回一行。为什么?
正如前面所述, LIKE匹配整个列。如果被匹配的文本在列值中出现, LIKE将不会找到它,相应的行也不被返回(除非使用通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现, REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。
那么, REGEXP能不能用来匹配整个列值(从而起与LIKE相同的作用)?答案是肯定的,使用^和$定位符( anchor)即可,
匹配不区分大小写 MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大小写,可使用BINARY关键字,如WHERE prod_name REGEXP BINARY 'JetPack .000'

进行OR匹配

SELECT prod_name
FROM products
WHERE prod_name REGEXP '1000|2000'
ORDER BY prod_name;
语句中使用了正则表达式1000|2000|为正则表达式的OR操作符。它表示匹配其中之一,因此1000和2000都匹配并返回。
使用|从功能上类似于在SELECT语句中使用OR语句, 多个OR条件可并入单个正则表达式。
两个以上的OR条件 可以给出两个以上的OR条件。例如,'1000 | 2000 | 3000'将匹配1000或2000或3000。

匹配几个字符之一

SELECT prod_name
FROM products
WHERE prod_name REGEXP '[123] Ton'
ORDER BY prod_name;
分析:这里,使用了正则表达式[123] Ton。 [123]定义一组字符,它的意思是匹配1或2或3,因此, 1 ton和2 ton都匹配且返回(没有3 ton)。
正如所见, []是另一种形式的OR语句。 事实上,正则表达式[123]Ton为[1|2|3]Ton的缩写,也可以使用后者。但是,需要用[]来定义OR语句查找什么。为更好地理解这一点,请看下面的例子:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1|2|3 Ton'
ORDER BY prod_name;
分析:这并不是期望的输出。两个要求的行被检索出来,但还检索出了另外3行。之所以这样是由于MySQL假定你的意思是'1''2''3 ton'。除非把字符|括在一个集合中,否则它将应用于整个串。字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123]匹配字符1、 2或3,但[^123]却匹配除这些字符外的任何东西

匹配范围

集合可用来定义要匹配的一个或多个字符。例如,下面的集合将匹配数字0到9:[0123456789]
为简化这种类型的集合,可使用-来定义一个范围。下面的式子功能上等同于上述数字列表:[0-9]
范围不限于完整的集合, [1-3][6-9]也是合法的范围。此外,范围不一定只是数值的, [a-z]匹配任意字母字符。
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[1-5] Ton'
ORDER BY prod_name;
分析:这里使用正则表达式[1-5] Ton。 [1-5]定义了一个范围,这个表达式意思是匹配1到5,因此返回3个匹配行。由于5 ton匹配,所以返回.5 ton。

匹配特殊字符

正则表达式语言由具有特定含义的特殊字符构成。我们已经看到.、 []|和-等,还有其他一些字符。请问,如果你需要匹配这些字符,应该怎么办呢?例如,如果要找出包含.字符的值,怎样搜索?请看下面的例子:
SELECT vend_name
FROM vendors 
WHERE vend_name REGEXP '.'
ORDER BY vend_name;
分析:这并不是期望的输出, .匹配任意字符,因此每个行都被检索出来。
为了匹配特殊字符,必须用\\为前导。 \\-表示查找-, \\.表示查找.。
SELECT vend_name
FROM vendors
WHERE vend_name REGEXP '\\.'
ORDER BY vend_name;
分析:这才是期望的输出。 \\.匹配.,所以只检索出一行。这种处理就是所谓的转义( escaping),正则表达式内具有特殊意义的所有字符都必须以这种方式转义。这包括.、 |[]以及迄今为止使用过的其他特殊字符

空白元字符

元字符说明
\f换页
\n换行
\r回车
\t制表
\v纵向制表
匹配\:为了匹配反斜杠( \)字符本身,需要使用\\\\\\?:多数正则表达式实现使用单个反斜杠转义特殊字符,以便能使用这些字符本身。但MySQL要求两个反斜杠( MySQL自己解释一个,正则表达式库解释另一个)。

匹配字符类
在这里插入图片描述
匹配多个实例

目前为止使用的所有正则表达式都试图匹配单次出现。如果存在一个匹配,该行被检索出来,如果不存在,检索不出任何行。但有时需要对匹配的数目进行更强的控制。例如,你可能需要寻找所有的数,不管数中包含多少数字,或者你可能想寻找一个单词并且还能够适应一个尾随的s(如果存在),等等。

在这里插入图片描述

SELECT prod_name
FROM products
WHERE prod_name REGEXP '\\([0-9] sticks?\\)'
ORDER BY prod_name;
分析:正则表达式\\([0-9] sticks?\\)需要解说一下。 \\(匹配)[0-9]匹配任意数字(这个例子中为1和5), sticks?匹配stick和sticks( s后的?使s可选,因为?匹配它前面的任何字符的0次或1次出
现), \\)匹配)。没有?,匹配stick和sticks会非常困难。
以下是另一个例子。这次我们打算匹配连在一起的4位数字:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[[:digit:]]{4}'
ORDER BY prod_name;
分析:如前所述, [:digit:]匹配任意数字,因而它为数字的一个集合。 {4}确切地要求它前面的字符(任意数字)出现4次,所以[[:digit:]]{4}匹配连在一起的任意4位数字。
需要注意的是,在使用正则表达式时,编写某个特殊的表达式几乎总是有不止一种方法。上面的例子也可以如下编写:
SELECt prod_name
FROM products
WHERE prod_name REGEXP '[0-9][0-9][0-9][0-9]'
ORDER BY prod_name;

定位符
在这里插入图片描述

SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]'
ORDER BY prod_name;
分析:^匹配串的开始。因此, ^[0-9\\.]只在.或任意数字为串中第一个字符时才匹配它们。没有^, 则还要多检索出4个别的行(那些中间有数字的行)。
^的双重用途 ^有两种用法。在集合中(用[]定义),用它来否定该集合,否则,用来指串的开始处。
使REGEXP起类似LIKE的作用 本章前面说过, LIKE和REGEXP的不同在于, LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。
简单的正则表达式测试:可以在不使用数据库表的情况下用SELECT来测试正则表达式。REGEXP检查总是返回0(没有匹配)或1(匹配)。可以用带文字串的REGEXP来测试表达式,并试验它们。相应的语法如下:
这个例子显然将返回0(因为文本hello中没有数字)。
创建计算字段

1.计算字段

存储在数据库表中的数据一般不是应用程序所需要的格式。下面举
几个例子。
(1)如果想在一个字段中既显示公司名,又显示公司的地址,但这两
个信息一般包含在不同的表列中。
(2)城市、州和邮政编码存储在不同的列中(应该这样),但邮件标签
打印程序却需要把它们作为一个恰当格式的字段检索出来。
(3)列数据是大小写混合的,但报表程序需要把所有数据按大写表示
出来。
(4)物品订单表存储物品的价格和数量,但不需要存储每个物品的总
价格(用价格乘以数量即可)。为打印发票,需要物品的总价格。
(5)需要根据表数据进行总数、平均数计算或其他计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
这就是计算字段发挥作用的所在了。与前面各章介绍过的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
字段(field):基本上与列( column) 的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。
重要的是要注意到,只有数据库知道SELECT语句中哪些列是实际的表列,哪些列是计算字段。从客户机(如应用程序)的角度来看,计算字段的数据是以与其他列的数据相同的方式返回的。
客户机与服务器的格式:可在SQL语句内完成的许多转换和格式化工作都可以直接在客户机应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户机中完成要快得多,因为DBMS是设计来快速有效地完成这种处理的。

2.拼接字段

拼接(concatenate) 将值联结到一起构成单个值。
解决办法是把两个列拼接起来。在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。
MySQL的不同之处 多数DBMS使用+或||来实现拼接,MySQL则使用Concat()函数来实现。当把SQL语句转换成
MySQL语句时一定要把这个区别铭记在心。
SELECT Concat(vend_name,'(',vend_country,')')
FROM vendors
ORDER BY vend_name
分析:Concat()拼接串,即把多个串连接起来形成一个较长的串。Concat()需要一个或多个指定的串,各个串之间用逗号分隔。上面的SELECT语句连接以下4个元素:
(1)存储在vend_name列中的名字;
(2)包含一个空格和一个左圆括号的串;
(3)存储在vend_country列中的国家;
(4)包含一个右圆括号的串
从上述输出中可以看到, SELECT语句返回包含上述4个元素的单个列(计算字段)。
在前面曾提到通过删除数据右侧多余的空格来整理数据,这可以使用MySQL的RTrim()函数来完成,如下所示:
SELECT Concat(RTrim(vend_name),'(',RTrim(vend_country),')')
FROM vendors
ORDER BY vend_name;
分析:RTrim()函数去掉值右边的所有空格。通过使用RTrim(),各个列都进行了整理。
Trim函数:MySQL除了支持RTrim()(正如刚才所见,它去掉串右边的空格),还支持LTrim()(去掉串左边的空格)以及Trim()(去掉串左右两边的空格)。

使用别名

从前面的输出中可以看到, SELECT语句拼接地址字段工作得很好。但此新计算列的名字是什么呢?实际上它没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。为了解决这个问题, SQL支持列别名。 别名( alias) 是一个字段或值的替换名。别名用AS关键字赋予。请看下面的SELECT语句:
SELECT Concat(RTrim(vend_name),'(',RTrim(vend_country),')') AS vend_title
FROM vendors 
ORDER BY vend_name;
分析:SELECT语句本身与以前使用的相同,只不过这里的语句中计算字段之后跟了文本AS vend_title。它指示SQL创建一个包含指定计算的名为vend_title的计算字段。从输出中可以看到,结果与以前的相同,但现在列名为vend_title,任何客户机应用都可以按名引用这个列,就像它是一个实际的表列一样。
别名的其他用途:别名还有其他用途。常见的用途包括在实际的表列名包含不符合规定的字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它,等等。
导出列:别名有时也称为导出列( derived column),不管称为什么,它们所代表的都是相同的东西。

3.执行算术计算

计算字段的另一常见用途是对检索出的数据进行算术计算。举一个例子, orders表包含收到的所有订单, orderitems表包含每个订单中的各项物品。下面的SQL语句检索订单号20005中的所有物品:
SELECT prod_id,quantity,item_price
FROM orderitems
WHERE order_num=20005;
item_price列包含订单中每项物品的单价。如下汇总物品的价格(单价乘以订购数量):
SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price
FROM orderitems
WHERE order_num=20005;
输出中显示的expanded_price列为一个计算字段,此计算为quantity*item_price。客户机应用现在可以使用这个新计算列,就像使用其他列一样。

在这里插入图片描述

如何测试计算 SELECT提供了测试和试验函数与计算的一个很好的办法。虽然SELECT通常用来从表中检索数据,但可以省略FROM子句以便简单地访问和处理表达式。例如, SELECT 3*2;将返回6, SELECT Trim('abc');将返回abc,而SELECT Now()利用Now()函数返回当前日期和时间。通过这些例子,
可以明白如何根据需要使用SELECT进行试验。
使用数据处理函数

1.函数

函数没有SQL的可移植性强:能运行在多个系统上的代码称为可移植的( portable)。相对来说,多数SQL语句是可移植的,在SQL实现之间有差异时,这些差异通常不那么难处理。而函数的可移植性却不强。几乎每种主要的DBMS的实现都支持其他实现不支持的函数,而且有时差异还很大。为了代码的可移植,许多SQL程序员不赞成使用特殊实现的功能。虽然这样做很有好处,但不总是利于应用程序的性能。如果不使用这些函数,编写某些应用程序代码会很艰难。必须利用其他方法来实现DBMS非常有效地完成的工作。如果你决定使用函数,应该保证做好代码注释,以便以后你(或其他人)能确切地知道所编写SQL代码的含义。

2.使用函数

大多数SQL实现支持以下类型的函数。
(1)用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数
(2)用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
(3)用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
(4)返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。

文本处理函数

SELECT vend_name,Upper(vend_name) AS vend_name_upcase
FROM vendors
ORDER BY vend_name;
正如所见, Upper()将文本转换为大写,因此本例子中每个供应商都列出两次,第一次为vendors表中存储的值,第二次作为列vend_name_upcase转换为大写。

在这里插入图片描述

表中的SOUNDEX需要做进一步的解释。 SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。虽然SOUNDEX不是SQL概念,但MySQL(就像多数DBMS一样)都提供对SOUNDEX的支持。
下面给出一个使用Soundex()函数的例子。 customers表中有一个顾客Coyote Inc.,其联系名为Y.Lee。但如果这是输入错误,此联系名实际应该是Y.Lie,怎么办?显然,按正确的联系名搜索不会返回数据,如下所示:
SELECT cust_name,cust_contact
FROM customers
WHERE cust_contact ='Y.Lie';
现在试一下使用Soundex()函数进行搜索,它匹配所有发音类似于
Y.Lie的联系名:
SELECT cust_name,cust_contact
FROM customers
WHERE Soundex(cust_contact) =Soundex(Y'Lie');
在这个例子中, WHERE子句使用Soundex()函数来转换cust_contact列值和搜索串为它们的SOUNDEX值。因为Y.Lee和Y.Lie发音相似,所以它们的SOUNDEX值匹配,因此WHERE子句正确地过滤出了所需的数据。

日期和时间处理函数

日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。
一般,应用程序不使用用来存储日期和时间的格式,因此日期和时间函数总是被用来读取、统计和处理这些值。由于这个原因,日期和时间函数在MySQL语言中具有重要的作用。

在这里插入图片描述

应该总是使用4位数字的年份 支持2位数字的年份, MySQL处理00-69为2000-2069,处理70-99为1970-1999。虽然它们可能是打算要的年份,但使用完整的4位数字年份更可靠,因为MySQL不必做出任何假定。
SELECT cust_id,order_num
FROM orders
WHERE order_date='2005-9-01';
分析:此SELECT语句正常运行。它检索出一个订单记录,该订单记录
的order_date为2005-09-01。
但是,使用WHERE order_date = '2005-09-01'可靠吗? order_date的数据类型为datetime。这种类型存储日期及时间值。样例表中的值全都具有时间值00:00:00,但实际中很可能并不总是这样。如果用当前日期和时间存储订单日期(因此你不仅知道订单日期,还知道下 订 单 当 天 的 时 间 ), 怎 么 办 ? 比 如 , 存 储 的 order_date 值 为2005-09-01 11:30:05,则WHERE order_date = '2005-09-01'失败。即使给出具有该日期的一行,也不会把它检索出来,因为WHERE匹配失败。解决办法是指示MySQL仅将给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。为此,必须使用Date()函数。Date(order_date)指示MySQL仅提取列的日期部分,更可靠的SELECT语句为:
SELECT cust_id,order_num
FROM orders
WHERE Date(order_date)='2005-09-01';
如果要的是日期,请使用Date():如果你想要的仅是日期,则使用Date()是一个良好的习惯,即使你知道相应的列只包含日期也是如此。这样,如果由于某种原因表中以后有日期和时间值,你的SQL代码也不用改变。当然,也存在一个Time()函数,在你只想要时间时应该使用它。
Date()和Time()都是在MySQL 4.1.1中第一次引入的。
在你知道了如何用日期进行相等测试后,其他操作符的使用也就很清楚了。不过,还有一种日期比较需要说明。如果你想检索出2005年9月下的所有订单,怎么办?简单的相等测试不行,因为它也要匹配月份中的天
数。有几种解决办法,其中之一如下所示:
SELECT cust_id,order_num
FROM orders
WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';
其中, BETWEEN操作符用来把2005-09-01和2005-09-30定义为一个要匹配的日期范围。还有另外一种办法(一种不需要记住每个月中有多少天或不需要操心闰年2月的办法):
SELECT cust_id,order_num
FROM orders
WHERE Year(order_date)=2005 AND Month(order_date)=9;
分析:Year()是一个从日期(或日期时间)中返回年份的函数。类似,Month()从日期中返回月份。因此, WHERE Year(order_date)= 2005 AND Month(order_date) = 9检索出order_date为2005年9月的
所有行。
MySQL的版本差异 MySQL 4.1.1中增加了许多日期和时间函数。如果你使用的是更早的MySQL版本,应该查阅具体的文档以确定可以使用哪些函数。

数值处理函数

数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此没有串或日期—时间处理函数的使用那么频繁。
具有讽刺意味的是,在主要DBMS的函数中,数值函数是最一致最统一的函数。表中列出一些常用的数值处理函数。

在这里插入图片描述

汇总数据

1.聚集函数

我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数。使用这些函数, MySQL查询可用于检索数据,以便分析和报表生成。这种类型的检索例子有以下几种。
 (1)确定表中行数(或者满足某个条件或包含某个特定值的行数)。
(2)获得表中行组的和。
(3)找出表列(或所有行或某些特定的行)的最大值、最小值和平均值。
聚集函数( aggregate function) 运行在行组上,计算和返回单个值的函数。

在这里插入图片描述
AVG()函数

AVG()通过对表中行数计数并计算特定列值之和,求得该列的平均值。 AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。
SELECT AVG(prod_price) AS avg_price
FROM  products;
分析:此SELECT语句返回值avg_Price,它包含products表中所有产品的平均价格。如第10章所述, avg_price是一个别名。
AVG()也可以用来确定特定列或行的平均值。 下面的例子返回特定供应商所提供产品的平均价格:
SELECT AVG(prod_price) AS avg_price
FROM products
WHERE vend_id=1003;
分析:这条SELECT语句与前一条的不同之处在于它包含了WHERE子句。此WHERE子句仅过滤出vend_id为1003的产品,因此avg_price中返回的值只是该供应商的产品的平均值。
只用于单个列:AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。
NULL值:AVG()函数忽略列值为NULL的行。

COUNT()函数

COUNT()函数进行计数。 可利用COUNT()确定表中行的数目或符合特
定条件的行的数目。
COUNT()函数有两种使用方式。
(1)使用COUNT(*)对表中行的数目进行计数, 不管表列中包含的是空值( NULL)还是非空值。
(2)使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。
SELECT COUNT(*) AS num_cust
FROM customers;
分析:在此例子中,利用COUNT(*)对所有行计数,不管行中各列有什么值。计数值在num_cust中返回。
SELECT COUNT(cust_email) AS num_cust
FROM customers;
分析:这条SELECT语句使用COUNT(cust_email)对cust_email列中有值的行进行计数。 在此例子中, cust_email的计数为3(表示5个客户中只有3个客户有电子邮件地址)。
NULL值 如果指定列名,则指定列的值为空的行被COUNT()函数忽略,但如果COUNT()函数中用的是星号( *),则不忽略。

MAX()函数

MAX()返回指定列中的最大值。 MAX()要求指定列名,如下所示:
SELECT MAX(pro_price) AS max_price
FROM products;
分析:这里, MAX()返回products表中最贵的物品的价格。
对非数值数据使用MAX():虽然MAX()一般用来找出最大的数值或日期值,但MySQL允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,如果数据按相应的列排序,则MAX()返回最后一行。
NULL值:MAX()函数忽略列值为NULL的行。

MIN()函数

MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。与MAX()一样, MIN()要求指定列名,如下所示:
SELECT MIN(prod_price) AS min_price
FROM products;
分析:其中MIN()返回products表中最便宜物品的价格。
对非数值数据使用MIN() MIN()函数与MAX()函数类似,MySQL允许将它用来返回任意列中的最小值,包括返回文本列中的最小值。在用于文本数据时,如果数据按相应的列排序,则MIN()返回最前面的行。
NULL值:MIN()函数忽略列值为NULL的行。

SUM()函数

SUM()用来返回指定列值的和(总计)。下面举一个例子, orderitems表包含订单中实际的物品,每个物品
有相应的数量( quantity) 。可如下检索所订购物品的总数(所有quantity值之和):
SELECT SUM(quantity) AS items_ordered
FROM orderitems
WHERE order_num=20005;
分析:函数SUM(quantity)返回订单中所有物品数量之和, WHERE子句保证只统计某个物品订单中的物品。
SUM()也可以用来合计计算值。在下面的例子中,合计每项物品的item_price*quantity,得出总的订单金额:
SELECT SUM(item_price*quantity) AS total_price
FROM orderitems
WHERE order_num=20005;
分析:函数SUM(item_price*quantity)返回订单中所有物品价钱之和, WHERE子句同样保证只统计某个物品订单中的物品。
在多个列上进行计算:如本例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。
NULL值:SUM()函数忽略列值为NULL的行

2.聚集不同值

MySQL 5 及后期版本 :下面将要介绍的聚集函数的DISTINCT的使用,已经被添加到MySQL 5.0.3中。下面所述内容在MySQL 4.x中不能正常运行。
ALL为默认 ALL参数不需要指定,因为它是默认行为。如果
不指定DISTINCT,则假定为ALL。
以上5个聚集函数都可以如下使用:
(1)对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为);
(2)只包含不同的值,指定DISTINCT参数。
下面的例子使用AVG()函数返回特定供应商提供的产品的平均价格。它与上面的SELECT语句相同,但使用了DISTINCT参数,因此平均值只考虑各个不同的价格:
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM products
WHERE vend_id =1003;
分析:可以看到,在使用了DISTINCT后,此例子中的avg_price比较高,因为有多个物品具有相同的较低价格。排除它们提升了平均价格。
注意:如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*),因此不允许使用COUNT( DISTINCT),否则会产生错误。类似地, DISTINCT必须使用列名,不能用于计算或表达式。
将DISTINCT用于MIN()和MAX():虽然DISTINCT从技术上可用于MIN()和MAX(),但这样做实际上没有价值。一个列中的最小值和最大值不管是否包含不同值都是相同的。

3.组合聚集函数

目前为止的所有聚集函数例子都只涉及单个函数。但实际上SELECT语句可根据需要包含多个聚集函数。请看下面的例子:
SELECT COUNT(*) AS num_items,
	   MIN(prod_price) AS price_min,
	   MAX(prod_price) AS price_max,
	   AVG(prod_price) AS price_avg
	   FROM products;
分析:这里用单条SELECT语句执行了4个聚集计算,返回4个值(products表中物品的数目,产品价格的最高、最低以及平均值)。
取别名 在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。虽然这样做并非不合法,但使用唯一的名字会使你的SQL更易于理解和使用(以及将来容易排除故障)。
分组数据

1.数据分组

SELECT COUNT(*AS num_prods
FROM products
WHERE vend_id=1003;

2.创建分组
在这里插入图片描述

分析:上面的SELECT语句指定了两个列, vend_id包含产品供应商的ID,num_prods为计算字段(用COUNT(*)函数建立)。 GROUP BY子句指示MySQL按vend_id排序并分组数据。这导致对每个vend_id而不是整个表计算num_prods一次。从输出中可以看到,供应商1001有3个产品,供应商1002有2个产品,供应商1003有7个产品,而供应商1005有2个产品。因为使用了GROUP BY,就不必指定要计算和估值的每个组了。系统会自动完成。 GROUP BY子句指示MySQL分组数据,然后对每个组而不是整个结果集进行聚集。
在具体使用GROUP BY子句前,需要知道一些重要的规定。
(1)GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套,
为数据分组提供更细致的控制。
(2)如果在GROUP BY子句中嵌套了分组,数据将在最后规定的分组上
进行汇总。换句话说,在建立分组时,指定的所有列都一起计算
(所以不能从个别的列取回数据)。
(3)GROUP BY子句中列出的每个列都必须是检索列或有效的表达式
(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在
GROUP BY子句中指定相同的表达式。不能使用别名。
 除聚集计算语句外, SELECT语句中的每个列都必须在GROUP BY子
句中给出。
(4)如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列
中有多行NULL值,它们将分为一组。
(5)GROUP BY子句必须出现在WHERE子句之后, ORDER BY子句之前。
使用ROLLUP 使用WITH ROLLUP关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值,如下所示:
SELECT vend_id,COUNT(*) AS num_prods
FROM products
GROUP BY vend_id WITH ROLLUP;

3.过滤分组
在这里插入图片描述

分析:这条SELECT语句的前3行类似于上面的语句。最后一行增加了
HAVING子句,它过滤COUNT(*) >=2(两个以上的订单)的那些
分组。
正如所见,这里WHERE子句不起作用,因为过滤是基于分组聚集值而
不是特定行值的。
HAVING和WHERE的差别 这里有另一种理解方法,WHERE在数据分组前进行过滤, HAVING在数据分组后进行过滤。这是一个重要的区别, WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。

在这里插入图片描述

分析:这条语句中,第一行是使用了聚集函数的基本SELECT,它与前面的例子很相像。 WHERE子句过滤所有prod_price至少为10的行。然后按vend_id分组数据, HAVING子句过滤计数为2或2以上的分组。如果没有WHERE子句,将会多检索出两行(供应商1002,销售的所有产品价格都在10以下;供应商1001,销售3个产品,但只有一个产品的价格大于等于10):

在这里插入图片描述

4.分组和排序
5.SELECT子句顺序

使用子查询
连接表
创建高级联结
组合查询
全文本搜索
插入数据
更新和删除数据
创建和操纵表
使用视图
使用存储过程
使用游标
使用触发器
管理事务处理
全球化和本地化
安全管理
数据库维护
改善性能
事务

(1)事务概念
事务:指的是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么全都成功,要么全都失败。
(2)事务特性
原子性:事务的不可分割,组成事务的各个逻辑单元不可分割。
一致性:事务执行的前后,数据完整性保持一致。
隔离性:事务执行不应该受到其他事务的干扰。
持久性:事务一旦结束,数据就持久化到数据库中。

1.8 索引
INNODB存储引擎
MyISAM存储引擎
MEMORY存储引擎
SQL优化整合
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。
2、应尽量避免在where子句中对字段进行null值判断,创建表时NULL是默认值,但大多数时候应该使用NOT NULL,或者使用一个特殊的值,如0,-1作为默认值。
3、应尽量避免在where子句中使用!=<>操作符,MySQL只有对以下操作符才使用索引:<<==>>=,BETWEEN,IN,以及某些时候的LIKE。
4、应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,可以使用UNION合并查询:select id from t where num=10 union all select id from t where num=205、in和not in也要慎用,否则会导致全表扫描,对于连续的数值,能用between就不要用in了:Select id from t where num between 1 and 36、下面的查询也将导致全表扫描:select id from t where name like‘%abc%’或者select id from t where name like‘%abc’若要提高效率,可以考虑全文检索。而select id from t where name like‘abc%’才用到索引。
7、如果在where子句中使用参数,也会导致全表扫描。
8、应尽量避免在where子句中对字段进行表达式操作,应尽量避免在where子句中对字段进行函数操作。
9、很多时候用exists代替in是一个好的选择:select num from a where num in(select num from b)。用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)10、索引固然可以提高相应的select的效率,但同时也降低了insert及update的效率,因为insert或update时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。关于索引可以关注公众号Java技术栈搜索阅读更多详细教程。
11、应尽可能的避免更新clustered索引数据列, 因为clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新clustered索引数据列,那么需要考虑是否应将该索引建为clustered索引。
12、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
13、尽可能的使用varchar/nvarchar代替char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
14、最好不要使用”“返回所有:select from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
15、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
16、使用表的别名(Alias):当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。
17、使用“临时表”暂存中间结果 :
简化SQL语句的重要方法就是采用临时表暂存中间结果,但是临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。
18、一些SQL查询语句应加上nolock,读、写是会相互阻塞的,为了提高并发性能,对于一些查询,可以加上nolock,这样读的时候可以允许写,但缺点是可能读到未提交的脏数据。
使用nolock有3条原则:
查询的结果用于“插、删、改”的不能加nolock;
查询的表属于频繁发生页分裂的,慎用nolock ;
使用临时表一样可以保存“数据前影”,起到类似Oracle的undo表空间的功能,能采用临时表提高并发性能的,不要用nolock。
19、常见的简化规则如下:
不要有超过5个以上的表连接(JOIN),考虑使用临时表或表变量存放中间结果。少用子查询,视图嵌套不要过深,一般视图嵌套不要超过2个为宜。
20、将需要查询的结果预先计算好放在表中,查询的时候再Select。这在SQL7.0以前是最重要的手段,例如医院的住院费计算。
21、用OR的字句可以分解成多个查询,并且通过UNION 连接多个查询。他们的速度只同是否使用索引有关,如果查询需要用到联合索引,用UNION all执行的效率更高。多个OR的字句没有用到索引,改写成UNION的形式再试图与索引匹配。一个关键的问题是否用到索引。
22、在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。
23、尽量将数据的处理工作放在服务器上,减少网络的开销,如使用存储过程。
存储过程是编译好、优化过、并且被组织到一个执行规划里、且存储在数据库中的SQL语句,是控制流语言的集合,速度当然快。反复执行的动态SQL,可以使用临时存储过程,该过程(临时表)被放在Tempdb中。
24、当服务器的内存够多时,配制线程数量 = 最大连接数+5,这样能发挥最大的效率;否则使用 配制线程数量<最大连接数启用SQL SERVER的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。
索引设计原则:
1)搜索的索引列不一定是所要选择的列,换句话来说就是最适合的索引列是出现在WHERE子句中的列,或连接子句中指定的列,而不是出现在SELECT关键字后的选择列表中的列。
2)使用唯一索引。考虑某列中值的分布, 索引的列的基数越大,索引的效果越好。例如,存放出生日期的列具有不同值,很容易区分各行。而用来记录性别的列,只含有“ M”和“F”,则对此列进行索引没有多大用处,因为不管搜索哪个值,都会得出大约一半的行。
3) 使用短索引。如果对字符串列进行索引,应该指定一个前缀长度,只要有可能就应该这样做,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
4) 利用最左前缀。在创建一个 n 列的索引时,实际是创建了 MySQL 可利用的 n 个索引。多列索引可起几个索引的作用,因为可利用索引中最左边的列集来匹配行。这样的列集称为最左前缀。
5)不要过度使用索引,因为每个额外的索引都要占用额外的磁盘空间,并减低写操作的性能。 在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,索引越多,所花的时间越长。如果有一个索引很少利用或从不使用,那么会不必要地减缓表的修改速度。此外,MySQL 在生成一个执行计划时,要考虑各个索引,这也要花费时间。创建多余的索引给查询优化带来了更多的工作。索引太多,也可能会使 MySQL 选择不到所要使用的最好索引。只保持所需的索引有利于查询优化。
6) 对于 InnoDB 存储引擎的表,记录默认会按照一定的顺序保存,如果有明确定义的主键,则按照主键顺序保存。 如果没有主键,但是有唯一索引,那么就是按照唯一索引的顺序保存。如果既没有主键又没有唯一索引,那么表中会自动生成一个内部列,按照这个列的顺序保存。按照主键或者内部列进行的访问是最快的,所以 InnoDB 表尽量自己指定主键,当表中同时有几个列都是唯一的,都可以作为主键的时候,要选择最常作为访问条件的列作为主键,提高查询的效率。另外,还需要注意,InnoDB 表的普通索引都会保存主键的键值,所以主键要尽可能选择较短的数据类型,可以有效地减少索引的磁盘用,提高索引的缓存效果。
7)定义有外键的数据列一定要建立索引。
8)基数较小的表,索引效果较差,没有必要在此列建立索引。
9)更新频繁字段不适合创建索引。
10)如果不能有效区分数据的列不适合做索引列(如性别,男女未知,涉及到的情况最多三种,区分度实在太低了)。
11)尽量的扩展索引,不要新建索引,比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
12)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
13)对于定义为text,image和bit的数据类型的列不要建立索引。

2.Oracle

3.Redis

4. MongoDB

5.Kafka

(六)操作系统

1.进程,线程

2.进程/线程间通讯方式

3.进程调度算法

4.进程/线程状态

5.死锁

6.内存管理

7.Linux

(1)相关知识点

1.Linux系统安装
2.环境变量
3.文件管理
4.用户管理
5.内存管理
6.磁盘管理
7.进程管理
8.网络管理
9.软件包管理
10.服务管理
11.日志管理
12.linux内核
13.常用命令
14.常用环境搭建
15.shell脚本编程
16.VM的使用

(七)其他技术

其他面试题

java后台如何调用别人的接口的
1.openfeign
首先openfeign为微服务框架下服务之间的调用提供了解决饭方案,openfeign是一种声明式,模板化的HTTP客户端。在springcloud使用openfegin可以做到使用HTTP请求访问远程服务,就像调用本地方法一样
使用openfegin可以用来简化HHTP的调用
2.rpc
3.Http协议
java如何写接口让别人调用的

总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值