Java高级语言特性

视频来自:阿里云大学—Java高级语言特性

一、多线程

1.1 进程与线程

进程:在同一个时间段上会有多个程序依次执行,但是在同一个时间点上只会有一个进程执行。

线程:是在进程基础之上划分的更小的程序单元,线程是在进程基础上创建并且使用的,所以线程依赖于进程的支持,但是线程的启动速度要比进程快许多,所以当使用多线程进行并发处理的时候,其执行的性能要高于进程。

操作系统的设计,可以归结为三点:

  • 以多进程形式,允许多个任务同时运行;

  • 以多线程形式,允许单个任务分成不同的部分运行;

  • 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

1.2 实现多线程

  • Thread实现多线程
    • 继承java.lang.Thread的程序类
    • 覆写Thread类中提供的一个run()方法(public void run())
    • 使用start()方法完成启动(public void start())

run()方法牵扯到资源调度问题,所以用start()方法启动多线程。并且每一个线程类的对象只允许启动一次,如果重复启动则就抛出此异常“IllegalThreadStateException”

class MyThread extends Thread {//线程的主体
	private String title;

	public MyThread(String title) {
		this.title = title;
	}

	@Override
	public void run() {//线程的主体方法
		for (int x = 0; x < 10; x++) {
			System.out.println(this.title + "运行,x=" + x);
		}
	}
}
public class Testdemo {

	public static void main(String[] args) {
		new MyThread("线程A").start();//启动线程
		new MyThread("线程B").start();
		new MyThread("线程C").start();
	}
}

由start()源码发现,线程启动依赖于JNI技术(Java Native Interface)调用本地操作系统函数start0()来实现资源调度,依赖于不同操作系统。

在这里插入图片描述

  • Runnable接口实现多线程
    • 定义java.lang.Runnable接口
    • 覆写接口中提供的一个run()方法
    • 调用Thread类中的构造方法,Thread(Runnable target)
@FunctionalInterface		// 从JDK1.8引入了Lambda表达式之后就变为了函数式接口
public interface Runnable {
	public void run​() ;
}
class MyThread implements Runnable {
	private String title;

	public MyThread(String title) {
		this.title = title;
	}

	@Override
	public void run() {//线程的主体方法
		for (int x = 0; x < 10; x++) {
			System.out.println(this.title + "运行,x=" + x);
		}
	}
}

public class Testdemo {

	public static void main(String[] args) {
		new Thread(new MyThread("线程A")).start();//调用Thread提供的构造方法
		new Thread(new MyThread("线程B")).start();
		new Thread(new MyThread("线程C")).start();
	}
}

此时线程主体类没有了单继承局限,是一个标准设计。

由于符合SAM(单抽象方法接口),可以使用Lamda表达式简化

Runnable run = ()->{//使用Lamda表达式,可以在使用端实现接口类
	for(int x=0; x<3; x++) {
		String title = "线程-"+x;
		for(int y=0; y<10; y++) {
			System.out.println(title+"运行,x="+y);
		}
	}
};
new Thread(run).start();

永恒用Thread(Runnable run).start()启动多线程

Runnable与Thread关系:

Thread类也是Runnable接口的子类

在这里插入图片描述

​ 在进行Thread启动多线程的时候调用的是start()方法,而后找到的run()方法。

​ 当通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存;在start()方法执行的时候会调用Thread类中的run()方法,而这个run()方法去调用Runnable接口子类被覆写过的run()方法。

在这里插入图片描述

Thread类实现多线程对象抢占资源

范例:利用卖票程序来实现多个线程的资源并发访问

class MyThread implements Runnable {
	private int ticket = 5;//总票数

	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {

			if (this.ticket > 0) {
				System.out.println("现在的票数是" + this.ticket--);
			}
		}
	}
}
public class Testdemo {

	public static void main(String[] args) {
		MyThread md = new MyThread();
		new Thread(md).start();//1号线程启动
		new Thread(md).start();//2号线程启动
		new Thread(md).start();//3号线程启动
	}
}
  • Callable接口实现多线程

Runnable接口有一个缺点:当线程执行完毕之后无法获取一个返回值 ,所以从JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable接口.

@FunctionalInterface
public interface Callable<V> {//返回值类型V
	public V call​() throws Exception ;
}

在这里插入图片描述

实线表示继承,细虚线表示构造方法,粗虚线表示子类

总而言之:找了FutureTask当中间点

范例:使用Callable实现多线程处理

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String>{

	@Override
	public String call() throws Exception {
		for(int x=0; x<10; x++) {
			System.out.println("线程进行中,x="+x);
		}
		return "线程执行完毕";
	}
	
}

public class Testdemo {
//线程启动方法只有new Thread().start()
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask<String> task = new FutureTask<String>(new MyThread());//将callable中线程方法传入FutureTask
		new Thread(task).start();//传入Thread
		System.out.println(task.get());
	}
}

面试题:请解释Runnable与Callable的区别?

  • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
  • java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值;
  • 线程执行流程:

​ 对于多线程的开发而言,编写程序的过程之中总是按照:定义线程主体类,而后通过Thread类进行线程的启动,调用start()方法,线程开始就绪,具体执行由操作系统调度。

在这里插入图片描述

1.任何一个线程的对象都应该使用Thread类进行封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行;
2.进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源,而后这个线程就将进入到阻塞状态,随后重新回归到就绪状态;
3.当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。

1.3 线程常用操作方法

1.线程的命名与取得

在Thread类中定义了

1.构造方法:public Thread(Runnable target, String name)2.设置名字:public final void setName(String name)3.取得名字:public final String getName()

​ 对于线程对象的获得是不可能只是依靠一个this来完成的,因为线程的状态不可控,在Thread类里面提供有获取当前线程的方法;

获取当前线程:public static Thread currentThread()//返回当前线程的名字    
Thread.currentThread().getName()

​ 当开发者为线程设置名字的时候就使用设置的名字,而如果没有设置名字,则会自动生成一个不重复的名字,这种自动的属性命名主要是依靠了static属性完成的。

class MyThread implements Runnable {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread() ;
		new Thread(mt,"线程对象").start(); // 设置了线程的名字
		mt.run(); // 对象直接调用run()方法
	}
}
  • 主方法也是一个主线程

  • 每当使用java命令执行程序的时候就表示启动了一个JVM的进程(java.exe),一台电脑上可以同时启动若干个JVM进程,所以每一个JVM进程都会有各自的线程。

  • 创建子线程的目的是复杂的逻辑可以交给子线程去执行,而不耽误主线程的进度

范例:子线程处理

package cn.mldn.demo;
public class ThreadDemo {
	public static void main(String[] args) throws Exception{
		System.out.println("1。执行操作任务一。");
		new Thread(()->{
			int temp = 0 ;
			for (int x = 0;x < Integer.MAX_VALUE ; x ++) {
				temp += x ;
			}
		}).start();
		System.out.println("2。执行操作任务二。");
		System.out.println("n。执行操作任务N。");
	}
}

主线程负责处理整体流程,而子线程负责处理耗时操作。

2.线程的休眠

在Thread类之中定义的休眠方法如下:

public static void sleep(long millis) throws InterruptedExceptionpublic static void sleep(long millis,int nanos) throws InterruptedException

在进行休眠的时候有可能会产生中断异常“InterruptedException”,中断异常属于Exception的子类,所以证明该异常必须进行处理。

产生多个线程对象进行休眠处理

public class TestXC {
    public static void main(String[] args) {
        for (int x = 0; x < 5; x++) {
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                 	System.out.println(Thread.currentThread().getName()+":i="+i);
                    try {
                        Thread.sleep(1000);//暂缓执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"线程对象-"+x).start();
        }
    }
}

线程执行有先后顺序:

在这里插入图片描述

3.线程的中断

打断是由其它线程完成的,在Thread类里面提供有这种中断执行的处理方法:

判断线程是否被中断:public boolean isInterrupted​()//中断返回true,没中断返回false
中断线程执行:public void Interrupt()
public class TestXC {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            System.out.println("线程开始执行");
            try {
                Thread.sleep(10000);
                System.out.println("线程结束");
            } catch (InterruptedException e) {
                System.out.println("线程被打断了");
            }
        });
        thread.start();
        Thread.sleep(1000);//休眠1s后打断
        if(!thread.isInterrupted()){
            System.out.println("1s后打断线程");
            thread.interrupt();
        }
    }
}

所有正在执行的线程都是可以被中断的,中断线程必须进行异常的处理。

4.线程的强制执行

强制执行:public final void join() throws InterruptedException

在进行线程强制执行的时候一定要获取强制执行线程对象之后才可以执行join()调用:

public class TestXC {
    public static void main(String[] args) throws InterruptedException {
       Thread mainThread = Thread.currentThread();//获取主线程对象
        Thread thread = new Thread(()->{
           for (int x = 0; x < 100; x++) {
               try {
                   Thread.sleep(100);//进程休眠100ms
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               if(x == 10){//强制执行的条件
                   try {
                       mainThread.join();//进程的强制执行
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }           System.out.println(Thread.currentThread().getName()+":x="+x);
           }
       },"子线程");
       thread.start();
        for (int i = 0; i < 100; i++) {
            Thread.sleep(100);
            System.out.println("[main线程]i="+i);
        }
    }
}

5.线程的礼让

礼让:public static void yield()
    Thread.yield();//该线程礼让

礼让执行的时候每一次调用yield()礼让方法都只会礼让一次当前的资源(另一个线程先执行)

6.线程优先级

线程的优先级越高越有可能先执行(越有可能先抢占到资源)

设置优先级:public final void setPriority​(int newPriority)
获取优先级:public final int getPriority​()
    
最高优先级:public static final int MAX_PRIORITY、10
中等优先级:public static final int NORM_PRIORITY、5
最低优先级:public static final int MIN_PRIORITY、1

主线程是属于中等优先级5,而默认创建的线程也是中等优先级5。

7.线程的同步与死锁

  • 同步问题的引出:

在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程访问同一资源的时候如果处理不当就会产生数据的错误操作。

三个线程同时卖票:

在这里插入图片描述

  • 同步问题的解决:

解决同步问题的关键是锁,指的是当某一个线程执行操作的时候,其它线程外面等待:

在这里插入图片描述

1.利用同步代码块进行处理:

synchronized(同步对象) {
	同步代码操作
}
class MyThread implements Runnable {
	private int ticket = 10 ; // 总票数为10张
	@Override
	public void run() {
		while (true) {
			synchronized(this) { // 每一次只允许一个线程进行访问
				if (this.ticket > 0) {
					try {
						Thread.sleep(100); // 模拟网络延迟
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket --);			
				} else {
					System.out.println("***** 票已经卖光了 ********");
					break ;
				}
			}
		} 
	}
}
public class ThreadDemo {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread() ;
		new Thread(mt,"票贩子A").start();
		new Thread(mt,"票贩子B").start();
		new Thread(mt,"票贩子C").start();
	}
}

加入同步处理之后,程序的整体的性能下降了。

1.利用同步方法解决:只需要在方法定义上使用synchronized关键字

class MyThread implements Runnable {
    private int ticket = 10;
    public synchronized boolean sale(){
        if(this.ticket > 0){
            try {
                Thread.sleep(100);//休眠100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖票,票 = " + ticket--);
            return true;
        }else{
            System.out.println("票已经卖完");
            return false;
        }
    }
    @Override
    public void run() {
        while (this.sale()){
            ;//调用主体方法
        }
    }
}

public class TestXC {
    public static void main(String[] args) throws Exception{
        MyThread md = new MyThread();
        new Thread(md,"票贩子A").start();
        new Thread(md,"票贩子B").start();
        new Thread(md,"票贩子C").start();
    }
}
  • 死锁

死锁是在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态,等待着对方先让出资源。

死锁实际上是一种开发中出现的不确定的状态,有的时候代码如果处理不当则会不定期出现死锁,这是属于正常开发中的调试问题。

若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。

1.4 生产者-消费者模型

流程如下:

  • 生产者负责信息内容的生产;
  • 每当生产者生产完成一项完整的信息之后消费者要从这里面取走信息;
  • 如果生产者没有生产完则消费者要等待它生产完成,如果消费者还没有对信息进行消费,同生产者应该等待消费处理完成后再继续生产。

设计:

  • 既然生产者与消费者是两个独立的线程,那么这两个独立的线程之间就需要有一个数据的保存集中点,那么可以单独定义一个Message类实现数据的保存。

在这里插入图片描述

  • 由于多线程并发生产与消费,用synchronized关键字解决数据同步的处理问题,在Message对象中完成

  • 为解决生产者与消费者依次完成,没有重复生产或消费的问题。最好的解决方案就是使用等待与唤醒机制。而对于等待与唤醒的机制主要依靠的是Object类中提供的方法处理的

    死等:public final void wait​() throws InterruptedException;
    设置等待时间:public final void wait​(long timeout) throws InterruptedException;
    唤醒第一个等待线程:public final void notify​();
    唤醒全部等待线程:public final void notifyAll​()
class Message{
    private String title;
    private String content;
    // flag == true:只允许生产者生产,消费者等待
    // flag == false:只允许消费者消费,生产者等待
    private boolean flag = true;

    public synchronized void setTitle(String title, String content){
        while(flag == false){
            try {
                super.wait();//生产进程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.title = title;
        this.content = content;
        //生产完成
        this.flag = false;//更改标志
        super.notify();//消费被唤醒
    }
    public synchronized String getTitle(){//
        while(flag == true){
            try {
                super.wait();//消费进程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.flag = true;//更改标志
        super.notify();
        return this.title + "---" + this.content;//在return 后的代码都将不执行


    }
}
class Producer implements Runnable{//定义生产者线程
    private Message msg;
    public Producer(Message msg){
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            if(x % 2 == 0){
                try {
                    Thread.sleep(100);//设置休眠时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                msg.setTitle("研究生","浙江大学");
            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                msg.setTitle("本科生","山东大学");
            }
        }
    }
}
//定义消费者线程
class  Consumer implements Runnable{
    private Message msg;
    public Consumer(Message msg){
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(msg.getTitle());
        }
    }
}
public class TestXC {
    public static void main(String[] args) throws Exception{
        Message msg = new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}

1.5 多线程深入讨论

1.停止线程

利用其他线程来控制flag的内容,使得线程的停止不会突然中止导致可能死锁,而是会在执行中判断flag的内容来完成。

public class XC {
    private static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            long x = 0;
            while (flag) {//每休眠50ms判断一次
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "x=" + x++);
            }
        },"执行线程").start();
        Thread.sleep(200);
        flag = false;
    }
}

2. 后台守护线程

如果现在主线程的程序或者其它的线程还在执行的时候,那么守护线程将一直存在,并且运行在后台状态。

Thread类里面提供有如下的守护线程的操作方法:
1.设置为守护线程:public final void setDaemon​(boolean on)2.判断是否为守护线程:public final boolean isDaemon​()
public class XC {

    public static void main(String[] args)  {
        Thread userThread = new Thread(()->{
            for (int x = 0; x < 10; x++) {//当用户线程结束,守护线程自动结束
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"x="+x);
            }
        },"用户线程");

        Thread DaemonThread = new Thread(()->{
            for (int x = 0; x < 100; x++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"x="+x);
            }
        },"守护线程");
        DaemonThread.setDaemon(true);//设置为守护线程
        userThread.start();
        DaemonThread.start();
    }
}

3. Volatile关键字

在多线程的定义之中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。

在正常进行变量处理的时候往往会经历如下的几个步骤:

  • 获取变量原有的数据内容副本;
  • 利用副本为变量进行数学计算;
  • 将计算后的变量,保存到原始空间之中;

在这里插入图片描述

而如果一个属性上追加了volatile关键字,表示的就是不使用副本,而是直接操作原始变量,相当于节约了:拷贝副本、重新保存的步骤。

面试题:请解释volatile 与 synchronized 的区别?

  • volatile 主要在属性上使用,而synchronized 是在代码块与方法上使用的;
  • volatile 无法描述同步的处理,它只是一种直接内存的处理,避免了副本的操作,而synchronized 是实现同步的;

1.6 多线程案例分析

1. 数字加减

需求:设计四个线程对象,两个进行加操作,两个进行减操作

