文章目录
- 第11章 多线程
- 第12章 IO流
- 第13章 网络编程
- 第14章 反射
- 第15章 JDBC 和数据库连接
- 第16章 正则表达式
第11章 多线程
多线程基础
1、线程相关概念
1.1程序(program)?
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码
1.2进程
1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
1.3什么是线程
1.线程由进程创建的,是进程的一个实体。
2.一个进程可以拥有多个线程。
1.4 其他相关概念
1.单线程:同一个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
3.并发:同一个时刻,多个任务/指令在单个cpu上交替执行
,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
4.并行:同一个时刻,多个任务/指令在多个cpu同时执行
。可以说是并发和并行。
2、线程基本使用
2.1创建线程的两种方式
提示:可以使用“jconsole”命令查看进程的运行状况
2.2线程应用案例1-继承Thread类
package hsp.thread_;
//通过继承 Thread 类创建线程
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建 Cat 对象,可以当做线程使用
Cat cat = new Cat();
cat.start();//启动线程->最终会执行cat的run方法
//cat.run();//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
//说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
//下面主线程和子线程是交替执行
for (int i = 1; i <= 60; i++) {
System.out.println("我是主线程-->"+i+"\t线程名:"+Thread.currentThread().getName());
Thread.sleep(1000);
}
/*源码:
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
//真正实现多线程的效果, 是 start0(), 而不是 run
private native void start0()
*/
}
}
class Cat extends Thread {
private int count = 0;
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
/*源码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
@SuppressWarnings({"all"})
@Override
public void run() {
while (true){
System.out.println("我是自定义线程-->"+(++count)+"\t线程名:"+Thread.currentThread().getName());
try {
//线程休眠1s,ctrl+alt+t
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (this.count==30){ //十次后让线程退出
break;
}
}
}
}
2.3线程应用案例2-实现Runnable接口
package hsp.thread_;
//通过实现接口Runnable来开发线程
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用 start
//创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
private int times = 0;
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
System.out.println("Runnable线程"+(++times)+"\t"+Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times==6){
break;
}
}
}
}
2.4 线程使用应用案例-多线程执行
package hsp.thread_;
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
new Thread(t1).start();//启动第 1 个线程
new Thread(t2).start();//启动第 1 个线程
}
}
class T1 implements Runnable{
private int cur = 0;
@Override
public void run() {
while (true){
System.out.println("T1线程输出:hi"+(++cur)+"\t"+"线程名:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(cur==15){
break;
}
}
}
}
class T2 implements Runnable{
private int cur = 0;
@Override
public void run() {
while (true){
System.out.println("T2线程输出:say"+(++cur)+"\t"+"线程名:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(cur==10){
break;
}
}
}
}
3、 继承Thread vs实现Runnable的区别
1)从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用实现Runnable接口方式
4、线程终止
4.1基本说明
1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
package hsp.thread_;
public class ThreadStop {
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
person.start();
//终止
//希望在主线程控制 Person 线程的终止,去修改loop即可
//让期退出run()方法,从而达到终止的目的---->通知方式
System.out.println("main线程休眠10s...");
Thread.sleep(10 * 1000);
person.exit(false);
}
}
class Person extends Thread {
private int count = 0;
private boolean loop = true;//控制线程变量
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println((++count) + "Person运行中...");
}
}
public void exit(boolean b) {
this.loop = b;
}
}
5、线程常用方法
5.1 常用方法第一组
1.setName
//设置线程名称,使之与参数name相同
2. getName
//返回该线程的名称
3. start
//使该线程开始执行;Java虚拟机底层调用该线程的start0()方法
4.run
//调用线程对象 run方法;
4. setPriority
//更改线程的优先级(1-10)
6. getPriority
//获取线程的优先级
7.sleep
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8.interrupt
//中断线程
5.2 注意事项和细节
1.start底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
2.线程优先级的范围
3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4.sleep:线程的静态方法,使当前线程休眠
package hsp.thread_;
class ThreadMethod extends Thread {
public static void main(String[] args) throws InterruptedException {
T td = new T();
td.setName("老大");
td.setPriority(Thread.MIN_PRIORITY);
td.start();//启动子线程
//主线程开始输出后,就中断子线程
for (int i = 1; i <= 5; i++) {
Thread.sleep(1000);
System.out.println("主线程开始做事情~~~"+i);
}
td.interrupt();//中断T的休眠
System.out.println("默认优先级=" + Thread.currentThread().getPriority());//测试interrupt
}
}
class T extends Thread { //自定义线程类
@Override
public void run() {
while (true){
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在做事情~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中~~~");
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
//当该线程执行到一个interrupt方法时,就会catch一个 异常,可以加入自己的业务代码
System.out.println(Thread.currentThread().getName() + "被interrupt了");
break;
}
}
}
}
5.4 常用方法第二组
package hsp.thread_;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
Second second = new Second();
second.start();
//主线程
for (int i = 1; i <= 20; i++) {
Thread.sleep(500);
System.out.println("main线程正在做事情~~~~" + i);
if (i == 5) {
System.out.println("======到第5次主线程暂停,让给子线程======");
second.join();//线程插队,相当于让second先执行完毕
// Thread.yield();//线程礼让,不一定礼让成功
System.out.println("======子线程完事,主线程继续======");
}
}
}
}
class Second extends Thread { //自定义线程类
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程正在做事情~~~~" + i);
}
}
}
package hsp.thread_;
public class ThreadMethod04 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new TT());
for (int i = 1; i <= 10; i++) {
System.out.println("hi"+i);
Thread.sleep(500);
if(i==5){
t.start();//启动子线程
t.join();
}
}
}
}
class TT implements Runnable{
private int count = 0;
@Override
public void run() {
while (true){
System.out.println("hello"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(count==10){
break;
}
}
}
}
6、用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程(备胎线程):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制
package hsp.thread_;
public class ThreadDaemon {
public static void main(String[] args) throws InterruptedException {
MyGirlFriend myGirlFriend = new MyGirlFriend();
//当main线程结束后,子线程自动结束,只需要将子线程设置为守护线程
myGirlFriend.setDaemon(true);
myGirlFriend.start();
for (int i = 1; i < 11; i++) {
System.out.println("main线程在持续工作"+i+".....");
Thread.sleep(600);
}
}
}
class MyGirlFriend extends Thread{
@Override
public void run() {
for (;;){//无线循环
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程在持续工作....");
}
}
}
7、线程的生命周期
JDK 中用 Thread.State 枚举表示了线程的几种状态
线程状态转换图(重要)
package hsp.thread_;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
State state = new State();
Thread thread = new Thread(state);
System.out.println(thread.getName()+"状态:"+thread.getState());
thread.start();
while (Thread.State.TERMINATED != thread.getState()){
System.out.println(thread.getName()+"状态:"+thread.getState());
Thread.sleep(800);
}
System.out.println(thread.getName()+"状态:"+thread.getState());
}
}
class State implements Runnable{
@Override
public void run() {
while (true){
for (int i = 0; i < 11; i++) {
System.out.println("ho"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
break;
}
}
}
8、 线程的同步(Synchronized)、线程安全
8.1 线程同步机制
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
8.2 同步具体方法-Synchronized
1.同步代码块
synchronized(对象){/得到对象的锁,才能操作同步代码
//需要被同步代码;
}
2.synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
3.如何理解:
就好像上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了
4.使用synchronized解决售票问题
package hsp.thread_;
public class SellTicketSynchronized {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第 1 个线程-窗口
new Thread(sellTicket03).start();//第 2个线程-窗口
new Thread(sellTicket03).start();//第 3 个线程-窗口
}
}
//实现接口方式,使用Synchronized实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;
public synchronized void sell() { //同步方法,同一时刻,只能有一个线程来执行,锁起来
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() +
" 售出一张票" + "" + " 剩余票数=" + (--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
9、互斥锁
1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized
来与对象的互斥锁联系。当某个对象用synchronized修饰时,
表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法((非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身。
package hsp.thread_;
public class SellTicketLock {
public static void main(String[] args) {
SellTicket04 sellTicket04 = new SellTicket04();
new Thread(sellTicket04).start();//第 1 个线程-窗口
new Thread(sellTicket04).start();//第 2个线程-窗口
new Thread(sellTicket04).start();//第 3 个线程-窗口
}
}
//实现接口方式,使用Synchronized实现线程同步
class SellTicket04 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;
Object obj = new Object();
//同步方法(静态的)的锁为当前对象本身
//1.public synchronized static void m1(){} 锁是加在SellTicket0.class
//2.如果在静态方法中,实现一个同步代码块
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket04.class) {
System.out.println("m2");
}
}
//1.public synchronized void sell(){} 就是一个同步方法
//2.也可以使用代码块写synchronized,同步代码块,互斥锁还是在this对象
public /*synchronized*/ void sell() { //同步方法,同一时刻,只能有一个线程来执行
synchronized (/*this*/ obj) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() +
" 售出一张票" + "" + " 剩余票数=" + (--ticketNum));
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
注意事项和细节
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
·需要先分析上锁的代码
·选择同步代码块或同步方法
·要求多个线程的锁对象为同一个即可
10、线程的死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
package hsp.thread_;
public class ThreadLock01 {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}
11、释放锁
11.1下面操作会释放锁
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return。
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
11.2下面操作不会释放锁
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
12、本章巩固练习
package hsp.thread_.lianxi;
import java.util.Scanner;
/*
* (1)在main方法中启动两个线程
* (2)第1个线程循环随机打印100以内的整数(3)直到第2个线程从键盘读取了“Q”命令。
* */
public class Test01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);//注意传入a对象
a.start();
b.start();
}
}
class A extends Thread{
private boolean loop = true;
@Override
public void run() {
while (loop){
System.out.println((int)(Math.random()*100+1));
try {
Thread.sleep(600);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
class B extends Thread{
private A a;
private Scanner scanner = new Scanner(System.in);
public B(A a){//构造器直接传入A对象
this.a = a;
}
@Override
public void run() {
//
while (true){
System.out.println("请输入你的指令(Q)才可以退出!!");
char key = scanner.next().toUpperCase().charAt(0);
if(key=='Q'){
//以通知的方法结束线程A
a.setLoop(false);
System.out.println("b线程退出~~~");
break;
}
}
}
}
```java
package hsp.thread_.lianxi;
/*
* (1)有2个用户分别从同一个卡上取钱(总额:10000
* (2)每次都取1000,当余额不足时,就不能取款了
* (3)不能出现超取现象=》线程同步问题.
*/
public class Test02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(t);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
class T implements Runnable{
private int money =10000;
@Override
public void run() {
while (true){
//使用synchronized,实现线程同步,谁在此刻争夺到this对象锁,谁就去执行,另一个则就阻塞等待,
// 执行完了会释放this对象锁,两个准备继续夺到锁
synchronized (this){
if(money<1000){
System.out.println("余额不足");
break;
}
money-=1000;
System.out.println(Thread.currentThread().getName()+"取出来1000, 当前余额:"+money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
第12章 IO流
1、文件
1.1什么是文件
文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件…都是文件。它既可以保存一张图片,也可以保持视频,声音.…
1.2 文件流
2 常用的文件操作
2.1 创建文件对象相关构造器和方法
相关方法:
new File(String pathname)
//根据路径构建一个File对象
new File(File parent,String child)
//根据父目录文件+子路径构
package hsp.file;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
public class FileCreate {
public static void main(String[] args) {
}
//new File(String pathname)//根据路径构建一个File对象
//new File(File parent,String child)//根据父目录文件+子路径构建
//方式 2
@Test
public void create01() {
String filePath = "E:\\new01.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件1创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//方式 2
@Test
public void create02(){
String parentPath = "e:\\";
String fileName ="new02.txt";
//这里的 file 对象,在 java 程序中,只是一个对象
//只有执行了 createNewFile 方法,才会真正的,在磁盘创建该文件
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("文件2创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2.2 获取文件的相关信息
getName、getAbsolutePath、getParent、 length、exists、isFile、isDirectory
如何获取到文件的大小,文件名,路径,父File,是文件还是目录(自录本质也文件,一种特殊的文件).是否存在.
package hsp.file;
import org.testng.annotations.Test;
import java.io.File;
public class FileGetInfo {
public static void main(String[] args) {
}
//获取文件信息
@Test
public void fileInformation (){
//先创建文件对象
File file = new File("e:\\new01.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//F
}
}
2.3目录的操作和文件删除
mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件
package hsp.file;
import org.testng.annotations.Test;
import java.io.File;
public class FileMkdir {
public static void main(String[] args) {
}
//判断文件是否存在,并删除
@Test
public void m1() {
String filePath = "e:\\new02.txt";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println(filePath + "文件不存在");
}
}
//判断文件目录是否存在,并删除
//java中,目录也当成文件
@Test
public void m2() {
String filePath = "e:\\demo";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println(filePath + "目录不存在");
}
}
@Test
public void m3() {
String filePath = "e:\\demo\\a\\b\\c";
File file = new File(filePath);
if (file.exists()) {
System.out.println(filePath + "目录存在");
} else {
//创建一级目录使用mkdirs(),创建多级目录使用mkdirs()
if (file.mkdirs()){
System.out.println(filePath + "目录创建成功");
}else {
System.out.println(filePath + "目录创建失败");
}
}
}
}
3、 IO流原理及流的分类
3.1 Java IO流原理
1.I/O是lnput/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输.
如读/写文件,网络通讯等。
2.Java程序中,对于数据的输入/输出操作以”流(stream)”的方式进行。
3. java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
4.输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
5.输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
3.2 流的分类
4、IO 流体系图-常用的类
IO 流体系图
4.1 FileInputStream
package hsp.io_;
import org.testng.annotations.Test;
import java.io.FileInputStream;
import java.io.IOException;
//FileInputStream 的使用(字节输入流 文件--> 程序)
public class FileInputStream01 {
public static void main(String[] args) {
}
/*
* 单个字节的读取,效率低
* */
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char)readData);//转成 char 显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 使用 read(byte[] b) 读取文件,提高效率
* */
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
int readData = 0;
//字节数组
byte[] buf = new byte[8]; //一次读取 8 个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多 b.length 字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2 FileOutputStream
package hsp.io_;
import org.testng.annotations.Test;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream01 {
public static void main(String[] args) {
}
@Test
public void writeFile() {
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream 对象
//说明
//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
//fileOutputStream = new FileOutputStream(filePath);
fileOutputStream = new FileOutputStream(filePath ,true);
//写入一个字节
//fileOutputStream.write('H');
//写入字符串
String str = "hello,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
//write(byte[] b, int off, int len) 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流
fileOutputStream.write(str.getBytes(), 0, 5);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
实现文件拷贝
package hsp.io_;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
//1. 创建文件的输入流 , 将文件读入到程序
//2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件
String secFilePath = "e:\\picture.jfif";
String destFilePath = "e:\\picture_copy.jfif";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(secFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
//定义一个字节数组,提高读取效果
byte[] buf = new byte[1024];
int readLength = 0;
while ((readLength = fileInputStream.read(buf))!=-1){
//读取到后,就写入到文件 通过 fileOutputStream
//即,是一边读,一边写
fileOutputStream.write(buf,0,readLength);// 一定要使用这个方法
}
System.out.println("拷贝 ok~");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileInputStream != null){
fileInputStream.close();
}
if(fileOutputStream != null){
fileOutputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5、FileReader 和 FileWriter 介绍
5.1 FileReader 相关方法:
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-13) read(char[):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
相关API: - new String(char[1]:将char[]转换成String
- new String(char[.off,len):将char[的指定部分转换成String
package hsp.io_;
import java.io.FileReader;
import java.io.IOException;
/*
* * 单个字符读取文件
* */
public class FileReader01 {
public static void main(String[] args) {
String filePath = "E:\\test01.txt";
FileReader fileReader = null;
int data = 0;
try {
fileReader = new FileReader(filePath);
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/*
* 字符数组读取文件
* */
class FileReader02 {
public static void main(String[] args) {
String filePath = "E:\\test01.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
try {
fileReader = new FileReader(filePath);
//循环读取 使用 read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5.2 FileWriter常用方法
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[],off,len):写入指定数组的指定部分
- write (string):写入整个字符串
- write(string,off,len):写入字符串的指定部分
相关API: String类:toCharArray:将String转换成char[]
注意: FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入 不到指定的文件!
package hsp.io_;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriter01 {
public static void main(String[] args) {
String filePath = "e:\\note.txt";
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);
// 3) write(int):写入单个字符
fileWriter.write('H');
// 4) write(char[]):写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len):写入指定数组的指定部分
fileWriter.write("中华人民共和国".toCharArray(), 0, 3);
// 6) write(string):写入整个字符串
fileWriter.write(" 你好玉儿~");
fileWriter.write("风雨之后,定见彩虹");
// 7) write(string,off,len):写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
//在数据量大的情况下,可以使用循环操作
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
//对应 FileWriter , 一定要关闭流,或者 flush 才能真正的把数据写入到文件
//fileWriter.flush();
//关闭文件流,等价 flush() + 关闭
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
6、 节点流和处理流
6.3 节点流和处理流的区别和联系
1.节点流是底层流/低级流,直接跟数据源相接。
2、处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。[源码理解]
3.处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连[模拟修饰器设计模式=》]
6.4处理流的功能主要体现在以下两个方面:
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
2、操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
6.5 处理流-BufferedReader和 BufferedWriter
BufferedReader 和 BufferedWriter属于字符流,是按照字符来读取数据的关闭时处理流,只需要关闭外层流即可。
package hsp.io_;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReader01 {
public static void main(String[] args) throws IOException {
String filePath = "e:\\aa.java";
//创建 bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回 null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭节点流
//FileReader。
bufferedReader.close();
}
}
package hsp.io_;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriter01 {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建 BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("床前明月光,");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("疑是地上霜。");
bufferedWriter.newLine();
bufferedWriter.write("举头望明月,");
bufferedWriter.newLine();
bufferedWriter.write("低头思故乡。");
bufferedWriter.newLine();
//说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭
bufferedWriter.close();
}
}
拷贝
package hsp.io_;
import java.io.*;
public class BufferCopy {
public static void main(String[] args) {
//说明
//1. BufferedReader 和 BufferedWriter 是安装字符操作
//2. 不要去操作 二进制文件[声音,视频,doc, pdf ], 可能造成文件损坏
String secFilePath = "e:\\aa.java";
String destFilePath = "e:\\aa_copy.java";
BufferedReader bufReader = null;
BufferedWriter bufWriter = null;
String line;
try {
bufReader = new BufferedReader(new FileReader(secFilePath));
bufWriter = new BufferedWriter(new FileWriter(destFilePath));
//readLine 读取一行内容,但是没有换行
while ((line = bufReader.readLine()) != null) {
//每读取一行,就写入
bufWriter.write(line);
//插入一个换行
bufWriter.newLine();
}
System.out.println("拷贝完毕...");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufReader != null) {
bufReader.close();
}
if (bufWriter != null) {
bufWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
6.6 处理流-BufferedInputStream 和 BufferedOutputStream
package hsp.io_;
import java.io.*;
/*
* 演示使用 BufferedOutputStream 和 BufferedInputStream 使用
* 使用他们,可以完成二进制文件拷贝.
*字节流可以操作二进制文件,可以操作文本文件
* */
public class BufferInputOutputStreamCopy01 {
public static void main(String[] args) {
String srcFilePath = "e:\\picture.jfif";
String destFilePath = "e:\\picture_copy.jfif";
//创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6.7 对象流-ObjectInputStream 和 ObjectOutputStream
看一个需求
1.将int num = 100这个int数据保存到文件中,注意不是100数字,而是int 100,并且,能够从文件中直接恢复int 100
2.将Dog dog = new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件恢复.3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作
序列化和反序列化
1.序列化就是在保存数据时,保存数据的值和数据类型
2.反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable //这是一个标记接口,没有方法
Externalizable//该接口有方法需要实现,因此我们一般实现上面的 Serializable接口
6.8对象流介绍
功能:提供了对基本类型或对象类型的序列化和反序列化的方法:
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能
package hsp.io_;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjectOutStream01 {
public static void main(String[] args) throws IOException {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("中国人");//String
//保存一个 dog 对象
oos.writeObject(new Dog("旺财", "白色", 10,"俄罗斯"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
//需要序列化某个对象,实现Serializable
class Dog implements Serializable {
private String name;
private String color;
private int age;
//序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
private transient String nation;
//serialVersionUID序列化版本号,可以提高兼容性
private static final long serialVersionUID = 1l;
public Dog(String name, String color, int age,String nation) {
this.name = name;
this.color = color;
this.age = age;
this.nation =nation;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", age=" + age +
'}'+nation;
}
}
package hsp.io_;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStream01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.创建流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:\\data.dat"));
// 2.读取, 注意顺序一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println("运行类型:"+dog.getClass());
System.out.println("obj信息:"+dog);
//1.如果需要调用Dog的方法,需要向下转型
//2.需要将Dog的定义,拷贝到可以引用的位置即可
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName());
// 3.关闭
ois.close();
System.out.println("以反序列化的方式读取(恢复)ok~");
}
}
注意事项和细节说明:
1)读写顺序要一致
2)要求序列化或反序列化对象,需要实现Serializable
3)序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
4)序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
5)序列化对象时,要求里面属性的类型也需要实现序列化接口
6)序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
6.8 标准输入输出流
介绍 | 类型 | 默认设备 |
---|---|---|
System.in标准输入 | lnputStream | 键盘 |
System.out标准输出 | PrintStream | 显示器 |
应用案例1
传统方法System.out.printIn(“”);是使用out对象将数据输出到显示器
应用案例2
传统的方法, Scanner是从标准输入键盘接收数据
package hsp.io_;
import java.util.Scanner;
public class SystemInputOut01 {
public static void main(String[] args) {
//System.in
//编译类型:InputStream
//运行类型:BufferedInputStream
//表示标准输入 键盘
System.out.println(System.in.getClass());//class java.io.BufferedInputStream
//System.out
//编译类型:PrintStream
//运行类型:PrintStream
//表示标准输出 显示器
System.out.println(System.out.getClass());//class java.io.PrintStream
Scanner scanner = new Scanner(System.in);
System.out.println("请输入内容。。。");
String next = scanner.next();
System.out.println("next="+next);
}
}
6.9转换流-InputStreamReader和 OutputStream
- InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成(转换)Reader(字符流)
- OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
package hsp.io_;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
//乱码问题:指定编码
//* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
public class OutputStreamReader01 {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);
//将 2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath), "gbk"));
//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();
}
}
package hsp.io_;
import java.io.*;
//* 将字节流 FileOutputStream 转成字符流 OutputStreamReader, 指定编码 gbk/utf-8
public class OutputStreamWriter01 {
public static void main(String[] args) throws IOException {
String filePath = "e:\\lxc.txt";
String charSet = "utf-8";
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
writer.write("codeSE,中国人");
writer.close();
System.out.println("文件写入成功。。。");
}
}
7、 打印流-PrintStream 和 PrintWriter
package hsp.io_;
/*
*PrintStream (字节打印流/输出流)
* */
import java.io.IOException;
import java.io.PrintStream;
public class PrintStream01 {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
out.println("hello,你好");
//因为 print 底层使用的是 write , 所以我们可以直接调用 write 进行打印/输出
out.write("hello,你好".getBytes());
out.close();
//我们可以去修改打印流输出的位置/设备
System.setOut(new PrintStream("e:\\f.txt"));
System.out.println("日照香炉生紫烟");//就会输出到该文件下
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了 out
// }
}
}
package hsp.io_;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/*
* PrintWriter (字节打印流/输出流)
* */
public class PrintWriter01 {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 玉儿你好~~~~");
printWriter.close();//flush + 关闭流, 才会将数据写入到文件
}
}
8、 Properties 类
8.1 看需求
package hsp.properties_;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取 mysql.properties 文件,并得到 ip, user 和 pwd
BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\hsp\\properties_\\mysql.properities"));
String line = "";
while ((line=bufferedReader.readLine())!=null){ //循环读取
//System.out.println(line);
String[] str = line.split("=");
System.out.println(str[0]+"值是:"+str[1]);
//如果我们要求指定的 ip 值
if("ip".equals(str[0])) {
System.out.println(str[0] + "值是: " + str[1]);
}
}
bufferedReader.close();
}
}
8.2基本介绍
1)专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值
2)注意:键值对不需要有空格,值不需要用引号一起来。默认类型是String
3) Properties的常见方法
- | - |
---|---|
load | 加载配置文件的键值对到Properties对象 |
list | 将数据显示到指定设备 |
getProperty(key) | 根据键获取值 |
setProperty(key,value) | 设置键值对到Properties对象 |
store | 将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码 |
(http://tool.chinaz.com/tools/unicode.aspx) unicode码查询工具
package hsp.properties_;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用 Properties 类来读取 mysql.properties 文件
//1. 创建 Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\hsp\\properties_\\mysql.properties"));
//3. 把 k-v 显示控制台
properties.list(System.out);
//4. 根据 key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}
package hsp.properties_;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用 Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有 key 就是创建
//2.如果该文件有 key ,就是修改
/*
* Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
* */
properties.setProperty("charset","utf-8");
properties.setProperty("user","kk中国");//注意保存时,是中文的 unicode 码值
properties.setProperty("pwd","admin");
properties.setProperty("pwd","admin123");
//将 k-v 存储文件中即可
properties.store(new FileOutputStream("src\\mysql.properties"),"this is a comments,这里是注释");
System.out.println("保存配置文件成功。。。");
}
}
本章练习
package hsp.properties_.lianxi;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/*
*(1)在判断e盘下是否有文件夹mytemp ,如果没有就创建mytemp
* (2)在e:\mytemp目录下,创建文件 hello.txt
* (3)如果hello.txt已经存在,提示该文件已经存在,就不要再重复创建了
* (4)并且在hello.txt 文件中,写入hello,world~
* */
public class HomeWork01 {
public static void main(String[] args) throws IOException {
String dirPath = "e:\\mytemp";
File file = new File(dirPath);
if (!(file.exists())){
//创建
if (file.mkdir()){
System.out.println("创建 "+dirPath +" 创建成功");
}else {
System.out.println("创建 "+dirPath +" 创建失败");
}
}
String filePath = dirPath+"\\hello.txt";
file = new File(filePath);
if (!file.exists()){
if (file.createNewFile()){
System.out.println(filePath+"创建成功");
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));
bufferedWriter.write("hello,中国");
bufferedWriter.close();
}else {
System.out.println(filePath+"创建失败");
}
}else {
System.out.println("文件已经存在,就不要再重复创建了");
}
}
}
第13章 网络编程
1、网络的相关概念
1.1 网络通信
1.概念:两台设备之间通过网络实现数据传输
2.网络通信:将数据通过网络从一台设备传输到另一台设备
3.java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信
1.2 网络
1.概念:两台或多台设备通过一定物
理设备连接起来构成了网络
2.根据网络的覆盖范围不同,对网络
进行分类:
局域网:
覆盖范围最小,仅仅覆盖一个教室或一个机房
城域网:
覆盖范围较大,可以覆盖一个城市
广域网:
覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表
1.3 ip 地址
1.概念:用于唯一标识网络中的每台计算机/主机2.查看ip地址: ipconfig
3. ip地址的表示形式:点分十进制XX.XX.XX.XX
4.每一个十进制数的范围:0~255
5. ip地址的组成=网络地址+主机地址,比如:192.168.16.69
6. iiPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址[1]。
7.由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍
1.4 ipv4 地址分类
类型 | 范围 |
---|---|
A | 0.0.0.0——127.255.255.255 |
B | 128.0.0.0——191.255.255.255 |
C | 192.0.0.0——223.255.255.255 |
D | 224.0.0.0——239.255.255.255 |
E | 240.0.0.0——247.255.255.255 |
1.5 域名
1.www.baidu.com
2.好处:为了方便记忆,解决记ip的困难
3.概念:将ip地址映射成域名,这里怎么映射上,HTTP协议
端口号
1.概念:用于标识计算机上某个特定的网络程序
2.表示形式:以整数形式,端口范围0~65535 [2个字节表示端口0-0~2^16-1]
3. 0~1024已经被占用,比如 ssh 22, ftp 21, smtp 25 http 80
4.常见的网络程序端口号:
tomcat :8080
mysql:3306
oracle:1521
sqlserver:1433
1.6 网络通信协议
协议(tcp/ip)
TCP/IP (Transmission ControlProtocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。
图:
TCP和UDP
TCP协议:传输控制协议
1.使用TCP协议前,须先建立TCP连接,形成传输数据通道
2.传输前,采用"三次握手"方式,是可靠的
3. TCP协议进行通信的两个应用进程:客户端、服务端
4.在连接中可进行大数据量的传输
5.传输完毕,需释放已建立的连接,效率低
UDP协议:用户数据协议
1.将数据、源、目的封装成数据包,不需要建立连接
2.每个数据报的大小限制在64K内,不适合传输大量数据
3.因无需连接,故是不可靠的
4.发送数据结束时无需释放资源(因为不是面向连接的),速度快
5.举例:厕所通知:发短信
2 、InetAddress
2.1相关方法
1.获取本机InetAddress对象getLocalHost
2.根据指定主机名/域名获取ip地址对象getByName
3.获取InetAddress对象的主机名getHostName
4.获取InetAddress对象的地址getHostAddress
package hsp.network;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class API01 {
public static void main(String[] args) throws UnknownHostException {
//获取本机 InetAddress 对象 getLocalHost
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-O44F4A7/10.100.3.248
//根据指定主机名/域名获取 ip 地址对象 getByName
InetAddress host2 = InetAddress.getByName("DESKTOP-O44F4A7");
System.out.println(host2);//
InetAddress host3 = InetAddress.getByName("www.code.com");
System.out.println(host3);//
//获取 InetAddress 对象的主机名 getHostName
String host3Name = host3.getHostName();
System.out.println(host3Name);
//获取 InetAddress 对象的地址 getHostAddress
String host3Address = host3.getHostAddress();
System.out.println(host3Address);
}
}
3、 Sockt
3.1基本介绍
1.套接字(Socket)开发网络应用程序被广 泛采用,以至于成为事实 上的标准。
2.通信的两端都要有Socket, 是两台机器间通信的端点
3.网络通信其实就是Socket间的通信。
4.Socket允许程序把网络连接当成一 个流,数据在两个Socket间通过I0传输。
5.一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
案例一:
package hsp.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 服务端
* */
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的 9898 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9898
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9898);
System.out.println("服务端在9898端口监听,等待连接...");
//2. 当没有客户端连接 9898 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket=" + socket.getClass());
//
//3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println("服务端收到:"+new String(buf, 0, readLen));//根据读取到的实际长度,显示内容. }
//5.关闭流和 socket
inputStream.close();
socket.close();
serverSocket.close();//关闭
}
}
}
package hsp.socket_;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/*
* 客户端,发送 "hello, 我是客户端,发消息给你啦!!" 给服务端
* */
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9898 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9898);
System.out.println("客户端socket返回=" + socket.getClass());
//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
// 得到 和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, 我是客户端,发消息给你啦!!".getBytes());
//4. 关闭流对象和 socket, 必须关闭
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}
案例二:
package hsp.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/*
* 服务端
* */
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//1. 在本机 的 7878 端口监听, 等待连接
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(7878);
System.out.println("服务端在7878端口监听,等待连接...");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继
Socket socket = serverSocket.accept();
System.out.println("服务端socket=" + socket.getClass());
//3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println("服务端收到:"+new String(buf, 0, readLen));
}
//5. 获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我是服务端,收到,返回你啦".getBytes(StandardCharsets.UTF_8));
// 设置结束标记
socket.shutdownOutput();
//6.关闭流和 socke
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();
}
}
package hsp.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 客户端
* */
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket
Socket socket = new Socket(InetAddress.getLocalHost(), 7878);
//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
// 得到 和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello,我是客户端".getBytes());
// 设置结束标记
socket.shutdownOutput();
//4. 获取和 socket 关联的输入流. 读取数据(字节),并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen=inputStream.read(buf))!=-1){
System.out.println("客户端收到:"+new String(buf,0,readLen));
}
//5. 关闭流对象和 socket,必须关闭
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}
案例三:
案例四:
4、 TCP网络通信编程
4.1 基本介绍
1.基于客户端一服务端的网络通信
2.底层使用的是TCP/IP协议
3.应用场景举例:客户端发送数据,服务端接受并显示控制台
4.基于Socket的TCP编程
4.2 netstat 指令
1.netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
2.netstat -an | more 可以分页显示
3.要求在dos控制台下执行win+r
说明:
(1) Listening表示某个端口在监听
(2)如果有一个外部程序(客户端)连接到该端口,就会显示条连接信息
(3)可以输入ctrl + C退出指令
第14章 反射
1、反射机制
1.1 Java Reflection
1.反射机制允许程序在执行期借助于Reflection的API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
2.加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
p对象 --> 类型Person类
Class对象cls —>类型Class类
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理
1.2 反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某 个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
这些类在java.lang.reflection
package hsp.reflection_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Reflection01 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, IOException, InvocationTargetException {
//1. 使用 Properties 类, 可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
System.out.println(properties);
String classfullpath = properties.get("classfullpath").toString();//"hsp.test.Cat"
String methodName = properties.get("method").toString();//"m1"
System.out.println(classfullpath);
System.out.println(methodName);
//2. 使用反射机制解决
//(1) 加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName("hsp.test.Cat");
System.out.println(cls); //运行类型
System.out.println(cls.getMethod("getName"));
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o 的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.test.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod("hi");
//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象
//java.lang.reflect.Field: 代表类的成员变量, Field 对象表示某个类的成员变量
//得到 name 字段
//getField 不能得到私有的属性
//Field nameField = cls.getField("name"); //报错
Field nameField = cls.getField("age"); //报错
System.out.println("获取公有属性:"+nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
//java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
Constructor constructor1 = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
System.out.println("获取构造器:"+constructor1);//Cat()
Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是 String 类的Class 对象
System.out.println(constructor2);//Cat(String name)
}
}
package com.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//获得类的属性
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.reflection.User");
//获得类的名字
System.out.println(c1.getName());//获得包名+类名
System.out.println(c1.getSimpleName());//获得类名
//获得属性名
System.out.println("================================================================");
Field[] fields = c1.getFields();//只能找到public属性
fields = c1.getDeclaredFields();//找到全部的属性
for(Field field:fields){
System.out.println(field);
}
//获得指定属性
Field name = c1.getDeclaredField("name");
System.out.println("获得指定属性:"+name);
//获得类的方法
System.out.println("================================================================");
Method[] methods = c1.getMethods();//获得本类和父类的所有
for (Method method:methods){
System.out.println("获得本类和父类的所有:"+method);
}
methods = c1.getDeclaredMethods();//获得本类的所有方法
for (Method method:methods){
System.out.println("获得本类的所有方法:"+method);
}
//获得指定方法
System.out.println("================================================================");
Method getName = c1.getMethod("getName");
Method setName = c1.getMethod("setName", String.class);
System.out.println("获得指定方法:"+getName);
System.out.println("获得指定方法:"+setName);
//获得构造器
System.out.println("================================================================");
Constructor[] constructors = c1.getConstructors();
for(Constructor constructor:constructors){
System.out.println("获得本类public构造器:"+getName);
}
constructors = c1.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println("获得本类全部构造器:"+getName);
}
//获得指定构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class,int.class,boolean.class);
System.out.println("指定构造器:"+declaredConstructor);
}
}
1.3 反射优点和缺点
1.优点:可以动态的创建和使用对象(也是框架底层核心,使用灵活没有反射机
制,框架技术就失去底层支撑。
2.缺点:使用反射基本是解释执行,对执行速度有影响.
1.4 反射调用优化-关闭访问检查
- Method和Field, Constructor对象都有setAccessible0方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
package hsp.reflection_;
import hsp.test.Cat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* 测试反射调用的性能,和优化方案
* */
public class Reflection02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//Field
//Method
//Constructor
m1();//传统
m2();//反射
m3();//反射优化
}
//传统方法来调用 hi
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 90; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1() 耗时=" + (end - start));
}
//反射机制调用方法 hi
public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
Class cls = Class.forName("hsp.test.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m2() 耗时=" + (end - start));
}
//反射调用优化 + 关闭访问检查
public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("hsp.test.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m3() 耗时=" + (end - start));
}
}
/*
m1() 耗时=0
m2() 耗时=1500
m3() 耗时=1484
*/
2、Class 类
2.1 基本介绍
1.Class也是类,因此也继承Object类
2.Class类对象不是new出来的, 而是系统创建的
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪个Class实例所生成
5.通过Class对象可以完整地得到一个类的完整结构, 通过系列API
6. Class对象是存放在堆的
7.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等) https://www.zhihu.com/question/38496907
package hsp.reflection_;
/*
Class类的特点
*/
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class 也是类,因此也继承 Object 类
//Class
//2. Class 类对象不是 new 出来的,而是系统创建的
//(1) 传统 new 对象
/* ClassLoader 类
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//Cat cat = new Cat();
//(2) 反射方式
/*
ClassLoader 类, 仍然是通过 ClassLoader 类加载 Cat 类的 Class 对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
Class cls1 = Class.forName("hsp.test.Cat");
//3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("hsp.test.Cat");
System.out.println(cls1.hashCode());//312714112
System.out.println(cls2.hashCode());//312714112
Class cls3 = Class.forName("hsp.test.Cat");
System.out.println(cls3.hashCode());//312714112
}
}
2.2 Class 类的常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名name的Class对象 |
object newInstance() | 调用缺省构造函数,返回该Class对象的一 个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称 |
Class [ ] getlnterfaces( ) | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass0 | 返回表示此Class所表示的实体的超类的Class |
Constructor[ ] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Field[ ] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod() | 返回一个Method对象,此对象的形参类型为paramType |
package hsp.reflection_;
import hsp.test.Car;
import java.lang.reflect.Field;
/*
* Class常用方法
* */
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classAllPath = "hsp.test.Car";
//1 . 获取到 Car 类 对应的 Class 对象
//<?> 表示不确定的 Java 类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出 cls
System.out.println(cls); //显示 cls 对象, 是哪个类的 Class 对象 hsp.test.Car
System.out.println(cls.getClass());//输出 cls 运行类型 java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//包名 hsp.test
//4. 得到全类名
System.out.println(cls.getName());//hsp.test.Car
//5. 通过 cls 创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car); // 调用car.toString()
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f: fields) {
System.out.println(f.getName());//名称
}
}
}
2.3 获取Class类对象
1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()获取,可能抛出ClassNotFoundException
实例: Class cls1 = Class.forName( “java.lang.Cat” );
应用场景:多用于配置文件,读取类全路径,加载类.
2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
实例: Class cls2 = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象.
3.前提:已知某个类的实例,调用该实例的getClass(方法获取Class对象
实例:Class clazz =对象.getClassQ;//运行类型
应用场景:通过创建好的对象,获取Class对象.
4.其他方式
ClassLoader cl =对象.getClass0.getClassLoader()
Class clazz4 = cl.loadClass( "类的全类名”);
5.基本数据(int, charboolean,float,double,byte,long,short)按如下方式得到Class类对象
Class cls =基本数据类型.class
6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE
package hsp.reflection_;
import hsp.test.Car;
public class Class03 {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName
String classAllPath = "hsp.test.Car"; //通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
//2. 类名.class , 应用场景: 用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);
//3. 对象.getClass(), 应用场景,有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
//4. 通过类加载器【4 种】来获取到类的 Class 对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到 Class 对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());// 312714112
System.out.println(cls2.hashCode());// 312714112
System.out.println(cls3.hashCode());// 312714112
System.out.println(cls4.hashCode());// 312714112
//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到 Class 类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int
//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类 BOOLEAN, DOUBLE, LONG,BYTE 等待
System.out.println(type2);//char
System.out.println(integerClass.hashCode());//692404036
System.out.println(type1.hashCode());//692404036
}
}
2.4 哪些类型有Class对象
如下类型有Class对象
1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2. interface :接口
3.数组
4.enum :枚举
5. annotation :注解
6.基本数据类型
7. void
package hsp.reflection_;
import java.io.Serializable;
public class Class04 {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void 数据类型
Class<Class> cls9 = Class.class;//
System.out.println(cls1);// class java.lang.String
System.out.println(cls2);// interface java.io.Serializable
System.out.println(cls3);// class [Ljava.lang.Integer;
System.out.println(cls4);// class [[F
System.out.println(cls5);// interface java.lang.Deprecated
System.out.println(cls6);// class java.lang.Thread$State
System.out.println(cls7);// long
System.out.println(cls8);// void
System.out.println(cls9);// class java.lang.Class
}
}
3、类加载
3.1 基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
1.静态加载:
编译时加载相关的类,如果没有则报错,依赖性太强
2.动态加载:
运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
类加载时机
1.当创建对象时(new) //静态加载
2.当子类被加载时,父类也加载//静态加载
3.调用类中的静态成员时//静态加载
4.通过反射//动态加载
类加载过程图:
类加载各阶段完成任务:
加载阶段
连接阶段-验证
1.目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证[举例说明]
3. 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段_准备
1.JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如O、OL、null. false 等)。这些变量所使用的内存都将在方法区中进行分配
第15章 JDBC 和数据库连接
1 JDBC概述
1.1基本介绍
1.JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
3.JDBC的基本原理图[重要!]
1.2 JDBC API
2、JDBC程序编写步骤
1.注册驱动–加载Driver类
2.获取连接-得到Connection
3.执行增删改查-发送SQL给mysql执行
4.释放资源-关闭相关连接
注意:使用前需要将数据库依赖jar包放在目录下
例如:
package jdbc;
import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/*
* 这是第一个 Jdbc 程序,完成简单的操作
* */
public class JDBC01 {
public static void main(String[] args) throws SQLException {
//前置工作: 在项目下创建一个文件夹比如 libs
// 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//1. 注册驱动
Driver driver = new Driver(); //创建 driver 对象
//2. 得到连接
//(1) jdbc:mysql:// 规定好表示协议,通过 jdbc 的方式连接 mysql
//(2) localhost 主机,可以是 ip 地
//(3) 3306 表示 mysql 监听的端口
//(4) lxc_db01 连接到 mysql dbms 的哪个数据库
//(5) mysql 的连接本质就是 socket 连接
String url = "jdbc:mysql://localhost:3306/lxc_db01";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);
//3. 执行 sql
String sql = "insert into actor values(null, '朱茵', '女', '1975-11-11', '')";
//String sql = "update actor set name='周星驰' where id = 1";
//String sql = "delete from actor where id = 1";
//statement 用于执行静态 SQL 语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql); // 如果是 dml 语句,返回的就是影响行数
System.out.println(rows > 0 ? "成功" : "失败");
//4. 关闭连接资源
statement.close();
connect.close();
}
}
2.2 获取数据库连接 5 种方式
package jdbc;
import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/*
* 获取数据库连接 5 种方式
* */
public class JDBC02 {
//方式1:获取实Driver现类对象
@Test
public void connect01() throws SQLException, SQLException {
Driver driver = new Driver(); //创建 driver 对象
String url = "jdbc:mysql://localhost:3306/lxc_db01";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
//方式2:通过方式1会直接使用com.mysql.jdbc.Driver(),属于静态加载。灵活性差,依赖强
// //使用反射加载 Driver 类 , 动态加载,更加的灵活,减少依赖性
@Test
public void connect02() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) c.newInstance();
String url = "jdbc:mysql://localhost:3306/lxc_db01";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
//方式3:使用DriverManger替代Driver进行同一管理
@Test
public void connect03() throws SQLException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) c.newInstance();
String url = "jdbc:mysql://localhost:3306/lxc_db01";
String user = "root";
String password = "123456";
DriverManager.registerDriver(driver);//注册Diver驱动
Connection connect = DriverManager.getConnection(url,user,password);
System.out.println(connect);
}
//方式4:使用Class.forName 自动注册,简化代码
//推荐使用
@Test
public void connect04() throws SQLException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用反射加载了 Driver 类
//在加载 Driver 类时,完成注册
/*
源码:
1. 静态代码块,在类加载时,会执行一次.
2. DriverManager.registerDriver(new Driver());
3. 因此注册 driver 的工作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/lxc_db01";
String user = "root";
String password = "123456";
Connection connect = DriverManager.getConnection(url,user,password);
System.out.println(connect);
}
//方式5:在方式 4 的基础上改进,增加配置文件,让连接 mysql 更加灵活
@Test
public void connect05() throws SQLException, SQLException, ClassNotFoundException, IOException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写
Connection connect = DriverManager.getConnection(url,user,password);
System.out.println(connect);
}
}
提示:
1.mysqL驱动5.1.6可以无需CLass . forName(“com.mysql.jdbc.Driver”);
2.从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动
jar包下META-INF\servicesVjava.sql.Driver文本中的类名称去注册
3.建议还是写上CLass . forName(“com.mysql.jdbc.Driver”),更加明确
3、 ResultSet【结果集】
3.1基本介绍
1.表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
3. .next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集
package jdbc;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
* select 语句返回 ResultSet ,并取出结果
* */
public class ResultSet01 {
public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1.注册驱动
Class.forName(driver);//建议写
//2.得到连接
Connection connect = DriverManager.getConnection(url,user,password);
//3.得到Statement
Statement statement = connect.createStatement();
//4.组织sql
String sql = "select id,name,sex,borndate from actor";
ResultSet resultSet = statement.executeQuery(sql); //返回结果集
//5.使用while取出数据
while (resultSet.next()){ //next() 光标下移,没有更多行就返回false
int id = resultSet.getInt(1);//获取改行的第1列
String name = resultSet.getNString(2);//获取改行的第2列
String sex = resultSet.getNString(3);//获取改行的第3列
Date date = resultSet.getDate(4);//获取改行的第4列
//String phone = resultSet.getNString(5);//获取改行的第5列
System.out.println(id+"\t"+name+"\t"+sex+"\t"+date+"\t");
}
//6.关闭连接
resultSet.close();
statement.close();
connect.close();
}
}
4.Statement
4.1 基本介绍
1.Statement对象用于执行静态SQL语句并返回其生成的结果的对象
2.在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过Statement[存在SQL注入]
PreparedStatement[预处理]CallableStatement[存储过程]
3. Statement对象执行SQL语句,存在SQL注入风险
4.SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
5.要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代Statement就可以了
-- 演示 sql 注入
-- 创建一张表
CREATE TABLE admin ( -- 管理员表
NAME VARCHAR(32) NOT NULL UNIQUE,
pwd VARCHAR(32) NOT NULL DEFAULT '') CHARACTER SET utf8; -- 添加数据
INSERT INTO admin VALUES('tom', '123'); -- 查找某个管理是否存在
SELECT *FROM adminWHERE NAME = 'tom' AND pwd;
-- SQL
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1
SELECT *
FROM admin
WHERE NAME = '1' OR' AND pwd = 'OR '1'= '1' SELECT *
package jdbc;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
public class Statement01 {
public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
Scanner scanner = new Scanner(System.in);
//用户输入用户名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 如果希望看到 SQL 注入,这里需要用 nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1.注册驱动
Class.forName(driver);//建议写
//2.得到连接
Connection connect = DriverManager.getConnection(url,user,password);
//3.得到Statement
Statement statement = connect.createStatement();
//4.组织sql
String sql = "select name , pwd from admin where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'";
ResultSet resultSet = statement.executeQuery(sql);
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//6.关闭连接
resultSet.close();
statement.close();
connect.close();
}
}
5、 PreparedStatemen
1.PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数. setXxox()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
2.调用executeQuery),返回ResultSet 对象
3.调用executeUpdate():执行更新,包括增、删、修改
预处理好处
1.不再使用+拼接sql语句,减少语法错误
2.有效的解决了sql注入问题!
3.大大减少了编译次数,效率较高
package jdbc;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
@SuppressWarnings({"all"})
public class PreparedStatement01 {
public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
Scanner scanner = new Scanner(System.in);
//用户输入用户名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 老师说明,如果希望看到 SQL 注入,这里需要用 nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1.注册驱动
Class.forName(driver);//建议写
//2.得到连接
Connection connect = DriverManager.getConnection(url, user, password);
//3. 得到 PreparedStatement
//3.1 组织 SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd from admin where name = ? and pwd = ?";
//3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connect.prepareStatement(sql);
//3.3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4. 执行 select 语句使用 executeQuery
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这里执行 executeQuery,不要再写sql了
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//6.关闭连接
resultSet.close();
preparedStatement.close();
connect.close();
}
}
package jdbc;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/*
* PreparedStatement 在del的使用
* */
@SuppressWarnings({"all"})
public class PreparedStatement02 {
public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
Scanner scanner = new Scanner(System.in);
//用户输入用户名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 老师说明,如果希望看到 SQL 注入,这里需要用 nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1.注册驱动
Class.forName(driver);//建议写
//2.得到连接
Connection connect = DriverManager.getConnection(url, user, password);
//3. 得到 PreparedStatement
//添加记录 //修改记录 //删除记录
//String sql = "insert into admin values(?,?)";
String sql = "update admin set pwd = ? where name = ? ";
//String sql = "delete from admin where name = ? ";
PreparedStatement preparedStatement = connect.prepareStatement(sql);
preparedStatement.setString(1, admin_pwd);
preparedStatement.setString(2, admin_name);
//4. 执行 select 语句使用 executeQuery
int rows = preparedStatement.executeUpdate();
System.out.println(rows > 0 ? "执行成功" : "执行失败");
//6.关闭连接
preparedStatement.close();
connect.close();
}
}
6、阶段小结
7、封装JDBCUtils【关闭连接,得到连接】
7.1 说明
在jdbc操作中,获取连接和释放资源是经常使用到,可以将其封装JDBC连接的工真类JDBCUtils
package jdbc;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
* 封装一个工具类,完成mysql的连接和关闭操作
* */
@SuppressWarnings({"All"})
public class JDBCUtils {
//定义相关属性(4个),只要一份,做出static即可
private static String user;
private static String password;
private static String url;
private static String driver;
//静态代码块去初始化
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
//读取相关属性
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//实际开发中,将编译异常转成运行异常,
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
}
}
//连接数据库 返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
* ResultSet 结果集
* Statement 或 PreparedStatement
* Connection
* 如果需要关闭资源,就传入对象,否则null
* */
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
package jdbc;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* 使用封装好的JDBCUtils工具类
* */
public class JDBCUtils_use01 {
@Test
public void test1() {
//1得到连接
Connection connection = null;
PreparedStatement preparedStatement = null;
//2.组织sql
String sql = "update actor set name = ? where id = ? ";
//3.创建preparedStatement对象
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
//给占位符
preparedStatement.setString(1, "codeSE");
preparedStatement.setInt(2, 1);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
}
package jdbc;
import org.testng.annotations.Test;
import java.sql.*;
/*
* 使用封装好的JDBCUtils工具类
* */
public class JDBCUtils_use02 {
@Test
public void test1() {
//1得到连接
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//2.组织sql
String sql = "select * from actor";
//3.创建preparedStatement对象
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
//执行,得到结果集
resultSet = preparedStatement.executeQuery();
//遍历
while (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getNString("name");
String sex = resultSet.getNString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getNString("phone");
System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//关闭资源
JDBCUtils.close(resultSet, preparedStatement, connection);
}
}
}
8 、事务
8.1 基本介绍
1.JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
2. JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
3.调用Connection的 setAutoCommit(false)可以取消自动提交事务
4.在所有的SQL语句都成功执行后,调用Connection 的commit();方法提交事务
5在其中某个操作失败或出现异常时,调用Connection的rollback();方法回滚事务
package jdbc;
import org.testng.annotations.Test;
import java.sql.*;
/*
* 使用事务
* */
public class Transaction01 {
@Test
public void test1() {
//操作转账业务
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
String sql1 = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
try {
connection = JDBCUtils.getConnection();//默认情况下,connection自动提交
connection.setAutoCommit(false);//设置为不自动提交,相当开启了事务
//执行1
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
//int i = 1 / 0; //人为制造异常
//执行2
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
//这里提交事务
connection.commit();
} catch (SQLException e) {
//这里可以进行回滚,撤销执行的sql
try {
connection.rollback();//默认回滚到开始状态
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
}
9、批处理
9.1 基本介绍
1.当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
2JDBC的批量处理语句包括下面方法:
addBatch():添加需要批量处理的SQL语句或参数executeBatch():执行批量处理语句;
clearBatch():清空批处理包的语句
3.JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatements=true
4.批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高
package jdbc;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* java批处理
* */
public class Batch01 {
// 传统方法,添加 5000条数据到admin表
@Test
public void test1() throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行:···");
long start = System.currentTimeMillis();
for (int i = 1; i <= 5000; i++) {
preparedStatement.setString(1, "codeSE" + i);
preparedStatement.setString(2, "666666" + i);
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统方法耗时:" + (end - start) + "ms");//4504ms
//关闭
JDBCUtils.close(null, preparedStatement, connection);
}
//使用批处理
@Test
public void test2() throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行:···");
long start = System.currentTimeMillis();
for (int i = 1; i <= 5000; i++) {
preparedStatement.setString(1, "codeSE" + i);
preparedStatement.setString(2, "666666" + i);
//将sql加入批处理包中
preparedStatement.addBatch();
//当有积攒慢1000条,再批量执行
if(i%1000==0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("使用批处理耗时:" + (end - start) + "ms");//63ms
//关闭
JDBCUtils.close(null, preparedStatement, connection);
}
}
10、数据库连接池
10.1 传统获取 Connection 问题
1.传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证IP地址,用户名和密码(0.05s ~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
2.每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
3.传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
4.解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。
10.2 数据库连接池基本介绍
1.预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
2.数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
3.当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
10.3 数据库连接池种类
1.JDBC的数据库连接池使用javax.sql.DataSource 来表示,DataSource只是一个接口,该接口通常由第三方提供实现[提供.jar]
2.C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate, spring)
3.DBCP数据库连接池,速度相对c3p0较快,但不稳定
4.Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
5.BoneCP数据库连接池,速度快
6. Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool,优点于一身的数据库连接池
<c3p0-config>
<!--数据源名称代表连接池-->
<named-config name="hello">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/lxc_db01</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">123456</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
package jdbc.connectionPool_;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.testng.annotations.Test;
import java.beans.PropertyVetoException;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*
* c3p0的使用
* */
public class C3p0_01 {
@Test
//方式1:: 相关参数,在程序中指定 user, url , password
public void test1() throws IOException, PropertyVetoException, SQLException {
//1.创建一个对象,相关参数,在程序中指定 user, url , password
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2.通过Properties配置文件获取相关连接信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//给数据源 comboPooledDataSource 设置相关参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//设置最大连接数
comboPooledDataSource.setMaxPoolSize(50);
//测试连接5000次的效率
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();//这个方法就是从 DataSource 接口实现的
//System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0连接5000次耗时:"+(end-start)+"ms");//208ms
}
@Test
public void test2() throws SQLException {
//第二种方式 使用配置文件模板来完成
//1. 将 c3p0 提供的 c3p0.config.xml 拷贝到 src 目录下
//2. 该文件指定了连接数据库和连接池的相关参数
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0第二种方式连接5000次耗时:"+(end-start)+"ms");//188ms
}
}
# druid.properties
#key=value
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/lxc_db01?rewriteBatchedStatements=true
username=root
password=123456
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait time (5000 mil seconds)
maxWait=5000
package jdbc.connectionPool_;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.testng.annotations.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class Druid_01 {
@Test
public void test1() throws Exception {
//1. 加入 Druid jar 包
//2. 加入 配置文件 druid.properties , 将该文件拷贝项目的 src 目录
//3. 创建 Properties 对象, 读取配置文
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4.创建一个指定参数的数据库连接池,Druid连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//测试500000次Druid连接
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection();
//System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("Druid连接500000次耗时:"+(end-start)+"ms");//315ms
}
}
10.4 将 JDBCUtils 工具类改成 Druid(德鲁伊)实现
package jdbc.connectionPool_;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/*
* 将 JDBCUtils 工具类改成 Druid(德鲁伊)实现
* */
public class JDBCUtilsDruid {
private static DataSource ds;
//静态代码块完成初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//编写getConnection方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接, 强调: 在数据库连接池技术中,close 不是真的断掉连接
// 而是把使用的 Connection 对象放回连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
package jdbc.connectionPool_;
import org.testng.annotations.Test;
import java.sql.*;
public class JDBCUtilsDruid_use01 {
@Test
public void test1() {
//1得到连接
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//2.组织sql
String sql = "select * from actor";
//3.创建preparedStatement对象
try {
connection = JDBCUtilsDruid.getConnection();
System.out.println(connection.getClass());//运行类型class com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
//执行,得到结果集
resultSet = preparedStatement.executeQuery();
//遍历
while (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getNString("name");
String sex = resultSet.getNString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getNString("phone");
System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//关闭资源
JDBCUtilsDruid.close(resultSet, preparedStatement, connection);
}
}
}
11、Apache—DBUtils
11.1 入门介绍
package jdbc.connectionPool_;
import java.util.Date;
/*
*
* Actor对象和actor表的记录对应
* */
public class Actor {
private Integer id;
private String nane;
private String sex;
private Date borndate;
private String phone;
public Actor(){}//一定给个无参构造器【反射需要】
public Actor(Integer id, String nane, String sex, Date borndate, String phone) {
this.id = id;
this.nane = nane;
this.sex = sex;
this.borndate = borndate;
this.phone = phone;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNane() {
return nane;
}
public void setNane(String nane) {
this.nane = nane;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBorndate() {
return borndate;
}
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Actor{" +
"id=" + id +
", nane='" + nane + '\'' +
", sex='" + sex + '\'' +
", borndate=" + borndate +
", phone='" + phone + '\'' +
'}';
}
}
package jdbc.connectionPool_;
import org.testng.annotations.Test;
import java.sql.*;
import java.util.ArrayList;
public class JDBCUtilsDruid_use01 {
//使用土方法来解决 ResultSet =封装=> Arraylist
@Test
public void test2() {
//1得到连接
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//2.组织sql
String sql = "select * from actor";
ArrayList<Actor> list = new ArrayList<>();
//3.创建preparedStatement对象
try {
connection = JDBCUtilsDruid.getConnection();
System.out.println(connection.getClass());//运行类型class com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
//执行,得到结果集
resultSet = preparedStatement.executeQuery();
//遍历
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getNString("name");
String sex = resultSet.getNString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getNString("phone");
//把得到的 resultSet 的记录,封装到 Actor 对象,放入到 list
list.add(new Actor(id, name, sex, borndate, phone));
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
System.out.println("list集合数据:" + list);
for (Actor actor : list
) {
System.out.println("id=" + "\t" + actor.getId() + "name=" + actor.getNane() + "\t" + "sex=" + actor.getSex() + "\t" + "borndate=" + actor.getBorndate() + "\t" + "phone=" + actor.getPhone());
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//关闭资源
JDBCUtilsDruid.close(resultSet, preparedStatement, connection);
}
}
}
11.2 基本介绍
1.commons-dbutils 是 Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量[真的]。
DbUtils类
1.QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
2.使用QueryRunner类实现查询
3. ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式,
- | - | - |
---|---|---|
ArrayHandler | 把结果集中的第一行数据转成对象数组。 | |
ArrayListHandler | 把结果集中的每一行数据都转成一个数组,再存放到List中.BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。 | |
BeanListHandler | 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 | |
ColumnListHandler | 将结果集中某一列的数据存放到List中。 | |
KeyedHandler(name) | 将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key | |
MapHandler | 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。 | |
MapListHandler | 将结果集中的每一行数据都封装到一个Map里,然后再存放到List |
package jdbc.connectionPool_;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/*
* 使用 apache-DBUtils 工具类 + druid
* */
@SuppressWarnings({"All"})
public class DBUtils_use01 {
/*
* 返回结果是多行的情况
* */
@Test
public void testQueryMany() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的 jar , 加入到本 Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回 ArrayList结果集
//String sql = "select * from actor where id >= ?";
String sql = "select name, phone from actor where id >= ?";
//解读:
//(1) query 方法就是执行 sql 语句,得到 resultset ---封装到 -->ArrayList 集合中
//(2) 返回集合
//(3) connection: 连接
//(4) sql : 执行的 sql 语句
//(5) new BeanListHandler<>(Actor.class): 在将 resultset -> Actor 对象 -> 封装到 ArrayList
// 底层使用反射机制 去获取 Actor 类的属性,然后进行封装
//(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数 Object... params
//(7) 底层得到的 resultset ,会在 query 关闭, 关闭 PreparedStatment
List<Actor> actorList = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
for (Actor actor : actorList){
System.out.println(actor);
}
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
//演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
@Test
public void testQuerySingle() throws SQLException {
Connection connection = JDBCUtilsDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from actor where id = ?";
//使用BeanHandler方法,返回单个对象
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 3);
System.out.println(actor);
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
/*
* apache-dbutils + druid 完成查询结果是单行单列-返回的就是 objec
* */
@Test
public void testQueryCcalar() throws SQLException {
Connection connection = JDBCUtilsDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select name from actor where id = ?";
//使用ScalarHandler方法,返回单行单列,返回就是Object
Object obj = queryRunner.query(connection, sql, new ScalarHandler<>(), 3);
System.out.println(obj);
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
/*
* apache-dbutils + druid 完成 dml (update, insert)
* */
@Test
public void testQueryDML() throws SQLException {
Connection connection = JDBCUtilsDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
//String sql = "insert into actor values (?,?,?,?,?)";
String sql = "update actor set name = ? where id = ?";
//(1) 执行 dml 操作是 queryRunner.update()
//(2) 返回的值是受影响的行数 (affected: 受影响)
//int affected = queryRunner.update(connection, sql, null,"周润发","男","1955-01-01","13567678888");
int affected = queryRunner.update(connection,sql,"释小龙",1);
System.out.println(affected>0?"执行成功":"执行没有影响到表");
//释放资源
JDBCUtilsDruid.close(null,null,connection);
}
}
11.3 DAO和增删改查通用方法-BasicDao
11.1先分析一个问题
apache-dbutils+Druid简化了JDBC开发,但还有不足:;
1.SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
2对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
3.将来的表很多,业务需求复杂,不可能只靠一个Java类完成
4.引出–>BasicDAO画出示意图,看看在实际开发中,应该如何处理
第16章 正则表达式
1、正则表达式基本介绍
1.一个正则表达式,就是用某种模式去匹配字符串的一个公式。很多人因为它们看上去比较古怪而且复杂所以不敢去使用,不过,经过练习后,就觉得这些复杂的表达式写起来还是相当简单的,而且,一旦你弄懂它们,你就能把数小时辛苦而且易错的文本处理工作缩短在几分钟(甚至几秒钟)内完成
2.正则表达式不是只有java才有,实际上很多编程语言都支持正则表达式进行字符串操作!
2、正则表达式语法
2.1 基本介绍
各种元字符的功能,元字符从功能上大致分为:
1.限定符
2.选择匹配符
3.分组组合和反向引用符
4.特殊字符
5.字符匹配符
6.定位符
2.2元字符(Metacharacter)-转义号\
\符号说明:在我们使用正则表达式去检索某些特殊字符的时候,需要用到转义符号,否则检索不到结果,甚至会报错的。
在Java的正则表达式中,两个\代表其他语言中的一个\
需要用到转义符号的字符有以下: . * + () $ / \ ? [ ] ^ { }
2.3元字符-字符匹配符
符号 | 含义 | 事例 | 解释 | 匹配输入 |
---|---|---|---|---|
[ ] | 可接收的字符列表 | [efgh] | e、f、g、h中的任意1个字符 | |
[^] | 不接收的字符列表 | [abc] | 除a、b、c之外的任意1个字符,包括数字和特殊符号 | |
– | 连字符 | A-z | 任意单个大写字母 | |
. | 匹配除\n以外的任何字符 | a…b | 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 | aaab、aefb、a35b、a#*b |
\\d | 匹配单个数字字符,相当于[O-9] | \\d{3}(\\d)? | 包含3个或4个数字的字符串匹配单个非数字字符 | 123、9876 |
\\D | 匹配非单个数字字符,相当于[^O-9] | \\D(\\d)* | 以单个非数宁字符开头,后接任意个数字字符串 | a、A342 |
\\w | 匹配单个数字、大小写字母字符,相当手[0-9a-zA-Z] | \\d{3}\\w{4} | 以3个数字字符开头的长度为7的数字字母字符串 | 234abcd、12345Pe |
\\W | 匹配单个非数字、大小写字母字相当于[^0-9a-zA-Z] | \\W+\\d{2} | 以至少1个非数字字母字符开头,2个数字符结尾的字符串 | #29、#?@10 |
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 转义字符的使用
* */
public class Regexp02 {
public static void main(String[] args) {
String content1 = "abcd$(abc(123(.";
String content2 = "a11\nc8k_ abc.ABC&~";
//匹配( ==>\\(
//匹配. => \\.
//String regStr = "\\.";
//String regStr = "\\d\\d\\d";
//String regStr = "\\d{3}";
//String regStr = "[a-z]";//匹配 a-z之间任意一个字符
//String regStr = "[A-Z]";//匹配 A-Z之间任意一个字符
//String regStr = "abc";//匹配 abc字符串【默认区分大小写】
//String regStr = "(?i)abc";//匹配 abc字符串【不区分大小写】
//String regStr = "[0-9]";//匹配 0-9 之间任意一个字符
//String regStr = "[^a-z]";//匹配 不在 a-z 之间任意一个字符
//String regStr = "[^0-9]";//匹配 不在 0-9 之间任意一个字符
//String regStr = "[abcd]";//匹配 在 abcd 中任意一个字符
//String regStr = "\\D";//匹配 不在 0-9 的任意一个字符
//String regStr = "\\w";//匹配 大小写英文字母, 数字,下划线
//String regStr = "\\W";//匹配 等价于 [^a-zA-Z0-9_]
//\\s 匹配任何空白字符(空格,制表符等)
//String regStr = "\\s";
//\\S 匹配任何非空白字符 ,和\\s 刚好相反
//String regStr = "\\S";
//. 匹配出 \n 之外的所有字符,如果要匹配.本身则需要使用 \\.
String regStr = ".";
//说明
//1. 当创建 Pattern 对象时,指定 Pattern.CASE_INSENSITIVE, 表示匹配是不区分字母大小写.
// Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE)
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content2);
while (matcher.find()){
System.out.println("找到:"+matcher.group(0));
}
}
}
2.4元字符-选择匹配符
在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以四配那个,这时你需要用到选择匹配符号
符号 | 含义 | 事例 | 解释 |
---|---|---|---|
| | 匹配“\”之前或之后的表达式 | ab|cd | ab或者cd |
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 选择匹配符使用
* */
public class Regexp03 {
public static void main(String[] args) {
String content2 = "abc.ABC& ~li 李四 力";
String regStr = "li|李|里|力";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content2);
while (matcher.find()){
System.out.println("找到:"+matcher.group(0));
}
}
}
2.5元字符-限定符
用于指定其前面的字符和组合项连续出现多少次
符号 | 含义 | 事例 | 说明 | 匹配输入 |
---|---|---|---|---|
* | 指定字符重复0次或n次(无要求)零到多 | (abc)* | 仅包含任意个abc的字符串,等效于\w* | abc,abcabcabc |
+ | 指定字符重复1次或n次((至少-次)1到多 | m+(abc)* | 以至少1个m开头,后接任意个abc的字符串 | m、abc、mabca |
? | 指定字符重复0次或1次(最多-次)0到1 | m+abc? | 以至少1个m开头,后接ab或abc的字符串 | mab,mabc,mmmmabc,mmabc |
{n} | 只能输入n个字符 | [abcd]{3} | 由abcd中字母组成的任意长度为3的字符串 | abc,dbc,adc |
{n,} | 指定至少n个匹配 | [abcd]{3,} | 由abcd中字母组成的任意长度不小3的字符串 | aab,dbc,aaabdc |
{n,m} | 指定至少n个但不多于m个匹配 | [abcd]{3,5} | 由abcd中字母组成的任意长度不小于3.不大于5的字符串 | abc,abcd,aaaaa,bcdab |
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 限定符的使用
* */
public class Regexp04 {
public static void main(String[] args) {
String content = "a1211111aaaaaahello11";
//a{3},1{4},\\d{2}
//String regStr = "a{3}";// 表示匹配 aaa
//String regStr = "1{4}";// 表示匹配 1111
//String regStr = "\\d{2}";// 表示匹配 两位的任意数字字符
//a{3,4},1{4,5},\\d{2,5}
//细节:java 匹配默认贪婪匹配,即尽可能匹配多的
//String regStr = "a{3,4}"; //表示匹配 aaa 或者 aaaa
//String regStr = "1{4,5}"; //表示匹配 1111 或者 11111
//String regStr = "\\d{2,5}"; //匹配 2 位数或者 3,4,5位数
//1+
//String regStr = "1+"; //匹配一个 1 或者多个 1
//String regStr = "\\d+"; //匹配一个数字或者多个数字
//String regStr = "1*"; //匹配 0 个 1 或者多个 1
//?的使用, 遵守贪婪匹配
String regStr = "a1?"; //匹配 a 或者 a1
Pattern pattern = Pattern.compile(regStr/*, Pattern.CASE_INSENSITIVE*/);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
}
2.6 正则定位符
符号 | 含义 | 事例 | 说明 | 匹配输入 |
---|---|---|---|---|
^ | 指定起始字符 | ^[0-9]+[a-z]* | 以至少一个数字开头,后接任意小写字母的字符串 | 123,6aa,555ede |
$ | 指定结束字符 | ^[0-9]\\-[a-z]*+$ | 以一个数字开头后接连字符“-”,并以至少1个小写字母结束的字符串 | 1-a |
\\b | 匹配目标字符串的边界 | code\\b | 这里说的是字符串的边界指的是字串有空格,或者时目标字符串的结束位置 | codeshunpp pcode nmcode |
\\B | 匹配目标字符串的非边界 | code\\B | 和\b含义相反 | code shunpp pcode nmcode |
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Regexp05 {
public static void main(String[] args) {
//String content = "123-abc";
String content= "codeshun sscode hkcode";
///以至少 1 个数字开头,后接任意个小写字母的字符串
//String regStr = "^[0-9]+[a-z]*";
//以至少 1 个数字开头, 必须以至少一个小写字母结束
//String regStr = "^[0-9]+\\-[a-z]+$";
//表示匹配边界的 han[这里的边界是指:被匹配的字符串最后, // 也可以是空格的子字符串的后面]
//String regStr = "code\\b";
//和\\b 的含义刚刚相反
String regStr = "code\\B";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println("找到 " + matcher.group(0));
}
}
}
2.7 分组
常用分组构造形式 | 说明 |
---|---|
(pattern) | 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。 |
(?<name>pattern) | 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如(?‘name’) |
(?:pattern) | 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符(|)组合模式部件的情况很有用。例如,"industr(?:y|ies)是比"industry|industries’更经济的表达式。 |
(?=pattern) | 它是一个非捕获匹配。例如,“Windows (?=95|98|NT|2000)’匹配"Windows 2000"中的"Windows”,但不匹配"Windows 3.1"中的"Windows"。 |
(!=pattern) | 该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,“Windows(?!95|98|NT|2000)”匹配"Windows 3.1"中的“Windows”,但不匹配"Windows 2000"中的"Windows"。 |
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Regexp06 {
public static void main(String[] args) {
String content= "codeshun s6756 hk1009code";
//非命名分组
// 1. matcher.group(0) 得到匹配到的字符串
// 2. matcher.group(1) 得到匹配到的字符串的第 1 个分组内容
// 3. matcher.group(2) 得到匹配到的字符串的第 2 个分组内
//String regStr = "(\\d\\d)(\\d)(\\d)";//匹配四个数字的字符串
//命名分组: 即可以给分组取名
String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";//匹配 4 个数字的字符串
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
//System.out.println("找到: " + matcher.group(0));
//System.out.println("第1分组: " + matcher.group(1));
//System.out.println("第2分组: " + matcher.group(2));
//System.out.println("第3分组: " + matcher.group(3));
System.out.println("找到: " + matcher.group(0));
System.out.println("第1分组: " + matcher.group(1));
System.out.println("第1分组【通过组名】: " + matcher.group("g1"));
System.out.println("第2分组: " + matcher.group(2));
System.out.println("第2分组【通过组名】: " + matcher.group("g2"));
}
}
}
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 非捕获分组,语法奇特
* */
public class Regexp07 {
public static void main(String[] args) {
String content= "code小明同学 s6756小明家长 hk小明对象";
//String regStr = "小明同学|小明家长|小明对象";
//上面的写法可以等价非捕获分组, 注意:不能 matcher.group(1)
// String regStr = "小明(?:家长|对象|同学)";
//找到 '小明' 这个关键字,但是要求只是查找小明同学和小明对象 中包含有的小明
//下面也是非捕获分组,不能使用 matcher.group(1)
//String regStr = "小明(?=对象|同学)";
//找到 '小明' 这个关键字,但是要求只是查找 不是 (小明同学 和 小明对象) 中包含有的小明
//下面也是非捕获分组,不能使用 matcher.group(1)
String regStr = "小明(?!同学|对象)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println("找到: " + matcher.group(0));
}
}
}
应用实例
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexpTest01 {
/*
*
* 1.汉字
* 2邮政编码
* 要求:是1-9开头的一个六位数.比如:1238903.
* 3.QQ号码
*要求:是1-9开头的一个(5位数-10位数)比如:12389,1345687,187698765
* 4.手机号码
*要求:必须以13,14,15,18开头的11位数,比如13588889999
*5.URL:https://www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326
* */
public static void main(String[] args) {
//1.汉字
String content1 = "中华人民共和国";
String regStr1 = "^[\u0391-\uffe5]+$";
Pattern pattern1 = Pattern.compile(regStr1);
Matcher matcher1 = pattern1.matcher(content1);
if (matcher1.find()){
System.out.println("汉字满足格式");
}else{
System.out.println("汉字不满足格式");
}
//2.邮政编码
String content2 = "232124";
String regStr2 = "^[1-9]\\d{5}$";
Pattern pattern2 = Pattern.compile(regStr2);
Matcher matcher2 = pattern2.matcher(content2);
if (matcher2.find()){
System.out.println("邮政编码满足格式");
}else{
System.out.println("邮政编码不满足格式");
}
//3.QQ号码
String content3 = "124";
String regStr3 = "^[1-9]\\d{4,9}$";
Pattern pattern3 = Pattern.compile(regStr3);
Matcher matcher3 = pattern3.matcher(content3);
if (matcher3.find()){
System.out.println("QQ号码满足格式");
}else{
System.out.println("QQ号码不满足格式");
}
//4.手机号码
String content4 = "13528830843";
String regStr4 = "^1[3|4|5|8]\\d{9}$";
Pattern pattern4 = Pattern.compile(regStr4);
Matcher matcher4 = pattern4.matcher(content4);
if (matcher4.find()){
System.out.println("手机号码满足格式");
}else{
System.out.println("手机号码不满足格式");
}
//5.url
//String content5 = "https://www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326";
String content5 = "www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326";
/*
* 1、确定开始以http://或者https://
* 2.然后通过 ([\w-]+\.)+[\w-]+ 匹配 www.bilibili.com
* 3. /video/BV1fh411y7R8?from=sear 匹配(\/[\w-?=&/%.#]*)?
* */
String regStr5 = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$";//注意:[. ? *]表示匹配就是.本身
Pattern pattern5 = Pattern.compile(regStr5);
Matcher matcher5 = pattern5.matcher(content5);
if (matcher5.find()){
System.out.println("url满足格式");
}else{
System.out.println("url不满足格式");
}
}
}
正则表达式三个常用类
java.util.regex 包主要包括以下三个类 Pattern类、Matcher类和PatternSyntaxException
Pattern类
pattern对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern对象,调用其公共静态方法,它返回一个 Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);
Matcher类
Matcher对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher 也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个 Matcher 对象
· PatternSyntaxException
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
package regexp;
import java.util.regex.Pattern;
/*
* matches方法,用于整体匹配,在验证输入的字符串是否满足条件使用
*返回 true|false
* */
public class PatternMethod {
public static void main(String[] args) {
String content = "hello abcd hi,中国人民";
//String regStr = "hello";
String regStr = "hello.*";
boolean matches = Pattern.matches(regStr, content);
System.out.println("整体匹配="+matches);
}
}
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* Matcher 类的常用方法
* */
public class MatcherMethod {
public static void main(String[] args) {
String content = "hello edu jack codetom hello smith hello code code";
String regStr = "hello";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("=================");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("找到: " + content.substring(matcher.start(), matcher.end()));
}
//整体匹配方法,常用于,去校验某个字符串是否满足某个规则
System.out.println("整体匹配=" + matcher.matches());
//完成如果 content 有 code 替换成 中国人
regStr = "code";
pattern = Pattern.compile(regStr);
matcher = pattern.matcher(content);
//注意:返回的字符串才是替换后的字符串 原来的 content 不变化
String newContent = matcher.replaceAll("中国人");
System.out.println("newContent=" + newContent);
System.out.println("content=" + content);
}
}
分组、捕获、反向引用
(\\d\\d)(\\d\\d)
1.分组
我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。
2捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
3.反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用
,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号
,外部反向引用$分组号
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 分组、捕获、反向引用
* */
public class Regexp08 {
public static void main(String[] args) {
String content = "hello66666 jack333 tom11 jack22 xxx155134 yyy12321-333999111";
//String regStr = "(\\d)\\1";//1.要匹配两个连续的相同数字:(\\d)\\1
//String regStr = "(\\d)\\1{4}";//2.要匹配五个连续的相同数字:(\\d)\\1{4}
//String regStr = "(\\d)(\\d)\\2\\1";//3.要匹配个位与千位相同,十位与百位相同的数5225,1551 (\\d)(\\d)\\2\\1
String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";//请在字符串中检索商品编号,形式如:12321-333999111这样的号码,要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位要相同
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println("找到:"+matcher.group(0));
}
}
}
package regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Regexp09 {
public static void main(String[] args) {
String content = "我....我要....学学学学....编程 java!";
//1. 去掉所有的.
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");
System.out.println("content=" + content);
//2. 去掉重复的字 我我要学学学学编程 java!
// 思路
//(1) 使用 (.)\\1+
//(2) 使用 反向引用$1 来替换匹配到的内容
// 注意:因为正则表达式变化,所以需要重置 matcher
// pattern = Pattern.compile("(.)\\1+");
// matcher = pattern.matcher(content);
// while (matcher.find()) {
// System.out.println("找到:" + matcher.group(0));
// }
// //使用 反向引用$1
// content = matcher.replaceAll("$1");
// System.out.println("content:" + content);
//3. 使用一条语句 去掉重复的字 我我要学学学学编程 java!
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
System.out.println("content=" + content);
}
}
String类中使用正则表达式
package regexp;
public class StringReg {
public static void main(String[] args) {
String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其" +
"获得了Apple公司Mac OS X的工业标准的支持。2001年9月24日,J2EE1.3发布。" +
"2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升";
//使用正则表达式方式,将JDK1.3和JDK1.4替换成JDK
content = content.replaceAll("JDK1.3|JDK1.4", "JDK");
System.out.println(content);
//要求 验证一个 手机号, 要求必须是以 138 139 开头的
content = "13888889999";
if (content.matches("1(38|39)\\d{8}")) {
System.out.println("验证成功");
} else {
System.out.println("验证失败");
}
//要求按照 # 或者 - 或者 ~ 或者 数字 来分割
System.out.println("=======================================================");
content = "hello#abc-jack12smith~北京";
String[] split = content.split("#|-|~|\\d+");
for (String s : split) {
System.out.println(s);
}
}
}