Java基础知识点清单

7 篇文章 0 订阅

参考:廖雪峰 Java 教程

1. 多线程
	a. 多任务基础
		i. 进程
			1) 计算机中,一个任务为一个进程,某些进程内部可能需要同时执行多个子任务,子任务称为线程。
			2) 一个进程至少包含一个线程
			3) 操作系统调度的最小任务单位是线程。Windows、Linux等操作系统都采用抢占式多任务,如何调度线程完全由操作系统决定,程序不能自己决定什么时候执行,执行多长时间。
			4) 一个应用程序可以有多个进程,所以实现多任务的方法有以下几种:
				a) 多进程模式,一个进程只有一个线程
				b) 多线程模式,一个进程多个线程
				c) 多进程+多线程
			5) 多进程的缺点是:
				a) 创建进程比创建线程开销大
				b) 进程间通信比线程间通信慢
			6) 多进程的优点是:稳定,一个进程崩溃不会影响其他进程
			7) Java内置了多线程支持,一个Java程序其实是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部可以启动多个线程。JVM还有负责垃圾回收的其他工作线程等。
			8) 和单线程相比:多线程经常需要读写共享数据,并且需要同步。
			9) Java多线程编程的特点:
				a) 多线程模型是Java程序最基本的并发模型
				b) 后续读写网络、数据库、Web开发等都依赖Java多线程模型
	b. 创建新线程
		i. 实例化一个Thread然后调用start()方法
		ii. 让新线程执行指定的代码有几种方法:
			1) 从Thread派生一个自定义类,然后覆写run()方法
			2) 创建一个Thread实例传入一个Runnable实例,或者传入一个Lambda语句
		iii. 线程的执行顺序
		iv. 可以对线程设定优先级:Thread.setPriority(int n);//(1-10),默认5
	c. 线程的状态
		i. Java中一个线程只能执行一次start()方法启动新线程,并在新线程中执行run()方法,一旦run()方法执行完毕,线程就结束了。Java线程的状态有以下几种:
			1) New:创建还未执行
			2) Runnable:正在执行run()方法
			3) Blocked:运行中的线程,因为某些操作被阻塞而挂起
			4) Waiting:运行中的线程,因为某些操作在等待
			5) Timed Wainting:运行中的线程,因为执行sleep()方法正在计时等待
			6) Terminated:线程已终止,run()执行完毕
		ii. 线程终止的原因有:
			1) 正常终止
			2) 意外终止,因未捕获的异常导致线程终止
			3) 对某个线程实例调用stop方法强制终止(不推荐)
		iii. 一个线程可以等待另一个线程知道其运行结束,如main()线程在启动t线程后,可通过t.join()等待t线程结束后再继续运行。
	d. 中断线程
		i. 如果一个线程需要执行一个长时间任务,就可能需要终端线程。中断线程只需要在其他线程对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是就立即结束运行。
		ii. 另一个常用的中断线程的方法是设置标志位,我们通常会用一个running标志位来标识线程是否应该继续运行,外部线程中通过把实例的running设置为false,就可以让线程结束了。
		iii. 线程间共享变量需要使用volatile关键字标记,确保每个线程都能读取到更新后的线程。
		iv. Java虚拟机中,变量的值保存在主内存中,但当线程访问变量时它会先获取一个副本并保存在自己的工作内存中,如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值写回到主内存中,但这个时间是不确定的。这样会使得多线程间共享的变量不一致。volatile告诉虚拟机:
			1) 每次访问变量时总是获取主内存的最新值
			2) 每次修改变量时,立刻回写到主内存
		v. volatile解决了可见性问题:一个线程修改了某个共享变量的值,其他线程能例可看到修改后的值
	e. 守护线程
		i. Java程序入口就是JVM启动main线程,main线程又可以启动其他线程。如果有一个线程没有退出,JVM就不会退出,有一种线程的任务就是无限循环,如定时触发任务的线程。这时就不太好办。
		ii. 此时可以使用守护线程,所有非守护线程都执行完毕后无论有没有守护线程,虚拟机都会自动退出,这样就不用关心守护线程是否结束了。创建守护线程只需调用setDaemon(true)就行了。
	f. 线程同步
		i. 多线程下,如果多线程同时读写共享数据,会导致数据出现不一致的问题。
		ii. 通过加锁和解锁的操作,可以保证一个线程执行时其他线程必须等待。
		iii. 这种加锁和解锁之间的代码块我们称为临界区。任何时候临界区最多只有一个线程能执行。
		iv. 用synchronized关键字对一个对象进行加锁
		v. synchronized(Counter.lock){
		vi. }
		vii. 这样对Counter.count变量进行读写就不能同时进行。
		viii. 它解决了多线程同步访问共享变量的正确性问题,但同时又使得性能下降。因为synchronized代码块无法并发执行,而且加锁解锁需要消耗时间。
		ix. JVM规定了几种原子操作:
			1) 基本类型赋值
			2) 引用类型赋值
		x. 单条原子操作是不需要同步的
	g. 同步方法
		i. 有时我们可以把synchronized逻辑封装起来,这样在调用一些方法时也不用关心同步逻辑。synchronized(this){}
		ii. 也可以用synchronized修饰这个方法,也能起到同样的效果。
		iii. 用synchronized修饰一个静态方法实际上锁住的是这个类的class实例
		iv. sychronized(Counter.class){}
	h. 死锁
		i. 一个线程可以获取一个锁后再获取另一个锁。
		ii. 如果又两个线程:
			1) 线程1:获得lockA,线程2:获得lockB
			2) 线程1:准备获得lockB,等待中,线程2:准备获得lockA,等待中
		iii. 此时两个线程各自持有不同的锁并试图获取对方手里的锁,这样双方会无限等待下去,这样就造成死锁。
		iv. 死锁没有任何机制解除,只能强制结束JVM进程。所以再编写多线程应用时要特别注意防止死锁。
	i. 使用wait和notify
		i. 对线程协调运行的原则是:条件不满足是线程进入等待状态,条件满足时线程被唤醒,继续执行任务。
		ii. 通过调用锁的wait方法使线程进入等待状态。wait()方法的执行机制很复杂,它不是一个普通的Java方法,而是定义在Object类的一个native方法,是由JVM的C代码实现的。wait()方法调用时会释放线程获得的锁,wait方法返回后线程又会试图重新获得锁。
		iii. 要让等待的线程被重新唤醒,然后从wait()方法返回,应该在相同的锁对象上调用notify()方法。
		iv. 示例	
	j. 使用ReentrantLock
		i. Java5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写。
		ii. java.util.concurrent.locks包提供的ReentrantLock可以用来替代synchronized加锁。ReentrantLock是Java代码实现的锁。
		iii. ReentrantLock是可重入锁,它和synchronized一样,一个线程可以获取多个锁,但不同的是它可以尝试获取锁。
	k. 使用Condition
		i. 对于ReentrantLock,可以用Condition对象来实现wait和notify的功能。
		ii. Condition对象提供的await()、signal()、signalAll()原理和synchronized锁的wait()、notify()、notifyAll()是一致的,行为也是一样的。
		iii. 此外还可以给await()指定时间。
	l. 使用ReadWriteLock
		i. 很多时候,我们希望允许多个线程读取数据,只能有一个线程写数据,这时可以使用ReadWriteLock解决这个问题,它保证:
			1) 只允许一个线程写入,此时其他线程既不能读取也不能写入
			2) 没有写入时允许多个线程同时读
	m. stampedLock
		i. 很多时候我们并不写入数据,所以如果想进一步提高效率的话可以允许一个线程写入的同时多个线程读取数据,这是一种乐观锁。这时需要额外的代码判断读的过程中是否有写入。
	n. 使用ConCurrent集合
		i. 前面已经可以通过ReentrantLock和Condition实现了一个BlockingQueue:当一个线程调用这个TaskQueue的getTask()方法时,该方法内部可能会让线程变成等待状态,直到队列条件满足不为空,线程被唤醒后,getTask()方法才会返回。
		ii. 我们其实可以直接使用标准库的java.util.concurrent包提供的线程安全的集合ArrayBlockingQueue。此外这个包还提供了其他并发集合类。
	o. 使用Atomic
		i. java.util.concurrent包还提供了一组原子操作的封装类,它们位于java.util.concurrent.atomic包。
		ii. Atomit类是通过无锁的方式实现的线程安全访问。它的主要原理是利用了CAS:Compare and Set。
		iii. 可以利用AtomicLong编写一个多线程安全的全局唯一ID生成器
	p. 使用线程池
		i. 把很多小任务让一组线程来执行,这种能接受大量下任务并进行分发处理的就是线程池。
		ii. 线程池内部维护了若干线程,没有任务时这些线程都处于等待状态,如果有项任务,就分配一个空闲的线程执行,如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。
		iii. 标准库提供ExecutorService接口表示线程池。标准库提供了几个常用的实现类:
			1) FixedTreadPool:线程数固定的线程池
			2) CachedTreadPool:线程数根据任务动态调整的线程池
			3) SingleThreadExecutor:仅单线程执行的线程池
		iv. 创建这些线程池的方法都被封装到Executors类中。
		v. 用shutdown()方法关闭线程池时它会等待正在执行的任务先完成,然后再关闭。shutdownNow()会立即停止正在执行的任务,awaitTermination()则会等待指定的时间让线程池关闭。
		vi. 需要定期反复执行的任务可以使用ScheduledThreadPool。放入ScheduledThreadPool的任务可以定期反复执行。
		vii. ScheduledExecutorService ses=Executors.newScheduledThreadPool(4);
	q. 使用Future
		i. 实现多线程时,提交的任务只需实现Runnable接口就可以让线程池去执行。但Runnable接口的方法没有返回值,如果任务需要一个返回结果就只能保存到变量,还需要提供额外的方法读取,很不方便。所以标准库提供了一个Callable接口,多了一个返回值,而且它是泛型接口,可以返回指定类型的结果。
		ii. 那么由于时间是不同步的,我们怎么得到任务完成后返回的结果呢。注意ExecutorService.submit()方法它返回一个Future类型,一个Future类型的实例代表一个未来能获得结果的对象。
		iii. 调用Future对象的get()方法可以获得异步执行的结果,此时如果还没有返回结果那么get()会阻塞知道任务完成后才返回结果。
		iv. Future<V>接口定义了如下的方法:
			1) get()
			2) get(long timeout,TimeUnit unit)
			3) cancel(boolean mayInteruptIfRunning)
			4) isDone()
	r. CompletableFuture
		i. 用Futrue获得异步执行的结果时,要么调用get(),可能造成阻塞,要么不断询问isDone(),这两种方法都不是很好。
		ii. 通过CompletableFuture,可以传入回调对象,可以在当任务完成或者发生异常时自动调用回调对象的回调方法。
		iii. 它的优点:
			1) 异步任务结束时自动回调某个对象的方法
			2) 异步任务出错时自动回调某个任务的方法
			3) 主线程设置好回调后不用关心异步任务的执行
		iv. 多个CompletableFuture可以串行执行,或者并行执行
		v. 示例	import java.util.concurrent.CompletableFuture;
			public class completableTest{
			    public static void main(String[] args) throws Exception{
			        CompletableFuture<String> cf=CompletableFuture.supplyAsync(()->{
			            return queryCode("中国石油","https://finance.sina.com.cn/code/");
			        });
			        CompletableFuture<String> cf1=CompletableFuture.supplyAsync(()->{
			            return queryCode("中国石油", "https://money.163.com/code/");
			        });
			        CompletableFuture<Object> cfq=CompletableFuture.anyOf(cf,cf1);
			        CompletableFuture<Double>cff=cfq.thenApplyAsync((code)->{
			            return fetchPrince((String)code, "https://finance.sina.com.cn/price/");
			        });
			        CompletableFuture<Double>cff1=cfq.thenApplyAsync((code)->{
			            return fetchPrince((String)code, "https://money.163.com/code/");
			        });
			        CompletableFuture<Object> cffe=CompletableFuture.anyOf(cff,cff1);
			        cffe.thenAccept((Res)->{
			            System.out.println("price: "+Res);
			        });
			        Thread.sleep(2000);
			    }
			    static String queryCode(String name,String url){
			        System.out.println("query code from "+url+"...");
			        try{
			            Thread.sleep((long)Math.random()*1000);
			        }catch(InterruptedException e){
			        }
			        return "601857";
			    }
			    static Double fetchPrince(String code,String url){
			        System.out.println("query price from "+url+"...");
			        try{
			            Thread.sleep((long)Math.random()*1000);
			        }catch(InterruptedException e){
			        }return 5+Math.random()*20;
			    }
			}
	s. 使用ForkJoin
		i. Fork/Join线程池,可以执行一种特殊的任务:把一个大任务成多个小任务并行执行。
	t. 使用ThreadLocal
		i. 多线程是Java实现多任务的基础,在代码中调用Thread.currentThread()获取当前线程。
		ii. 多任务,可以用线程池方便的执行这些任务,同时服用线程,Web应用程序就是典型的多任务应用,每个用户请求页面时都会创建一个任务,然后通过线程池去执行这些任务。
		iii. 有时一个任务需要传递user示例,这个任务中的一些方法又会接受这个实例,这样这个示例会到所有的地方。
		iv. 标准库提供了一个特殊的ThreadLocal,它可以在一个线程中传递同一个对象。它总是以静态字段初始化
		v. static ThreadLocal<String>threadLocalUser=new ThreadLocal<>();
		vi. 通过设置一个user实例关联到ThreadLocal中,在移除前所有的方法可以随时获取到该user实例。获取时以Thread自身为key,可以看成一个全局的Map<Thread,Object>
		vii. Object threadLocalValue=threadLocalMap.get(Thread.currentThread());
		viii. 注意,ThreadLocal一定要在finally中清楚,threadLocalUser.remove();
		ix. 为了保证能释放ThreadLocal关联的实例,可以通过AutoCloseable接口配合try(resource){…}结构,让编译器自动为我们关闭。