class Resource{//定义资源操作类
    private volatile int num = 0;
    private boolean flag = true;
    //flag = true 执行add(),无法执行减操作
    public synchronized void add() throws InterruptedException {
        while (flag == false){//把进来的所有减线程休眠
            super.wait();
        }
        this.num++;
        System.out.println(Thread.currentThread().getName()+"num ="+ num);
        this.flag = false;//加线程休眠
        super.notifyAll();//唤醒其他线程
    }
    public synchronized void sub() throws InterruptedException {
        while (flag == true){//把进来的加线程休眠
            super.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"num ="+ num);
        this.flag = true;//减线程休眠
        super.notifyAll();//唤醒所有等待线程
    }

}
class AddThread implements Runnable{//定义加法线程主体
    private Resource resource;
    public AddThread(Resource resource){
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int x = 0; x < 50; x++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.resource.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
class SubThread implements Runnable{//定义减法线程主体
    private Resource resource;
    public SubThread(Resource resource){
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int x = 0; x < 50; x++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.resource.sub();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class XC{
    public static void main(String[] args) {
        Resource resource = new Resource();
        AddThread at = new AddThread(resource);
        SubThread st = new SubThread(resource);
        new Thread(at,"加线程A").start();
        new Thread(at,"加线程B").start();
        new Thread(st,"减线程X").start();
        new Thread(st,"减线程Y").start();
    }
}

2. 生产电脑

class Computer{
    private String name;
    private int price;
    private static int count = 0;
    public Computer(String name, int price){
        this.name = name;
        this.price = price;
        count++;
    }
    public String toString() {
        return "【电脑" + count + "】:" + " 型号:" + this.name + "  价格:" + this.price;
    }
}
class Resource{
    private Computer computer;
    public synchronized void set() throws InterruptedException {//生产电脑
        while (computer != null){
            super.wait();//当电脑还没搬走,生产线程等待
        }
        this.computer = new Computer("Dell",5000);
        System.out.println("生产电脑"+computer);
        super.notifyAll();

    }
    public synchronized void get() throws InterruptedException {//搬运电脑
        while(computer == null){
            super.wait();//电脑还没生产,搬运线程等待
        }
        System.out.println("搬运电脑"+computer);
        this.computer = null;
        super.notifyAll();
    }
}
class Producer implements Runnable{
    private Resource resource;
    public Producer(Resource resource){
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int x = 0; x < 50; x++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                resource.set();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Consumer implements Runnable{
    private Resource resource;
    public Consumer(Resource resource){
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int x = 0; x < 50; x++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                resource.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Computer_produce {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(new Producer(resource)).start();
        new Thread(new Consumer(resource)).start();
    }
}

3.抢答器的实现

实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。

class Mythread implements Callable<String>{
    private static boolean flag = false;
    @Override
    public String call() throws Exception {
        synchronized (this){//同步
            while (this.flag == false){
                this.flag = true;
                return Thread.currentThread().getName()+":抢答成功";
            }
                return Thread.currentThread().getName()+":抢答失败";
        }
    }
}
public class Competitor {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> fskA = new FutureTask<String>(new Mythread());
        FutureTask<String> fskB = new FutureTask<String>(new Mythread());
        FutureTask<String> fskC = new FutureTask<String>(new Mythread());
        new Thread(fskA,"线程A").start();
        new Thread(fskB,"线程B").start();
        new Thread(fskC,"线程C").start();
        System.out.println(fskA.get());
        System.out.println(fskB.get());
        System.out.println(fskC.get());
    }
}

二、Java类库

2.1 基础类库

1. StringBuffer类

String 类无法修改内容,专门提供有一个StringBuffer类可以实现字符串内容的修改处理,这种修改指的并不是针对于静态常量池的改变。

构造方法:public StringBuffer();
构造方法:public StringBuffer(String str),接收初始化字符串内容;
数据追加:public StringBuffer append(数据类型 变量),相当于字符串中的“+” 操作;
------------------------------------------------------------------------
插入数据:public StringBuffer insert(int offset,数据类型 b)
删除指定范围的数据:public StringBuffer delete(int start,int end)
字符串内容反转:public StringBuffer reverse()
public class Stringbuffer {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("Hello");
        change(str);
        System.out.println(str);
    }
    public static void change(StringBuffer temp){
        temp.append(",World");
    }
}
  • 所有的“+”在编译之后都变为了StringBuffer中的append()方法;
  • String类对象变为StringBuffer可以依靠StringBuffer类的构造方法或者使用append()方法;
  • 所有类对象都可以通过toString()方法将其变为String类型;

面试题:请解释String、StringBuffer、StringBuilder的区别?

  • String类是字符串的首选类型,其最大的特点是内容不允许修改;
  • StringBuffer与StringBuilder类功能近似,内容允许修改;
  • StringBuffer是在JDK1.0的时候提供的,属于线程安全的操作,全部使用了synchronized关键字进行标注;而StringBuilder是在JDK1.5之后提供的,属于非线程安全的操作。

2. CharSequence接口

CharSequence是一个描述字符串结构的接口,在这个接口里面有:String类、StringBuffer类、StringBuilder类

在这里插入图片描述

  • 看见了CharSequence 描述的就是一个字符串
  • CharSequence 本身是一个接口,在该接口之中也定义有如下操作方法:
获取指定索引字符:public char charAt(int index);
获取字符串的长度:public int length();
截取部分字符串:public CharSequence subSequence(int start, int end)CharSequence str = "fkd.zju.com" ; // 子类实例向父接口转型

3. AutoCloseable接口

AutoCloseable主要是用于日后进行资源开发的处理上,以实现资源的自动关闭(释放资源),例如:在以后进行文件、网络、数据库开发的过程之中由于服务器的资源有限,所以使用之后一定要关闭资源,这样才可以被更多的使用者所使用。

该接口只提供有一个方法:

  • 关闭方法:public void close() throws Exception;
  • 自动关闭资源:try(实现了AutoCloseable的类){}catch(Exception e){}实现

在这里插入图片描述

interface IMessage extends AutoCloseable{//继承该接口需要覆写close方法
    public void send();
}
class Netconnect implements  IMessage{
    private String message;
    public Netconnect(String message){
        this.message = message;
    }
    public boolean open(){
        System.out.println("【网络资源】:打开");
        return true;
    }
    @Override
    public void send() {
        if(this.open()){
            System.out.println("【发送消息】:"+this.message);
        }
    }
    @Override
    public void close() throws Exception {
        System.out.println("【网络资源】:关闭");
    }
}
public class Connect {
    public static void main(String[] args) throws Exception{
        //try()表示TRY-WITH-RESOURCE
        //所有实现了AutoCloseable的类都可以放在在括号中,这样就可以自动关闭资源。
        try ( IMessage msg = new Netconnect("FKD")){
            msg.send();
        }catch (Exception e){}
    }
}

4. Runtime类

JVM通过系统函数管理操作系统,Runtime类描述的是JVM运行时的状态,由于要保存操作信息,Runtime类属于单例设计模式,只有一个实例化对象

在这里插入图片描述

获取实例化对象:public static Runtime getRuntime()Runtime run = Runtime.getRuntime();//获取实例化对象
System.out.println(run.availableProcessors());//获得本机CPU核数

其他重要方法:

获取最大可用内存空间:public long maxMemory(),默认的配置为本机系统内存的4分之1;
获取可用内存空间:public long totalMemory(),默认的配置为本机系统内存的64分之1;
获取空闲内存空间:public long freeMemory();
手工进行GC处理:public void gc();

返回的都是字节(B)。

面试题:请问什么是GC?如何处理?

  • GC(Garbage Collector)垃圾收集器,是可以由系统自动调用的垃圾释放功能,或者使用Runtime类中的gc()手工调用。

5. System类

数组拷贝:public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)//任意数组
获取当前的日期时间数值:public static long currentTimeMillis();
进行垃圾回收:System.gc()=====  Runtime.getRuntime.gc();

范例:操作耗时的统计

public static void main(String[] args) throws Exception{
        long start = System.currentTimeMillis();
        String str = "";
        for (int x  = 0; x  < 30000; x ++) {
            str += x ; // 产生大量的垃圾空间
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);//ms为单位2202ms
    }

6.Cleaner类

提供了给用户收尾的操作,每一个实例化对象在回收之前至少给它一个喘息的机会(回收前操作)

在对象中覆写finalize()方法即可

@Deprecated(since="1.9")
protected void finalize​() throws Throwable
class Member{
    public Member(){
        System.out.println("[构造]成员诞生");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("[回收]");
        throw new Exception("回收遗言");//抛出异常不影响
    }
}

public class Connect {
    public static void main(String[] args) {
        Member member = new Member();
        member = null; //变为垃圾
        Runtime run = Runtime.getRuntime();
        run.gc();//垃圾回收
        System.out.println("成员已经被完全处理");
    }
}

对于对象手工回收释放,从JDK1.9开始建议开发者使用AutoCloseable或者使用java.lang.ref.Cleaner类进行回收处理(Cleaner也支持有AutoCloseable处理)

package cn.mldn.demo;
import java.lang.ref.Cleaner;
class Member implements Runnable {
	public Member() {
		System.out.println("【构造】在一个雷电交加的日子里面,林强诞生了。");
	}
	@Override
	public void run() { // 执行清除的时候执行的是此操作
		System.out.println("【回收】最终你一定要死的。");
	}
}
class MemberCleaning implements AutoCloseable { // 实现清除的处理
	private static final Cleaner cleaner = Cleaner.create(); // 创建一个清除处理
    private Member member;
    private Cleaner.Cleanable cleanable;
    public MemberCleaning() {
    	this.member = new Member(); // 创建新对象
    	this.cleanable = this.cleaner.register(this,this.member); // 注册使用的对象
    }
	@Override
		public void close() throws Exception {
			this.cleanable.clean(); // 启动多线程
		}
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		try (MemberCleaning mc = new MemberCleaning()) {
			// 中间可以执行一些相关的代码
		} catch (Exception e) {}
	}
}

Cleanable的clean方法只是实现,垃圾回收仍靠gc()。

​ 在新一代的清除回收处理的过程之中,更多的情况下考虑的是多线程的使用,即:为了防止有可能造成的延迟处理,所以许多对象回收前的处理都是单独通过一个线程完成的。

7.对象克隆

所谓的对象克隆指的就是对象的复制,而且属于全新的复制

实现对象的克隆:

  • 需要使用到Object类中提供的clone()方法:

    protected Object clone() throws CloneNotSupportedException//protect类私有,除子类外不能调用
    
  • 在需要克隆的对象所在的类实现一个Cloneable接口,它描述的是一种能力

class Member implements Cloneable{//1.实现接口,表能力
    private String name;
    private int age;
    public Member(String name, int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString(){
        return super.toString()+"name:"+this.name+"、age:"+this.age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();//2.因为父类私有方法无法外部调用,所以覆写父类方法
    }
}

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Member member = new Member("kd",22);
        Member member1 =(Member)member.clone();
        System.out.println(member);
        System.out.println(member1);
    }
}

2.2 数字操作类

1.Math类

构造方法私有化,无法实例化对象。该类之中提供的所有方法都是static型的方法,即:这些方法都可以通过类名称直接调用

绝对值:Math.abs(); 
最大值:Math.max();
四舍五入:Math.round();
乘方:Math.pow();
e对数:Math.log()
Math.E;Math.PI;

自定义指定位数四舍五入操作:

class MathUtil{
    private MathUtil(){}
    /**
     *
     * @param num:输入数
     * @param scale:保留小数位数
     * @return:四舍五入后的结果
     */
    public static double round(double num, int scale){
        return Math.round(num*Math.pow(10, scale))/(Math.pow(10, scale));
    }
}

Math类提供基础的数学计算式,其他功能需要自己整合

2.Random类

产生随机数主要依靠类内部提供的普通方法来实现的

产生一个不大于边界的随机正整数:public int nextInt​(int bound)

范例:随机生成彩票号,不重复不含有0

class MathUtil{
    private MathUtil(){}

    /**
     * 
     * @param num:取走的彩票
     * @param border:彩票边界值
     * @return:排序过的无重复的彩票
     */
    public static int[] luckyticket(int num, int border){
        int data[] = new int[num];
        int foot = 0;
        while (foot < num) {//用while控制循环次数不定的循环
            Random ran = new Random();
            int temp = ran.nextInt(border);
            if(MathUtil.isUse(temp, data)){
                data[foot ++] = temp;//只有当存入新数后,foot才能++
            }

        }
        Arrays.sort(data);
        return data;
    }
    /**
     *
     * @param num:判断数字
     * @param data:数组
     * @return:当数字在数组内部时返回false
     */
    public static boolean isUse(int num, int data[]){
        if(num == 0){
            return false;
        }
        for (int i = 0; i < data.length; i++) {
            if(num == data[i]){
                return false;
            }
        }
        return true;
    }

}
public class Demo {
    public static void main(String[] args) {
       //产生随机7个不重复的小于36的正整数
        for(int temp: MathUtil.luckyticket(7,36)){
            System.out.print(temp+"、");
        }

    }
}

3.大数字操作类

假设一个数字很大,超过了double范围,只能够利用字符串描述数字操作

两个大数字类:

BigInteger 类构造:public BigInteger(String val);//整数运算
BigDecimal 类构造:public BigDecimal(String val);//可进行小数运算

在这里插入图片描述

BigInteger bigA = new BigInteger("12425331341241214");
BigInteger bigB = new BigInteger("2134412411");
加法:bigA.add(bigB)
减法:bigA.subtract(bigB)
乘法:bigA.multiply(bigB)
除法:bigA.divide(bigB)
求商和余数:public BigInteger[] divideAndRemainder(BigInteger val)
      BigInteger result[] = bigA.divideAndRemainder(bigB);
      System.out.println("商:"+result[0]+"余数:"+result[1]);

当计算没有超过基本数据类型所包含的位数强烈不建议使用大数字类,因为这种计算性能很差的

在BigDecimal中定义有除法计算:

除法计算:public BigDecimal divide(BigDecimal divisor,int scale,RoundingMode roundingMode);
定义四舍五入方法:
public static double round(double num,int scale) {
		return new BigDecimal(num).divide(new BigDecimal(1.0),scale,RoundingMode.HALF_UP).doubleValue();
	}

2.3 日期操作类

  • 直接实例化Date()就可以获取当前的时间
  • Date类中只是对long数据的一种包装。Date类中提供有日期与long数据类型之间转换的方法:
    • 将long 转为Date:public Date(long date);
    • 将Date 转为long:public long getTime();

当前时间再加上1小时:

Date date = new Date();
long currenttime = date.getTime() + 3600*1000;
System.out.println(new Date(currenttime));

对Date的时间进行格式化处理。利用SimpleDateFormat程序类,该类是DateFormat 的子类。

DateFormat 继承】将日期格式化:public final String format(Date date);
【DateFormat 继承】将字符串转为日期:public Date parse(String source) throws ParseException;

构造方法:public SimpleDateFormat(String pattern);
——日期格式:年(yyyy)、月(MM)、日(dd)、时(HH)、分(mm)、秒(ss)、毫秒(SSS);
例如:SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");//必须指定

定义日期格式:格式化日期后的格式为String

public class TimeFormat {
    public static void main(String[] args) {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(date));
    }
}

将String日期转为Date日期:

public class TimeFormat {
    public static void main(String[] args) throws ParseException {
        String birthday = "1999-01-28 20:28";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        Date date = sdf.parse(birthday);
        System.out.println(date);
    }
}

2.4 正则表达式

String类支持有向各个数据类型的转换功能,所以在项目的开发之中,只要是用户输入的信息基本上都用String表示。

于是在向其它数据类型转换的时候,为了保证转换的正确性,往往需要对其进行一些复杂的验证处理,那么这种情况下如果只是单纯的依靠String类中的方法是非常麻烦的。

使用正则(regex)最大的特点在于方便进行验证处理,以及方便进行复杂字符串的修改处理。

判断字符串是否由数字所组成。

public class Demo {
    public static void main(String[] args) {
        String str = "234";
        if(isNumber(str)) {
            int num = Integer.parseInt(str);//基本数据类型都有字符串转换方法
            System.out.println(num*2);
        }
    }
    public static boolean isNumber(String str){
        char [] data = str.toCharArray();//转换为字符数组
        for(char temp: data){
            if (temp < '0' || temp > '9'){
                return false;
            }
        }
        return true;
    }
}

使用正则表达式实现同样的效果:

//判断str是数字
if(str.matches("\\d+")) {
    int num = Integer.parseInt(str);//基本数据类型都有字符串转换方法
    System.out.println(num*2);
}

1.常用正则标记

  • 【数量:单个】字符匹配
任意字符:表示由任意字符组成;
\\:匹配“\”,表示转义(定义了特殊含义);
\n:匹配换行;
\t:匹配制表符;
  • 【数量 :单个】字符集(可以从里面任选一个字符)
[abc]:表示可能是字母a、b、c中的任意一个;
[^abc]:表示不是由字母a、b、c中的任意一个;
[a-zA-Z]:表示由一个任意字母所组成,不区分大小写;
[0-9,\\+]:表示由一位数字或+所组成;
  • 【数量:单个】简化字符集 == 字符集
. :表示 任意的一个字符;
\d:等价于“[0-9]”范围;
\D:等价于“[^0-9]”范围;
\s:匹配任意的一位空格,可能是空格、换行、制表符;
\S:匹配任意的一位非空格数据;
\w:匹配字母、数字、下划线,等价于“[a-zA-Z_0-9]”;
\W:匹配非字母、数字、下划线,等价于“[^a-zA-Z_0-9]”;
  • 边界匹配:
^:匹配边界开始;
$:匹配边界结束;
  • 数量表示,默认情况下只有添加上了数量单位才可以匹配多位字符
表达式?:该正则可以出现0次或1次;
表达式*:该正则可以出现0次、1次或多次;
表达式+:该正则可以出现1次或多次;
表达式{n}:表达式的长度正好为n次;
表达式{n,}:表达式的长度为n次以上;
表达式{n,m}:表达式的长度为n~m次;
  • 逻辑表达式:可以连接多个正则:
表达式X表达式YX表达式之后紧跟上Y表达式;
表达式X | 表达式Y:有一个表达式满足即可;
(表达式):为表达式设置一个整体描述,可以为整体描述设置数量单位。

举例说明:

判断str是否由字母组成:str.matches("[a-zA-Z]+")
判断str是否由数字组成:str.matches("\\d+")("[0-9]+")
判断str是否是字母、数字、下划线组成的8位以上密码:password.matches("\\w{8,}")

2.String类对正则的支持

现阶段对正则的使用大致用于拆分、匹配与替换上,只能够对格式进行判断处理

String类中提供的方法:

public boolean matches(String regex)//将指定字符串进行正则判断
public String replaceAll(String regex,String replacement)//替换全部
public String replaceFirst(String regex,String replacement)//替换首个
public String[] split(String regex)//正则拆分
public String[] split(String regex,int limit)//正则拆分
  • 实现字符串替换(删除掉非字母与数字)“DLA123J*%& 4214 4214% 4214D5125FAKF((%1%31”
String str = "DLA123J*%&$4214%$D5125FAKF(^(^%1%31";
String regex = "[^a-zA-Z0-9]+";//定义正则表达式
System.out.println(str.replaceAll(regex,""));
  • 实现字符串的拆分? “a1b22c333d4444e55555f666666g”
String str = "a1b22c333d4444e55555f666666g";
String regex = "[^a-zA-Z]+";//定义正则表达式
String result[] = str.split(regex);//拆分返回的是一个字符串数组
for(String temp:result){
    System.out.print(temp+"、");
}
  • 判断一个数据是否为小数,如果是小数则将其变为double类型
String str = "100.01";
String regex = "\\d+(\\.\\d+)?";//定义正则表达式
	if(str.matches(regex)){
		double num = Double.parseDouble(str);
		//局部变量作用域在{}范围内
		System.out.println(num);
	}
  • 现在判断一个字符串是否由日期所组成,如果是由日期所组成则将其转为Date类型
public static void main(String[] args) throws ParseException {
    String str = "1999-01-28";
    String regex = "\\d{4}-\\d{2}-\\d{2}";//定义正则表达式
    if(str.matches(regex)){
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
        System.out.println(date);
    }
}
  • 判断给定的电话号码是否正确?电话号码:(010)-51283346
String str = "(0540)-87925281";
String regex = "((\\(\\d{3,4}\\)-)|(\\d{3,4}))?\\d{8}";
System.out.println(str.matches(regex));
  • 验证email格式
email的用户名可以由字母、数字、_所组成(不应该使用“_”开头);
email的域名可以由字母、数字、_、-所组成;
域名的后缀必须是:.cn、.com、.net、.com.cn、.gov;

在这里插入图片描述

String str = "fkdzju_87925281@163.com";
String regex = "[a-zA-Z0-9]\\w+@\\w+\\.(com|cn|com.cn)";
System.out.println(str.matches(regex));//判断是否满足正则表达式的格式

3.java.util.regex包支持

这个包里面一共定义有两个类:Pattern(正则表达式编译)、Matcher(匹配)

Pattern 类:

提供有正则表达式的编译处理支持:public static Pattern compile(String regex);

  • 拆分:public String[] split(CharSequence input)
String str = "DOFA*%*%DKHF*&%2dfa14NMK142*ihewoj*431YHERF";
String regex = "[^a-zA-Z]+";
Pattern pat = Pattern.compile(regex);//编译正则表达式
String[] result = pat.split(str);
for (int i = 0; i < result.length; i++) {
     System.out.print(result[i]+" ");
}

Matcher类:

实现了正则匹配的处理类,这个类的对象实例化依靠Pattern类完成:
Pattern类提供的方法:public Matcher matcher(CharSequence input);

  • 正则匹配:public boolean matches();
String str = "101";
String regex = "\\d+";
Pattern pat = Pattern.compile(regex);//编译正则表达式
Matcher matcher = pat.matcher(str);//实例化Matcher对象
System.out.println(matcher.matches());
  • 字符串替换:public String replaceAll(String replacement)。

Matcher类里面提供有一种分组的功能,而这种分组的功能是String不具备的

//要求取出“#{内容}”标记中的所有内容
String str = "INSERT INTO dept(deptno,dname,loc) VALUES (#{deptno},#{dname},#{loc})";
String regex = "#\\{\\w+\\}";
Pattern pat = Pattern.compile(regex);//编译正则表达式
Matcher matcher = pat.matcher(str);
while(matcher.find()){//判断是否有匹配内容
      System.out.print(matcher.group().replaceAll("#|\\{|\\}","")+" ");
}
复杂的正则处理才用到java.util.regex包

2.5 国际化程序实现

程序核心业务逻辑都一样,文字不一样

  • 如何可以定义保存文字的文件信息;
  • 如何可以根据不同的区域语言的编码读取指定的资源信息。

在这里插入图片描述

1.Locale类

在java.util包里面提供有一个专门描述区域和语言编码的类:Locale

//此时需要的是国家和语言的代码,而中文的代码:zh_CN、美国英语的代码:en_US
构造方法:public Locale(String language,String country);
读取本地默认环境:public static LocalegetDefault()//Locale将世界上一些比较著名的国家的编码设置为了常量
Locale loc = new Locale("zh","CN"); // 中文环境
Locale loc = Locale.getDefault();
Locale loc = Locale.CHINA;

2.ResourceBundle读取资源文件

  • 资源文件(zju.com.Messages.properities) 根据key读取value

  • 读取资源文件主要依靠java.util.ResourceBundle类完成,该类为抽象类(依靠Locale)。

    实例化对象利用ResourceBundle.getBundle(String baseName)完成
    根据key读取资源内容:public final String getString(String key)
public class Demo {
    public static void main(String[] args)  {
        //读取本地资源 baseName = properties文件位置
        ResourceBundle rc = ResourceBundle.getBundle("zju.com.Messages");
        String value = rc.getString("info");//根据key读取信息
        System.out.println(value);
    }
}

如果资源没有放在包里面,则直接编写资源名称即可。

  • 依靠资源文件、Locale、ResourceBundle类就可以实现国际化的处理操作(核心:读取资源信息)
    • 在CLASSPATH下建立三个资源文件:

在这里插入图片描述

//读取中文资源
ResourceBundle rc = ResourceBundle.getBundle("zju.message.Messages",Locale.CHINA);
//读取英文资源
ResourceBundle rc = ResourceBundle.getBundle("zju.message.Messages",Locale.US);

资源读取顺序:读取指定区域的资源文件 > 默认的本地(zh_CN)资源 > 公共的资源(没有区域设置)

3.格式化文本国际化

资源信息通过占位符{0}{1}来进行描述,对于读取出来的数据也需要进行消息格式化的处理

在这里插入图片描述

如果进行资源读取则会将占位符的信息一起读取出来,所以此时就需要利用MessageFormat类进行格式化处理:

在这里插入图片描述

国际格式化文本:

//实例化读取资源类 baseName = properties文件位置
ResourceBundle rc = ResourceBundle.getBundle("zju.message.Messages",Locale.US);
String value = rc.getString("info");//根据key读取信息
//MessageFormat.format()提供格式化文本方法
System.out.println(MessageFormat.format(value,"fkd",new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date())));

在开发的过程之中见到资源文件里面出现有“{1}”“{2}”的结构表示的都是占位符,该信息一定都要进行格式化处理

2.6 开发支持类库

1.UUID类

在对一些文件进行自动命名处理的情况下,UUID类型非常好用

获取UUID对象:public static UUID randomUUID();

UUID uid = UUID.randomUUID();//生成文件名

2.Optional类

传统对于引用传递接受对象往往要在接收端被动进行null的判断,出错后也很难找根源。Java类中提供有Optional类,对对象包装后可以实现对null的处理:

返回空数据:public static Optional empty​();
装箱:
保存数据,但是不允许出现nullpublic static Optional of​(T value);
保存数据,允许为空:public static Optional ofNullable​(T value);
拆箱:    
获取数据:public T get​();
空的时候返回其它数据:public T orElse​(T other)

如果说现在数据保存的内容是null,则就会在保存处出现异常,或者当出现null时直接在接收端用orElse()处理:

范例:

interface IMessage{
    public String getContent();
}
class IMessageImpl implements IMessage{
    public String getContent(){
        return "fkd from zju.";
    }
}
class MessageUtil{
    private MessageUtil(){}//构造方法私有化
    public static Optional<IMessage> getMessage(){//利用静态方法实例化对象
        return Optional.ofNullable(null);//用Optional包装保存数据,实现在保存端检查null
        //return Optional.of(new IMessageImpl());
    }
    public static void useMessage(IMessage msg){//引用传递
        if(msg != null){
            System.out.println(msg.getContent());
        }
    }
}
public class Javademo {
    public static void main(String[] args) {
        MessageUtil.useMessage(MessageUtil.getMessage().orElse(new IMessageImpl()));//处理了null
        // MessageUtil.useMessage(MessageUtil.getMessage().get());
    }
}

3.ThreadLocal类

解决了核心资源与多线程并发访问的处理情况

原多线程并发程序:

//向用户发送消息
class Message{
    private String info;
    public Message(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }
}
class Channel{//定义通道类
    private static Message msg;//静态方法只能调用静态属性
    private Channel(){}//私有化
    //保存传入的Message对象
    public static void setMessage(Message m){
        msg = m;//静态方法中不能调用this
    }
    public static void send(){
        System.out.println(Thread.currentThread().getName()+":"+msg.getInfo());
    }
}

public class Javademo {
    public static void main(String[] args) {
        new Thread(()->{
            Message msg = new Message("第一个线程的消息.");
            Channel.setMessage(msg);
            Channel.send();
        },"线程A").start();
        new Thread(()->{
            Message msg = new Message("第二个线程的消息.");
            Channel.setMessage(msg);
            Channel.send();
        },"线程B").start();
        new Thread(()->{
            Message msg = new Message("第三个线程的消息.");
            Channel.setMessage(msg);
            Channel.send();
        },"线程C").start();
    }
}

这个时候消息的处理产生了影响:

在这里插入图片描述

考虑到每个线程的独立操作问题,那么这个时候可以通过ThreadLocal类来存放数据,建立起存放数据与线程之间的联系。相当于把要保存的数据包装到ThreadLocal中

在ThreadLocal类里面提供有如下的操作方法:

构造方法:public ThreadLocal();
设置数据:public void set(T value);
取出数据:public T get();
删除数据:public void remove()

在这里插入图片描述

解决线程的同步问题,修改存放数据的Channel类:

class Channel{//定义通道类
    private  static final ThreadLocal<Message> THREADLOCAL = new ThreadLocal<>();//实例化ThreadLocal,规定其存入的数据是Message对象
    private Channel(){}//私有化
    //保存传入的Message对象
    public static void setMessage(Message m){
       THREADLOCAL.set(m) ;//存入数据
    }
    public static void send(){
        System.out.println(Thread.currentThread().getName()+":"+THREADLOCAL.get().getInfo());
    }
}

每一个线程通过ThreadLocal只允许保存一个数据

4.定时调度

如果要想实现定时的处理操作主要需要有一个定时操作的主体类,以及一个定时任务的控制。可以使用两个类实现:

java.util.TimerTask类:实现定时任务处理;
java.util.Timer类:进行任务的启动,启动的方法;

任务启动(一次)public void schedule​(TimerTask task, long delay)、延迟单位为毫秒;
间隔触发(循环多次)public void scheduleAtFixedRate​(TimerTask task, long delay, long period)

在这里插入图片描述

//定义任务主体
class myTimer extends TimerTask{
    //多线程处理方法
    @Override
    public void run() {
        String clock = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println(Thread.currentThread().getName()+"定时任务执行,当前的时间是"+ clock);
    }
}
public class Clock {
    public static void main(String[] args) {
        Timer timer = new Timer();//定时任务
        //定义间隔任务,每秒执行一次
        timer.scheduleAtFixedRate(new myTimer(),1000,1000);
    }
}

这种定时是由JDK最原始的方式提供的支持,但是实际上开发之中利用此类方式进行的定时处理实现的代码会非常的复杂

5.Base64加密与解密

在JDK1.8开始提供有一组新的加密处理操作类,Base64处理,在这个类里面有两个内部类:

Base64.Encoder:进行加密处理;
|—加密处理:public byte[] encode​(byte[] src)Base64.Decoder:进行解密处理。
|—解密处理:public byte[] decode​(String src)
public class EncodeDemo {
    public static void main(String[] args) {
        //加密
        String salt = "{Shandong university}";
        String str = "fkd is from Zhejiang university." + salt;
        String encode = new String(Base64.getEncoder().encode(str.getBytes()));
        //解密
        String code = new String(Base64.getDecoder().decode(encode));
        System.out.println(code.replaceAll("\\{\\w+\\s\\w+\\}",""));
    }
}

最好的j加密做法是使用2-3种加密程序,同时再找到一些完全不可解密的加密算法。

2.7 比较器

对对象数组进行排序,可以采用Arrays.sort()方法,因为系统已经定义了比较规则。而自定义的类是无法比较的,因为没有指定比较规则。

Java里面为了统一比较规则的定义,提供有比较器的接口:Comparable 接口

  • Comparable 接口基本的定义结构:
public interface Comparable<T> {
	/**
	 * 实现对象的比较处理操作
	 * @param o 要比较的对象
	 * @return 当前数据比传入的对象小返回负数,如果大于返回正数,如果等于返回0
	 */
	int compareTo​(T o) ;//规则是升序(this-o)
    //降序则规则相反
}

在这里插入图片描述

为满足对象数组的比较需求,只需将自定义类实现Comparable接口,然后覆写方法,自定义比较规则即可。

class Person implements Comparable<Person>{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person person) {
        return this.age - person.age;
    }

    @Override
    public String toString() {
        return "姓名:"+this.name+"、年龄:"+this.age;
    }
}

public class Demo {
    public static void main(String[] args)  {
        Person data[] = new Person[]{
                new Person("mother", 54),
                new Person("sister", 30),
                new Person("me", 22)
        };
        Arrays.sort(data);//Arrays通过CompareTo判断大小关系
        System.out.println(Arrays.toString(data));//将对象数组输出Arrays.toString

    }
}
  • 挽救的比较器支持:Comparator比较器

当无法修改Person类定义比较规则时,在Arrays类里面排序有另外一种实现:基于Comparator 的排序处理:

public static void sort(T[] a, Comparator<? super T> c)

在这里插入图片描述

挽救的排序类:

//方法一:
class PersonComparator implements Comparator<Person> {
	@Override
	public int compare(Person p1, Person p2) {
		return p1.getAge() - p2.getAge();
	}
}
Arrays.sort(data,new PersonComparator()); // 进行对象数组的排序
//方法二:
Arrays.sort(data, (Person p1, Person p2 )->{
       return p1.getAge() - p2.getAge();
});//函数式接口Comprator可用Lamda表达式

一般情况下强烈不建议使用Comparator,最好以Comparable为主。

面试题:请解释Comparable与Comparator的区别?

  • java.lang.Comparable是在类定义的时候实现的父接口,主要用于定义排序规则,里面只有一个compareTo()方法;
  • java.util.Comparator是挽救的比较器操作,需要设置单独的比较器规则类实现排序,里面有compare()方法。

2.8 二叉树

链表数据结构进行查询时时间复杂度为O(n),为尽可能的减少数据查询次数,采用二叉树结构来完成,其性能为O(logn)

  • 二叉树的基本实现原理

取第一个数据为保存的根节点,小于等于根节点的数据要放在节点的左子树,而大于节点的数据要放在该节点的右子树

在这里插入图片描述

对于二叉树而言,在进行数据获取的时候也有三种形式:前序遍历(根-左-右)、中序遍历(左-根-右)、后序遍历(左-右-根)。

那么现在只是以中序遍历为主,则以上的数据进行中序遍历的时候最终的结果:10、20、25、30、38、50、80、100,可以发现二叉树中的内容全部都属于排序的结果。

  • 二叉树的删除

情况一:如果待删除节点没有子节点,那么直接删掉即可;

情况二:如果待删除节点只有一个子节点,那么直接删掉,并用其子节点去顶替它;

情况三:如果待删除节点有两个子节点,这种情况比较复杂:首选找出它的后续节点,然后处理“后续节点”和“被删除节点的父节点”之间的关系,最后处理“后续节点的子节点”和“被删除节点的子节点”之间的关系。

在这里插入图片描述

class Person implements Comparable<Person>{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名:"+this.name+"、年龄:"+this.age;
    }

    @Override
    public int compareTo(Person person) {
        return this.age - person.age;
    }
}

/**
 * 实现二叉树数据保存与查询
 * @param <T> 要保存的数据类型,继承Comparable表示可比较
 */
class BinaryTree<T extends Comparable<T>>{
    //定义内部节点类
    private class Node{
        private Comparable<T> data;//保存数据
        private Node left;//保存左节点
        private Node parent;//保存父节点
        private Node right;//保存右节点
        public Node(Comparable<T> data){
            this.data = data;
        }
        public void addNode(Node newNode){
            if(newNode.data.compareTo((T) this.data) <= 0){
                if(this.left == null){
                    this.left = newNode;
                    newNode.parent = this;
                }else{//需要向左继续判断
                    this.left.addNode(newNode);//继续向下判断
                }
            }else{
                if(this.right == null){
                    this.right = newNode;
                    newNode.parent = this;
                }else{//需要向右继续判断
                    this.right.addNode(newNode);//继续向下判断
                }
            }
        }
        public void toNodeArray(){
            if(this.left != null){//有左子树
                this.left.toNodeArray();//递归调用
            }
            //BinaryTree.returnArray错误,没有调用对象this
            BinaryTree.this.returnArray[foot ++] = this.data;
            if(this.right != null){
                this.right.toNodeArray();
            }
        }
        public Node getRemoveNode(Comparable<T> data){
            if(data.compareTo((T) this.data) == 0){
                return this;
            }else if(data.compareTo((T)this.data) < 0){//数据在节点左边
                if(this.left != null){
                    return this.left.getRemoveNode(data);//更换当前节点,递归调用继续找
                }else{
                    return null;
                }
            }else{//数据在右边
                if(this.right != null){
                    return this.right.getRemoveNode(data);//更换当前节点,递归调用继续找
                }else{
                    return null;
                }
            }
        }

    }
    private Node root;//保存根节点
    private int count;//定义保存数据个数
    private Object[] returnArray;//定义返回数组
    private int foot;
    //保存数据入二叉树
    public void add(Comparable<T> data){
        if(data == null){
            throw new NullPointerException("数据保存不为空。");
        }
        Node newNode = new Node(data);//保存数据入节点
        if(this.root == null){
            this.root = newNode;
        }else{
            this.root.addNode(newNode);//把保存操作交给Node类负责
        }
        count ++;
    }
    //获取数据
    public Object[] toArray(){
        if(this.count == 0){
            return null;//没有节点保存
        }
        this.returnArray = new Object[this.count];//定义保存数组长度
        this.foot = 0;//脚标清零
        this.root.toNodeArray();//交给Node类读取
        return returnArray;
    }

    /**
     * 执行数据删除操作
     * @param data要删除的数据
     * 删除节点时要两边断开,否则会出现空指向异常
     */

    public void remove(Comparable<T> data){
        if (this.root == null){
            return;
        }else{
            //删除的是根节点
            if(this.root.data.compareTo((T) data) == 0){
                Node moveNode = this.root.right;//找到右边节点的最小值
                while(moveNode.left != null){
                    moveNode = moveNode.left;
                }
                moveNode.parent.left = null;//断开连接
                moveNode.left = root.left;
                moveNode.right = root.right;
                this.root = moveNode;//改变根节点
            }else{
                //获取被删除的节点
                Node removeNode = this.root.getRemoveNode(data);
                if(removeNode != null){
                    //情况一:没有任何子节点
                    if(removeNode.left == null && removeNode.right ==null){
                        if(removeNode.parent.left == removeNode){
                            removeNode.parent.left = null;//父与子断开连接
                        }else{
                            removeNode.parent.right = null;//父与子断开连接
                        }
                        removeNode.parent = null;//子与父断开连接
                        //情况二:只有单边子节点
                    }else if(removeNode.left != null && removeNode.right == null){//单左边子节点
                        removeNode.left.parent = removeNode.parent;//子与父连接
                        removeNode.parent.left = removeNode.left;//父与子连接

                    }else if(removeNode.left == null && removeNode.right != null){//单右边子节点
                        removeNode.right.parent = removeNode.parent;
                        removeNode.parent.right = removeNode.right;
                    }else{//左右子节点都存在
                        Node moveNode = removeNode.right;
                        while (moveNode.left != null){
                            moveNode = moveNode.left;
                        }
                        removeNode.parent.left = moveNode;//父与子连接
                        moveNode.parent.left = null;//断开连接
                        moveNode.parent = removeNode.parent;//改变移动节点指向
                        moveNode.left = removeNode.left;
                        moveNode.right = removeNode.right;
                    }
                }
            }
        }
        count --;
    }

}
public class Demo {
    public static void main(String[] args)  {
        BinaryTree<Person> tree = new BinaryTree<>();
        tree.add(new Person("G",80));
        tree.add(new Person("C",50));
        tree.add(new Person("E",60));
        tree.add(new Person("B",30));
        tree.add(new Person("I",90));
        tree.add(new Person("A",10));
        tree.add(new Person("D",55));
        tree.add(new Person("F",70));
        tree.add(new Person("H",85));
        tree.add(new Person("J",95));
        tree.remove(new Person("G",80));
        System.out.println(Arrays.toString(tree.toArray()));

    }
}
  • 红黑树原理分析

红黑树原理分析

2.9 案例分析

各种对象数组的输出:Arrays.toString()

字符串转换为数字:Integer.parseInT(String str)

1.StringBuffer的使用

定义一个StringBuffer类对象,然后通过append()方法向对象中添加26个小写字母,要求每次只添加一次,共添加26次,然后按照逆序的方式输出,并且可以删除前5个字符。

//定义对象
StringBuffer buffer = new StringBuffer();
for (int x = 'a'; x <= 'z'; x++) {
    buffer.append((char) x);//添加小写字母
}
buffer.reverse().delete(0,5);
System.out.println(buffer);

2.随机数组

利用Random 类产生5个1~30之间(包括1和30)的随机整数。
Random产生随机数的操作之中会包含有数字0,所以此时不应该存在有数字0的问题。

public class MathUtil {
    private static Random random = new Random();//定义静态常量属性
    /**
     * @param num:创建的随机数个数
     *       border:随机数的界限
     * @return:返回不为0的随机数组
     */
    public static int[] createRandom(int num, int border) {
        int data[] = new int[num];
        int foot = 0;
        while(foot < num){//不确定循环次数时用while
            int number = random.nextInt(border);
            if(number != 0){
                data[foot++] = number;
            }
        }
        return data;
    }
}

3.Email验证

输入一个Email地址,然后使用正则表达式验证该Email地址是否正确。
对于此时的输入可以通过命令参数实现数据的输入,如果要想进行验证,最好的做法是设置一个单独的验证处理类。

class Validator {
    private Validator(){}
    public static boolean isEmail(String email){
        //判断email对象为空或者内容为空
        if(email == null || "".equals(email)){
            return false;
        }
        //定义正则表达式
        String regex = "\\w+@\\w+\\.\\w+(\\.cn)?";
        if(email.matches(regex)){
            return true;
        }else{
            return false;
        }
    }
}

如果以后要有更多的验证,只需要在Validator类之中扩展方法即可。

4.扔硬币

编写程序,用0~1之间的随机数来模拟扔硬币试验,统计扔1000次后出现正、反面的次数并输出。

class Coin {
    private int front;//正面朝上次数
    private int back ;//反面朝上次数
    private static Random random = new Random();

    /**
     * 模拟抛硬币
     * @param num:总共抛硬币的次数
     */
    public void throwCoin(int num){
        int count = 0;
        while(count < num){
            int data = random.nextInt(2);
            if(data == 0){
                front ++;
                count ++;
            }else{
                back ++;
                count ++;
            }
        }
    }
    public int getFront(){
        return front;
    }
    public int getBack(){
        return back;
    }
}

5.IP验证

编写正则表达式,判断给定的是否是一个合法的IP地址。
IP地址的组成就是数字,对于数字的组成有一个基础的要求,第一位的内容只能是无、1、2,后面的内容可以0-9、第三位的内容是0-9。

public static boolean isIp(String Ip){
    //判断IP内容为空
    if(Ip == null || "".equals(Ip)){
         return false;
    }
    String regex = "([12]?[0-9]?[0-9]\\.){3}[12]?[0-9]?[0-9]";
    if(Ip.matches(regex)){
        //Ip分隔后小于256
        String data[] = Ip.split("\\.");
        for (int x = 0; x < data.length ; x++) {
            if (Integer.parseInt(data[x]) > 255){
             return false;
        }
      }
      return true;
    }else{
      return false;
    }
}

6.HTML拆分

给定下面的HTML代码:

<font face="Arial,Serif" size="+2" color="red">

要求对内容进行拆分,拆分之后的结果是:
face Arial,Serif
size +2
color red

public static void main(String[] args) {
    String str = "<font face=\"Arial,Serif\" size=\"+2\" color=\"red\">";
    String regex = "\\w+=\"[a-zA-Z0-9,\\+]+\"";//按组分类
    Matcher matcher = Pattern.compile(regex).matcher(str);//先编译后匹配
    while (matcher.find()){
        String temp = matcher.group();
        String result[] = temp.split("=");
        System.out.println(result[0]+" "+result[1].replaceAll("\"", ""));
        //System.out.println(temp.replaceAll("=\"|\"", " "));
    }

7.国家代码

编写程序,实现国际化应用,从命令行输入国家的代号,例如,1表示中国,2表示美国,然后根据输入代号的不同调用不同的资源文件显示信息。
本程序的实现肯定要通过Locale类的对象来指定区域,随后利用ResourceBundle类加载资源文件,而对于数据的输入可以继续使用初始化的参数形式来完成。
1、定义中文的资源文件:cn.mldn.message.Messages_zh_CN.properties
2、定义英文的资源文件:cn.mldn.message.Messages_en_US.properties

class MessageUtil{
    private static final int CHINA = 1;
    private static final int USA = 2;
    private static final String BaseName = "zju.message.Message" ;
    public String getMessage(int num){
        Locale loc = this.getLocale(num);//指定区域
        if(loc == null){
            return "无法定位指定地区";
        }
        ResourceBundle rc = ResourceBundle.getBundle(BaseName,loc);//根据位置和区域读取资源文件
        String value = rc.getString("info");
        String data = MessageFormat.format(value,"fkd");//有占位符,需要格式化文本
            return data;
    }
    public Locale getLocale(int num){
        switch (num){
            case CHINA:
                return new Locale("zh", "CN");
            case USA:
                return new Locale("en", "US");
            default:
                return null;
        }
    }
}

public class Taskdemo {
    public static void main(String[] args) {
        //第一步定义资源文件
        //第二步指定Locale区域
        //读取资源文件ResourseBundle.getBundle(BaseName).getString(key);
        if(args.length != 1){
            System.out.println("程序执行错误,没有设置正确的区域编码");
        }
        //字符串转换为数字
        int choose = Integer.parseInt(args[0]);
        System.out.println(new MessageUtil().getMessage(choose));

    }
}

8.学生信息比较

按照”姓名:年龄:成绩|姓名:年龄:成绩“的格式定义字符串“张三:21:98|李四:22: 89|王五:20:70",要求将每组值分别保存在Student对象之中,并对这些对象进行排序,排序的原则为:按照成绩由高到低排序,如果成绩相等,则按照年龄由低到高排序。
本程序最典型的做法是直接利用比较器来完成处理,如果不使用比较器也可以完成,相当于自己采用冒泡的方式进行排列,使用了比较器就可以利用Arrays类做处理。

class Student implements Comparable<Student>{
    private String name;
    private int age;
    private double score;
    public Student(String name, int age, double score){
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "姓名:"+this.name+" 年龄:"+this.age+" 成绩:"+this.score;
    }

    @Override
    public int compareTo(Student stu) {
        if(this.score > stu.score){//从高到低
            return -1;//正常逻辑是this.score - stu.score
        }else if(this.score < stu.score){
            return 1;
        }else{
            return this.age - stu.age;
        }
    }
}

public class Taskdemo {
    public static void main(String[] args) {
        //想要排序,调用Arrays.sort(object[] obj);
        String input = "张三:21:98|李四:22: 89|王五:20:70";
        String data[] = input.split("\\|");
        Student student[] = new Student[data.length];//生成对象数组
        for (int i = 0; i < data.length; i++) {
            String temp[] = data[i].split(":");
                student[i] = new Student(temp[0],Integer.parseInt(temp[1]),Double.parseDouble(temp[2]));
        }
        Arrays.sort(student);
        for(Student temp : student){
            System.out.println(temp);
        }
    }
}

三、IO操作

3.1 File类

File类是Comparable接口的子类,是唯一一个与文件本身操作(创建、删除、重命名等等)有关的类,与文件里的内容无关。

构造方法:
    public File(String pathname)//设置要操作完整路径;
    public File(String parent,String child)//设置父路径与子目录
文件的基本操作方法:
    创建新的文件:public boolean createNewFile() throw IOException;
	判断文件是否存在:public boolean exists();
	删除文件:public boolean delete()
  • 针对不同操作系统下分隔符不同的问题,提供常量separator来代表Window分隔符“\”,Linux分隔符“/”
    • File进行文件处理时有延迟:程序---->JVM—>操作系统函数—>在磁盘上(文件处理)。不要重名,可用UUID。
    • 在进行文件创建的时候有一个重要的前提:文件的父路径必须首先存在
获取父路径:public File getParentFile​();
创建目录:public boolean mkdirs​()//父路径
public class FileIo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:\\VS\\zju\\edu\\cn\\fkd.txt");
        if(!file.getParentFile().exists()){//父路径不存在
            file.mkdirs();//创建父路径
        }
        if(file.exists()){//如果存在
            file.delete();
        }else{
            file.createNewFile();
        }
    }
}
  • 通过File类还可以获取文件本身提供的信息:
文件是否可读:public boolean canRead​();
文件是否可写:public boolean canWrite​();
获取文件长度:public long length​()、该方法返回的是long数据类型、字节长度;
最后一次修改日期时间:public long lastModified​();
判断是否是目录:public boolean isDirectory​();
判断是否是文件:public boolean isFile();
    
列出目录内容:public File[] listFiles​()

例:列出目录中的全部文件信息:

public class FileIo {
    public static void main(String[] args){
        File file = new File("F:\\");
        listDir(file);
    }
    /**
     * 递归输出目录中包含的文件
     * @param file
     */
    public static void listDir(File file){
        if(file.isDirectory()){//判断是否是目录
            File result[] = file.listFiles();
            if(result != null){
                for(File temp:result){
                    listDir(temp);
                }
            }
        }else{
            System.out.println(file);//输出文件名
        }
    }
}

例:批量修改文件名称(.txt)

public class FileIo {
    public static void main(String[] args){
        File file = new File("f:\\test\\复试");
        long begin = System.currentTimeMillis();
        rename(file);
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
    }
    //批量文件更名
    public static void rename(File file){
        if(file.isDirectory()){//判断是否是目录
            File result[] = file.listFiles();
            if(result != null){
                for(File temp:result){
                    rename(temp);
                }
            }
        }else{
            if(file.isFile()){
                String filename = null;
                if(file.getName().contains(".")){//文件名中含.
                    filename = file.getName().substring(0,file.getName().lastIndexOf("."))+".txt";
                }else{
                    filename = file.getName() + ".txt";
                }
                File newfile = new File(file.getParentFile(),filename);//新创建一个文件
                file.renameTo(newfile);//重命名原文件
            }
        }
    }
}

3.2 字符流与字节流

对于服务器以及客服端来说,传递的实质上就是一种数据流的处理形式,而所谓的数据流指的就是字节数据

  • 字节处理流: OutputStream(输出字节流),InputStream(输入字节流)
  • 字符处理流:Writer(输出字符流),Reader(输入字符流)。

在这里插入图片描述

文件处理的流程为例:

  • 通过File类找到一个文件的输入输出路径;
  • 通过字节流或字符流的子类来完成父类对象的实例化;
  • 利用字节流或字符流中的方法实现数据的输入与输出操作;
  • 流的操作属于资源操作,资源操作必须进行关闭处理;

1. OutputStream

在进行字节内容输出的时候可以使用OutputStream类完成,这个类的基本定义如下:

public abstract class OutputStream extends Object implements Closeable, Flushable

在这里插入图片描述

这个操作标准里面一共定义有三个内容输出的方法:

输出单个字节数据:public abstract void write(int b) throws IOException;
输出一组字节数据:public void write(byte b[]) throws IOException;
输出部分字节数据:public void write(byte b[], int off, int len) throws IOException;

通过FileOutputStream子类向上转型实例化对象

【覆盖】构造方法:public FileOutputStream(File file) throws FileNotFoundException;
【追加】构造方法:public FileOutputStream(File file, boolean append) throws FileNotFoundException;
File file = new File("F:\\zju\\edu\\test.txt");//1.指定文件路径
if(!file.getParentFile().exists()){//如果父路径不存在
     file.getParentFile().mkdirs();//创建父目录
}
try (OutputStream output = new FileOutputStream(file,true)){//2.子类实例化
     String str = "fkd from zju.\r\n";
     output.write(str.getBytes());//3.传入字符数组
}catch (IOException e){
     e.printStackTrace();//4.用try-resourse-catch方式自动释放资源
}
// output.close();//手动释放资源

2.InputStream

InputStream类主要实现的就是字节数据读取,该类定义如下:

public abstract class InputStream implements Closeable

定义有如下的几个核心方法:

读取单个字节数据,如果数据已经读取到底了,返回-1:public abstract int read() throws IOException
读取一组字节数据,返回的是读取的个数,如果没有数据已经读取到底则返回-1:public int read(byte[] b) throws IOException
读取一组字节数据(只占数组的部分):public int read(byte b[], int off, int len) throws IOException
    构造方法:public FileInputStream(File file) throws FileNotFoundException

在这里插入图片描述

File file = new File("F:\\zju\\edu\\test.txt");//1.指定文件路径
if(!file.getParentFile().exists()){//如果父路径不存在
    file.getParentFile().mkdirs();//创建父目录
}
InputStream input = new FileInputStream(file);//2.实例化对象
byte data[] = new byte[1024];//3.开辟一个字节缓冲数组
int length = input.read(data);//4.读入数据,返回字节长度
 System.out.println("["+new String(data,0,length)+"]");
input.close();//手动关闭资源

3.Writer

Writer输出的最大优势在于可以直接利用字符串完成。Writer是字符流,且其中append()方法可以改变输出内容。字符输出流:Writer,这个类的定义如下:

public abstract class Writer extends Object implements Appendable, Closeable, Flushable

主要输出方法:

输出字符数组:public void write​(char[] cbuf) throws IOException
输出字符串:public void write​(String str, int off, int len) throws IOException

在这里插入图片描述
writer第二个参数默认为false不追加内容,可以设置为true

public class FileIo {
    public static void main(String[] args) throws IOException {
       File file = new File("F:\\zju\\edu\\test.txt");//1.指定文件路径
       if(!file.getParentFile().exists()){//如果父路径不存在
           file.getParentFile().mkdirs();//创建父目录
       }
       Writer out = new FileWriter(file,true);//文件可追加
        out.write("我爱你");
        //out,append("中华");追加输出内容
        out.close();//手动关闭资源
    }
}

4.Reader

Reader是实现字符输入流的一种类型,其本身属于一个抽象类,这个类的定义如下:

public abstract class Reader extends Object implements Readable, Closeable

Reader类里面并没有像Writer类一个提供有整个字符串的输入处理操作,只能够利用字符数组来实现接收:

接收数据:public int read(char[] cbuf) throws IOException

在这里插入图片描述

public class FileIo {
    public static void main(String[] args) throws IOException {
       File file = new File("F:\\zju\\edu\\test.txt");//1.指定文件路径
       if(!file.getParentFile().exists()){
           file.getParentFile().mkdirs();
       }
       char data[] = new char[1024];
       Reader input = new FileReader(file);
       int length = input.read(data);
        System.out.println("读取的内容:"+ new String(data,0,length));
        input.close();
    }
}
  • 字节流与字符流的区别

​ 使用OutputStream类输出,没用close()方法关闭输出流,内容可以实现正常输出,但使用Writer没有使用close()方法关闭输出流,内容将无法输出。

​ 原因:字符流输出时使用到了缓冲区,close()方法会强制刷新缓冲区。同样用flush()方法也可以将全部输出内容从缓冲区清空。

5. 转换流

所谓的转换流指的是可以实现字节流与字符流操作的功能转换,在java.io包里面提供有两个类:InputStreamReader、OutputStreamWriter

在这里插入图片描述

​ 通过类的继承结构与构造方法可以发现,所谓的转换处理就是将接收到的字节流对象通过向上转型变为字符流对象。

OutputStream output = new FileOutputStream(file);
Writer out = new OutputStreamWriter(output); // 字节流变为字符流
out.write("fkd zju"); // 直接输出字符串,字符流适合于处理中文
out.close();

继承关系:

OutputStream类有FileOutputStream直接子类、InputStream类有FileInputStream直接子类。通过理解转换流和FileWriter、FileReader类的继承关系能更好理解缓存

在这里插入图片描述

在这里插入图片描述

数据传输过程:实际上所谓的缓存都是指的是程序中间的一道处理缓冲区。

在这里插入图片描述

6. 文件拷贝

需求分析:

  • 通过初始化源文件路径与目标文件路径实现文件的拷贝处理
  • 为适应各种类型的文件,使用字节流
  • 考虑到大文件拷贝问题

方案:采用部分拷贝,读取一部分输出一部分数据。核心的操作方法:

  • InputStream:public int read(byte[] b) throws IOException;
  • OutputStream:public void write(byte[] b, int off, int len) throws IOException
class FileUtil{
    private File srcFile;//源文件路径
    private File desFile;//目标文件路径

    public FileUtil(File srcFile, File desFile){
        this.srcFile = srcFile;
        this.desFile = desFile;
    }
    public FileUtil(String src, String des){
        this(new File(src), new File(des));//构造方法有this先执行
    }

    public boolean copy() throws Exception {//文件拷贝处理
        if(!srcFile.exists()){//判断源文件是否存在
            return false;
        }
        if (!desFile.getParentFile().exists()){//目标文件父路径是否存在
            desFile.getParentFile().mkdirs();
        }
        InputStream input = null;
        OutputStream output = null;
        byte data[] = new byte[1024];//定义每次读取数据范围
        try {
            int length = 0;//定义每次读取长度
            input = new FileInputStream(this.srcFile);//定义输入流对象
            output = new FileOutputStream(this.desFile);//定义输出流对象
            //先读出数据到data数组,返回读取的个数。
            // 判断数据是否是-1,再将数据写入
            while((length = input.read(data)) != -1){
                output.write(data,0, length);
                //System.out.println(length);
            }
            return true;
        }catch(Exception e){
            throw e;
        }finally{//
            input.close();
            output.close();
        }
    }
}
public class FileIo {
    public static void main(String[] args) throws Exception {
        if(args.length != 2){
            System.out.println("输入命令错误,请输入争取的源文件路径和目标文件路径。");
            System.exit(1);//退出程序
        }
        long start = System.currentTimeMillis();
        FileUtil fu = new FileUtil(args[0],args[1]);
        System.out.println(fu.copy()?"拷贝成功" : "拷贝失败");
        long end = System.currentTimeMillis();
        System.out.println("拷贝完成时间:" + (end - start));
    }
}

拷贝整个文件夹:

class FileUtil{
    private File srcFile;//源文件路径
    private File desFile;//目标文件路径