2. Maven基础
	a. Maven介绍
		i. Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是Java项目不可缺少的工具。
		ii. 首先,对于一个Java项目,我们需要确定引入哪些依赖包,把一些需要用到的第三方包都放到classpath中,这是依赖包的管理。
		iii. 其次,要确定项目的目录结构,如src目录放Java源码,resources目录放配置文件,bin目录放编译生成的.class文件。
		iv. 然后,我们还需要配置环境,如JDK的版本,编译打包的流程,当前代码的版本号。
		v. 最后,要能通过命令行工具进行编译,才能让项目在一个独立的服务器上编译、测试、部署。
		vi. 这一系列的操作也不难,但很繁琐,所以我们需要一个标准化的Java项目管理和构建工具。
		vii. Maven就是专门为Java项目打造的管理和构建工具,功能有如下:
			1) 提供了一套标准化的项目结构
			2) 提供了一套标准化的构建流程(编译、测试、打包、发布…)
			3) 提供了一套依赖管理机制
		viii. 一个Maven项目的结构
			1) pom.xml,项目描述文件
			2) Java源码目录:src/main/java
			3) 资源目录:src/main/resources
			4) 存放测试源码的目录是src/test/java
			5) 存放测试资源的目录是src/test/resources
			6) 所有编译打包生成的文件都放在target目录下
		ix. 安装Maven只需要从官网下载最新版Maven然后加压添加到系统Path下就行了。在命令行输入Mvn -version可以看到版本信息。
	b. 依赖管理
		i. mvn archetype:generate,创建一个标准的项目模版
		ii. Maven的一个作用就是解决依赖管理,你只要声明项目需要的直接依赖,它会自动解析并判断你项目依赖的所有依赖。
		iii. Maven定义了几种依赖关系,有compile、test、runtime和provided:
			1) compile:编译时需要用到该jar包,默认
			2) test:编译Test时需要用到该jar包,如junit
			3) runtime:编译时不用,运行时要用,如mysql
			4) provided:编译时要用,运行时有JDK或某个服务器提供,如servlet-api
		iv. 默认的compile是最常用的,Maven会把这种类型的依赖直接放入classpath。
		v. Maven维护了一个中央仓库,所有的第三方库将自身的jar以及相关信息上传至中央仓库,Maven就可以从中央仓库把所需的依赖下载到本地。
		vi. 唯一ID
			1) 对某个依赖,Maven只需三个变量就可唯一确定某个jar包
				a) groupId,组织名,类似Java的包名
				b) artifactId,jar包自身的名字,类似Java的类名
				c) version,jar包的版本
			2) Maven通过对jar包进行PGP签名确保任何一个jar包一经发布就无法修改。
			3) Maven镜像
	c. 构建流程
		i. 常用的命令:
			1) mvn clean
			2) mvn clean compile
			3) mvn clean test
			4) mvn clean package
	d. 使用插件
		i. 执行每个phase都是通过插件plugin来执行的。
		ii. 如果标准插件无法满足需求,可以使用自定义插件。使用自定义插件时,需要在pom.xml中声明
	e. 模块管理
	f. 使用mvmw