    public FileUtil(File srcFile, File desFile){
        this.srcFile = srcFile;
        this.desFile = desFile;
    }
    public FileUtil(String src, String des){
        this(new File(src), new File(des));//构造方法有this先执行
    }
    public boolean copyDir() {//拷贝整个文件夹
        try {
            this.copyDirImpl(this.srcFile);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    public boolean copyFile() throws Exception {//拷贝单个文件
        if(!srcFile.exists()){//源文件不存在
            return false;
        }
        copyFileImpl(this.srcFile,this.desFile);
        return true;
    }
    private void copyDirImpl(File file) throws Exception {//拷贝文件夹内部实现
        if(file.isDirectory()){//是一个文件夹
            File[] results = file.listFiles();
            if(results != null) {
                for (int i = 0; i < results.length; i++) {
                    copyDirImpl(results[i]);//递归
                }
            }
        }else{//是一个文件
            String Filepath = file.getPath().replace(this.srcFile.getPath()+File.separator,"");//去除源路径前缀
            File newFile = new File(this.desFile, Filepath);//父路径+子路径地址 = 目标路径
            this.copyFileImpl(file , newFile);
        }
    }

    private void copyFileImpl(File srcFile, File desFile) throws Exception {//拷贝单个文件实现
        if(! desFile.getParentFile().exists()){//如果父目录不存在
            desFile.getParentFile().mkdirs();
        }
        InputStream input = null;
        OutputStream output = null;
        byte data[] = new byte[1024];//定义每次读取数据范围
        try {
            int length = 0;//定义每次读取长度
            input = new FileInputStream(srcFile);//定义输入流对象
            output = new FileOutputStream(desFile);//定义输出流对象
            //先读出数据到data数组,返回读取的个数。
            // 判断数据是否是-1,再将数据写入
            while((length = input.read(data)) != -1){
                output.write(data,0, length);
            }
        }catch(Exception e){
            throw e;
        }finally{
            input.close();
            output.close();
        }
    }
}
public class FileIo {
    public static void main(String[] args) throws Exception {
        if(args.length != 2){
            System.out.println("输入命令错误,请输入争取的源文件路径和目标文件路径。");
            System.exit(1);//退出程序
        }
        long start = System.currentTimeMillis();
        FileUtil fu = new FileUtil(args[0],args[1]);
        if(new File(args[0]).isFile()){
            System.out.println(fu.copyFile()?"文件拷贝成功!" : "拷贝失败!");
        }else {
            System.out.println(fu.copyDir()?"文件夹拷贝成功!" : "拷贝失败!");
        }
        long end = System.currentTimeMillis();
        System.out.println("拷贝完成时间:" + (end - start));
    }
}

3.3 IO操作深入

  • 编码与解码都采用UTF-8的方式能够最好的解决项目中的额乱码问题(UTF-8:像形文字部分使用十六进制编码,普通的字母采用的是ISO8859-1编码)
File file = new File("F:\\zju\\fkd.txt");
if(!file.exists()){
    file.createNewFile();//创建新文件
}
 OutputStream output = new FileOutputStream("F:\\zju\\fkd.txt");
output.write("你好".getBytes(StandardCharsets.ISO_8859_1));//不统一导致乱码
output.close();
  • 内存操作流(实现IO但不希望产生临时文件),区别于以上的文件操作流

字节内存操作流:ByteArrayOutputStream、ByteArrayInputStream;
字符内存操作流:CharArrayWriter、CharArrayReader;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

构造方法:
ByteArrayInputStream构造:public ByteArrayInputStream(byte[] buf)ByteArrayOutputStream构造:public ByteArrayOutputStream();ByteArrayOutputStream类里面有一个重要的方法,这个方法可以获取全部保存在内存流中的数据信息,该方法为:
获取数据:public byte[] toByteArray();
使用字符串的形式来获取:public String toString()
 //用内存流读取数据
String str = "fkd from zju.";
InputStream input = new ByteArrayInputStream(str.getBytes());//将数据保存在内存流
ByteArrayOutputStream output = new ByteArrayOutputStream();//用到了ByteArrayOutputStream独有的方法
int data = 0;
while ((data = input.read()) != -1){
     output.write(Character.toUpperCase(data));//每次读取一个数据
}
byte result[] = output.toByteArray();//采用字符数组方式读取
System.out.println(new String(result));
//System.out.println(result);//toString方式读取
input.close();
output.close();

在最初的时候可以利用ByteArrayOutputStream实现大规模文本文件的读取,不需要文件流的方式每次定义1024B的缓存空间读取数据。

  • 管道流

管道流的主要功能是实现两个线程之间的IO操作,类似于医院打点滴的效果

字节管道流:PipedOutputStreamPipedInputStream;
——连接处理:public void connect(PipedInputStream snk) throws IOException;
字符管道流:PipedWriterPipedReader;
——连接处理:public void connect(PipedReader snk) throws IOException

在这里插入图片描述
在这里插入图片描述

class SendThread implements Runnable{
    private PipedOutputStream output;
    public SendThread(){
        this.output = new PipedOutputStream();//在构造方法实例化对象
    }

    @Override
    public void run() {
        try {//利用管道发送数据处理
            output.write((Thread.currentThread().getName()+":fkd").getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public PipedOutputStream getOutput(){
        return this.output;
    }
}
class RecieveThread implements Runnable{
    private PipedInputStream input;
    public RecieveThread(){
        input = new PipedInputStream();
    }
    @Override
    public void run() {
        byte data[] = new byte[1024];
        int len = 0;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();//定义内存输出流
        try {
            if (((len = input.read(data)) != -1)){
                bos.write(data,0, len);//保存入内存
            }
            //输出
            System.out.println(new String(bos.toByteArray()));
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public PipedInputStream getInput(){
        return this.input;
    }

}
public class IODemo {
    public static void main(String[] args) throws IOException {
        SendThread st = new SendThread();
        RecieveThread rd = new RecieveThread();
        st.getOutput().connect(rd.getInput());//管道出口与入口相连接
        new Thread(st,"发送通道").start();
        new Thread(rd, "接收通道").start();
    }
}
  • RandomAccessFile

这个类可以实现文件的跳跃式的读取,可以只读中间的部分内容,但规定数据的长度要保持一致。

整体的使用之中由用户自行定义要读取的位置,而后按照指定的结构进行数据的读取。

RandomAccessFile类里面定义有如下的操作方法:
—-构造方法:public RandomAccessFile(File file,String mode) throws FileNotFoundException//文件的处理模式:r、rw;
RandomAccessFile最大的特点是在于数据的读取处理上,因为所有的数据是按照固定的长度进行的保存,所以读取的时候就可以进行跳字节读取:
向下跳:public int skipBytes(int n) throws IOException;
向回跳:public void seek(long pos) throws IOException

文件保存:

public static void main(String[] args) throws Exception {
    File file = new File("F:\\zju\\fkd.txt");
    RandomAccessFile raf = new RandomAccessFile(file,"rw");
    //统一格式存入文件,字符串8位,数字4位
    String names[] = {"zhangsan","lisi    ","wangwu  "};
    int ages[] = {22 , 24 , 29};
    for (int i = 0; i < names.length; i++) {
         raf.write(names[i].getBytes());//写入字符串
         raf.writeInt(ages[i]);
     }
    raf.close();//IO流都得关资源
}

RandomAccessFile最大的特点是在于数据的读取处理上,因为所有的数据是按照固定的长度进行的保存,所以读取的时候就可以进行跳字节读取:
向下跳:public int skipBytes(int n) throws IOException;
向回跳:public void seek(long pos) throws IOException。

public class IODemo {
    public static void main(String[] args) throws Exception {
        File file = new File("F:\\zju\\fkd.txt");
        RandomAccessFile raf = new RandomAccessFile(file,"rw");
        //统一格式存入文件,字符串8位,数字4位(一个数字2位)
        {
            raf.skipBytes(24);
            byte[] data = new byte[8];//每次读取字符缓冲区
            int length = raf.read(data);//读取字符的长度
            System.out.println("姓名:" + new String(data, 0, length).trim() + "、年龄:" + raf.readInt());
        }
        {
            raf.seek(12);//回到12位
            byte[] data = new byte[8];
            int length = raf.read(data);//读取字符的长度
            System.out.println("姓名:" + new String(data, 0, length).trim() + "、年龄:" + raf.readInt());
        }

        raf.close();//IO流都得关资源
    }
}

3.4 输入与输出支持

1. 打印流[重要]

由于OutputStream功能有限,需要将数据转换为byte[],且输出内存流、管道流、文件流有多样性——产生了打印流,可以理解为一种装饰设计模式,本质仍然是OutputStream的形式。

PrintStreamPrintWriter
public class PrintStream extends FilterOutputStream implements Appendable,Closeablepublic class PrintWriter extends Writer
public PrintStream(OutputStream out)//传入所有子类public PrintWriter(OutputStream out)、public PrintWriter(Writer out)
File file = new File("F:\\zju\\fkd.txt");
PrintWriter pw = new PrintWriter(new FileOutputStream(file));
pw.println("my name is fkd.");
pw.close();

2. System类对IO的支持

System类是一个系统类,有三个常量:

标准输出(显示器):public static final PrintStream out;
错误输出:public static final PrintStream err;
标准输入(键盘):public static final InputStream in;

·还提供有修改输出位置的操作:(一般不用)

修改out的输出位置:public static void setOut​(PrintStream out);
修改err的输出位置:public static void setErr​(PrintStream err)

实现键盘输入:

System.out.print("请输入内容:");
InputStream input = System.in;
byte [] data = new byte[1024];
int length = input.read(data);
System.out.println("输入的内容:"+ new String(data, 0, length));

3.BufferedReader缓冲输入流

BufferedReader类提供的是一个缓冲字符输入流的概念,提供有转换为String的方法,便于各种转换与验证

读取一行数据:public String readLine​() throws IOException

在这里插入图片描述

BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));//定义缓冲数据流
System.out.print("请输入您的年龄:");
String msg = bfr.readLine();//读取一行内容
if(msg.matches("\\d{1,3}")){
    int age = Integer.parseInt(msg);//字符串转换为数字
System.out.println("年龄为:"+age);
}else{
     System.out.println("请输入正确的年龄");
}
bfr.close();

4.Scanner扫描流[重要]

在以后的开发过程之中,如果程序需要输出数据一定使用打印流,输入数据使用Scanner (BufferedReader)

构造:public Scanner(InputStream source);
判断是否有数据:public boolean hasNext​();
取出数据:public String next​();
设置分隔符:public Scanner useDelimiter​(String pattern)//一般默认问空格,可以设置为“、”

读取一个人的生日:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
    	Scanner scan = new Scanner(System.in);
    	System.out.print("请输入您的生日:");
    	if (scan.hasNext("\\d{4}-\\d{2}-\\d{2}")) {
    		String str = scan.next("\\d{4}-\\d{2}-\\d{2}");
    		System.out.println("输入信息为:" + new SimpleDateFormat("yyyy-MM-dd").parse(str));
    	}
    	scan.close();
    }
}

读取所有文件信息:

public class IODemo {
    public static void main(String[] args) throws Exception {
        Scanner scan = new Scanner(new FileInputStream(new File("F:\\zju\\浙大\\密码管理.txt")));
        scan.useDelimiter("\n");//设置分隔符回车
        while (scan.hasNext()){//有数据
            System.out.println(scan.next());
        }
        scan.close();
    }
}

3.5 对象序列化

所谓对象序列化指的是将内存中保存的对象以二进制数数据流的形式进行处理,可以进行对象存储和网络传输(发送到数据库、服务器等)

  • 要序列化的对象实现Serializable父接口,描述的是一种能力,类似于cloneable

1.序列化与反序列化

类名称序列化:ObjectOutputStream反序列化:ObjectInputStream
类定义public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstantspublic class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
构造方法public ObjectOutputStream(OutputStream out) throws IOExceptionpublic ObjectInputStream(InputStream in) throws IOException
操作方法public final void writeObject(Object obj) throws IOExceptionpublic final Object readObject() throws IOException, ClassNotFoundException

实现序列化与反序列化:

public class IODemo {
    private static final File SAVE_FILE = new File("F:\\zju\\Person");
    public static void main(String[] args) throws Exception {
       // savaObject(new Person("fkd",22));
        System.out.println(loadObject());
    }
    public static void savaObject(Object obj) throws IOException {//序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SAVE_FILE));
        oos.writeObject(obj);
        oos.close();
    }
    public static Object loadObject() throws IOException, ClassNotFoundException {//反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SAVE_FILE));
        Object obj = ois.readObject();//二进制转对象
        ois.close();
        return obj;
    }
}
class Person implements Serializable{
    private int age;
    private String name;
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "姓名:" + this.name + "、年龄:" + this.age;
    }
}

项目开发中可以通过容器自动实现序列化与反序列化。

2.transient关键字

​ 利用transient关键字可以定义一些不需要序列化的属性,读取时其内容为null。例如一些是需要计算保存的属性内容往往是不需要被序列化的,这个时候就可以使用transient。但该关键字在实际开发中出现频率不高。

private transient String name;

3.6 JavaIO实例

1.数字比大小

编写Java程序,输入3个整数,并求出3个整数的最大值和最小值。

  • 定义一个工具输入类
public class InputUtil {
    private InputUtil(){}
    private static final Scanner scan = new Scanner(System.in);
    /**
     * 实现对键入数字的保存
     * @param prompt 提示信息
     * @return 保存的数字
     */
    public static int getInt(String prompt){
        System.out.println(prompt);
        boolean flag = true;
        int result = 0;
        while(flag){
            if(scan.hasNext("\\d+")){
                result = Integer.parseInt(scan.next());
                flag = false;
            }else{
                System.out.println("输入错误,"+prompt);
                scan.next();//跳过错误,继续下一个键入
            }
        }
        return result;
    }
}
  • 定义数据的输入接口
public interface INumberService {
    /**
     * 返回键入数的最大值与最小值
     * @param count:键入的总数
     * @return 最大值与最小值
     */
    public int[] statics(int count);
}
  • 定义接口的实现子类
public class NumberServiceImpl implements INumberService{
    @Override
    public int[] statics(int count) {
        int result[] = new int[2];//保存结果
        int data[] = new int[count];//保存键入的数
        for (int i = 0; i < count; i++) {
            data[i] = InputUtil.getInt("请输入数字:");
        }
        result[0] = data[0];//最大值
        result[1] = data[0];//最小值
        for (int i = 0; i < count; i++) {
            if (data[i] >= result[0]){
                result[0] = data[i];
            }
            if(data[i] < result[1]){
                result[1] = data[i];
            }
        }
        return result;
    }
}
  • 定义工厂类获取接口对象
public class Factory {
    //定义工厂类
    public static INumberService getInstance(){
        return new NumberServiceImpl();
    }
}
  • 编写测试类
public class Testdemo {
    public static void main(String[] args) {
        int result[] = Factory.getInstance().statics(5);
        System.out.println("最大值:" + result[0] + "、最小值:" + result[1]);
    }
}

2.文件保存

从键盘输入文件的内容和要保存的文件名称,然后根据输入的名称创建文件,并将内容保存到文件中。

  • 完善输入工具类
    /**
     * 使用Bufferedreader因为其能按行读,检测空
     * 获取键入的字符串信息并保存
     * @param prompt :提示信息
     * @return 保存的字符串
     */
public static String getString(String prompt) throws IOException {
    String data = null;
    boolean flag = true;
    while(flag){
        System.out.println(prompt);
        data = bfr.readLine();
        if(!"".equals(data)){
            flag = false;
         }else{
             System.out.print("输入错误");
        }
    }

      return data;
}
  • 定义文件保存接口
public interface IFileService {
    public static final String SAVE_DIR = "F:\\zju\\";

    /**
     * 定义文件的保存方法
     * @return 文件保存成功返回true; 否则false
     */
    public boolean save();
}
  • 定义接口实现类
public class FileServiceImpl implements IFileService{
    private String filename;
    private String content;
    public FileServiceImpl()  {
        try {
            this.filename = InputUtil.getString("请输入文件名:");
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.content = InputUtil.getString("请输入文件内容:");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public boolean save(){
        File file = new File(IFileService.SAVE_DIR + this.filename);
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(file));
            pw.print(this.content);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        }finally{
            pw.close();
        }
        return true;
    }
}
  • 定义工厂类去耦合
public class Factory {
    //定义泛型工厂类
    public static <T>T getInstance(String classname){
        switch (classname) {
            case "NumberServiceImpl":
                return (T) new NumberServiceImpl();
            case "FileServiceImpl":
                return (T) new FileServiceImpl();
            default:
                return null;
        }
    }
}
  • 定义测试类
IFileService ifs = Factory.getInstance("FileServiceImpl");
System.out.println(ifs.save());

3. 字符串的逆序显示

从键盘传入多个字符串到程序中,并将它们按照逆序输出在屏幕上

  • 定义字符串操作标准
public interface IStringService {
    /**
     * 实现追加数据
     * @param str 附加的字符串
     */
    public void append(String str);

    /**
     * 对字符串进行反转
     * @return 反转后的结果
     */
    public String[] reverse();
}
  • 定义实现类,用到StringBuffer
public class StringServiceImpl implements IStringService{
    private StringBuffer sbf = new StringBuffer();

    @Override
    public void append(String str) {
        this.sbf.append(str).append("|");
    }

    @Override
    public String[] reverse() {
        String result[] = sbf.toString().split("\\|");
        //反转操作
        int center = result.length/2;
        int head = 0;
        int tail = result.length - 1;
        for (int i = 0; i < center; i++) {
            String temp = result[head];
            result[head] = result[tail];
            result[tail] = temp;
            head ++ ; tail --;
        }
        return result;
    }
}
  • 追加工厂类
  • 定义munu处理类,交互式处理
public class menu {
    private IStringService ss;
    public menu(){
        ss = Factory.getInstance("StringServiceImpl");
        this.choosemenu();
    }
    public void choosemenu(){
        this.show();
        String select = InputUtil.getString("请选择:");
        switch (select){
            case "0":{
                System.out.println("程序退出!");
                System.exit(1);
            }
            case "1":{
                String str = InputUtil.getString("请输入添加的字符串信息:");
                ss.append(str);
                this.choosemenu();
            }
            case "2":{
                String result[] = ss.reverse();
                System.out.println(Arrays.toString(result));
                this.choosemenu();
            } default:{
                System.out.println("输入错误,请选择正确选项");
                this.choosemenu();
            }
        }
    }
    public void show(){
        System.out.println("【1】追加字符串");
        System.out.println("【2】逆序显示所有字符串");
        System.out.println("【0】退出程序");
    }
}
  • 编写测试类
new Menu(); // 启动程序界面

4. 数据排序处理

从键盘输入格式为以下的数据:“姓名:成绩 | 姓名:成绩 | 姓名:成绩”,对输入的内容按成绩进行排序,并将排序结果按照成绩由高到低排序。可以将全部输入的信息保存在文件中,还可以添加信息,并可以显示全部的数据。

  • 定义简单JAVA类Student
public class Student implements Comparable<Student>{
    private String name;
    private double score;
    public Student(String name, double score){
        this.name = name;
        this.score = score;
    }
    @ Override
    public String toString(){
        return "姓名:"+this.name+"、成绩:"+this.score;
    }


    @Override
    public int compareTo(Student stu) {
        if(this.score > stu.score){
            return -1;
        }else if(this.score < stu.score){
            return 1;
        }else{
            return 0;
        }
    }
}
  • 定义的文件处理类,能对文件进行追加保存与读取
public class FileUtil {
    /**
     * 从文件中读取信息
     * @param file 加载的文件
     * @return 读取的信息
     */
    public static String load(File file){
        Scanner scan = null;
        try {
            scan = new Scanner(new FileInputStream(file));
            if (scan.hasNext()){//有信息
                String content = scan.next();
                return content;
            }else{
                return null;
            }
        } catch (FileNotFoundException e) {
            return null;
        }finally{
            if(scan != null){
                scan.close();
            }
        }
    }

    /**
     * 将数据追加到文件中
     * @param file 保存的文件
     * @param content 保存的内容
     */
    public static void append(File file, String content){
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(file,true));
            pw.print(content);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            pw.close();
        }
    }
}
  • 定义输入数据处理接口
public interface IStudentService {
    /**
     * 对输入的信息按成绩从高到低排序
     * @return 排完序的对象数组
     */
    public Student[] sortAsScore();

    /**
     * 追加数据保存到文件中
     * @param str 追加的数据
     */
    public void append(String str);
}
  • 实现接口类
public class StudentServiceImpl implements IStudentService{
    private String content; //读取的数据信息
    private static final File SAVE_FILE = new File("F:\\zju\\fkd.txt");
    public StudentServiceImpl(){
        this.content = FileUtil.load(SAVE_FILE);
    }
    @Override
    public Student[] sortAsScore() {
        String data[] = this.content.split("\\|");
        Student result[] = new Student[data.length];
        for (int i = 0; i < data.length; i++) {
            String temp[] = data[i].split(":");
            Student student = new Student(temp[0], Double.parseDouble(temp[1]));
            result[i] = student;
        }
        Arrays.sort(result);
        return result;
    }

    @Override
    public void append(String str) {
        //前后不能有"|"
        if(str.startsWith("|")){
            str.substring(1);
        }
        if(!str.endsWith("|")){
            str = str + "|";
        }
        FileUtil.append(SAVE_FILE, str);//将内容存入文件
    }
}
  • 追加工厂类
  • 菜单处理类
public class StudentMenu {
    private IStudentService ss;
    public StudentMenu(){
        this.choose();
    }
    public void choose(){
        this.show();
        String choice = InputUtil.getString("请进行选择");
        switch (choice){
            case "0":{
                System.out.println("退出程序");
                System.exit(1);
            }
            case "1":{
                ss = Factory.getInstance("StudentServiceImpl");
                String content = InputUtil.getString("请输入学生信息([格式]姓名:成绩):");
                ss.append(content);
                choose();
            }
            case "2":{
                ss = Factory.getInstance("StudentServiceImpl");
                Student result[] = ss.sortAsScore();
                System.out.println(Arrays.toString(result));
                choose();
            }
            default:{
                System.out.println("请输入正确的选项");
                choose();
            }

        }
    }
    public void show(){
        System.out.println("【1】继续存入数据");
        System.out.println("【2】显示所有学生数据");
        System.out.println("【0】结束程序");
    }

}
  • 测试类
 new StudentMenu();

5. 奇偶数统计

编写程序,当程序运行后,根据屏幕提示输入一个数字字符串,输入后统计有多少个偶数数字和奇数数字。

  • 重构数字处理接口与实现类
public interface INumberService {
    /**
     * 返回键入数的最大值与最小值
     * @param count:键入的总数
     * @return 最大值与最小值
     */
    public int[] statics(int count);

    /**
     * 统计键入字符串中数字的奇数与偶数个数
     * @return 奇数与偶数个数
     */
    public int[] even_odd();
}
public class NumberServiceImpl implements INumberService{
    @Override
    public int[] statics(int count) {
        int result[] = new int[2];//保存结果
        int data[] = new int[count];//保存键入的数
        for (int i = 0; i < count; i++) {
            data[i] = InputUtil.getInt("请输入数字:");
        }
        result[0] = data[0];//最大值
        result[1] = data[0];//最小值
        for (int i = 0; i < count; i++) {
            if (data[i] >= result[0]){
                result[0] = data[i];
            }
            if(data[i] < result[1]){
                result[1] = data[i];
            }
        }
        return result;
    }

    @Override
    public int[] even_odd() {
        int result[] = new int[]{0,0};
        String str = InputUtil.getString("请输入一个数字字符串:");
        if(str.matches("\\d+")){
            String data[] = str.split("");//按照每个字符拆分
            for (int i = 0; i < data.length; i++) {
                int temp = Integer.parseInt(data[i]);
                if(temp % 2 == 1){//奇数
                    result[0] ++;
                }else{
                    result[1] ++;
                }
            }
        }else{
            result = this.even_odd();
        }
        return result;
    }
}

6. 用户登录

完成系统登录程序,从命令行输入用户名和密码,如果没有输入用户名和密码,则提示输入用户名和密码:如果输入了用户名但是没有输入密码,则提示用户输入密码,然后判断用户名是否是Java,密码是否是zjufkd,如果正确,则提示登录成功;如果错误,显示登录失败的信息,用户再次输入用户名和密码,连续3次输入错误后系统退出。

  • 定义用户操作接口
public interface IUserService {
    /**
     * 判断是否超过了三次错误输入
     * @return
     */
    public boolean isExit();

    /**
     * 判断是否登录成功
     * @param uesername:用户名
     * @param password:密码
     * @return
     */
    public boolean login(String uesername, String password);
}
  • 定义核心业务子类
public class UserServiceImpl implements IUserService{
    private static int count = 0;
    @Override
    public boolean isExit() {//检测控制
        return this.count <= 3;
    }

    @Override
    public boolean login(String username, String password) {
        this.count ++;
        return "Java".equals(username) && "fkdzju".equals(password);
    }
}
  • 定义代理业务
//定义IUserService的代理类,负责其中的代理业务
public class UserServiceProxy implements IUserService {
    private IUserService isu;

    public UserServiceProxy(IUserService isu) {
        this.isu = isu;//初始化时传入核心业务
    }


    @Override
    public boolean isExit() {
        return this.isu.isExit();
    }

    @Override
    public boolean login(String uesername, String password) {
        while (this.isExit()) {
            String str = InputUtil.getString("请输入用户名和密码:");
            if (str.contains("/")) {
                String data[] = str.split("/");
                if (isu.login(data[0], data[1])) {//匹配成功
                    return true;//有返回,直接结束循环
                } else {
                    System.out.println("登录失败,请输入正确的用户名及密码。");
                }
            }else{//只有用户名
                String pw = InputUtil.getString("请输入密码:");
                if(isu.login(str, pw)){
                    return true;
                }else{
                    System.out.println("登录失败,请输入正确的用户名密码。");
                }
            }

        }
        return false;
    }
}
  • 修改工厂类
  • 测试类
IUserService ius = Factory.getInstance("UserServiceImpl");
System.out.println(ius.login(null,null));

7.投票选举

有一个班采用民主投票方法推选班长,班长候选人共4位。程序操作员将每张选票上所填的代号(1,2,3,4)循环输入电脑,输入数字0结束输入,然后将所有候选人的得票情况显示出来,并显示最终结果。

  • 定义候选人类
public class Candidate implements Comparable<Candidate> {
    private String cname;
    private long cid;
    private int ticket;

    public Candidate(String cname, long cid, int ticket) {
        this.cname = cname;
        this.ticket = ticket;
        this.cid = cid;
    }

    public String getCname() {
        return this.cname;
    }

    public long getCid() {
        return this.cid;
    }

    public int getTicket() {
        return this.ticket;
    }

    public void setTicket(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public String toString() {
        return this.cid + ":" + this.cname + "【" + this.ticket + "票】";
    }

    @Override
    public int compareTo(Candidate can) {
        return can.ticket - this.ticket;//从高到低
    }
}
  • 定义投票服务业务接口
public interface IVoteService {
    /**
     * 判断选择编号,并增长票数
     * @param cid 投票的编号
     * @return
     */
    public boolean voteInc(long cid);

    /**
     * 返回全部的候选人信息
     * @return
     */
    public Candidate[] getData();

    /**
     * 返回最终的投票结果
     * @return
     */
    public Candidate[] getResult();
}
  • 定义接口实现子类
public class VoteServiceImpl implements IVoteService {
    private Candidate[] candidates = new Candidate[]{
            new Candidate("傅凯迪", 1, 0), new Candidate("郝文杰", 2, 0),
            new Candidate("刘云鹏", 3, 0), new Candidate("宋浩伟", 4, 0)
    };

    @Override
    public boolean voteInc(long cid) {
        for (int i = 0; i < candidates.length; i++) {
            if (cid == candidates[i].getCid()) {
                candidates[i].setTicket(candidates[i].getTicket() + 1);
                return true;
            }
        }
        return false;
    }

    @Override
    public Candidate[] getData() {
        return this.candidates;
    }

    @Override
    public Candidate[] getResult() {
        Arrays.sort(candidates);
        return candidates;
    }
}
  • 定义工厂类
  • 定义菜单显示类
public class VoteMenu {
    private IVoteService ivs;
    public VoteMenu(){
        ivs = Factory.getInstance("VoteServiceImpl");//初始化投票服务
        System.out.println(Arrays.toString(ivs.getData()));//显示候选人面板
        this.menuShow();
    }
    public void menuShow(){
        String vote = InputUtil.getString("请输入班长候选人代号(数字0退出):");
        if(!vote.matches("[0-4]")){//投票无效
            System.out.println("次选票无效,请重新输入正确的候选人编号!");
            this.menuShow();//重新获取输入
        }else if (ivs.voteInc(Long.parseLong(vote))){//投票有效
            this.menuShow();
        }else{
            System.out.println(Arrays.toString(ivs.getResult()));
            System.out.println("投票最终结果:" + ivs.getResult()[0].getCname() + "同学,最后以" +ivs.getResult()[0].getTicket()+ "票当选班长");
        }
    }
}
  • 定义测试类

四、反射机制

4.1 Class类对象实例化

Java最大的特征是反射机制,是Java开发的精髓。而反射之中的所有的核心操作都是通过Class类对象展开的,可以理解为类本身对象。这个类如果要想获取它的实例化对象,可以采用三种方式完成。

java.lang.Class类的定义:

public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement
  • 【Object类支持】Object类可以根据实例化对象获取Class对象:对象.getClass()
    • 特点:public final Class<?> getClass();//必须实例化对象
Student stu = new Student("kfd",98);//通过已有实例化对象
Class<? extends Student> cls = stu.getClass();//建立该类的cls类
System.out.println(cls);
  • 【JVM直接支持】采用“类.class”的形式实例化
    • 特点:如果要采用此种模式,则必须导入程序所对应的开发包;
Class<? extends Student> cls = Student.class;
System.out.println(cls.getName());
  • 【Class类支持】在Class类里面提供有一个static方法:Class.forName(“zju.com.model.Student”)
    • 加载类:public static Class<?> forName(String className) throws ClassNotFoundException;
    • 特点:可以直接采用字符串形式定义类型,不用Import引用
public static void main(String[] args) throws ClassNotFoundException {
    Class<?> cls = Class.forName("zju.com.model.Student");
    System.out.println(cls.getName());
}

4.2 反射应用案例

1.反射实例化对象

//class类中提供的反射实例化对象的方法代替了关键词new
public T newInstance​() throws InstantiationException, IllegalAccessException
//调用newInstance()代替new
Class<?> cls = Class.forName("zju.com.model.Student");
Object obj = cls.newInstance();//实例化对象,默认调用无参构造
System.out.println(obj);

2. 反射与工厂设计模式

1.工厂设计模式模式最有效的是解决子类与客户端的耦合问题。

2.随着接口子类的不断增多,使用关键字new会导致传统工厂类需要不断修改,而使用反射实例化只需要有一个明确表示类名称的字符串即可。其最大的优势在于,对于接口子类的扩充不再影响到工厂类的定义。

3.但考虑到不同接口定义工厂类的耦合,采用泛型方法,此时的工厂设计模式将不再受限于指定的接口,可以为所有的接口提供实例化服务。

在这里插入图片描述

    /**
     *
     * @param className :接口子类名称(含路径)
     * @param clazz 指定接口类型,要用接口实例化Class
     * @param <T>  <T>T 为定义泛型数据类型
     * @return 返回指定接口的实例化对象
     */
    public static <T>T getInstance(String className, Class<T> clazz){
        T instance = null;
        try {
            instance = (T) Class.forName("zju.com.service." + className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

3. 反射与单例设计模式

单例设计在多线程操作时会产生多个实例化对象,此时引入同步synchronized可以解决

在这里插入图片描述

//对整体方法引入同步,但此时代价太大,效率降低
public static synchronized Singleton getInstance();

标准的单例设计模式:懒汉式(对代码块引入同步)

class Singleton {
	private static volatile Singleton instance = null;//实例化对象后与主内存保持同步,而不是副本
	private Singleton() {
		System.out.println("【" + Thread.currentThread().getName() + "】 实例化Singleton类对象 ");
	}
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {//static方法不能用this,可用类.class表示类本身
				if (instance == null) {
					instance = new Singleton() ;
				}
			}
		}
		return instance ;
	}
	public void print() {
		System.out.println("www.mldn.cn");
	}
}

面试题:请编写单例设计模式
【100%】直接编写一个饿汉式的单例设计模式,并且实现构造方法私有化;
【120%】在Java中那里面使用到单例设计模式了?Runtime类、Spring框架;
【200%】懒汉式单例设计模式的问题?

4.3 反射与类操作

1. 获取类结构信息

当获取了一个类的Class对象之后就意味着这个对象可以获取类之中的一切继承结构信息

Class类提供有如下方法获取基础信息:

1、获取包名称:public Package getPackage()2、获取继承父类:public Class<? super T> getSuperclass()3、获取实现父接口:public Class<?>[] getInterfaces()
 public static void main(String[] args) throws Exception {
    //获取包名称
    Class<?> cls = String.class;
    Package pac = cls.getPackage();
    System.out.println("包名称:"+pac.getName());
    //获取父类
    Class<?> parent = cls.getSuperclass();
    System.out.println("父类:" + parent.getName());
    //获取接口
    Class<?> interfaces[] = cls.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
         System.out.println("实现接口:"+interfaces[i].getName());
    }
}

2. 反射调用构造方法

所有类的构造方法的获取都可以直接通过Class类来完成,该类中定义有如下的几个方法:

获取所有构造方法:public Constructor<?>[] getDeclaredConstructors​() throws SecurityException;
获取指定构造方法:public Constructor getDeclaredConstructor​(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException
//获取构造
Constructor<?>[] constructors = cls.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
    System.out.println(constructors[i]);
}

若此时想要调用类的有参构造进行实例化方法操作,可以利用Constructor类中提供的实例化方法:

public T newInstance​(Object… initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException

调用指定构造方法的实例化对象:

Constructor<?> constructor = cls.getDeclaredConstructor(String.class);//指明调用的参数属性类
Object obj = constructor.newInstance("aaa");//实例化对象
System.out.println(obj);

虽然程序允许开发者调用有参构造处理,但是所有使用反射的类中最好提供有无参构造,这样的实例化可以达到统一性。
在这里插入图片描述

3. 反射调用普通方法

参数用String.class,int.class等描述,declared一般指本类。利用反射整体的形式上不会有任何的明确的类对象产生,这样的处理避免了与某一个类的耦合问题

  • 在Class类里面提供有如下的操作可以获取方法对象:
获取全部方法:public Method[] getMethods​() throws SecurityException
获取指定方法:public Method getMethod​(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException
获取本类全部方法:public Method[] getDeclaredMethods​() throws SecurityException
获取本类指定方法:public Method getDeclaredMethod​(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException
    //参数为方法的名字,参数的class属性值
//获取本类方法
Method methods[] = cls.getDeclaredMethods();
for(Method temp: methods){
    System.out.println(temp);
}
  • 根据指定方法对象,实现调用反射方法
public Object invoke​(Object obj, Object… args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    //参数为调用方法的对象,和给定的参数值

【重要】在不导入指定开发包的情况下实现方法调用:

public class TestDemo {
    public static void main(String[] args) throws Exception {
        //1.获取指定类的Class对象
        Class<?> cls = String.class;
        //2.调用有参构造实例化
        Object obj = cls.getDeclaredConstructor(String.class).newInstance("fkd.sdu");
        //3.获取指定方法
        //参数为方法的名字,参数的class属性值
        Method methodReplace = cls.getDeclaredMethod("replaceAll", String.class, String.class);
        //4.调用方法,相当于String.replaceAll("edu","zju")
        System.out.println(methodReplace.invoke(obj,"sdu","zju"));
    }
}
Class<?> cls = Candidate.class;//获取指定类的class对象
Object obj = cls.getDeclaredConstructor(String.class, long.class, int.class).newInstance("fkd", 1, 100);  //实例化对象      System.out.println(cls.getMethod("getTicket").invoke(obj));//调用方法

(不重要)自定义方法信息显示(类似于编辑器实现)

Method methods [] = cls.getMethods();
for (Method met : methods) {
int mod = met.getModifiers(); // 修饰符
System.out.print(Modifier.toString(mod) + " ");
System.out.print(met.getReturnType().getName() + " ");
System.out.print(met.getName() + "(");
Class<?> params [] = met.getParameterTypes() ; // 获取参数类型
for (int x = 0; x < params.length; x++) {
     System.out.print(params[x].getName() + " " + "arg-" + x);
     if (x < params.length - 1) {
         System.out.print(",");
      }
}
System.out.print(")");
Class<?> exp [] = met.getExceptionTypes();
if (exp.length > 0) {
      System.out.print(" throws ");
}
for (int x = 0; x < exp.length; x++) {
      System.out.print(exp[x].getName());
if (x < exp.length  - 1) {
      System.out.println(",");
	}
}
      System.out.println(); // 换行
}

4. 反射调用成员属性

获取类中全部成员属性,在Class类中提供有如下方法:

获取本类全部成员:public Field[] getDeclaredFields() throws SecurityException;
获取本类指定成员:public Field getDeclaredField(String name) throws NoSuchFieldException SecurityException;
获取全部成员:public Field[] getFields() throws SecurityException;
获取父类指定成员:public Field getField(String name) throws NoSuchFieldException SecurityException

一定要先有实例化对象之后才可以进行成员属性操作

设置属性内容:public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;
获取属性内容:public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException;
解除封装:public void setAccessible(boolean flag)
  • Method,Field,Constructor都可以解除封装

设置方法:public void setAccessible(boolean flag);

Class<?> cls = Candidate.class;
Object obj = cls.getDeclaredConstructor(String.class, long.class, int.class).newInstance("fkd", 111, 22);//实例化对象
Field field = cls.getDeclaredField("cname");//获取成员属性对象
field.setAccessible(true);//接触private封装
field.set(obj, "jsy");//调用设置属性方法
System.out.println(field.get(obj));

成员属性的更改获取一般可以通过反射getter与setter方法实现,开发中不常用。而对于Field类在实际开发之中只有一个方法最为常用:

注意返回的是class类型,获取名字后要加getSimpleName()

获取成员类型:public Class<?> getType()
Class<?> cls = Candidate.class;
Field nameField = cls.getDeclaredField("cname");
System.out.println(nameField.getType().getName());//java.lang.String   
System.out.println(nameField.getType().getSimpleName());//String

5. unsafe工具类

Java中提供有一个Unsafe类(不安全的操作),这个类的主要特点是可以利用反射来获取对象,并且直接使用底层的C++来代替JVM执行.

即:可以绕过JVM的相关的对象的管理机制,如果你一旦使用了Unsafe类,那么你的项目之中将无法继续使用JVM的内存管理机制以及垃圾回收处理。

范例:使用Unsafe类绕过实例化对象的管理,即强行实例化

public class TestDemo {
    public static void main(String[] args) throws Exception {
        //获取Unsafe实例化对象通过私有常量属性:private static final Unsafe theUnsafe = new Unsafe();
        Field field = Unsafe.class.getDeclaredField("theUnsafe");//获取成员
        field.setAccessible(true);//接触封装
        Unsafe instance = (Unsafe) field.get(null);//static属性不需要传递实例化对象
        
        // 利用Unsafe类绕过了JVM的管理机制,可以在没有实例化对象的情况下获取一个Singleton类实例化对象
        Singleton singleton = (Singleton) instance.allocateInstance(Singleton.class);
        singleton.print();
    }
}
class Singleton{
    private Singleton(){
        System.out.println("实例化对象");
    }
    public void print(){
        System.out.println("打印成功");
    }
}

4.4 反射与简单Java类

传统开发:产生对象并调用方法setter设置属性时有很多代码重复。

反射机制可以根据其自身的特点(Object类直接操作、可以直接操作属性或方法)实现相同功能类的重复操作的抽象处理。

1.属性自动赋值实现框架

思路:

  • 1.利用字符串的形式来自定义描述对应的类型。下面就采用“属性:内容|属性:内容|”的形式来为简单Java类中的属性初始化,即使类中属性再多也能轻松初始化。
  • 类设计的基本结构:应该由一个专门的ClassInstanceFactory类负责所有的反射处理,即:接收反射对象与要设置的属性内容,同时可以获取指定类的实例化对象

在这里插入图片描述

public class TestDemo {
    public static void main(String[] args) throws Exception {
        String value = "cname:fkd|job:CTO";
        Candidate can = ClassInstanceFactory.create(Candidate.class, value);
        System.out.println("姓名:" + can.getCname() + "职业:" + can.getJob());
    }
}

class ClassInstanceFactory {
    private ClassInstanceFactory() {};

    /**
     * @param clazz :要实现反射实例化的Class对象
     * @param value :要设置给对象的属性内容
     * @param <T> :返回的对象类型
     * @return:一个已经配置好属性内容的Java类对象
     */
    public static <T> T create(Class<?> clazz, String value) {
        return null;
    }
}

2. 单级属性赋值

类中属性的数据类型没有其他引用关联。此时应该:

  • 通过反射进行指定类对象的实例化处理;
  • 进行内容的设置(Field属性类型、方法名称、要设置的内容)

在这里插入图片描述

  • 定义StringUtils实现首字母大写功能:
public class StringUtil {
    private StringUtil(){}//构造方法私有化
    /**
     * 实现字符串首字母转大写
     * @param str :传入的字符串
     * @return:首字母大写的字符串
     */
    public static String initCap(String str){
        if(str == null || "".equals(str)){
            return str;
        }
        if(str.length() == 1){
            return str.toUpperCase();
        }else{
            return str.substring(0,1).toUpperCase() + str.substring(1);
        }
    }
}
  • 定义BeanUtils工具类,该工具类主要实现属性的设置
public class BeanUtil {
    private BeanUtil(){}
    public static void setValue(Object obj, String value){
        String results[] = value.split("\\|");
        for (int i = 0; i < results.length; i++) {
            String data[] = results[i].split(":");
            try {
                Field field = obj.getClass().getDeclaredField(data[0]);//定义属性对象
                Method setMethod = obj.getClass().getDeclaredMethod("set" + StringUtil.initCap(data[0]),field.getType());//定义方法对象(方法名+参数属性)
                setMethod.invoke(obj, data[1]);//调用类中setter方法来设置内容
            }catch (Exception e){}
        }
    }
}
  • ClassInstanceFactory 负责实例化对象并且调用BeanUtils类实现属性内容的设置。
public class ClassInstanceFactory {
    private ClassInstanceFactory() {};
    /**
     * @param clazz :要实现反射实例化的Class对象
     * @param value :要设置给对象的属性内容
     * @param <T> :返回的对象类型
     * @return:一个已经配置好属性内容的Java类对象
     */
    public static <T> T create(Class<?> clazz, String value) {
        try {//通过反射设置属性时,类中必须要有无参构造
            Object obj = clazz.getDeclaredConstructor().newInstance();//获取实例化对象
            BeanUtil.setValue(obj, value);//通过反射设置属性
            return (T) obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;//出现错误时设置为空
        }
    }
}
  • 测试类
public class TestDemo {
    public static void main(String[] args) throws Exception {
        String value = "cname:fkd|job:CTO";
        Candidate can = ClassInstanceFactory.create(Candidate.class, value);
        System.out.println("姓名:" + can.getCname() + "、职业:" + can.getJob());
    }
}

3.设置多种数据类型

简单java类中的属性类型一般的可选为:long(Long)、int(Integer)、double(Double)、String、Date(日期、日期时间),所以这个时候对于当前的程序代码就必须做出修改,要求可以实现各种数据类型的配置。

public class BeanUtil {
    private BeanUtil(){}
    public static void setValue(Object obj, String value){
        String results[] = value.split("\\|");
        for (int i = 0; i < results.length; i++) {
            String data[] = results[i].split(":");
            try {
                Field field = obj.getClass().getDeclaredField(data[0]);//定义属性对象
                Method setMethod = obj.getClass().getDeclaredMethod("set" + StringUtil.initCap(data[0]),field.getType());//定义方法对象(方法名+参数属性)
                // 利用Object来接收所有类型的数据
                Object convertValue = BeanUtil.convertAttributeValue(field.getType().getSimpleName(), data[1]);
                setMethod.invoke(obj, convertValue);//调用类中setter方法来设置内容
            }catch (Exception e){}
        }
    }

    /**
     * 实现属性类型的转换的功能
     * @param type :要转换的属性类型,通过Field属性对象获取
     * @param value :属性的内容,传入的都是字符串,将其转换为指定类型
     * @return:转换后的数据
     */
    public static Object convertAttributeValue(String type,String value){
        if("long".equals(type)){//转换为Long类型
            return Long.parseLong(value);
        }else if("int".equals(type)){//转换int类型
            return Integer.parseInt(value);
        }else if("Date".equals(type)){//转换为Date类型
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            try {
                return sdf.parse(value);
            } catch (ParseException e) {
                return new Date();
            }
        }else {//返回字符串
            return value;
        }
    }
}

4.级联对象实例化与赋值

为解决有些属性属于级联属性,对其进行实例化处理:

pro.pname:浙江省: Candidate类实例化对象.getPro().setDname(“浙江省”)
pro.country.name:中国:Candidate类实例化对象.getPro().getCountry().setName(“中国”)

要实现属性自动赋值,首先要对级联对象实例化,实例化对象时由于最后一级是属性类型,因此少一位的循环完成后可对currentObj对象进行属性赋值。

public class BeanUtil {
    private BeanUtil(){}
    public static void setValue(Object obj, String value){
        String results[] = value.split("\\|");
        for (int i = 0; i < results.length; i++) {
            //data[0]保存属性类型,data[1]保存属性内容
            String data[] = results[i].split(":");
            try {
                //多级配置,首先实例化多级对象
                if(data[0].contains(".")){
                    String temp[] = data[0].split("\\.");
                    Object currentObj = obj;
                    for (int j = 0; j < temp.length - 1; j++) {//一级级实例化对象,最后一位是属性名称,不考虑
                        //调用getter方法判断是否实例化对象了
                        Method getMethod = currentObj.getClass().getDeclaredMethod("get"+ StringUtil.initCap(temp[j]));
                        //getter调用的对象
                        Object tempObject = getMethod.invoke(currentObj);
                        if (tempObject == null){//该对象没有实例化
                            Field field = currentObj.getClass().getDeclaredField(temp[j]);//获取对应属性类型
                            //调用setter方法
                            Method setMethod = currentObj.getClass().getDeclaredMethod("set"+StringUtil.initCap(temp[j]),field.getType());
                            //用object类型接受实例化对象
                            Object newInstance = field.getType().getDeclaredConstructor().newInstance();
                            setMethod.invoke(currentObj, newInstance);
                            currentObj = newInstance;
                        }else{//已经实例化,继续下一级
                            currentObj = tempObject;
                        }
                    }
                    //此时的currentObj应该指向最后一级的对象,可以进行属性内容的设置
                    Field field = currentObj.getClass().getDeclaredField(temp[temp.length - 1]);
                    Method setMethod = currentObj.getClass().getDeclaredMethod("set"+StringUtil.initCap(temp[temp.length -1]),field.getType());
                    Object convertValue = BeanUtil.convertAttributeValue(field.getType().getSimpleName(), data[1]);
                    setMethod.invoke(currentObj, convertValue);
                }else{//单级配置
                    Field field = obj.getClass().getDeclaredField(data[0]);//定义属性对象
                    Method setMethod = obj.getClass().getDeclaredMethod("set" + StringUtil.initCap(data[0]),field.getType());//定义方法对象(方法名+参数属性)
                    // 利用Object来接收所有类型的数据
                    Object convertValue = BeanUtil.convertAttributeValue(field.getType().getSimpleName(), data[1]);
                    setMethod.invoke(obj, convertValue);//调用类中setter方法来设置内容
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

4.5 ClassLoader类加载器

在JVM里面可以根据类加载器而后进行指定路径中类的加载,也就是说找到了类的加载器就意味着找到了类的来源。

在这里插入图片描述

Class类(反射的根源)实现,方法:public ClassLoader getClassLoader()。
继续获取其父类的ClassLoader 类对象:public final ClassLoader getParent()

JDK1.8及以前提供有“ExtClassLoader”。开发者可以将*.jar文件拷贝到java路径\lib\ext目录里面,这样就可以直接执行了。但之后因为不安全被废除。

在这里插入图片描述

系统类中的类加载器都是根据CLASSPATH路径进行类加载的,而如果有了自定义类的加载器,就可以由开发者任意指派类的加载位置,其顺序在系统类加载器之后。

由于对于类加载器提供有双亲加载机制,当自定义类与系统类重名时,为保证系统安全性将不加载自定义类。

结合到网络程序开发的话,就可以通过一个远程的服务器来确定更新类的功能。

  • 任意编写一个程序类,保存到磁盘上—>编译

  • 自定义一个类加载器,并且继承自ClassLoader类。

    ClassLoader类里面为用户提供有一个字节转换为类结构的方法:
    定义类:protected final Class<?> defineClass(String name, byte[] b, int off, int len) thorws ClassFormatError
public class MyClassLoader extends ClassLoader{
    private static final File MessageFile = new File("E:\\Message.class");
    /**
     * 进行指定类的加载
     * @param className :类的完整名称:包.类
     * @return:返回一个指定类的class对象
     */
    public Class<?> loadData(String className) throws Exception {
        byte data[] = this.loadClassData();//读取到了数据
        if(data != null){
            return super.defineClass(className,data,0,data.length);//转换为类结构
        }
        return null;
    }
    private byte[] loadClassData() throws Exception {//通过文件进行类的加载
        InputStream input = new FileInputStream(MessageFile);
        ByteArrayOutputStream output = new ByteArrayOutputStream();//内存流
        byte data[] = new byte[1024];
        int length = 0;
        while((length = input.read(data))!=-1){//读取数据
            output.write(data,0,length);//保存入内存
        }
        byte result[] = output.toByteArray();
        input.close();
        output.close();
        return result;
    }
}
  • 定义测试类,实现加载控制
public class TestDemo {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> cls = classLoader.loadData("zju.edu.Message");
        //反射调用类方法
        Object obj = cls.getDeclaredConstructor().newInstance();
        Method method = cls.getDeclaredMethod("send");
        method.invoke(obj);
    }
}

4.6 反射与代理设计模式

1.静态代理模式

定义接口,核心类与代理类都实现该接口方法。代理类在构造方法中引入核心类对象,并在方法中调用。

package cn.mldn.demo;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
    	IMessage msg = new MessageProxy(new MessageReal());
    	msg.send();
    }
}
interface IMessage { // 传统代理设计必须有接口
	public void send(); // 业务方法
}
//核心业务类
class MessageReal implements IMessage {
	@Override
	public void send() {
		System.out.println("【核心业务】www.mldn.cn");
	}
}
// 代理类
class MessageProxy implements IMessage { 
	private IMessage message ; // 代理对象,一定是业务接口实例
	public MessageProxy(IMessage message) {
		this.message = message ;
	}
	@Override
	public void send() {
		if (this.connect()) {
			this.message.send(); // 消息的发送处理
			this.close();
		}
	}
	public boolean connect() {
		System.out.println("【代理业务】进行消息发送通道的连接。");
		return true ;
	}
	public void close() {
		System.out.println("【代理业务】关闭消息通道。");
	}
}

弊端:一个代理类只为一个接口服务,耦合严重。如何让一个代理类满足于所有的业务接口操作要求?

2.动态代理设计模式

为所有功能一致的业务操作接口提供有统一的代理处理操作

在这里插入图片描述

  • InvocationHandler接口规定了代理方法的执行
public interface InvocationHandler {
	/**
	 * 代理方法调用,代理主题类里面执行的方法最终都是此方法
	 * @param proxy 要代理的对象
	 * @param method 要执行的接口方法名称
	 * @param args 传递的参数
	 * @return 某一个方法的返回值
	 * @throws Throwable 方法调用时出现的错误继续向上抛出
	 */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}
  • java.lang.reflect.Proxy程序类提供动态代理对象,在处理时依赖于类加载器与接口进行代理对象的伪造。
代理对象:public static Object newProxyInstance​(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//ClassLoader loader:获取当前真实主体类的ClassLoader ;
//Class<?>[] interfaces:代理是围绕接口进行的,所以一定要获取真实主题类的接口信息;
//InvocationHandler h:代理处理的方法。

实现动态代理机制:

public class MessageProxy implements InvocationHandler {
    private Object target;//接收真实业务对象
    /**
     * 接收真实业务对象,返回代理后的业务对象
     * @param target: 真实业务对象
     * @return 生成的代理业务对象
     */
    public Object bind(Object target){
        this.target = target;
        //返回代理业务对象,传入真实业务类加载器、代理接口、代理方法
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }
    //代理业务逻辑
    public boolean connect(){
        System.out.println("[代理业务]建立连接");
        return true;
    }
    public void close(){
        System.out.println("[代理业务]关闭连接");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //定义方法返回值
        Object returnData = null;
        if(this.connect()){
            //反射真实业务的方法
            returnData = method.invoke(this.target,args);
            this.close();
        }
        return returnData;
    }
}
public interface IMessage {
    public void send();
}
public class MessageReal implements IMessage{

    @Override
    public void send() {
        System.out.println("[核心业务]发送");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = (IMessage) new MessageProxy().bind(new MessageReal());
        msg.send();
    }
}

3.CGLIB实现代理设计模式

基于类的代理设计,不再强制性与接口绑定

https://blog.csdn.net/kenidi8215/article/details/109998643

4.7 反射与Annotation

使用Annotation之后的最大特点是可以结合反射机制实现程序的处理

Annotation有三类:

  • 运行时生效的Annotation:@FunctionalInterface
  • 在源代码时生效的Annotation:@SuppressWarnings
  • 在类定义时生效

获取全部Annotation:

public Annotation[] getAnnotations();

获取指定的Annotation:

public <T extends Annotation> T getAnnotation​(Class<T> annotationClass)

在这里插入图片描述

  • 自定义Annotation
package cn.mldn.demo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

//自定义注解
@Retention(RetentionPolicy.RUNTIME)//定义运行策略
@interface DefaultAnnotation {
    public String title();
    public String content() default "fkd from zju.";
}
public class Message {
    @DefaultAnnotation(title = "2021")
    public void send(String msg){
        System.out.println("[消息发送]"+msg);
    }
}
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Method method = Message.class.getDeclaredMethod("send", String.class);
        //获取指定的Annotation
        DefaultAnnotation anno = method.getAnnotation(DefaultAnnotation.class);
        //调用注解中的方法
        String str = anno.title() + anno.content();
        method.invoke(Message.class.getDeclaredConstructor().newInstance(),str);
    }
}

**代理+工厂+Annotation:**由注解指定不同的核心业务

  • 核心业务接口:
public interface IMessage {
    public void send(String msg);
}
  • 核心业务类:
public class NetMessageReal implements IMessage{
    @Override
    public void send(String msg) {
        System.out.println("[网络信息发送]:"+msg);
    }
}
  • 代理业务类:
//代理业务类
public class MessageProxy implements InvocationHandler {
    private Object target;//接受核心业务类
    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(),this);
    }
    private boolean connect(){
        System.out.println("[代理业务]:建立网络连接");
        return true;
    }
    private void close(){
        System.out.println("[代理业务]:关闭连接");
    }
    //实现代理方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnData = null;
        if (this.connect()){
            returnData = method.invoke(this.target,args);
        }
        this.close();
        return returnData;
    }
}
  • 工厂类
public class Factory {
    private Factory(){}

    /**
     * //直接返回一个与接口属性相关的实例化对象
     * @param clazz 核心业务的类属性
     * @param <T> 接口属性,由外部指定
     * @return 实例化对象
     */

    public static <T>T getInstance(Class<T> clazz){
        try {
            return (T) new MessageProxy().bind(clazz.getDeclaredConstructor().newInstance());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 注解+核心业务服务类
//利用自定义注解指定核心业务指向
@Retention(RetentionPolicy.RUNTIME)//运行准则
@interface UserMessage{
    public Class<?> clazz();
}

@UserMessage(clazz = NetMessageReal.class)
public class MessageService {
    private IMessage message;
    public MessageService(){
        //获取注解
        UserMessage usg = MessageService.class.getAnnotation(UserMessage.class);
        this.message = (IMessage) Factory.getInstance(usg.clazz());
    }
    public void send(String msg){
        this.message.send(msg);
    }
}
  • 测试类
public class TestDemo {
    public static void main(String[] args) throws Exception {
        MessageService msv = new MessageService();
        msv.send("fkd");
    }
}

五、类集框架

对常见的数据结构进行完整的实现包装,并提供了一系列的接口与实现子类,来帮助用户减少数据结构所带来的开发困难

在整个类集框架中,提供了如下几个核心接口:Collection、List、Set、Map、Iterator、Enumeration、Queue、ListIterator。以及Collection集合接口中的一些方法。

5.1 Collection接口

java.util.Collection是单值集合操作的最大的父接口,在该接口中定义有所有的单值数据的处理操作

No.方法名称类型描述
01public boolean add(E e)普通向集合保存数据
02public boolean addAll(Collection<? extends E> c)普通追加一组数据
03public void clear()普通清空集合,让根节点为空,同时执行GC处理
04public boolean contains(Object o)普通查询数据是否存在,需要equals()方法支持
05public boolean remove(Object o)普通数据删除,需要equals()方法支持
06public int size()普通获取数据长度,最大值为Integer. MAX_VALUE
07public Object[] toArray()普通将集合变为对象数组返回
08public Iterator iterator()普通将集合变为Iterator接口返回

5.2 List集合

是Collection的子接口,并进行了功能扩充,其有三个子类:ArrayList、Vector、LinkedList

特点:允许保存有重复元素数据

注意:在使用List保存自定义对象时,如果需要使用到contains()、remove()方法进行查询或删除处理时一定要保证类中已经覆写了equals()方法。

No.方法名称类型描述
01public E get(int index)普通获取指定索引上的数据
02public E set(int index, E element)普通修改指定索引数据
03public ListIterator listIterator()普通返回ListIterator接口对象

1.ArrayList子类

其继承结构关系为:

在这里插入图片描述

ArrayList封装的是一个数组,默认开辟长度为10的数组(无参构造),当数组长度不够时会进行新数组开辟,每次2倍扩充,然后将旧数组拷贝到新数组中。

2.LinkedList子类

其继承关系为:

在这里插入图片描述

LinkedList封装的就是一个链表实现

请问ArrayList与LinkedList有什么区别?

  • ArrayList是数组实现的集合操作,而LinkedList是链表实现的集合操作;
  • 在使用List集合中的get()方法根据索引获取数据时,ArrayList的时间复杂度为“O(1)”、而LinkedList时间复杂度为“O(n)”(n为集合的长度);
  • ArrayList在使用时默认的初始化对象数组的大小长度为10,如果空间不足则会采用2倍形式进行容量的扩充,如果保存大数据量的时候有可能会造成垃圾的产生以及性能的下降,但是这时候可以使用LinkedList类保存。

3.Vector子类

继承关系和操作同ArrayList,但Vector类中的操作方法采用的都是synchronized同步处理,虽线程安全但性能不如ArrayList

5.3 Set集合

是Collection的子接口,有两个常用的子类:HashSet、TreeSet

特点:不允许保存重复元素

1.HashSet子类

特点:保存的数据是无序的,自动消灭重复。开发首选

其继承关系为:

在这里插入图片描述

分析重复数据消除:

  • 重复元素的判断处理利用的就是Object类提供的**hashCode()和equals()**两个方法共同作用完成的。
  • 在进行重复元素判断的时候首先利用hashCode()进行编码的匹配,如果该编码不存在,则表示数据不存在,证明没有重复,如果该编码存在,则进一步进行对象比较处理,如果发现重复了,则此数据是不允许保存的。

2.TreeSet子类

特点:保存的数据是升序的,自动消灭重复

其继承关系为:

在这里插入图片描述

分析重复数据消除:

  • TreeSet数据排序的类必须实现Comparable类接口。一定要将该类中所有属性都依次进行大小关系的匹配,否则某一个或者几个属性相同的时候也会被认为是重复数据。
  • 所以TreeSet是利用了Comparable接口来确认重复数据的

5.4 集合输出

一共有四种输出形式:Iterator迭代输出(95%)、ListIterator双向迭代输出(0.1%)、Enumeration枚举输出(4.9%)、foreach输出(与Iterator相当)

在这里插入图片描述

1. Iterator迭代输出

Collection接口类是Iterator的子接口,其中有调用Iterator类对象的方法

在Iterator接口里面定义有如下的方法:

No.方法名称类型描述
01public boolean hasNext()普通判断是否有数据
02public E next()普通取出当前数据
03public default void remove()普通删除

利用Iterator输出:

List<String> all = new ArrayList<>();
all.add("fkd");all.add(" is");all.add(" a");all.add(" handsome");all.add(" boy");
Iterator<String> iter = all.iterator();
while(iter.hasNext()){//只知道循环结束条件,不知道次数
     System.out.print(iter.next());
}

注意:请解释Collection.remove()与Iterator.remove()的区别?

  • 在进行迭代输出的时候,如果使用了Collection.remove()则会造成并发更新的异常,导致程序删除出错,

  • 而此时只能够利用Iterator接口中remove()方法实现正常的删除处理。

2.ListIterator双向迭代输出

只有List<>子接口能实现相关处理。想实现由后向前遍历,首先要实现的由前向后遍历(指针的感觉)。

No.方法名称类型描述
01public boolean hasPrevious()普通判断是否有前一个元素
02public E previous()普通取出当前数据

3.Enumeration枚举输出

只为Vector一个类服务,获取Enumeration:

public Enumeration elements()

No.方法名称类型描述
01public boolean hasMoreElements()普通判断是否有下一个元素
02public E nextElement()普通取出当前数据

4.foreach输出

整体输出结构与数组基本一致

List<String> all = new ArrayList<>();
all.add("fkd");all.add(" is");all.add(" a");all.add(" handsome");all.add(" boy");
for(String temp:all){
    System.out.print(temp);
}

5.5 Map集合

Collection接口的主要目的是为了输出,而Map的主要目的是为了查询

Map接口是进行二元偶对象(key=value)的形式保存的最大父接口。

常用的子类:HashMap、HashTable、TreeMap、LinkedHashMap。

在Map接口中定义有许多操作方法,但是需要记住以下的核心操作方法:

No.方法名称类型描述
01public V put(K key,V value)普通向集合中保存数据
02public V get(Object key)普通根据key查询数据
03public Set<Map.Entry<K,V>> entrySet()普通将Map集合转为Set集合
04public boolean containsKey(Object key)普通查询指定的key是否存在
05public Set keySet()普通将Map集合中的key转为Set集合
06public V remove(Object key)普通根据key删除指定的数据
  • Map.Entry内部接口

Map中的所有的key和value的数据都被封装在Map.Entry接口之中,,并且这个内部接口提供有两个重要的方法:

1)获取key:K getKey()2)获取value:V getValue()

Map.Entry的主要作用就是作为一个Key和Value的包装类型使用,而大部分情况下在进行数据存储的时候都会将key和value包装为一个Map.Entry对象进行使用

1.HashMap子类

主要特点是无序存储

继承关系:

在这里插入图片描述

  • HashMap中Key和Value都可以为null;
  • key重复时,会出现value内容的替换
  • Map接口中提供的put()方法本身是提供有返回值的,指的是在重复key的情况下返回旧的value

HashMap之中肯定需要存储大量的数据,那么对于数据的存储,来看看HashMap是怎样操作的:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // 该属性默认的内容为“0.75”
}
//在使用put()方法进行数据保存时会调用一个putVal()方法,同时会将key进行hash处理(生成一个hash码)
//而对于putVal()方法中会发现会提供一个Node节点类进行数据的保存,而在使用putVal()方法操作的过程中,会调用一个resize()方法可以进行容量的扩充
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);

面试题:在进行HashMap的put()操作时,如何实现容量扩充?

  • 在HashMap类中提供了一个“DEFAULT_INITIAL_CAPACITY”的常量,作为初始化的容量配置,而这个常量的默认大小为16个元素,也就是说默认的可以保存的最大内容是16;
  • 当保存的内容的容量超过了一个阈值(DEFAULT_LOAD_FACTOR=0.75f),相当于“容量*阈值=12”保存12个元素的时候就会进行容量的扩充;
  • 在进行扩充的时候HashMap采用的是成倍(移位)的扩充模式,即:每一次都扩充2倍的容量。

面试题:请解释HashMap的工作原理(JDK1.8之后开始的)

  • 在HashMap中进行数据存储依然是利用Node类完成的,那么这种情况下就证明可以使用的数据结构只有两种:链表(时间复杂度“O(n)”)、二叉树(时间复杂度“O(logn)”);
  • 从JDK1.8开始,HashMap为适应于大数据时代的海量数据问题,所以对其存储发生了变化,在HashMap类的内部提供有一个阈值常量:“TREEIFY_THRESHOLD = 8;”,在使用HashMap进行数据保存时,如果保存的数据没有超过阈值8,那么会按照链表的形式进行存储,如果超过了阈值,则会将链表转为红黑树以实现树的平衡,并且利用左旋与右旋保证数据的查询性能。

2.LinkedHashMap子类-子类

是HashMap的子类,是基于链表实现的,添加顺序即为其顺序

使用LinkedHashMap类时数据量不要特别大

3.HashTable子类

Map的子类,属于最早一批的动态数组实现类,在进行数据存储的时候key和value都不能为null

面试题:请解释HashMap与HashTable的区别?

  • HashMap中的方法都属于异步操作,非线程安全,HashMap允许保存有null的数据;
  • HashTable都属于同步方法(线程安全),HashTable不允许保存null,否则会出现NullPointerException异常;

4.使用Iterator输出Map集合

Map集合里面里面保存的实际上是一组Map.Entry接口对象(里面包装的是Key和Value),其实也是单值保存,故可以将Map集合转为Set集合后调用Iterator输出

实现步骤:

  • 利用Map接口中提供的entrySet()方法将Map集合转为Set集合;
  • 利用Set接口中的iterator()方法将Set集合转为Iterator接口实例;
  • 利用Iterator进行迭代输出获取每一组的Map.Entry对象,随后通过getKey()与getValue()获取数据。
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
//此时保存的数据类型为封装后的对象Map.Entry<>
Set<Map.Entry<String, Integer>> set = map.entrySet();//转为Set集合
Iterator<Map.Entry<String, Integer>> iter = set.iterator();
while (iter.hasNext()){
     Map.Entry<String, Integer> temp = iter.next();//取出封装后的对象Map.Entry
     System.out.println(temp.getKey()+" = "+temp.getValue());
}
//用foreach输出
Set<Map.Entry<String, Integer>> set = map.entrySet();//转为Set集合
for(Map.Entry<String, Integer> temp:map.entrySet()){
      System.out.println(temp.getKey()+" = "+temp.getValue());
}

5.自定义key类型

对于自定义Key类型所在的类中一定要覆写hashCode()和equals()方法,否则无法查找到

在实际的开发之中对于Map集合的Key常用的类型就是:String、Long、Integer,尽量使用系统类。

面试题:

如果在进行HashMap进行数据操作的时候出现了Hash冲突(Hash码相同),HashMap是如何解决的?
当出现了Hash冲突之后为了保证程序的正常执行,会在冲突的位置上将所有Hash冲突的内容转为链表保存。

5.6 集合工具类

1.Stack栈操作

栈是一种先进后出的数据结构,功能同撤销。

栈Stack是Vector的子类,主要的核心方法为push(E item)入栈与pop()出栈

Stack<String> all = new Stack<String> ();
all.push("fkd");
all.pop();

2.Queue队列

先进先出,队列的实现可以使用LinkedList子类。或者可以使用PriorityQueue实现优先级队列(有排序)

队列的使用主要依靠Queue接口之中提供的方法来处理,提供有如下方法:

向队列中追加数据:boolean offer(E e),可以直接使用add()方法;
通过队列获取数据:public E poll(),弹出后删除数据

使用队列:

Queue<String> queue = new LinkedList<String>();//队列
//Queue<String> queue=new PriorityQueue<String>();//优先级队列
queue.offer("X");//追加队列数据,通过队尾追加
//queue.add("X");//追加队列数据,通过队尾追加
System.out.println(queue.poll());//X

3.Properties属性操作

Properties类型是HashTable的子类,按key = value的形式进行保存,保存内容只能是字符串。

最大特点就是可以进行资源内容的输入与输出的处理操作,主要用于读取配置资源的信息

No.方法名称类型描述
01public Object setProperty(String key, String value)普通设置属性
02public String getProperty(String key)普通获取属性,key不存在返回null
03public String getProperty(String key, String defaultValue)普通获取属性,key不存在返回默认值
04public void store(Writer out, String comments) throws IOException普通输出属性内容
05public void load(Reader in) throws IOException普通通过输入流读取属性内容

保存与读取资源文件:

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Properties pro = new Properties();
        pro.setProperty("fkd", "BF");
        pro.setProperty("jsy", "GF");
        //将属性资源保存入文件中
        pro.store(new FileWriter(new File("F://zju//fkd.properties"), true), "资源信息");
        //读取资源文件
        Properties pro1 = new Properties();
        pro1.load(new FileReader(new File("D://IDEA_workspace//JavaProject//Module_object//src//zju//message//fkd.properties")));
        System.out.println(pro.getProperty("fkd"));
    }
}