3. 网络编程
	a. 网络编程基础
		i. 互联网中,一个IP地址用于唯一标识一个网络接口。一台连入互联网的计算机可以有多个IP地址。
		ii. IP地址分为IPv4和IPv6两种。IPv4采用32位,IPv6采用128位。
		iii. IP地址又分为公网IP地址和内网IP地址。公网IP地址可以直接被访问,内网IP地址只能在内网访问。有一个特殊的IP地址,称为本机地址,总是127.0.0.1。
		iv. 如果两台计算机位于同一网络,那么它们之间可以直接通信,因为它们的ip地址前段是相同的,也就是网络号是相同的。网络号是ip地址通过子网掩码过滤后得到的。Network = IP & Mask
		v. 如果两台计算机网络号相同,可以直接通信。如果不同,必须通过路由器或者交换机这样的网络设备间接通信,这种设备称为网关,网关的作用是连接多个网络,负责把来自同一个网络的数据发送到另一个网络,这个过程叫路由。
		vi. 一台计算机的一个网卡有三个关键配置:
			1) IP地址
			2) 子网掩码
			3) 网关的IP地址
		vii. 域名
			1) 通常用域名来访问某个特定的服务,域名解析器DNS负责把域名翻译成对应的IP,客户端再根据IP地址访问服务器。
			2) 用nslookup可以直接查看域名对应的IP地址。
			3) 有一个特殊的本机域名 localhost,对应的ip地址总是127.0.0.1
		viii. 网络模型
			1) 计算机网络从底层的传输到高层的软件设计十分复杂,要合理的设计计算机网络模型,需要采用分层模型。OSI(Open System Interconnect)网络模型是ISO组织定义的一个计算机互联的标准模型,目的是为了简化网络各层的操作,提供标准接口便于实现和维护。这个模型从上到下依次是
				a) 应用层:提供应用程序之间的通信
				b) 表示层:处理数据结构、加解密等
				c) 会话层:负责建立和维护会话
				d) 传输层:负责提供端到端的可靠传输
				e) 网络层:负责根据目标地址选择路由来传输数据
				f) 链路层和物理层负责把数据进行分片并且真正通过物理网络传输,如无线网、光纤等
			2) 互联网实际使用的TCP/IP模型不是对应OSI的7层模型,而是大致对应OSI的5层模型:
				a) 应用层
				b) 运输层
				c) 网络层
				d) 网络接口层(链路层+物理层)
			3) IP协议是一个分组交换,不保证可靠传输,TCP协议是传输控制协议,是面向连接的协议,支持可靠传输和双向通信。TCP协议是建立在IP协议之上的。TCP协议通过接受确认、超时重传这些机制保证数据的可靠传输。TCP协议允许双向通信,通信双方可以同时发送和接收数据。
			4) TCP是应用最广泛的协议,许多高级的协议都是建立在TCP协议之上的,如HTTP、SMTP等
			5) UDP协议是一种数据报文协议,是无连接协议,不保证可靠传输。它在通信前不需要建立连接,所以它的传输效率比TCP高,且比TCP协议简单得多。
			6) 选择UDP协议通常能容忍丢失数据,如一些语音视频通信的应用会选择UDP协议。
	b. TCP编程
		i. Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程通信,而Socket内部通过TCP/IP协议把数据传输到网络:
		ii. Socket、TCP、和部分IP的功能是有操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单封装。
		iii. Socket的必要性:同一台计算机同一时间会运行多个网络应用程序,当操作系统接受到一个数据包时,如果只有IP地址,无法判断应该发给哪个应用程序,所以操作系统抽象出了Socket接口,每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket正确地发送到对应的应用程序。
		iv. 一个SOcket是由IP地址和端口号组成,可以简单理解位IP地址加端口号,端口号有系统分配,为一个0~65535之间的数字,小于1024的端口属于特权接口,需要管理权限,大于1024的端口可以由用户的应用程序打开。
		v. 用Socket进行网络编程本质上是两个进程之间的网络通信,一个进程充当服务端,主动监听某个指定端口,另一个充当客户端,主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功建立了一个TCP连接,双方后续可以随时发送您和接收数据
		vi. 因此,Socket连接成功地在服务器端和客户端之间建立后:
			1) 对服务器端来说,它的Socket是指定的IP地址和指定的端口号
			2) 对客户来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。
		vii. 服务器端
			1) 要用Socket编程,首先要编写服务器端程序。 标准库提供ServerSocket来实现对指定IP和指定端口的监听。
			2) 示例	
				package xhh;
				import java.io.BufferedReader;
				import java.io.BufferedWriter;
				import java.io.IOException;
				import java.io.InputStream;
				import java.io.InputStreamReader;
				import java.io.OutputStream;
				import java.io.OutputStreamWriter;
				import java.net.ServerSocket;
				import java.net.Socket;
				import java.nio.charset.StandardCharsets;
				public class serversocketTest {
				    public static void main(String[] args) throws IOException{
				        ServerSocket ss=new ServerSocket(6666);//监听指定端口
				        System.out.println("Server is running...");
				        for(;;){
				            Socket sock=ss.accept();
				            System.out.println("connect from "+ sock.getRemoteSocketAddress());
				            Thread t=new Handler(sock);
				            t.start();
				        }
				    }
				}
				class Handler extends Thread{
				    Socket sock;
				    public Handler(Socket sock){this.sock=sock;}
				    @Override
				    public void run(){
				        try(InputStream input=this.sock.getInputStream()){
				            try(OutputStream output=this.sock.getOutputStream()){
				                handle(input,output);
				            }
				        }catch(Exception e){
				            try{
				                this.sock.close();
				            }catch(IOException ioe){}
				            System.out.println("client disconnected.");
				        }
				    }
				    private void handle(InputStream input,OutputStream output)throws IOException{
				        BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,StandardCharsets.UTF_8));
				        BufferedReader reader=new BufferedReader((new InputStreamReader(input,StandardCharsets.UTF_8)));
				        writer.write("Hello\n");
				        writer.flush();
				        for(;;){
				            String s=reader.readLine();
				            if(s.equals("bye")){
				                writer.write("bye\n");
				                writer.flush();
				                break;
				            }
				            writer.write("ok: "+s+"\n");
				            writer.flush();
				        }
				    }
				}
			3) ServerSocket ss=new ServerSocket(6666);//指定端口监听,没有指定IP地址,表示在计算机的所有网络接口上进行监听
			4) 如果监听成功,可以使用一个无限循环来处理客户的连接
			5) ss.accept()表示当有新的客户端连接进来后,就返回一个Socket示例,这个Socket示例就是用来和刚连接的客户端进行通信的。利用多线程实现并发处理。也可以用线程池处理客户端连接,可以大大提高效率。
			6) 没有客户端连接进来,accept()方法会阻塞并一直等待,多个客户端同时连接进来,ServerSocket会把连接扔到队列里,一个一个处理,这样通过循环不断调用accept()就可以获取新的连接。
			7) 客户端,示例,通过	
				package xhh;
				import java.io.BufferedReader;
				import java.io.BufferedWriter;
				import java.io.IOException;
				import java.io.InputStream;
				import java.io.InputStreamReader;
				import java.io.OutputStream;
				import java.io.OutputStreamWriter;
				import java.net.Socket;
				import java.nio.charset.StandardCharsets;
				import java.util.Scanner;
				public class clientTest {
				    public static void main(String[] args) throws IOException{
				        Socket sock=new Socket("localhost",6666);
				        try(InputStream input=sock.getInputStream()){
				            try(OutputStream output=sock.getOutputStream()){
				                handle(input,output);
				            }
				        }
				        sock.close();
				        System.out.println("disconnected.");
				    }
				    private static void handle(InputStream input,OutputStream output)throws IOException{
				        BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,StandardCharsets.UTF_8));
				        BufferedReader reader=new BufferedReader(new InputStreamReader(input,StandardCharsets.UTF_8));
				        Scanner scanner=new Scanner(System.in);
				        System.out.println("[server]"+reader.readLine());
				        for(;;){
				            System.out.println(">>>");
				            String s=scanner.nextLine();
				            writer.write(s);
				            writer.newLine();
				            writer.flush();
				            String resp=reader.readLine();
				            System.out.println("<<<"+resp);
				            if(resp.equals("bye")){
				                break;
				            }
				        }
				    }
				}
			8) Socket sock=new Socket("localhost",6666);
			9) 连接到服务器端,localhost表示本机地址,端口号是6666,连接成功则返回一个Socket实例,用于后续通信。
		viii. Socket流
			1) 连接成功后,无论服务端还是客户端都使用Socket实例进行网络通信。TCP是一种基于流的协议,因此,Java标准库使用InputStream和OutputStream来封装Socket的数据流,这样使用Socket和普通IO流类似。
	c. UDP编程
		i. 相比TCP,UDP编程简单得多,它没有创建连接,数据包也是一次收发,所以没有流的概念。
		ii. 示例。	
			package xhh;
			import java.io.IOException;
			import java.net.DatagramPacket;
			import java.net.DatagramSocket;
			import java.nio.charset.StandardCharsets;
			public class udptest {
			    public static void main(String[] args)throws IOException {
			        DatagramSocket ds=new DatagramSocket(6666);
			        for(;;){
			            byte[]buffer=new byte[1024];
			            DatagramPacket packet=new DatagramPacket(buffer, buffer.length);
			            ds.receive(packet);
			            String s=new String(packet.getData(),packet.getOffset(),packet.getLength(),StandardCharsets.UTF_8);
			            System.out.println(s);
			            byte[] data="ACK".getBytes(StandardCharsets.UTF_8);
			            packet.setData(data);
			            ds.send(packet);
			        }
			    }
			}
		iii. 首先通过语句 DatagramSocket ds=new DatagramSocket(6666);  //监听UDP数据包
		iv. 然后通过无限循环处理客户端的请求
		v. 要接受一个UDP数据包需要准备要给byte[]缓冲区,并通过DatagramPacket实现接受。
		vi. 如果接受到的是一个String,那么通过DatagramPacket返回的packet.getOffset()和packet.getLength()确定数据在缓冲区的起始位置:
		vii. String s=new String(packet.getData(),packet.getOffset(),packet.getLength(),StandardCharsets.UTF_8);
		viii. 服务端收到一个DatagramPacket后通常必须立即回复一个或多个UDP包,因为客户端地址在DatagramPacket中,每次收到的DatagramPacket可能是不同的客户端,如果不回复,客户端就收不到任何UDP包。
		ix. 相比较服务端,客户端使用UDP时,只用直接向服务器发送UDP包,然后接受返回的UDP包。
		x. 示例	
			package xhh;
			import java.io.IOException;
			import java.net.DatagramPacket;
			import java.net.DatagramSocket;
			import java.net.InetAddress;
			public class clent1 {
			    public static void main(String[] args)throws IOException {
			        DatagramSocket ds=new DatagramSocket();
			        ds.setSoTimeout(1000);
			        ds.connect(InetAddress.getByName("localhost"), 6666);
			        byte[] data="Hello".getBytes();
			        DatagramPacket packet=new DatagramPacket(data, data.length);
			        ds.send(packet);
			        byte[] buffer=new byte[1024];
			        packet=new DatagramPacket(buffer, buffer.length);
			        ds.receive(packet);
			        String resp=new String(packet.getData(),packet.getOffset(),packet.getLength());
			        System.out.println(resp);
			        ds.disconnect();
			    }
			}
		xi. DatagramSocket ds=new DatagramSocket();
		xii. 不需要指定端口,由操作系统自动指定一个未使用的端口。
		xiii. ds.connect(InetAddress.getByName("localhost"), 6666);
		xiv. 这个connect()方法不是真连接,是为了在客户端DatagramSocket实例中保存服务端的IP和端口号,确保DatagramSocket实例只能往指定地址和端口发送UDP包,不能往其他地址和端口发送,这不是UDP的限制,而是Java内置的安全检查。
		xv. 最后调用disconnect()断开连接,它也不是真断开连接,只是清楚了客户端DatagramSocket实例记录的远程服务器地址和端口号,这样,DatagramSocket实例就可以连接另一个服务器端。
	d. 发送Email
		i. 电子邮件的应用已经有几十年的历史了。
		ii. 传统的邮箱是通过邮局投递,然后一个邮局到另一个邮局,最终到达用户的邮箱。
		iii. 电子邮箱的发送过程也类似,但电子邮件通过电脑软件如Outlook,发送到邮件服务器,经过若干服务器的中转,最终到达对方邮件服务器上,收件方可以用软件接受邮件。
		iv. 类似Outlook这样的邮件软件称为MUA:Mail User Agent,意思是给用户服务的邮件代理,邮件服务器称为MTA:Mail Transfer Agent,邮件中转的代理,最终到达的邮件服务器称为MDA:Mail Delivery Agent,邮件到达的代理。邮件到达MDA就不再动了。电子邮件通常储存在MDA的硬盘上,然后等收件人通过软件或登录浏览器查看邮件。
		v. MUA到MTA发送邮件的协议是SMTP协议,Simple Mail Transport Protocol,使用标准端口25,也可以使用加密端口465或587。SMTP协议是建立在TCP之上的协议,任何程序发送邮件都必须遵守SMTP协议。我们只需使用JavaMail这个标准API直接发送邮件。
		vi. 准备SMTP登录信息
		vii. 用一个邮箱给另一个邮箱发送邮件,首先要知道对方的邮箱地址,要先确定作为MTA的邮件服务器和端口号,邮件服务器地址通常是stmp.example.com,端口号由邮件服务商确定使用25、465、还是587。如QQ邮箱的端口是465/587,服务器名字为stmp.qq.com。
		viii. 有了SMTP服务器的域名和端口号,还需要SMTP服务器的登录信息,通常用自己的邮件地址作为用户名,登录口令是用户口令或者独立设置的SMTP口令。
		ix. 使用JavaMail发送邮件,首先要创建一个Maven工程,把JavaMail相关的依赖加入进来。
	e. 接受email
		i. 接收邮箱是收件人用自己的客户端把邮件从MDA服务器上抓取到本地的过程。
		ii. 接受邮件使用最广泛的协议是POP3:Post Office Protocol version 3,他也是建立在TCP连接之上的协议。POP3服务器的标准端口是110,如果整个会话需要加密,那么使用加密端口995。
		iii. 另一种接受邮件的协议是IMAP :Internet Mail Access Protocol,它使用标准端口143和加密端口993,和POP3的只要区别是,IMAP协议在本地的所有操作都会自动同步到服务器,并且IMAP可以允许用户在邮件服务器的收件箱中创建文件夹。
	f. HTTP编程
		i. HTTP是目前使用最广泛的Web应用程序使用的基础协议,如浏览器访问网站、手机App访问后台服务器,都是通过HTTP协议实现的。
		ii. HTTP,Hyper Text Transfer Protocol,超文本传输协议,基于TCP协议之上的一种请求-响应协议。
		iii.  浏览器访问某个网站时,浏览器和网站服务器之间先建立TCP连接,且服务器总是使用80端口和加密端口443,然后浏览器向服务器发送一个HTTP请求,服务器收到后,返回一个HTTP响应,并在相应中包含HTML的网页内容,这样浏览器解析HTML后就可以给用户显示网页了。
		iv. 一个HTTP请求的格式是固定的,由HTTP Header和HTTP Body两部分构成,第一行总是请求方法、路径、HTTP版本,如GET / HTTP/1.1,表示用GET请求,路径是/,版本是HTTP/1.1。
		v. 后续的每一行都是固定的Header:Value格式,称为HTTP Header,服务器依靠某些特定的Header来识别客户端请求,如
			1) Host:表示请求的域名
			2) User-Agent:客户端自身的表示信息,不同的浏览器有不同的标时,服务器依靠User-Agent判断客户端类型
			3) Accept:表示客户端能处理的HTTP响应格式,/表示任意格式,text/*表示任意文本,image/png表示PNG格式的图片。
			4) Accept-Language:表示客户端接受的语言
		vi. 如果是GET请求,那么HTTP请求只有HTTP Header,没有HTTP Body。如果是POST请求,那么HTTP请求带有Body,以一个空行分隔。
		vii. POST请求通常要设置Content-Type表示Body的类型,Content-Length表示Body的长度,这样服务器可以根据请求的Header和Body做出正确的响应。
		viii. GET请求的参数必须附在URL上,以URLEncode的方式编码,而且请求的参数不能太多,而POST请求的参数没有长度限制,POST请求的参数必须放到Body中。而且可以是任意格式的编码,只要在Content-Type中正确设置即可。
		ix. HTTP响应也是由Header和Body两部分组成。响应的第一行总是HTTP版本、响应代码、响应说明,如HTTP/1.1 200 0k,响应代码是200,响应说明是ok,HTTP有固定的响应代码:
			1) 1XX:提示性响应,如101表示切换协议,常用于WebSocket连接
			2) 2XX:成功的响应,如200表示成功,206表示只发送部分内容
			3) 3XX:表示一个重定向的响应,如301表示永久重定向,303表示客户端应按指定路径重新发送请求
			4) 4XX:表示一个因为客户端问题导致的错误响应,如400表示因为Content-Type等各种原因导致的无效请求,404表示指定的路径不存在
			5) 5XX:表示一个因为服务器问题导致的错误响应,如500表示服务器内部故障,503表示服务器暂时无法响应
		x. 当浏览器收到一个HTTP响应后,他会解析HTML,然后发送一系列的HTTP请求。服务器总是被动地接受客户端的一个HTTP请求,然后响应它
		xi. 早期的HTTP/1.0协议,每次发送一个HTTP请求,客户端都需要先创建一个新的TCP连接,然后收到服务器响应后关闭这个TCP连接。为了提高效率,HTTP/1.1协议允许在一个TCP连接中反复发送响应。
		xii. HTTP协议是一个请求-响应协议,客户端发送一个HTTP请求后,必须等待服务器响应,才能发送下一个请求,这样如果某个响应太慢会堵住后面的请求,为了进一步提速,HTTP/2.0在没有收到响应的时候发送多个HTTP请求,服务器返回响应时不一定按顺序返回,只要双发能识别那个响应对应那个请求就可以做到并行的发送和接收。
		xiii. 我们这里只讨论客户端编程,服务器端的HTTP编程本质上是编写Web服务器,这涉及到一些复杂的体系,是JavaEE开发的核心内容。
		xiv. 早期的JDK版本是通过HttpURLConnection访问HTTP。
		xv. 从Java 11开始,引入新的HttpClient,使用链式调用的API,能大大简化HTTP的处理。
		xvi. Static HttpClient httpClient=HttpClient.newBuilder().build();
		xvii. HttpResponse<String> response=httpClient.send(request,HttpResponse.BodyHandlers.ofString());
		xviii. 如果需要获取图片这样的二进制内容,只需要把HttpResponse.BodyHandlers.ofString()换成HttpResponse.BodyHandlers.ofByteArray(),就可以获得一个HttpResponse<byte[]>对象。如果响应内容很大,不希望一次加载到内存,可以用HttpResponse.BodyHandlers.ofInputStream()获取一个InputString流。
		xix. 使用Post请求,需要准备好发送的Body数据并正确设置Content-Type
4. XML与JSON
	a. XML简介
		i. XML,eXtensible Markup Language,可扩展标记语言,一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。
		ii. XML有几个特点:纯文本,默认使用UTF-8编码,可嵌套,适合表示结构化数据,如果把XML内容存为文件,那么他就是XML文件,XML内容经常通过网络作为消息传输。
		iii. XML有固定的结构,首行必须是<?xml version="1.0"?>,可以加上可选的编码,如以类似的<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型,根元素可以包含任意个子元素,元素可以包含属性,如<isbn lang="CN">1234567</isbn>包含一个属性,且元素必须嵌套,如果是空元素,可以用<tag/>表示。
		iv. 由于使用<、>、!等标识符,如果内容出现特殊字符,需要使用&???;表示转义,常见的特殊字符如下:
			1) <	&lt;
			>	&gt;
			&	&amp;
			"	&quot;
			'	&apos;
			
		v. 格式正确的XML可以被解析器正常读取,而合法的XML指不但格式正确,而且它的数据结构可以被DTD或者XSD验证。
		vi. DTD文档可以指定一系列规则
		vii. 要验证XML文件的正确性可以通过浏览器验证
		viii. 和HTML不同,浏览器对HTML有一定的容错性,缺少关键标签也可以被解析,但XML要求严格的格式。
		ix. XML是一个技术体系,除了我们经常用到的XML文档外,XML还支持:
			1) DTD和XSD:验证XML结构和数据是否有效
			2) Namespace:XML节点和属性的名字空间
			3) XSLT:把XML转换为另一种文本
			4) XPath:一种XML节点查询语言
	b. 使用DOM
		i. XML是一种树形结构的文档,他有两种标准的解析API:
			1) DOM:一次性读取XML,并在内存中表示为树形结构
			2) SAX:以流的形式读取XML,使用时间回调
		ii. DOM,Document Object Model,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点可以包含任意个子节点
		iii. 一个DOM结构,最顶层的document代表XML文档,它是真正的根
		iv. Java使用下面的对象来表示XML的内容:
			1) Document:代表整个XML文档
			2) Element:代表一个XML元素
			3) Attribute:代表一个元素的某个属性
		v. 示例	
			package xhh;
			import javax.xml.parsers.DocumentBuilder;
			import javax.xml.parsers.DocumentBuilderFactory;
			import org.w3c.dom.Document;
			import org.w3c.dom.Node;
			public class domText {
			    public static void printNode(Node n,int indent){
			        for(int i=0;i<indent;i++){
			            System.out.print(' ');
			        }
			        switch(n.getNodeType()){
			            case Node.DOCUMENT_NODE:
			                System.out.println("Document: "+n.getNodeName());
			                break;
			            case Node.ELEMENT_NODE:
			                System.out.println("Element: "+n.getNodeName());
			                break;
			            case Node.TEXT_NODE:
			                System.out.println("Text: "+n.getNodeName()+" = "+n.getNodeValue());
			                break;
			            case Node.ATTRIBUTE_NODE:
			                System.out.println("Attr: "+n.getNodeName()+" = "+n.getNodeValue());
			                break;
			            default:
			                System.out.println("NodeType: "+n.getNodeType()+", NodeName: "+n.getNodeName());
			        }
			        for(Node child=n.getFirstChild();child!=null;child=child.getNextSibling()){
			            printNode(child, indent+1);
			        }
			    }
			    
			    public static void main(String[] args) {
			        //InputStream input=Main.class.getResourceAsStream("D:\\Program Files\\code\\mavTest\\xhh\\pom.xml");
			        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
			        try{
			            DocumentBuilder db=dbf.newDocumentBuilder();
			            Document doc=db.parse("D:\\Program Files\\code\\mavTest\\xhh\\pom.xml");
			            
			            printNode(doc,0);
			        }catch(Exception e){
			            e.printStackTrace();
			        }
			    }
			}
		vi. 作为Java的一个用来解析XML的API,其中包含了很多有用的方法,更多详细信息可以参考官方文档的相关介绍。
		vii. DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
		viii. DocumentBuilder db=dbf.newDocumentBuilder();
		ix. Document doc=db.parse("D:\\Program Files\\code\\mavTest\\xhh\\pom.xml");
	c. 使用SAX
		i. 用DOM解析虽然省事,但它的内存占用太大了。
		ii. SAX,Simple API for XML,它是基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。
		iii. SAX解析会触发一系列的事件
			1) startDocument:开始读取XML文档
			2) startElement:读取到一个元素,如<book>
			3) characters:读取到了字符
			4) endElement:读取到了一个结束的元素,如</book>
			5) endDocument:读取XML文档结束
		iv. 示例	
			package xhh;
			import java.io.FileInputStream;
			import java.io.IOException;
			import java.io.InputStream;
			import javax.xml.parsers.ParserConfigurationException;
			import javax.xml.parsers.SAXParser;
			import javax.xml.parsers.SAXParserFactory;
			import javax.xml.stream.events.Attribute;
			import org.xml.sax.SAXException;
			import org.xml.sax.SAXParseException;
			import org.xml.sax.helpers.DefaultHandler;
			public class saxTest {
			    public static void main(String[] args) throws SAXException, IOException, ParserConfigurationException {
			        InputStream input=new FileInputStream("D:\\Program Files\\code\\mavTest\\xhh\\pom.xml");
			        SAXParserFactory spf=SAXParserFactory.newInstance();
			        SAXParser sp=spf.newSAXParser();
			        sp.parse(input, new Myhandler());
			    }
			}
			class Myhandler extends DefaultHandler{
			    public void startDocument()throws SAXException{
			        print("start document");
			    }
			    public void endDocument()throws SAXException{
			        print("end document");
			    }
			    public void startElement(String uri,String localName,String qName,Attribute attributes)throws SAXException{
			        print("start element:",localName,qName);
			    }
			    public void endElement(String uri,String localName,String qName)throws SAXException{
			        print("end element:",localName,qName);
			    }
			    public void characters(char[]ch,int start,int length)throws SAXException{
			        print("characters:",new String(ch,start,length));
			    }
			    public void error(SAXParseException e)throws SAXException{
			        print("error",e);
			    }
			    void print(Object...objs){
			        for(Object obj:objs){
			            System.out.print(obj);
			            System.out.print(" ");
			        }
			        System.out.println();
			    }
			}
		v. 需要定义个DefaultHandler的子类,这个子类需要实现上面列出的事件的对应的方法:
		vi. 如:	InputStream input=new FileInputStream("D:\\Program Files\\code\\mavTest\\xhh\\pom.xml");
			SAXParserFactory spf=SAXParserFactory.newInstance();
			SAXParser sp=spf.newSAXParser();
			sp.parse(input, new Myhandler());
	d. 使用Jackson
		i. 虽然我们知道了两种解析XML的标准接口,但这两种接口用起来都不是很直观。
		ii. 对于一个XML,它完全可以对应到一个定义好的JavaBean中。
		iii. 如果可以从XML文档解析成一个JavaBean,那结果就比那两种方法直观的多。
		iv. 一个名叫Jackson的开源第三方库可以轻松的做到XML到JavaBean的转换。
		v. 示例	
			package xhh;
			import java.io.FileInputStream;
			import java.io.InputStream;
			import java.util.List;
			import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
			import com.fasterxml.jackson.dataformat.xml.XmlMapper;
			public class jacksonTest {
			    public static void main(String[] args) throws Exception{
			        InputStream input=new FileInputStream("src\\main\\java\\xhh\\book.xml");
			        JacksonXmlModule module=new JacksonXmlModule();
			        XmlMapper mapper=new XmlMapper(module);
			        Book book=mapper.readValue(input, Book.class);
			        System.out.println(book.id);
			        System.out.println(book.name);
			        System.out.println(book.author);
			        System.out.println(book.isbn);
			        System.out.println(book.tags);
			        System.out.println(book.pubDate);
			    }
			}
			class Book {
			    public long id;
			    public String name;
			    public String author;
			    public String isbn;
			    public List<String> tags;
			    public String pubDate;
			}
		vi. 我们必须要定义好一个能和待读取的XML相对应的JavaBean,这点是一个限制,但只要定义好了,XML文件中包含的信息我们也就可以轻易的通过JavaBean获取了。
	e. 使用JSON
		i. JSON,JavaScript Object Notation,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式
		ii. 作为数据传输的格式,JSON的优点有:
			1) JSON只允许使用UTF-8编码,不存在编码问题
			2) JSON只允许使用双引号作为key,特殊字符用\转义,格式简单
			3) 浏览器内置JSON支持,如果把数据用JSON发送给浏览器,可以使用JavaScript直接处理
		iii. JSON适合层次结构,格式简单,仅支持以下几种数据类型:
			1) 键值对
			2) 数组
			3) 字符串
			4) 数值
			5) 布尔值,ture,false
			6) 空值,null
		iv. 用于解析JSON的第三方库有很多:Jackson、Gson、Fastjson、…
		v. 用Jackson解析JSON时,需要注意JSON中的数据类型和Java中的数据类型的对应关系,JSON很强调那种一一对应的关系,如果一个变量的值是有多个键值对,那么这个变量在Java中的对应类型就是Map。
		vi. 我们可以像读取XML那样先定义一个与之对应的JavaBean,然后用它来解析,但其实有一个更简单的方法,可以不用特意定义一个对应的类,因为JSON中的内容都是以键值对的形式存储的,且key必定是String类型,所以直接可以用一个Map来读取JSON中的数据。
		vii. Map<String,Object>test=mapper.readValue(input, Map.class);
		viii. 然后可以根据Map的遍历方式来遍历这个JSON中的数据了。
5. JDBC编程
	a. JDBC简介
		i. JDBC,Java Database Connectivity,Java为关系库定义的一套标准的访问接口。
		ii. JDBC是一套接口规范,他在标准库java.sql中,但其中大部分都是接口,接口不能直接实例化,而是需要相关的实现类先实现这些接口。JDBC接口并不知道我们需要使用哪个数据库,所以我们要用某个数据库,就需要使用这个数据库的实现类,把某个数据库实现了JDBC接口的jar包称为JDBC驱动。
		iii. 所谓JDBC驱动,就是第三方jar包,在Maven中直接添加一个Maven依赖就行了,还是很简单的。
		iv. 示例,我们可以用三个嵌套的try语句,这样可以自动释放资源。
		v. 可以把三个常用的属性保存到本地文本文件下	
			package xhh;
			import java.io.File;
			import java.io.FileInputStream;
			import java.io.FileOutputStream;
			import java.sql.*;
			import java.util.Properties;
			public class jdbc {
			    public static void main(String[] args) throws Exception {
			        String filename="mysql.txt";
			        File f=new File(filename);
			        Properties prop=new Properties();
			        String url,user,password;
			        if(f.exists()){
			            prop.load(new FileInputStream(f));
			            url=prop.getProperty("url");
			            user=prop.getProperty("user");
			            password=prop.getProperty("password");
			        }else{
			            url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC";
			            user = "root";
			            password = "xhhh";
			            prop.setProperty("url", url);
			            prop.setProperty("user", user);
			            prop.setProperty("password", password);
			            prop.store(new FileOutputStream(f),"mysql properties");
			        }
			        
			        try (Connection conn = DriverManager.getConnection(url, user, password)) {
			            try (Statement stmt = conn.createStatement()) {
			                try (ResultSet rs = stmt.executeQuery("select *from students")) {
			                    while (rs.next()) {
			                        final int id = rs.getInt("id");
			                        final String name = rs.getString("name");
			                        final int score = rs.getInt("score");
			                        final int class_id = rs.getInt("class_id");
			                        final String gender = rs.getString("gender");
			                        System.out.println("ID:" + id + ",name:" + name + ",class_id:" + class_id + ",gender:" + gender
			                                + ", score:" + score);
			                    }
			                }
			            }
			        }
			    }
			}
		vi. 简单的读取属性文件,	
			package xhh;
		//当然这也只是个小技巧	
			import java.io.FileInputStream;
			import java.io.FileNotFoundException;
			import java.io.IOException;
			import java.util.Properties;
			//简单的读写数据
			public class propTest {
			    public static void main(String[] args) throws FileNotFoundException, IOException {
			        Properties prop=new Properties();
			        prop.load(new FileInputStream("mysql.txt"));
			        for(Object p:prop.keySet()){
			            System.out.println(p+": "+prop.getProperty((String)p));
			        }
			    }
			}
		vii. String url="jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC";//test为数据库名,使用的端口是3306
		viii. Connectionconn=DriverManager.getConnection(url,"root","xhhh");
		ix. 核心代码是DriverManager提供的静态方法getConnection(),DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。
		x. Statement的示例执行一个查询语句时,有时可能出现问题,有时查询语句的字符串可能是字符串拼接出来的,但拼接成的字符串整体不是正确的查询语句。一个方法是使用PreparedStatement,可以避免SQL注入问题,它使用?作为占位符,把数据连同SQL本身传给数据库。PreparedStatement更安全且更快。使用时需要调用setObject()设置每个占位符?的值,最后获取的仍是ResultSet对象。
		xi. 使用JDBC时,需要在Java数据类型和SQL数据类型之间转换。JDBC在java.sql.Types定义了一组常量来表示如何映射SQL数据类型。常用的有如下的几种:
		xii. SQL数据类型	Java数据类型
		BIT,BOOL	boolean
		integer	int
		bigint	long
		real	float
		float,double	double
		char,varchar	String
		decimal	BigDecimal
		date	java.sql.Date, LocalDate
		time	java.sql.Time, LocalDate
	b. JDBC更新
		i. 数据库操作:增删改查,CURD:Create,Retreive,Update和Delete。
		ii. insert
			1) 本质上也是用PreparedStatement执行一条SQL语句,但执行的是executeUpdate()。
			2) 示例	
				package xhh;
				import java.io.FileInputStream;
				import java.io.IOException;
				import java.util.Properties;
				import java.sql.*;
				public class updateTest {
				    public static void main(String[] args)throws Exception {
				        Properties prop=new Properties();
				        try{
				            prop.load(new FileInputStream("mysql.txt"));
				        }catch(IOException ie){
				            ie.printStackTrace();
				        }
				        try (Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"), prop.getProperty("password"))) {
				            try (PreparedStatement ps = conn.prepareStatement("insert into students (class_id,score,name,gender) values (?,?,?,?)",Statement.RETURN_GENERATED_KEYS)) {
				                ps.setObject(1, 1);//索引从1开始
				                ps.setObject(2, 90);
				                ps.setObject(3, "Cat");
				                ps.setObject(4, "F");
				                int n=ps.executeUpdate();
				                System.out.println(n);
				                ResultSet rs=ps.getGeneratedKeys();
				                while(rs.next()){
				                    System.out.println(rs.getRow());
				                }
				            }
				        }
				        
				    }
				}
			3) 注意:
				a) 调用PrepareStatement时第二个参数需要传入常量Statement.RETUREN_GENERATED_KEYS,否则JDBC不会返回自增主键
				b) 执行executeUpdate()方法后,必须调用getGeneratedKeys()获取一个ResultSet对象,这个对象包含数据库自动生成的主键的值,读取该对象的每一行来获取自增主键的值。
		iii. update
			1) 示例	
				package xhh;
				import java.io.FileInputStream;
				import java.io.IOException;
				import java.util.Properties;
				import java.sql.*;
				// 
				public class update {
				    public static void main(String[] args) throws Exception{
				        Properties prop = new Properties();
				        try {
				            prop.load(new FileInputStream("mysql.txt"));
				        } catch (IOException ie) {
				            ie.printStackTrace();
				        }
				        try (Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"),
				                prop.getProperty("password"))) {
				            try (PreparedStatement ps = conn.prepareStatement("update students set name=? where id=?")) {
				                ps.setObject(1, "ClearLove");// 索引从1开始
				                ps.setObject(2, 7);
				                int n = ps.executeUpdate();
				                System.out.println(n);
				            }
				        }
				    }
				}
		iv. delete
			1) 示例	
				package xhh;
				import java.io.FileInputStream;
				import java.io.IOException;
				import java.util.Properties;
				import java.sql.*;
				// 
				public class deleteTest {
				    public static void main(String[] args) throws Exception {
				        Properties prop = new Properties();
				        try {
				            prop.load(new FileInputStream("mysql.txt"));
				        } catch (IOException ie) {
				            ie.printStackTrace();
				        }
				        try (Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"),
				                prop.getProperty("password"))) {
				            try (PreparedStatement ps = conn.prepareStatement("delete from students where id =?")) {
				                ps.setObject(1, 27);// 索引从1开始
				                int n = ps.executeUpdate();
				                System.out.println(n);
				            }
				        }
				    }
				}
	c. JDBC事务
		i. 数据库事务是由若干个SQL语句构成的一个操作序列,有点类似有Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功要么全部不执行,即数据库事务具有ACID特性:
			1) Atomicity,原子性
			2) Consistency,一致性
			3) Isolation,隔离性
			4) Durability:持久性
		ii. 数据库事务可以并发执行,而数据库系统从效率考虑对事务定义了不同的隔离级别。
		iii. JDBC中执行事务,本质上是如何把多条SQL包裹在一个数据库事务中执行。
		iv. 示例	
			package xhh;
			import java.io.FileInputStream;
			import java.io.IOException;
			import java.util.Properties;
			import java.sql.*;
			// 
			public class event {
			    public static void main(String[] args) throws Exception {
			        Properties prop = new Properties();
			        try {
			            prop.load(new FileInputStream("mysql.txt"));
			        } catch (IOException ie) {
			            ie.printStackTrace();
			        }
			        try (Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"),
			                prop.getProperty("password"))) {
			            conn.setAutoCommit(false);//关闭自动提交
			            try(Statement stmt=conn.createStatement()){
			                stmt.execute("insert into students (class_id,name,gender,score) values(2,'wuzi','M',99)");
			                stmt.execute("update students set score =77 where id =2");
			                stmt.execute("delete from students where id >13");
			                conn.commit();
			            }catch(SQLException s){
			                s.printStackTrace();
			                conn.rollback();
			            }finally{
			                conn.setAutoCommit(true);
			            }
			        }
			    }
			}
	d. JDBC Batch
		i. 用Java操作数据库的一个优势就是方便执行一些批量操作,比用SQL语言操作数据库灵活一些。比如我们需要批量修改一个表中的每个记录中的某个数据时,JDBC操作时只需用一个循环就可以自动执行批量修改操作。
		ii. JDBC中,可以把同一个SQL但参数不同的若干操作合并为一个batch执行,如
		iii. 示例	
			package xhh;
			import java.io.FileInputStream;
			import java.io.IOException;
			import java.util.Properties;
			import java.sql.*;
			// 
			public class batch {
			    public static void main(String[] args) throws Exception {
			        Properties prop = new Properties();
			        try {
			            prop.load(new FileInputStream("mysql.txt"));
			        } catch (IOException ie) {
			            ie.printStackTrace();
			        }
			        try (Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"),
			                prop.getProperty("password"))) {
			            try (PreparedStatement ps = conn.prepareStatement("update students set score=score+? where id =?")) {
			                for(int i=3;i<10;i++){
			                    ps.setInt(1,1 );
			                    ps.setInt(2, i);
			                    ps.addBatch();
			                }
			                int[] n = ps.executeBatch();
			                for(int i:n){
			                    System.out.println(i);
			                }
			            }
			        }
			    }
			}
	e. JDBC连接池
		i. 前面说到多线程编程时,创建线程是一个昂贵的操作,频繁的创建和销毁线程是很消耗资源的,因此有时可以用线程池来提高效率。
		ii. 同样地,执行JDBC修改操作时,每次操作都会打开连接,操作,关闭连接,创建和销毁JDBC连接的开销也是很大的,所以我们可以通过连接池来服用已经创建好的连接。
		iii. 连接池有一个标准接口,javax.sql.DataSource,常见的实现有
			1) HikariCP
			2) C3P0
			3) BonecP
			4) Druid
		iv. 使用最广泛的是HikaricP,首先需要添加依赖
		v. 示例	
			package xhh;
			import java.io.FileInputStream;
			import java.io.IOException;
			import java.util.Properties;
			import javax.sql.DataSource;
			import com.zaxxer.hikari.HikariConfig;
			import com.zaxxer.hikari.HikariDataSource;
			import java.sql.*;
			public class datasource {
			    public static void main(String[] args) throws Exception{
			        Properties prop = new Properties();
			        try {
			            prop.load(new FileInputStream("mysql.txt"));
			        } catch (IOException ie) {
			            ie.printStackTrace();
			        }
			        HikariConfig config=new HikariConfig();
			        config.setJdbcUrl(prop.getProperty("url"));
			        config.setUsername(prop.getProperty("user"));
			        config.setPassword(prop.getProperty("password"));
			        config.addDataSourceProperty("connectionTimeout", 1000);
			        config.addDataSourceProperty("idleTimeout",60000);
			        config.addDataSourceProperty("maximumPoolsize", 10);
			        DataSource ds=new HikariDataSource(config);
			        try(Connection conn=ds.getConnection()){
			            try (Statement stmt = conn.createStatement()) {
			                try (ResultSet rs = stmt.executeQuery("select *from students")) {
			                    while (rs.next()) {
			                        final int id = rs.getInt("id");
			                        final String name = rs.getString("name");
			                        final int score = rs.getInt("score");
			                        final int class_id = rs.getInt("class_id");
			                        final String gender = rs.getString("gender");
			                        System.out.println("ID:" + id + ",name:" + name + ",class_id:" + class_id + ",gender:" + gender
			                                + ", score:" + score);
			                    }
			                }
			            }
			        }
			    }
			}
6. 函数式编程
	a. 简介
		i. 函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些调用的函数又可以调用其他函数,大的任务可以被一层层拆解并执行,函数就是面向过程的程序设计的基本单元。
		ii. Java不支持单独的定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带this参数的函数。
		iii. 函数式编程,Functional Programming,虽然也可以归结到面向过程的程序设计,但它的思想更接近数学计算。
		iv. 计算机层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以汇编是最贴近计算机的语言。
		v. 而计算指数学意义上的计算,越抽线的计算离计算机硬件越远。
		vi. 可以说,越低级的语言,越贴近计算机,抽象程度低,执行效率高,越高级的语言,则反过来。
		vii. 函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,任意一个函数,只要输入确定,输出就确定,这种纯函数我们称之为没有副作用。而允许使用变量的语言,由于函数内部的变量状态不确定,同样的输入可以得到不同的输出,这种函数是有副作用的。
		viii. 函数式编程的一个特点是:允许把函数本身作为参数传入另一个函数,也允许返回一个函数。
	b. Lambda表达式
		i. Java中经常会遇到一些但方法的接口,如Comparator,Runnable,Callable,有时需要传入这些接口的实现类时,可以采用匿名类,但用匿名类的写法还是有些繁琐。
		ii. 我们可以用Lambda表达式替代但方法接口,它只需要写出方法定义,这种写法非常简介。
		iii. Tip:实际使用时出现了非常意想不到的事情,vs code和idea都提示版本太低不支持lambda表达式,我以为是我的JDK版本太低了(实际上不是,JDK14怎么可能版本太低),卸载了再装上JDK15后还是不行。网上百度了以下对idea配置了一下就没问题了,它默认的语言级别是兼容Java7的,所以Java7没有的它也就不支持了,我直接给它改到Java15,这样就基本没问题了。但vs code的问题解决起来还是费了点劲,又是修改.classpath,又是修改org.eclipse.jdt.core.prefs,网上说要把.classpathz中的JavaSe-1.7修改为自己当前的版本,结果改了或Java标准库直接加载不了,后面又改回来,重新启动VS code然后就可以了,所以网上的一些教程还是不能太相信了。
		iv. 实例,其中有一个小坑,就是要用Integer而不能用int,这里往往不容易注意到。	
			package xhh;
			import java.util.Arrays;
			public class lambdaTest {
			    public static void main(String[] args) {
			        Integer[] arr={2,3,21,23,4,5,3,2,1,3,5,6,4,2};
			        Arrays.sort(arr,(i1,i2)->{
			            return i1-i2;
			        });
			        System.out.println(Arrays.toString(arr));
			    }
			}
	c. 方法引用
		i. 示例	package xhh;
			import java.util.Arrays;
			public class ref {
			    public static void main(String[] args) {
			        Integer[] arr={1,2,3,21,2,3,7,8,5,3,2};
			        Arrays.sort(arr,ref::comp);
			        System.out.println(Arrays.toString(arr));
			    }
			    static int comp(Integer i1,Integer i2){
			        return i1-i2;
			    }
			}
		ii. 除了引用静态方法和示例方法,还可以引用构造方法
		iii. 示例	
			package xhh;
			import java.util.Collections;
			import java.util.List;
			import java.util.stream.Collectors;
			public class constructTest {
			    public static void main(String[] args) {
			        List<String>names=List.of("Bob","Alice");
			        List<person>persons=names.stream().map(person::new).collect(Collectors.toList());
			        System.out.println(persons);
			    }
			}
			class person{
			    String name;
			    public person(String n){this.name=n;}
			    public String toString(){
			        return "Person:"+this.name;
			    }
			}
	d. 使用Stream
		i. Java8开始不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API,位于java.util.stream包中。
		ii. 这个Stream不同于java.io的InputStream和OutputStream,它代表任意Java 对象的序列。
		iii. 它是顺序输出任意Java对象实例。它也和List不同,Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
		iv. Stream的特点:可以“存储”有限个或无限个元素,这里的存储可以是实时计算出来的;一个Stream可以轻易的转换为另一个Stream,而不是修改原Stream本身。
		v. 通常在最后获取结果的时候才开始真正计算,即惰性计算。
	e. 创建Stream
		i. 创建Stream有很多种方法。
		ii. Stream.of()
			1) 示例	
				package xhh;
				import java.math.BigInteger;
				import java.util.stream.Stream;
				public class streamTest {
				    public static void main(String[] args) {
				        Stream<String> stream=Stream.of("A","B","C","D","E");
				        stream.forEach(System.out::println);
				    }
				}
		iii. 基于数组或Collection
			1) 示例	
				package xhh;
				import java.util.Arrays;
				import java.util.List;
				import java.util.stream.Stream;
				public class streamTest2 {
				    public static void main(String[] args) {
				        Stream<String> stream1=Arrays.stream(new String[]{"A","B","C"});
				        Stream<String>stream2=List.of("X","Y","Z").stream();
				        stream1.forEach(System.out::println);
				        stream2.forEach(System.out::println);
				    }
				}
		iv. 基于Supplier
			1) 通过Stream.generate()方法,传入一个Supplier对象
			2) 基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。
			3) 示例	
				package xhh;
				import java.util.function.Supplier;
				import java.util.stream.Stream;
				public class suplierTest {
				    public static void main(String[] args) {
				        Stream<Integer>stream=Stream.generate(new NatualSupplier());
				        stream.limit(20).forEach(System.out::println);
				    }
				}
				class NatualSupplier implements Supplier<Integer>{
				    int n=0;
				    public Integer get(){
				        n++;
				        return n;
				    }
				}
		v. 其他方法
			1) 通过一些API提供的接口直接获得Stream。
			2) Files类的lines()方法可以把一个文件变成一个Stream
				a) try(Stream<String>lines=Files.lines(Paths.get("/path/to/file.txt"))){…}
			3) 正则表达式的Pattern对象有一个splitAsStream()方法,可以直接把一个长字符串分割成Stream序列。
			4) 由于Java的泛型不支持基本类型,所以我们只能使用Stream<Integer>这样的,这样会产生频繁的装箱和拆箱,为了提高效率,标准库提供了IntStream、LongStream和DoubleStream这三种基本类型的Stream。
			5) 示例	
				package xhh;
				import java.util.function.LongSupplier;
				import java.util.function.Supplier;
				import java.util.stream.LongStream;
				import java.util.stream.Stream;
				public class fiboTest {
				    public static void main(String[] args) {
				        LongStream fi=LongStream.generate(new fibo());
				        fi.limit(20).forEach(System.out::println);
				    }
				}
				class fibo implements LongSupplier{
				    long i=0,j=1;
				    @Override
				    public long getAsLong() {
				        // TODO Auto-generated method stub
				        long k = j;
				        j = j + i;
				        i = k;
				        return i;
				    }
				}
	f. 使用map
		i. Stream.map()是Stream的一个常用的转换方法,它把一个Stream转换为另一个Stream。
		ii. 示例	
			package xhh;
			import java.time.LocalDate;
			import java.time.LocalDateTime;
			import java.util.regex.Pattern;
			import java.util.stream.Stream;
			public class mapTest {
			    public static void main(String[] args) {
			        LocalDateTime dt=LocalDateTime.now();
			        LocalDate d=dt.toLocalDate();
			        System.out.println(d);
			        String sd=d.toString();
			        Pattern p=Pattern.compile("\\-");
			        Stream<String>s=p.splitAsStream(sd);
			        Stream<Integer> si=s.map(n -> Integer.parseInt(n));
			        Object[] arr=si.toArray();
			        LocalDate dd=LocalDate.of((int)arr[0],(int)arr[1],(int)arr[2]);
			        System.out.println(dd.toString());
			    }
			}
	g. 使用filter
		i. Stream.filter()是Stream的另一个常用的转换方法
		ii. 示例	
			package xhh;
			import java.util.function.IntSupplier;
			import java.util.stream.IntStream;
			public class filterTest {
			    public static void main(String[] args) {
			        IntStream is=IntStream.generate(new IntSupplier(){
			            int i=0;
			            @Override
			            public int getAsInt() {
			                return i++;
			            }
			        });//直接可以用匿名类,这样省事不少,
			        IntStream els=is.filter(n -> n%2!=0);
			        els.limit(40).forEach(System.out::println);
			    }
			}
	h. 使用reduce
		i. 聚合方法,将所有的元素按照聚合函数聚合成一个结果。传入的对象是一个BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素进行运算并返回累加的结果。
		ii. 示例	
			package xhh;
			import java.util.function.IntSupplier;
			import java.util.stream.IntStream;
			public class reduceTest {
			    public static void main(String[] args) {
			        IntStream is=IntStream.generate(new IntSupplier(){
			            int i=0;
			            @Override 
			            public int getAsInt(){return i++;}
			        });
			        int sum= is.limit(30).reduce(1, (acc,n)->acc+n);
			        System.out.println(sum);
			    }
			}
	i. 输出集合
		i. 前面介绍的几个常见操作,可以分为转换操作和聚合操作,转换操作随便怎么做都不会触发任何计算,而聚合操作就不同了,聚合操作会触发一些列连锁反应,会实际的计算每一个元素。
		ii. 把一个Stream变为List是一个聚合操作。可以调用collect()并传入Collectors.toList()对象。
		iii. 输出为数组只需调用toArray()方法并传入数组的构造方法。
		iv. 输出为Map需要key和value,要指定两个映射函数。
		v. Stream还有分组功能,可以按组输出。
			1) 用Collectors.groupingBy(),它需要提供两个函数,一个是分组的key,一个是分组的value。
			2) 示例	
				package xhh;
				import java.util.Arrays;
				import java.util.List;
				import java.util.Map;
				import java.util.stream.Collectors;
				import java.util.stream.Stream;
				public class groupingbyTest {
				    public static void main(String[] args) {
				        students[] ss={new students(1, "Alice", 7,77 ),new students(2, "Bob", 10, 88),
				                        new students(1, "Cathy", 7, 89),new students(2, "Dandy", 7, 90)};
				        Stream<String> Ss=Arrays.stream(ss).map(s->s.toString());
				        //Ss.forEach(System.out::println);
				        Map<String,List<String>>m=Ss.collect(Collectors.groupingBy(x ->x.toString().substring(6,7),Collectors.toList()));
				        System.out.println(m);
				    }
				}
				class students{
				    int class_id;
				    String name;
				    int gradeId;
				    int score;
				    public students(int c,String s,int g,int sc){
				        class_id=c;
				        name=s;
				        gradeId=g;
				        score=sc;
				    }
				    @Override
				    public String toString(){
				        return "class="+class_id+",name="+name+",grade="+gradeId+",score="+score;
				    }
				}
	j. 其他操作
		i. 排序
			1) 示例	
				package xhh;
				import java.util.function.IntSupplier;
				import java.util.stream.IntStream;
				public class reduceTest {
				    public static void main(String[] args) {
				        IntStream is=IntStream.generate(new IntSupplier(){
				            int i=0;
				            @Override 
				            public int getAsInt(){return ++i;}
				        });
				        int sum= is.limit(10).reduce(1, (acc,n)->acc+n);
				        System.out.println(sum);
				    }
				}
		ii. 去重,distinct()
		iii. 截取,skip(),limit()
		iv. 合并,concat()
		v. flatMap
		vi. 并行,parallel()
		vii. 其他聚合方法
			1) count()
			2) max()
			3) min()
			4) sum()
			5) average()
			6) allMath()
			7) anyMath()
			8) forEach()
7. 设计模式
	a. 简介
		i. Design Patterns,指在设计中被反复使用的一种代码实际经验。使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性。
		ii. 设计模式主要是基于OOP编程提炼的,基于几个原则:
			1) 开闭原则:软件应该对扩展开放,而对修改关闭,即增加新功能时,能不该代码尽量不该,只通过增加代码来完成新功能是最好的。
			2) 里氏替换原则:一种面向对象的设计原则,即如果调用一个父类的方法可以成功,那么替换成子类调用也应该完全可以运行。
	b. 创建性模式
		i. 创建型模式关注点是如何创建对象,核心思想是把对象的创建和使用相分离,这样使两者能相对独立地变换。
			1) 工厂方法:Factory Method
				a) 创建对象和使用对象分离,客户端总是引用抽象方法和抽象产品。
					i) 比如,我们先定义一个接口Factory,这个接口实现了我们需要的某个功能。
					ii) 再编写一个实现类,FactoryImpl。
					iii) 示例	
						package xhh;
						import java.math.BigDecimal;
						interface factory{
						    Number parse(String s);
						}
						class factoryImpl implements product{
						    public Number parse(String s){
						        return new BigDecimal(s);
						    }
						}
						interface product {
						    Number parse(String s);
						    public static product getFactory(){
						        return p;
						    }
						    static product p=new factoryImpl();
						}
						public class factoryTest {
						    public static void main(String[] args) {
						        product fac=product.getFactory();
						        Number result=fac.parse("123.234");
						        System.out.println(result);
						    }
						}
				b) 大多数时候直接通过静态方法返回产品。这种简化的使用静态方法创建产品的方法称为静态工厂方法,这个方法广泛应用再标准库中。
					i) 如 Integer n=Integer.valueof(100);
					ii) 这种方式和直接new Integer(100)的区别,工厂方法可以隐藏产品创建的细节,可以创建产品或者返回缓存的产品。	
							public final class Integer {
						    public static Integer valueOf(int i) {
						        if (i >= IntegerCache.low && i <= IntegerCache.high)
						            return IntegerCache.cache[i + (-IntegerCache.low)];
						        return new Integer(i);
						    }
						    ...
						}
				c) 另一个常用的静态工厂方法:List<String>list=List.of("A","B","C");
				d) 示例	
					package xhh;
					import java.time.LocalDate;
					public class staticfTest {
					    public static void main(String[] args) {
					        LocalDate ld=LocalDateFactory.fromInt(20200918);
					        System.out.println(ld.toString());
					    }
					}
					class LocalDateFactory{
					    public static LocalDate fromInt(int yyyymmdd){
					        int y,m,d;
					        d=yyyymmdd%100;
					        yyyymmdd/=100;
					        m=yyyymmdd%100;
					        yyyymmdd/=100;
					        y=yyyymmdd;
					        return LocalDate.of(y,m,d);
					    }
					}
			2) 抽象工厂,Abstrac Factory
				a) 抽象工厂模式不同于工厂方法,它解决的问题比较复杂,工厂抽象,产品抽象,同时多个产品需要创建。
				b) 比如我们要提供一个Markdown文本转换为html和word的服务,定义抽象工厂convertFactory,两个抽象产品html和word	interface convertFactory{
					    html createHtml(String md);
					    word createWord(String md);
					}
					interface html{
					    String toHtml();
					    void save(Path path)throws IOException;
					}
					interface word{
					    void save(Path path)throws IOException;
					}
				c) 抽象工厂定义好了,如果我们自己实现不了,可以选择外包给供应商。供应商可能提供几种不同的方法。
				d) 分别定义这些接口的实现类,这样就可以用抽象工厂的实现类生成产品了。不同的方法对应抽象工厂的不同的实现类,这样实际使用时,只需要用不同的实现类就相当于选择了不同的方案。切换方法只需要修改抽象工厂的实现类而不需要修改其他的代码。
			3) 生成器,Builder
				a) 生成器是使用多个小型工厂最终创建出一个完整的对象。
				b) 比如,要实现markdown转HTML,我们可以一行一行的转,每一行根据首个字符来选择不同的转换器。
				c) 示例	
					package xhh;
					import java.util.HashMap;
					import java.util.Map;
					import java.util.TreeMap;
					public class builderTest {
					    public static void main(String[] args) {
					        String url=URLBuilder.builder()
					                            .setDomain("www.youdao.com")
					                            .setScheme("https")
					                            .setPath("/dict")
					                            .setQuery(Map.of("le","eng","q","test"))
					                            .build();
					        System.out.println(url);
					    }
					}
					class URLBuilder{
					    public static Builder builder(){return new Builder();}
					}
					class Builder{
					    private String Domain,scheme,path;
					    private Map<String,String> query;
					    public Builder(){
					        Domain="www.baidu.com";
					        scheme="https";
					        path="";
					        query=new TreeMap<String,String>();
					    }//初始化
					    public Builder setDomain(String s){
					        Domain=s;
					        return this;
					    }
					    public Builder setScheme(String s){
					        scheme=s;
					        return this;
					    }
					    public Builder setPath(String s){
					        path=s;
					        return this;
					    }
					    public Builder setQuery(Map m){
					        query=m;
					        return this;
					    }
					    public String build(){
					        StringBuilder sbf=new StringBuilder();
					        sbf.append(scheme).append("://").append(Domain).append(path);
					        if(!query.isEmpty()){
					            sbf.append("?");
					            query.forEach((key,value)->{sbf.append(key).append("=").append(value).append("&");});
					        }
					        return sbf.deleteCharAt(sbf.length()-1).toString();
					    }
					}
			4) 原型,Prototype
				a) 指创建新对象时根据现有的原型来创建
				b) 如,复制一个String[]对象:String[] copy=Arrays.copyOf(original,original.length);
				c) 一个普通类要实现原型拷贝的话,Object提供了一个clone()方法,这个方法就是复制一个新的对象,我们定义的一个类需要实现一个Cloneable接口来标识一个对象是可复制的。
			5) 单例,Singleton
				a) 目的是保证在一个进程中,某个类有且仅有一个实例。
				b) 单例的构造方法必须是private的,这样防止调用方自己构建实例,但在类的内部,可以用一个静态字段来引用唯一创建的实例。
				c) 然后再提供一个静态方法直接返回实例,或者直接把静态字段暴露给外部。
				d) 另一种实现Singleton的方式是利用Java的enum。
		ii. 结构型模式,主要涉及如何组合各种对象以便获得更好、更灵活的结构,它不仅仅简单的使用继承,更多地通过组合与运行期的动态组合来实现更灵活的功能。
			1) 适配器,Adapter,Wrapper
				a) 比如,我们有一个实现了Callable接口的实现类的实例,我们要通过一个线程执行,但Thread只接受Runable接口的,这时我们可以用一个Adapter把Callable接口变成Runnable接口的,RunnableAdapter。
				b) 编写Adapter的步骤如下:
					i) 实现目标接口
					ii) 内部持有一个待转换接口的引用
					iii) 在目标接口的实现方法内部调用待转换接口的方法。
				c) 适配器模式在标准库中有广泛作用
			2) 桥接
				a) 桥接模式是为了避免直接继承带来的子类爆炸。
			3) 组合,Composite
				a) 用于树形结构,用Composite可以把一个叶子节点和一个父节点统一起来处理。
			4) 装饰器,Decorator,运行期给某个对象的实例增加功能的方法
				a) 如	InputStream input = new GZIPInputStream( // 第二层装饰
					                        new BufferedInputStream( // 第一层装饰
					                            new FileInputStream("test.gz") // 核心功能
					                        ));
			5) 外观,Façade
				a) Facade相当于搞了一个中介。
			6) 享元,Flyweight
				a) 如果一个对象实例已经创建就不可变了,那么反复创建相同的实例就没有必要了,直接向调用方法返回一个共享的实例就行了,这样既节省内存又提高效率。
				b) 享元模式是通过工厂创建对象,在工厂方法内部,返回缓存实例而不是新创建的实例,从而实现不可变实例的复用。
			7) 代理,Proxy
				a) 可以用于权限检查,远程代理,虚代理,保护代理,只能引用
		iii. 行为型模式,主要涉及算法和对象件的职责分配,通过使用对象组合,行为型模式可以描述一组对象应该如何协作来完成一个整体的任务。
			1) 责任链模式,Chain of Responsibility,处理请求的模式,它让多个处理器有机会处理该请求,直到其中的某个处理成功。
				a) 比如财务审批,Manager审核1000元以下的报销,Director审核10000元以下的报销,CEO可以审核任意额度,以后继续添加审核者时不用改动现有的逻辑。
			2) 命令,Command,把请求封装成一个命令,然后执行该命令。
			3) 解释器,Interpreter,一种针对特定问题设计的一种解决方案。
				a) 如用正则表达式来匹配字符串,正则表达式本身是一个字符串,但要把正则表达式解析为语法树,然后再匹配指定的字符串,就需要解释器。
				b) 实现一个正则表达式的解释器非常复杂,但使用解释器模式却很简单。
			4) 迭代器,Iterator,在Java的集合类中已经广泛使用了。
			5) 中介,Mediator,又称调停者模式,把多方会谈变成单方会谈,从而实现多方的松耦合。
			6) 示例	
				import javax.print.attribute.standard.Media;
				import javax.swing.*;
				import java.awt.*;
				import java.util.ArrayList;
				import java.util.List;
				import java.util.jar.JarEntry;
				
				public class CheeckTest {
				    public void win(String...names){
				        JFrame jf=new JFrame();
				        jf.setTitle("test");
				        Container c= jf.getContentPane();
				        c.setLayout(new FlowLayout(FlowLayout.LEADING,20,20));
				        c.add(new JLabel("use mediator pattern----"));
				        JPanel p=new JPanel();
				        p.add(new JLabel("select:"));
				        List<JCheckBox> list=new ArrayList<>();
				        for(String s:names){
				            JCheckBox checkBox=new JCheckBox(s);
				            list.add(checkBox);
				            p.add(checkBox);
				        }
				        JButton selectAll=new JButton("select all");
				        JButton selectNone=new JButton("select none");
				        JButton selectInverse=new JButton("inverse select");
				        selectNone.setEnabled(false);
				
				        c.add(p);
				        c.add(selectAll);c.add(selectNone);c.add(selectInverse);
				        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				        jf.setSize(400,400);
				        jf.setVisible(true);
				        new Mediator(list,selectAll,selectNone,selectInverse);
				    }
				    public static void main(String[] args) {
				        new CheeckTest().win("a","b","c");
				    }
				}
				class Mediator{
				    private List<JCheckBox>ljc;
				    private JButton sa,sn,si;
				    public Mediator(List<JCheckBox>ljc,JButton sa,JButton sn,JButton si) {
				        this.ljc = ljc;
				        this.sa = sa;
				        this.si = si;
				        this.sn = sn;
				        this.ljc.forEach(cb->{
				            cb.addChangeListener(event->{
				                boolean allchecked=true,allunchecked=true;
				                for(var c:this.ljc){
				                    if(c.isSelected()){
				                        allunchecked=false;
				                    }else{
				                        allchecked=false;
				                    }
				                }
				                this.sa.setEnabled(!allchecked);
				                this.sn.setEnabled(!allunchecked);
				            });
				        });
				        this.sa.addActionListener(event->{
				            this.ljc.forEach(c->c.setSelected(true));
				            this.sa.setEnabled(false);
				            this.sn.setEnabled(true);
				        });
				        this.si.addActionListener(event->{
				            this.ljc.forEach(c->c.setSelected(!c.isSelected()));
				            this.ljc.get(0).getChangeListeners();
				        });
				    }
				}
			7) 备忘录,Memento,主要用于捕获一个对象的内部状态,以便在将来的某个时候回复此状态。
				a) 最简单的备忘录模式如保存到文件。
				b) 标准的备忘录模式有几种角色:
					i) Memonto:存储的内部状态
					ii) Originator:创建一个备忘录并设置状态
					iii) Caretaker:负责保存备忘录
				c) 其实也不用设计的太复杂,只需要提供getState()和setState()就行了。
			8) 观察者,Observer,又称发布-订阅模式,是一种通知机制,让发送通知的一方和接受通知的乙方能彼此分离,互不影响。
			9) 状态,State,经常用在带有状态的对象中。
			10) 策略,Strategy,指定义一组算法,并把其封装到一个对象中,可以灵活的使用其中的一个算法。
			11) 模板方法,Template Method,定义一个操作的一系列步骤,对于某些暂时的确定不下来的步骤,可以留给子类去实现,这样不同的子类可以定义出不同的步骤。
			12) 访问者,Visitor,是一种操作一组对象的操作,他的目的是不改变对象的定义,但允许新增不同的访问者,来定义新的操作。核心思想是为了访问比较复杂的数据结构,不去改变数据结构,而是对数据的操作抽象出来,在访问的过程中以回调形式在访问者中处理操作逻辑,如果新增一组操作,只需增加一个新的访问者。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值