4.Collections工具类

Collections是Java提供的一组集合数据的操作工具类,针对Map、List、Set、queue集合。需要数据类型实现Comparable

List<String> all = new ArrayList<String>();
Collections.addAll(all,"hello","my","girl");//数据批量添加
Collections.reverse(all);//数据的翻转
Collections.sort(all);//调用二分搜索前需要对集合按自然排序
Iterator<String> iter = all.iterator();
while (iter.hasNext()){
     System.out.print(iter.next()+"、");
}
System.out.println(Collections.binarySearch(all,"my"));//二分搜索

面试题:请解释Collection与Collections的区别?

  • Collection是集合接口,允许保存单值对象;
  • Collections是集合操作的工具类。

5.7 Stream数据流

针对大数据专门提供的数据流式分析处理接口,主要是利用其自身的特点实现数据的分析处理操作。

在Collection接口中有提供其实例化的方法:

//获取Stream接口对象:
    default Stream<E> stream()
//数据的筛选并采集并转换为集合类型:
    List<T> result = list.stream().filter(item -> item的逻辑判断).collect(Collectors.toList())
//设置取出最大的数据量:
    Stream<T> limit​(long maxSize)
//跳过指定数据量:
    Stream<T> skip​(long n)    

数据操作、采集与分页:

List<String> list = new ArrayList<String>();        Collections.addAll(list,"C","python","Java","JavaScript","Ruby", "Go", "Json", "JSP");
//筛选出大数据中含有"j"的元素,跳过第一个,取两个保存在集合中
List<String> result = list.stream().filter((element)->element.toLowerCase().contains("j")).skip(1).limit(2).collect(Collectors.toList());
Iterator<String> iter = result.iterator();
while (iter.hasNext()){
     System.out.println(iter.next());
}
  • MapReduce模型

对于这个模型一共是分为两个部分:Map处理部分、Reduce分析部分,在进行数据分析前必须要对数据进行合理的处理,而后才可以做统计分析操作

list.stream().mapToDouble((元素)->统计的数值double类型).summaryStatistics()

package cn.mldn.demo;
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;
import java.util.List;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
    	//如果要想使用Stream进行分析处理,则一定要将全部要分析的数据保存在集合中
    	List<Order> all = new ArrayList<Order>();
        all.add(new Order("小强娃娃", 9.9, 10));
        all.add(new Order("林弱充气娃娃", 2987.9, 3));
        all.add(new Order("不强版笔记本电脑", 8987.9, 8));
        all.add(new Order("弱强茶杯", 2.9, 800));
        all.add(new Order("阿强版煎饼", 0.9, 138));
        //分析购买商品中带有“强”的信息数据,,并且进行商品单价和数量的处理,随后分析汇总
        DoubleSummaryStatistics stat = all.stream().filter((ele) -> ele.getName().contains("强"))
        	.mapToDouble((orderObject)->orderObject.getPrice() * orderObject.getAmount()).summaryStatistics();
        System.out.println("购买数量:" + stat.getCount());
        System.out.println("购买总价:" + stat.getSum());
        System.out.println("平均花费:" + stat.getAverage());
        System.out.println("最高花费:" + stat.getMax());
        System.out.println("最低花费:" + stat.getMin());
    }
}
class Order { // 订单信息
    private String name; // 商品名称
    private double price; // 商品单价
    private int amount; // 商品数量
    public Order(String name,double price,int amount) {
    	this.name = name;
    	this.price = price;
    	this.amount = amount;
    }
    public int getAmount() {
		return amount;
	}
    public String getName() {
		return name;
	}
    public double getPrice() {
		return price;
	}
}

六、网络编程

所谓的网络编程指的是多台主机之间的数据通讯操作,通讯的实现包括一系列的处理协议:IP、TCP(可靠的数据连接)、UDP(不可靠数据连接)等等。

网络程序开发有两种模型:

  • C/S(Client/Server、客户端与服务器端):要开发出两套程序,一套程序为客户端,另外一套为服务端,如果现在服务端发生了改变之后客户端也应该进行更新处理,这种开发可以由开发者自定义传输协议,并且使用一些比较私密的端口,所以安全性是比较高的,但是开发与维护成本比较高;
  • B/S(Browser/Server、浏览器与服务器端):只开发一套服务端的程序,而后利用浏览器作为客户端进行访问,这种开发与维护的成本较低(只有一套程序),但是由于其使用的是公共的HTTP协议并且使用的公共的80端口,所以其安全性相对较差,现在的开发基本上以“B/S”结构为主。

6.1 Echo程序模型

TCP程序开发的核心的特点是使用两个类实现数据的交互处理:ServerSocket(服务端)、Socket(客户端)

ServerSocket的主要目的是设置服务器的监听端口,而Socket需要指明要连接的服务器地址和端口。

在这里插入图片描述

服务端:

1.设置服务器的监听端口

2.服务器连接后产生对应连接的客户端对象(server.accept())

3.服务端读取数据,服务端输出数据

4.服务器上启动多个线程,每一个线程单独为每一个客户端实现Echo服务支持

public class EchoServer {
    //为每一个接收的客户端提供多线程连接
    private static class ClientThread implements Runnable{
        private Socket client = null;
        private Scanner scanner = null;
        private PrintWriter pw = null;
        private boolean flag = true;
        public ClientThread(Socket client)throws Exception{
            this.client = client;
            //接受客户端的信息
            this.scanner = new Scanner(client.getInputStream());
            scanner.useDelimiter("\n");//设置读取分隔符
            //服务端输出流,等待被客户端接受
             this.pw = new PrintWriter(client.getOutputStream());
        }
        @Override
        public void run() {
            while(flag){
                //有数据发送
                if(scanner.hasNext()){
                    String val = scanner.next().trim();
                    if (val.equalsIgnoreCase("bye")){
                        //输出流
                        pw.println("Bye Bye");
                        flag = false;
                    }else{
                        pw.println("[ECHO]" + val);
                        pw.flush();//刷新缓冲区
                    }
                }
            }
            try {
                client.close();
                scanner.close();
                pw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)throws Exception {
        //设置服务器监听端口
        ServerSocket server = new ServerSocket(8080);
        System.out.println("等待客户端连接......");
        //客户端连接
        boolean flag = true;
        while(flag){
            Socket client = server.accept();
            new Thread(new ClientThread(client)).start();

        }
        server.close();
    }
}

客户端:

1.接受服务器端的输入内容

2.输出获取的内容

public class EchoClient {
    private static final Scanner SC = new Scanner(System.in);
    public static void main(String[] args) throws Exception {
        //定义服务端连接
        Socket client = new Socket("localhost", 9999);
        //读入服务器的输入内容
        Scanner scanner = new Scanner(client.getInputStream());
        scanner.useDelimiter("\n");//设置分隔符
        //向服务器发送内容
        PrintWriter pw = new PrintWriter(client.getOutputStream());
        boolean flag = true;
        while(flag){
            //从键盘中读入数据
            System.out.println("请输入要发送的内容:");
            String data = SC.nextLine().trim();//逐行读取键盘中的数据
            //消息内容发送给服务端
            pw.println(data);
            pw.flush();
            //读取服务端返回的消息
            if (scanner.hasNext()){
                System.out.println(scanner.next());
            }
            if ("Bye".equalsIgnoreCase(data)){
                flag = false;
            }
        }
        scanner.close();
        client.close();
        pw.close();
    }
}

6.2 UDP程序模型

UDP是基于数据报的网络编程实现,如果想要实现UDP程序需要两个类:DatagramPacket(数据内容)、DatagramSocket(网络的发送与接收)

数据报就好比发送的短消息一样,客户端是否收到与发送者无关

  • UDP客户端
public class UDPClient {
    public static void main(String[] args) throws Exception{
        //连接到8090端口
        DatagramSocket client = new DatagramSocket(8090);
        byte data[] = new byte[1024];//接受信息
        DatagramPacket packet = new DatagramPacket(data, data.length);
        System.out.println("客户端等待接受发送的消息.....");
        //接受消息,保存在data数组中
        client.receive(packet);
        System.out.println("接收到的消息为:"+ new String(data,0, data.length));
        client.close();
    }
}
  • UDP服务端
public class UDPServer {
    public static void main(String[] args) throws Exception{
        //开启网络服务端
        DatagramSocket server = new DatagramSocket(8080);
        String str = "fkd";
        //将数据包发送到8090口
        DatagramPacket packet = new DatagramPacket(str.getBytes(),0,str.length(), InetAddress.getByName("localhost"),8090);
        server.send(packet);
        System.out.println("消息发送完毕....");
        server.close();
    }
}

七、Java数据库编程

JDBC(Java Database Connectivity、Java数据库连接),JDBC属于一种服务,必须按照指定的套路来进行操作。

  • 在JDBC中核心的组成就是DriverManager类以及若干接口(Connection、Statement、PreparedStatement、ResultSet)

  • **JDBC网络连接:**通过特定的网络协议连接指定的数据库服务;
    —— 处理流程:程序→JDBC→网络数据库(IP地址、端口号)

7.1 连接数据库

  • 如果现在要连接Oracle数据库,则必须采用如下的步骤进行处理:

    1)通过反射机制将加载数据库驱动程序类:oracle.jdbc.driver.OracleDriver;
    (2)数据库连接需要有一个网络的连接地址,该地址结构如下:
    |——地址结构:jdbc:oracle:thin:@主机名称:端口号:SID;
    |——MLDN数据库:jdbc:oracle:thin:@localhost:1521:mldn;
    (3)数据库的用户名:scott;
    (4)数据库密码:tiger;
    

对于数据库的连接提供有java.sql.DriverManager的程序类,利用此类中的方法可以获取一个Connection接口对象。

获取Connection接口对象:public static Connection getConnection(String url, String user, String password) throws SQLException

实现具体连接:

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		System.out.println(conn);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

在这里插入图片描述

整个的JDBC设计实现的就是一个工厂类的处理机制。

DriverManager是一个工厂,不同的数据库生产商利用JDBC提供的标准实现各自的数据库处理操作

7.2 Statement数据库操作接口

java.sql.Statement是JDBC中提供的数据库的操作接口,利用其可以实现数据的更新与查询的处理操作(SQL语句)

在这里插入图片描述

当获取了Statement接口对象之后,就可以使用SQL进行处理了,而这里需要两个方法的支持:

  • 数据更新处理(Insert、Update、Delete): int executeUpdate(String sql) throws SQLException;
  • 数据查询处理(Select、统计查询、复杂查询):ResultSet executeQuery(String sql) throws SQLException;

这两个数据库的操作方法中都需要接收SQL字符串,也就是说Statement接口可以直接使用SQL语句实现开发:

1. 数据更新操作

  • 写SQL语句
String sql = "SQL";
  • 建立Connection对象
Connection connection = DriverManager.getConnection(URL,UESER,PASSWORD);
Class.forName(Driver);//加载驱动程序
  • 创建数据库的操作对象
Statement stmt = connection.createStatement();
  • 执行SQL更新语句
int num = stmt.executeUpdate(sql);//返回影响行数

实例:

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		//增加修改删除只需要更新SQL语句
        String sql = ""SQL语句.....;
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		Statement stmt = conn.createStatement(); // 创建数据库的操作对象
		int count = stmt.executeUpdate(sql); // 返回影响的行数
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

2.数据查询

  • 写SQL语句
String sql = "SQL";
  • 建立Connection对象
Connection connection = DriverManager.getConnection(URL,UESER,PASSWORD);
Class.forName(Driver);//加载驱动程序
  • 创建数据库的操作对象
Statement stmt = connection.createStatement();
  • 执行SQL查询语句(返回的是一个表),将返回值保存在ResultSet接口中
ResultSet rs = stmt.executeQuery(sql);
  • 读取查询结果
While(rs.next){
    int nid = rs.getInt("nid");
    String title = rs.getString("title");
    double price = rs.getDouble("price");
}

注意:ResultSet对象时保存在内存之中的

Statement执行SQL语句开发的弊端:

  • 不能很好的描述出日期的形式;
  • 需要进行SQL语句的拼凑处理,而导致的结果就是:SQL语句的编写与维护困难;
  • 对于一些敏感的字符数据无法进行合理拼凑

7.3 PreparedStatement数据库接口

是Statement接口的子接口,利用此接口可以将数据与SQL语句分离。利用占位符的形式,在SQL正常执行完毕后进行数据的设置。

1. 数据更新

  • 写SQL语句
String sql = "INSERT INTO news(nid, title, read, content, pubdate) VALUES"
    + "(news_seq.nextval,?,?,?,?)";
  • 初始化数据库,建立Connection对象
Connection connection = DriverManager.getConnection(URL,USER,PASSWORD);
Class.forName(Driver);
  • 创建数据库操作对象
PreparedStatement pstmt = connection.prepareStatement(sql);
  • 为占位符数据设置内容,按照顺序
pstmt.setString(1,"");
pstmt.setInt(2,34);
pstmt.setString(3,"");
pstmt.setDate(4,new Date())
  • 执行数据库操作
pstmt.executeUpdate();

实例:

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		String title = "MLDN新闻'老李写的"; 
		int read = 99;
		double price = 99.8;
		String content = "这个春天有点冷";
		Date pubdate = new Date();
		String sql = "INSERT INTO news(nid,title,read,price,content,pubdate) VALUES "
		+ " (news_seq.nextval,?,?,?,?,?)"; // 使用了"?"作为占位符
		System.out.println(sql);
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		pstmt.setString(1,title);
		pstmt.setInt(2,read);
		pstmt.setDouble(3,price);
		pstmt.setString(4,content);
		pstmt.setDate(5,new java.sql.Date(pubdate.getTime()));
		int count = pstmt.executeUpdate(); // 返回影响的行数
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

2. 数据查询

  • 根据ID进行查询

查询时变量用占位符设置

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		String sql = "SELECT nid,title,read,price,content,pudate FROM news WHERE nid=?";
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		pstmt.setInt(1,5); // 设置nid的数据
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		ResultSet rs = pstmt.executeQuery(sql);
		while (rs.next()) { // 现在如果发现还有数据行未输出
			int nid = rs.getInt(1);
			String title = rs.getString(2);
			int read = rs.getInt(3);
			double price = rs.getDouble(4);
			String content = rs.getString(5);
			Date pubdate = rs.getDate(6);
			System.out.println(nid + "、" + title + "、" + read
			+ "、" + price + "、" + content + "、" + pubdate);
		}
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}
  • 模糊查询,并用分页的形式进行显示
package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		int currentPage = 2; // 当前页
		int lineSize = 5 ; // 每页显示的数据行
		String column = "title" ; // 模糊查询列
		String keyWord = "MLDN"; // 查询关键字
		String sql = "SELECT * FROM ( "
		+ " SELECT nid,title,read,price,content,pudate,ROWNUM rn "
		+ " FROM news WHERE " + column + " LIKE ? AND ROWNUM<=? ORDER BY nid) temp "
		+ " WHERE temp.rn>?";
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		pstmt.setString(1,"%" + keyWord + "%");
		pstmt.setInt(2,currentPage * lineSize);
		pstmt.setInt(3,(currentPage - 1) * lineSize);
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		ResultSet rs = pstmt.executeQuery(sql);
		while (rs.next()) { // 现在如果发现还有数据行未输出
			int nid = rs.getInt(1);
			String title = rs.getString(2);
			int read = rs.getInt(3);
			double price = rs.getDouble(4);
			String content = rs.getString(5);
			Date pubdate = rs.getDate(6);
			System.out.println(nid + "、" + title + "、" + read
			+ "、" + price + "、" + content + "、" + pubdate);
		}
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

注意:

1.父类型能接收所有子类型

2.拼接字符串技巧"+num+"

String sql = “select * from t_user where loginname = '"+loginNmae +"' and loginPwd = '"+loginPwd+"'”;
loginName = fasd
loginPwd = fasd ' or'1' = '1
SQL注入:
String sql = “select * from t_user where loginname = 'fdsa' and loginPwd = 'fdsa' or '1' = '1';

3.Scanner中next()与nextLine()的区别

  • next() 对于录入的字符串碰到空格就会停止录入
  • nextLine()会把整行字符串全部录入

Statement执行SQL语句开发的弊端:

  • 不能很好的描述出日期的形式;
  • 需要进行SQL语句的拼凑处理,而导致的结果就是:SQL语句的编写与维护困难;
  • 对于一些敏感的字符数据无法进行合理拼凑

7.3 PreparedStatement数据库接口

是Statement接口的子接口,利用此接口可以将数据与SQL语句分离。利用占位符的形式,在SQL正常执行完毕后进行数据的设置。

1. 数据更新

  • 写SQL语句
String sql = "INSERT INTO news(nid, title, read, content, pubdate) VALUES"
    + "(news_seq.nextval,?,?,?,?)";
  • 初始化数据库,建立Connection对象
Connection connection = DriverManager.getConnection(URL,USER,PASSWORD);
Class.forName(Driver);
  • 创建数据库操作对象
PreparedStatement pstmt = connection.prepareStatement(sql);
  • 为占位符数据设置内容,按照顺序
pstmt.setString(1,"");
pstmt.setInt(2,34);
pstmt.setString(3,"");
pstmt.setDate(4,new Date())
  • 执行数据库操作
pstmt.executeUpdate();

实例:

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		String title = "MLDN新闻'老李写的"; 
		int read = 99;
		double price = 99.8;
		String content = "这个春天有点冷";
		Date pubdate = new Date();
		String sql = "INSERT INTO news(nid,title,read,price,content,pubdate) VALUES "
		+ " (news_seq.nextval,?,?,?,?,?)"; // 使用了"?"作为占位符
		System.out.println(sql);
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		pstmt.setString(1,title);
		pstmt.setInt(2,read);
		pstmt.setDouble(3,price);
		pstmt.setString(4,content);
		pstmt.setDate(5,new java.sql.Date(pubdate.getTime()));
		int count = pstmt.executeUpdate(); // 返回影响的行数
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

2. 数据查询

  • 根据ID进行查询

查询时变量用占位符设置

package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		String sql = "SELECT nid,title,read,price,content,pudate FROM news WHERE nid=?";
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		pstmt.setInt(1,5); // 设置nid的数据
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		ResultSet rs = pstmt.executeQuery(sql);
		while (rs.next()) { // 现在如果发现还有数据行未输出
			int nid = rs.getInt(1);
			String title = rs.getString(2);
			int read = rs.getInt(3);
			double price = rs.getDouble(4);
			String content = rs.getString(5);
			Date pubdate = rs.getDate(6);
			System.out.println(nid + "、" + title + "、" + read
			+ "、" + price + "、" + content + "、" + pubdate);
		}
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}
  • 模糊查询,并用分页的形式进行显示
package cn.mldn.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class JDBCDemo {
	private static final String DATABASE_DRIVER = "oracle.jdbc.driver.OracleDriver";
	private static final String DATABASE_URL = "jdbc:oracle:thin:@localhost:1521:mldn";
	private static final String DATABASE_USER = "scott";
	private static final String DATABASE_PASSWORD = "tiger";
	public static void main(String[] args) throws Exception{
		int currentPage = 2; // 当前页
		int lineSize = 5 ; // 每页显示的数据行
		String column = "title" ; // 模糊查询列
		String keyWord = "MLDN"; // 查询关键字
		String sql = "SELECT * FROM ( "
		+ " SELECT nid,title,read,price,content,pudate,ROWNUM rn "
		+ " FROM news WHERE " + column + " LIKE ? AND ROWNUM<=? ORDER BY nid) temp "
		+ " WHERE temp.rn>?";
		Connection conn = null;  //每一个Connection接口对象描述的就是一个用户连接
		Class.forName(DATABASE_DRIVER); //向容器之中加载数据库驱动程序
		conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
		PreparedStatement pstmt = conn.prepareStatement(sql); // 创建数据库的操作对象
		pstmt.setString(1,"%" + keyWord + "%");
		pstmt.setInt(2,currentPage * lineSize);
		pstmt.setInt(3,(currentPage - 1) * lineSize);
		// 在执行具体的数据库操作之前需要为占位符设置内容,按照顺序设置
		ResultSet rs = pstmt.executeQuery(sql);
		while (rs.next()) { // 现在如果发现还有数据行未输出
			int nid = rs.getInt(1);
			String title = rs.getString(2);
			int read = rs.getInt(3);
			double price = rs.getDouble(4);
			String content = rs.getString(5);
			Date pubdate = rs.getDate(6);
			System.out.println(nid + "、" + title + "、" + read
			+ "、" + price + "、" + content + "、" + pubdate);
		}
		System.out.println("更新操作影响的数据行数:" + count);
		conn.close();  //数据库的连接资源有限一定要关闭
	}
}

注意:

1.父类型能接收所有子类型

2.拼接字符串技巧"+num+"

String sql = “select * from t_user where loginname = '"+loginNmae +"' and loginPwd = '"+loginPwd+"'”;
loginName = fasd
loginPwd = fasd ' or'1' = '1
SQL注入:
String sql = “select * from t_user where loginname = 'fdsa' and loginPwd = 'fdsa' or '1' = '1';

3.Scanner中next()与nextLine()的区别

  • next() 对于录入的字符串碰到空格就会停止录入
  • nextLine()会把整行字符串全部录入
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东北大学高级Java语言期末作业是一个综合性的项目,要求学生能够运用所学的Java知识和技术完成一个相对复杂的软件开发任务。 这个作业有以下几个主要的步骤和要求: 1. 需求分析和设计:首先,学生需要根据老师提供的项目需求进行分析和设计,确定项目的功能和特性。 2. 编码和实现:接下来,学生需要运用Java语言和相关的开发工具,如IDE等,进行编码和实现。这个过程包括编写类和方法,处理数据、逻辑和界面等。 3. 测试和调试:完成编码和实现后,学生需要进行测试和调试,确保项目的正确性和稳定性。可以通过单元测试和集成测试等方法进行。 4. 文档撰写和展示:最后,学生需要撰写项目的文档,包括需求分析、设计思路、代码解释等内容,并进行展示。这个过程既是对项目工作的总结,也是对自己所学知识的巩固和回顾。 在完成这个作业的过程中,学生需要熟练掌握Java语言的基本语法和特性,包括面向对象、异常处理、多线程等内容。同时,还需要学会运用一些Java相关的开发框架和工具,如Spring、Hibernate等,来提高开发效率和质量。 此外,还需要学生具备良好的编码习惯和团队合作能力,能够按照规范进行编码和注释,以及与团队成员进行有效的沟通和协作。 总之,东北大学高级Java语言期末作业是一项重要的综合性实践任务,通过完成这个作业,学生能够提高自己的编码能力和解决问题的能力,为今后的工作和学习打下良好的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值