文章目录
1 多线程1
dy同名: cgblpx
1.1 进程
1.1.1 概念
就是正在运行的程序。也就是代表了程序锁占用的内存区域。
1.1.2 特点
独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
1.2 线程
1.2.1 概念
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
简而言之,一个程序运行后至少一个进程,一个进程里包含多个线程。
如果一个进程只有一个线程,这种程序被称为单线程。
如果一个进程中有多条执行路径被称为多线程程序。
1.2.2 进程和线程的关系
从上图中可以看出一个操作系统中可以有多个进程,一个进程中可以有多个线程,每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。(记清这个关系,非常重要!)
所以想使用线程技术,得先有进程,进程的创建是OS创建的,你能实现吗?不能,一般都是c或者c++语言完成的。
1.3 多线程的特性
1.3.1 随机性
1.3.2 线程状态
线程生命周期,总共有五种状态:
- 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
- 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a) 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c) 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.4 多线程创建1:继承Thread
1.4.1 概述
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
模拟开启多个线程,每个线程调用run()方法
1.4.2 常用方法
String getName()
返回该线程的名称。
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
void setName(String name)
改变线程名称,使之与参数 name 相同。
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
Thread(String name)
分配新的 Thread 对象。
1.4.3 测试
package seday13new;
public class Test1 {
public static void main(String[] args) {
//3、创建线程对象
ThreadDemo t1 = new ThreadDemo("钢铁侠");
ThreadDemo t2 = new ThreadDemo("美队");
//4、开启线程:谁抢到资源谁就先执行
t1.start();
t2.start();
//t1.run();//当做常规方法调用,且 不会发生多线程现象
}
}
//1、作为Thread的子类,并重写run方法。把多线程的业务写在run方法中
class ThreadDemo extends Thread{
public ThreadDemo() {}
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
//2、默认实现是super.run();
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
执行结果:
hello0
hello1
hello2
hello3
hello4
hello5
hello6
hello7
hello8
hello1
hello2
hello3
hello9
hello4
hello5
hello6
hello7
hello8
hello9
注意:从上面结果可以确认,start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了。
1.5 多线程创建2:实现Runnable接口
1.5.1 概述
如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口。
1.5.2 常用方法
void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
1.5.3 测试
package seday13new;
public class Test2 {
public static void main(String[] args) {
MyThread t = new MyThread ();
//2,构造创建对象,传入Runnable子类
Thread target = new Thread(t);
Thread target2 = new Thread(t);
//开启线程
target.start();
target2.start();
}
}
//1,实现Runnable接口,重写run()
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
执行结果:
Thread-0
Thread-2
Thread-1
Thread-5
Thread-8
Thread-6
Thread-7
Thread-4
Thread-3
Thread-9
注意:可以看到执行顺序是乱的,我们已经知道start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了。这就是乱序的原因,也是正常的。
1.5.4 比较
方式 优点 缺点
Thread 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。 线程类已经继承了Thread类,所以不能再继承其他父类
Runnable 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
Callable Runnable规定(重写)的方法是run()
Callable规定(重写)的方法是call()
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
Call方法可以抛出异常,run方法不可以。
运行Callable任务可以拿到一个Future对象,表示异步计算的结果。 存取其他项慢
Pool 线程池可以创建固定大小,
这样无需反复创建线程对象,线程是比较耗费资源的资源
同时线程不会一直无界的创建下去,拖慢系统, 编程繁琐,难以理解
1.6 售票案例
设计4个售票窗口,总计售票100张。
用多线程的程序设计并写出代码。
1.6.1 方案1:继承Thread
package seday13new;
public class Test3 {
public static void main(String[] args) {
Ticket t = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
//问题:票好像卖重复了,同一张票卖了好多次...
t.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread {
//卖了200张票,变成static的
static private int tic = 100;
@Override
public void run() {
while (true) {
//tic=1时,谁都可以进来,t t2 t3 t4
if (tic > 0) {
try {
//t t2 t3 t4都睡了
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//t醒了,tic--=1,tic=0;
//t2醒了,tic--=0,tic=-1;
//t3醒了,tic--=-1,tic=-2;
//t4醒了,tic--=-2,tic=-3;
System.out.println(tic--);
}
}
}
}
1.6.2 方案2:实现Runnable
package seday13new;
public class Test4 {
public static void main(String[] args) {
//只创建一次,就100张票
Ticket2 t = new Ticket2();
Thread target = new Thread(t,"窗口1");
Thread target2 = new Thread(t,"窗口2");
Thread target3 = new Thread(t,"窗口3");
Thread target4 = new Thread(t,"窗口4");
target.start();
target2.start();
target3.start();
target4.start();
}
}
class Ticket2 implements Runnable{
private int tickets=100;
@Override
public void run() {
while(true) {
if(tickets >0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+tic--);
}
}
}
}
1.6.3 问题
1、 每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。-- 用静态修饰
2、 产生超卖,-1张、-2张。
3、 产生重卖,同一张票卖给多人。
4、 多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟。
5、 以后如何判断程序有没有线程安全问题?在多线程程序中+有共享数据+多条语句操作共享数据。
2 多线程2
2.1 同步锁
把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychronized关键字实现同步。
当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。
2.1.1 synchronized
synchronized(对象){
需要同步的代码;
}
2.1.2 特点
1、 前提1,同步需要两个或者两个以上的线程。
2、 前提2,多个线程间必须使用同一个锁。
3、 同步的缺点是会降低程序的执行效率, 为了保证线程安全,必须牺牲性能。
4、 可以修饰方法称为同步方法,使用的锁对象是this。
5、 可以修饰代码块称为同步代码块,锁对象可以任意。
2.1.3 改造
package seday13new;
public class Test4 {
public static void main(String[] args) {
Ticket2 t = new Ticket2();
Thread target = new Thread(t, "窗口1");
Thread target2 = new Thread(t, "窗口2");
Thread target3 = new Thread(t, "窗口3");
Thread target4 = new Thread(t, "窗口4");
target.start();
target2.start();
target3.start();
target4.start();
}
}
class Ticket2 implements Runnable {
private int tic = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
// 把有线程安全问题的代码,用同步关键字包起来
// 原理:用一个对象作为一把锁,给代码上锁,一个线程访问锁代码时,其他线程只能等待锁释放才能进来。
// 多线程间要使用同一把锁才可以真的把代码锁住实现线程安全。
// synchronized (new Object()) {//锁了不同对象
// synchronized (obj) {//锁了同一个对象
//synchronized (Ticket2.class) {//锁了本类,针对于静态
synchronized (this) {
if (tic > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tic--);
}
}
}
}
}
2.2 线程锁
2.2.1 悲观锁和乐观锁
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
2.2.2 两种常见的锁
Synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
2.2.3 Synchronized
使用同步锁实现Synchronized
package javapro.thread;
public class TicketThread extends Thread{
//总票数,多个线程共享这个变量,能修改 ticket–
private int ticket = 10;
//执行业务,重写父类run方法
@Override
public void run() {
//业务处理,卖票:票–
while(true) { //线程非常多,我想尽量给我资源
synchronized (this) { //对象锁
//判断一个条件,出去条件
if(ticket<=0) { //多线程可能ticket=-1
break; //退出死循环
}
//不出现,线程run方法执行太快,不会发生线程冲突
try { //不能抛出异常,抛出就不是重写run方法
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“窗口:” + Thread.currentThread().getName()
+”, 剩余票数:” + ticket-- );
}
}
}
//3个窗口都买这一个票
public static void main(String[] args) {
//目标
Thread target = new TicketThread();
for(int i=0; i<3; i++) {
new Thread(target).start(); //3个线程共同作用一个target
}
}
}
2.2.4 ReentrantReadWriteLock
package game;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class bb {
public static void main(String[] args) {
My2 target = new My2();
Thread t = new Thread(target, "1号窗口:");
Thread t2 = new Thread(target, "2号窗口:");
Thread t3 = new Thread(target, "3号窗口:");
Thread t4 = new Thread(target, "4号窗口:");
t.start();
t2.start();
t3.start();
t4.start();
}
}
class My2 implements Runnable {
int sum = 100;
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
@Override
public void run() {
while (true) {
// t t2 t3 t4都要开门,t有钥匙,进来了出去后,t2再开门干活再锁门
// synchronized (this) {
lock.writeLock().lock();
// sum=1时 t t2 t3 t4都进来
try {
if (sum > 0) {
try {
// t t2 t3 t4都睡了
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// t醒了 sum=1时,sum-- = 1,sum=0
// t2醒了 sum=0时,sum-- = 0,sum=-1
// t3醒了 sum=-1时,sum-- = -1,sum=-2
// t4醒了 sum=-2时,sum-- = -2,sum=-3
System.out.println(Thread.currentThread().getName() + sum--);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.writeLock().unlock();//防止死锁,会自动释放,不释放就独占报错了
}
}
}
}
2.2.5 两种方式的区别
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。
2.3 线程创建的其他方式
2.3.1 ExecutorService/Executors
api
ExecutorService
execute(Runnable任务对象) 把任务丢到线程池
Executors 辅助创建线程池的工具类
newFixedThreadPool(5) 最多5个线程的线程池
newCachedThreadPool() 足够多的线程,使任务不必等待
newSingleThreadExecutor() 只有一个线程的线程池
测试
package game;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test_Pool {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);//2个线程的线程池
// pool = Executors.newCachedThreadPool();
// pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
pool.execute(new R1(i));//放入池中,并等待执行,底层会自动反射run()
}
}
}
class R1 implements Runnable {
int i ;
public R1(int i) {
this.i=i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ " : "+i);
}
}
2.3.2 Callable/Future
package game;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test6 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newSingleThreadExecutor();
C1 c1 = new C1();
//任务c1放入线程池并行执行
Future<Double> future = pool.submit(c1);
Double r = future.get();//获取结果
System.out.println(r);
pool.shutdown();//关闭任务
}
}
class C1 implements Callable<Double> {
@Override
public Double call() throws Exception {
return Math.random();
}
}
2.5 线程共享
2.5.1 ThreadLocal
JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的解决思路。使用这个工具类可以很简洁地编写出优美的多线程程序。这里注意线程锁是解决并发安全问题,而ThreadLocal是解决线程共享问题,何为共享呢?
例如:我们要使用一个对象,这个对象要在不同线程中传递,怎么保持一份呢?不是新对象呢?就需要使用ThreadLocal来保存和传递这个对象。
2.5.2 结构
可以看到ThreadLocal内部由ThreadLocalMap本质就是一个Map结构。其内部保存Entry对象,Entry的key是弱引用(用完就释放),value是强引用(GC回收,如果有引用回收不了)。ThreadLocal被保存在各自的线程Thread中,线程底层由操作系统保证其隔离。相当于共享变量再每个线程中复制了一份。这样共享变量就不会有访问冲突了。其本质是线程私有了。当然不会造成冲突,从而间接的解决了线程安全问题。实际开发,特别框架中广泛用到。
2.5.3 副本
数据保存在ThreadLocalMap集合中,这样数据就被每个线程所绑定,操作系统会维护线程的隔离,也就是不能互相访问,从而巧妙的避免线程安全问题,而它所消耗的资源几乎没有,多线程下也不会发生阻塞,性能非常好,框架底层广泛使用。
2.5.4 弱引用强引用
使用ThreadLocal最大一个缺点就是其会发生内存泄漏,那什么原因造成它会发生内存泄漏呢?由于ThreadLocalMap的key是弱引用,而Value是强引用。
查看源码:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
那什么是弱引用?什么是强引用呢?要讨论ThreadLocal内存泄漏问题,先得了解什么是对象的弱引用,什么是对象的强引用?
这里我们不做深层探讨,要讨论它又要牵扯出GC java的垃圾回收机制,又是一大片概念,一旦展开同学们直接就晕倒了,所以这里我们就不展开了,大家记住java中对象有四种引用关系:强引用、弱引用、软引用、虚引用,大概了解概念即可。
强引用:默认的对象都是强引用,如:String s = “tony”; 由于对象被其他对象所调用,GC干不掉。
弱引用:弱引用生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。也就是说GC时就会被干掉。
2.5.5 内存泄漏
对象申请后不释放,积累多了就会发生内存泄漏。这种情况实际开发中非常常见,但非常难以监测。我们可以由很多现象的发生推测内存有泄漏的情况。例如:刚开始我们使用一款软件时操作非常流畅,但使用时间长了,软件开始卡顿,重启一下,又飞快如飞。这就是典型的内存泄漏。
ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏?
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
try {
threadLocal.set(new Session(1, “tony”));
// 其它业务逻辑
} finally {
threadLocal.remove();
}
2.5.6 线程锁和ThreadLocal的区别
线程锁实现了线程的同步,线程排队按顺序执行。线程阻塞,前面没有执行完成,后面就只能等待,前面的执行完成,后面才能进行执行。
概括来说,对于多线程资源共享的问题,线程锁同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
2.5.7 评价
【了解】ThreadLocal不好理解,但很好使用,几行代码搞定,get/set/remove方法。一般企业中初级程序员也接触不多,更多是中高级去使用他们。所以初学者不用担心,先学会其形式了解其概念即可。
2.6 扩展
2.6.1 同步监视器
遇到 synchronized 关键字,会在加锁的对象上,关联同步监视器
2.6.2 定时任务
package X;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class A {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new B() , 0, 1000*10);//定时任务10s执行1次
}
}
class B extends TimerTask{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":hello:"+new Date().toLocaleString());
}
}
3 设计模式
3.1 概述
设计模式(英语 design pattern)是对 面向对象设计中 反复出现的问题的解决方案。
是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
Java中,提供了23种设计模式,每种模式都描述了一个在我们周围不断重复发生的问题,以及对该问题给出的解决方案。
3.2 单例设计模式
3.2.1 概念
单例模式可以说是大多数开发人员在实际中使用最多的。也是面试最常见的。
单例模式最重要的是确保对象只有一个。简单来说,保证一个类在内存中的对象就一个。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
3.2.2 RunTime源码剖析
/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
RunTime.java
package java.lang;
public class Runtime {
//1、创建静态的全局唯一的对象
private static Runtime currentRuntime = new Runtime();
//2、私有构造方法,不让外部来调用
/** Don't let anyone else instantiate this class */
private Runtime() {}
//3、通过自定义的静态方法获取实例
public static Runtime getRuntime() {
return currentRuntime;
}
}
3.2.3 饿汉式
package cn.tedu.design;
//测试单例设计模式--就是按照一定的开发步骤,按照一定的模板进行开发,达到程序中只会有一个实例在干活的目的!!
public class Test5_Design {
public static void main(String[] args) {
//4,测试 new多少次都是一个对象???--
// MySingleTon m = new MySingleTon();--构造方法私有化
MySingleTon m1 = MySingleTon.getMy();
MySingleTon m2 = MySingleTon.getMy();
System.out.println(m1==m2);//是同一个对象吗???
System.out.println(m1.equals(m2));//默认用Object的==
}
}
class MySingleTon {
//1,私有化改造方法 -- 目的就是控制外界创建对象的权利
private MySingleTon() {}
//2,封装创建好的对象 -- 封装,不让外界随意看到我创建的对象
static private MySingleTon my = new MySingleTon();
//3,提供公的获取对象的方法
//静态方法,因为没法new了,还想用,就用类名访问--修饰成静态的
static public MySingleTon getMy(){
return my;//静态只能调静态
}
}
3.2.4 懒汉式
//懒汉式 -- 面试重点!!延迟加载思想+线程不安全
class MySingleTon2 {
// 1,私有化改造方法 -- 目的就是控制外界创建对象的权利
private MySingleTon2() {
}
// 2,封装创建好的对象 -- 先不创建对象,啥时候用啥时候创建!!
static private MySingleTon2 my;
// 3,提供公的获取对象的方法
// 静态方法,因为没法new了,还想用,就用类名访问--修饰成静态的
synchronized static public MySingleTon2 getMy() {//b,方法里都是同步代码,可以是同步方法
// synchronized (MySingleTon2.class) {//a,同步代码块,静态方法的锁,是类名.class本类的字节码对象
// t1,t2,t3来准备new
if (my == null) {
// t1 new
// t2 new
// t3 new
my = new MySingleTon2();// 开始创建!! 延迟加载
}
// }
return my;// 静态只能调静态
}
}
4 注解+反射
4.1 注解
4.1.1 概念
注解很厉害,它可以增强我们的java代码,同时利用反射技术可以扩充实现很多功能。它们被广泛应用于三大框架底层。传统我们通过xml文本文件声明方式,而现在最主流的开发都是基于注解方式,代码量少,框架可以根据注解去自动生成很多代码,从而减少代码量,程序更易读。例如最火爆的SpringBoot就完全基于注解技术实现。
注解设计非常精巧,初学时觉得很另类甚至多余,甚至垃圾。有了java代码干嘛还要有@注解呢?但熟练之后你会赞叹,它竟然可以超越java代码的功能,让java代码瞬间变得强大。大家慢慢体会吧。
常见的元注解:@Target、@Retention,jdk提供将来描述我们自定义的注解的注解。听起来好绕,别着急,做两个例子,立刻清晰。现在现有“元注解”这个概念。
4.1.2 分类
JDK自带注解
元注解
自定义注解
4.1.3 JDK注解
JDK注解的注解,就5个:
Override
Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
SuppressWarnings(“deprecation”) 忽略警告
SafeVarargs jdk1.7出现,堆污染,不常用
FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用
4.1.4 元注解
描述注解的注解,就5个:
Target 注解用在哪里:类上、方法上、属性上
Retention 注解的生命周期:源文件中、class文件中、运行中
Inherited 允许子注解继承
Documented 生成javadoc时会包含注解,不常用
Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用
4.2 元注解
4.2.1 @Target ElementType.class
描述注解的使用范围:
ElementType.ANNOTATION_TYPE 应用于注释类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.FIELD 应用于字段或属性
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.METHOD 应用于方法级
ElementType.PACKAGE 应用于包声明
ElementType.PARAMETER 应用于方法的参数
ElementType.TYPE 应用于类的元素
4.2.2 @Retention RetentionPolicy.class
定义了该注解被保留的时间长短,某些注解仅出现在源代码中,而被编译器丢弃;
而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取。
为何要分有没有呢?没有时,反射就拿不到,从而就无法去识别处理。
SOURCE 在源文件中有效(即源文件保留)
CLASS 在class文件中有效(即class保留)
RUNTIME 在运行时有效(即运行时保留)
4.3 自定义注解
4.3.1 定义注解
//1,定义注解
//1.1,设置注解的使用范围@Target,啥都不写,哪儿都能用
//@Target({ElementType.METHOD})//作用于方法上
//@Target({ElementType.FIELD})//作用于属性上
@Target({ElementType.METHOD , ElementType.PACKAGE})//作用范围
@Retention(RetentionPolicy.SOURCE)//生命周期
@Target({ElementType.TYPE})//作用于类上
@interface Test{
//3,定义属性
int age() default 0;//使用时,必须给age属性赋值,如:age=X。除非设置好默认值。
//()不是参数,也不能写参数,只是特殊语法
//4,特殊属性value
String value() default "";//使用时,必须给value属性赋值,如:X | value=X。除非设置好默认值
}
注意:注解的语法写法和常规java的语法写法不同
4.3.2 使用注解
//2,使用注解
//@Test
//5,注解的组合属性
@Test(value="",age=0)
class HelloTest{
// @Test(value="",age=0)
String name;
}
4.3.3 解析注解
判断注解是否存在
package javapro.spring.annotation;
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("javapro.spring.annotation.HelloController");
Controller c = clazz.getAnnotation(Controller.class);
if( c != null) {
System.out.println(c);
System.out.println(c.value());
}
}
}
4.4 反射
4.4.1 概念
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,private的只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
4.4.2 为什么需要反射
好好的我们new User(); 不是很好,为什么要去通过反射创建对象呢?
那我要问你个问题了,你为什么要去餐馆吃饭呢?
例如:我们要吃个牛排大餐,如果我们自己创建,就什么都得管理。
好处是,每一步做什么我都很清晰,坏处是什么都得自己实现,那不是累死了。牛接生你管,吃什么你管,屠宰你管,运输你管,冷藏你管,烹饪你管,上桌你管。就拿做菜来说,你能有特级厨师做的好?
那怎么办呢?有句话说的好,专业的事情交给专业的人做,饲养交给农场主,屠宰交给刽子手,烹饪交给特级厨师。那我们干嘛呢?
我们翘起二郎腿直接拿过来吃就好了。
再者,饭店把东西做好,不能扔到地上,我们去捡着吃吧,那不是都成原始人了。那怎么办呢?很简单,把做好的东西放在一个容器中吧,如把牛排放在盘子里。
在开发的世界里,spring就是专业的组织,它来帮我们创建对象,管理对象。我们不在new对象,而直接从spring提供的容器中beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。有点抽象,别着急,我们做个案例,你就立马清晰。
4.4.3 反射Class类对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
4.4.4 常用方法
获得包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
!!成员变量定义信息
getFields()//获得所有公开的成员变量,包括继承的变量
getDeclaredFields()//获得本类定义的成员变量,包括私有,不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
!!构造方法定义信息
getConstructor(参数类型列表)//获得公开的构造方法
getConstructors()//获得所有公开的构造方法
getDeclaredConstructors()//获得所有构造方法,包括私有
getDeclaredConstructor(int.class, String.class)
方法定义信息
getMethods()//获得所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获得本类定义的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名, int.class, String.class)
反射新建实例
c.newInstance();//执行无参构造
c.newInstance(6, "abc");//执行有参构造
c.getConstructor(int.class, String.class); //执行含参构造,获取构造方法
反射调用成员变量
c.getDeclaredField(变量名); //获取变量
c.setAccessible(true); //使私有成员允许访问
f.set(实例, 值); //为指定实例的变量赋值,静态变量,第一参数给 null
f.get(实例); //访问指定实例的变量的值,静态变量,第一参数给 null
反射调用成员方法
Method m = c.getDeclaredMethod(方法名, 参数类型列表);
m.setAccessible(true) ;//使私有方法允许被调用
m.invoke(实例, 参数数据) ;//让指定的实例来执行该方法
4.5 反射的应用
4.5.1 创建类
class Student{
String name="jack";
int age=20;
public Student() {
System.out.println("无参构造");
}
public Student(String name) {
this.name=name;
System.out.println("含参构造"+name);
}
public void show(int a) {
System.out.println("show()..."+a);
}
}
4.5.2 获取类对象
private static void method() throws Exception {
Class clazz = Student.class;
Class<?> clazz2 = Class.forName("seday15.Student");
Class clazz3 = new Student().getClass();
System.out.println(clazz.getName());
System.out.println(clazz2.getName());
System.out.println(clazz3.getName());
}
4.5.3 获取构造方法
private static void method3(Class clazz) {
Constructor[] cs = clazz.getDeclaredConstructors();
for (Constructor c : cs) {
String name = clazz.getSimpleName();
System.out.println(name);
Class[] cs2 = c.getParameterTypes();//参数
System.out.println(Arrays.toString(cs2));
}
}
4.5.4 获取成员方法
private static void method4(Class clazz) {
Method[] ms = clazz.getMethods();
for (Method m : ms) {
String name = m.getName();
System.out.println(name);
Class<?>[] cs = m.getParameterTypes();
System.out.println(Arrays.toString(cs));
}
}
4.5.5 获取成员变量
private static void method2(Class clazz) {
Field[] fs = clazz.getFields();//获取public的属性
for (Field f : fs) {
String name = f.getName();
String tname = f.getType().getSimpleName();
System.out.println(name);
System.out.println(tname);
}
}
4.5.6 创建对象
package seday15;
import java.lang.reflect.Constructor;
import java.util.Scanner;
//反射新建两个对象
public class Test3 {
public static void main(String[] args) throws Exception {
String s = new Scanner(System.in).nextLine();
Class<?> clazz = Class.forName(s);
Object o1 = clazz.newInstance();//用无参构造
System.out.println(o1);
Constructor<?> c = clazz.getConstructor(String.class);//用含参构造
Object o2 = c.newInstance("jack");
System.out.println(o2);
}
}
4.5.7 泛型擦除
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<Integer>();
//1. 编译器挄泛型检查,类型报错。返是在编译阶段
//list.add("tony");
//2. 但在实际运行时,泛型的地方就被替代为通用类型 Object
Class<?> clazz = list.getClass();
Method m = clazz.getDeclaredMethod("add", Object.class);
//3. 刟用反射得刡的对象是运行时对象,其就可以设置非整形的数据
m.invoke(list, "tony");
System.out.println(list);
}
4.5.8 练习:熟悉API
创建Teacher.java
package seday16new;
public class Teacher {
public String name="jack";
int age = 20;
public Teacher() { }
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println("Teacher.show()");
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + "]";
}
}
测试
package seday16new;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test3_Reflect2 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("seday16new.Teacher");
// method(clazz);//方法
// method2(clazz);//属性
method3(clazz);//构造
}
private static void method3(Class<?> clazz) throws Exception {
Constructor c = clazz.getConstructor(null);//无参构造
System.out.println(c.newInstance(null));
Constructor<?> cc = clazz.getConstructor(String.class,int.class);//含参
System.out.println(cc.newInstance("rose",10));
}
private static void method2(Class<?> clazz) throws Exception {
Field f = clazz.getField("name");//要求属性必须是public的
Field f2 = clazz.getField("age");// java.lang.NoSuchFieldException
System.out.println(f.getType().getName());
System.out.println(f2.getType().getName());
}
private static void method(Class clazz) throws Exception {
Method m = clazz.getMethod("show", null);
Object obj = clazz.newInstance();
m.invoke(obj);//执行方法
}
}
4.6 暴力反射
指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:
4.6.1 创建Person类
class Person{
private String name="jack";
private int age = 30;
private void show(int[] a) {
System.out.println("show()..."+Arrays.toString(a));
}
private void test() {
System.out.println("test()...");
}
}
4.6.2 测试
1、 获取私有属性值并修改
2、 获取私有方法并执行
package seday16new;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test3_ReflectPerson {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("seday16new.Person");
// method(clazz);//隐私属性
method2(clazz);//执行方法
}
private static void method2(Class<?> clazz) throws Exception {
Method m = clazz.getDeclaredMethod("show", int[].class);
Object obj = clazz.newInstance();
m.setAccessible(true);//方法隐私可见
m.invoke(obj, new int[]{1,2,3});//执行
}
private static void method(Class clazz) throws Exception {
Field f = clazz.getDeclaredField("name");
System.out.println(f.getType().getName());
f.setAccessible(true);//属性隐私可见
Object obj = clazz.newInstance();
// f.set(obj, "rose");//设置值
System.out.println(f.get(obj));//获取值
//---所有属性
Field[] fs = clazz.getDeclaredFields();
for (Field ff : fs) {
System.out.println(ff);
ff.setAccessible(true);//暴力反射
System.out.println(ff.get(obj));
}
}
}
5 socket
5.1 网络模型
5.1.1 OSl 模型
全称: Open System Interconnection 开放系统互连,网络数据的传输分为7层,每层分工明确,互不干扰,7层模型如下:
- 层物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据单位叫做比特。
- 层数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
- 层网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
- 层传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP (传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
- 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识,可以是IP也可以是MAC或者是主机名)
- 表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。
- 应用层: 主要是一些终端的应用,比如说FTP (各种文件下载),WEB (IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西,就是终端应用)。
数据传输参考图,如下:
5.1.2 TCP/IP 模型
简化了OSI网路数据传输的过程,综合成了4层.
5.2 网络通讯的要素
5.2.1 IP地址
IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
IP协议中还有一个非常重要的内容,那就是给因特网上的每台设备提供的一个唯一的标识,叫做“IP地址”。由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的对象来。
但是,由于IP地址不便记忆,所以我们也可以用主机名。例如: 本地回环地址是127.0.0.1 ,我们也可以用主机名localhost表示。
5.2.2 端口号
端口号的主要作用是,标识一台计算机中的不同进程的逻辑地址。
网络中的计算机是通过IP地址来代表其身份的,它只能表示某台特定的计算机,但是,一台计算机上可以同时提供很多个服务,如数据库服务、FTP服务、Web服务等。
我们可以通过端口号 来区别这些不同的服务,如:常见的端口号21表示的是FTP服务,端口号23表示的是Telnet服务,端口号25指的是SMTP服务,端口号80 表示的是Telnet服务等。
端口号一般习惯为4位整数,在同一台计算机上端口号不能重复,否则,就会产生端口号冲突有效这样的例外。
有效的端口号范围是0 ~ 65535,其中,0~1024被系统占用我们无法使用。
5.2.3 传输协议
数据链路层的一部分, 为传送数据在 9 字节或以上的 PGN 提供的一种机制。是一种数据通讯的规则
传输协议中各层都为上一层提供业务功能。为了提供这种业务功能,下一层将上一层中的数据并入到本层的数据域中,然后通过加入报头或报尾来实现该层业务功能,该过程叫做数据封装。用户的数据要经过一次次包装,最后转化成可以在网络上传输的信号,发送到网络上。当到达目标计算机后,再执行相反的拆包过程。
常见的协议:UDP / TCP
5.3 常见协议
5.3.1 UDP
- 将数据及源和目的封装成数据包中,不需要建立连接
- 每个数据报的大小在限制在64k内
- 因无连接,是不可靠协议
- 不需要建立连接,速度快
5.3.2 TCP
- 建立连接,形成传输数据的通道
- 在连接中进行大数据量传输
- 通过三次握手完成连接,是可靠协议
- 必须建立连接,效率稍低
5.3.3 常用对象InetAddress
5.1 网络编程
查看本机ip地址
5.2 概述
也叫套接字编程,是一个抽象层。
应用程序可以通过它发送或接收数据,可对其像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口与协议的组合。
Socket就是为网络编程提供的一种机制 / 通信的两端都有Socket
网络通信其实就是Socket间的通信 / 数据在两个Socket间通过IO传输
5.3 服务器端-ServerSocket
在服务器端,选择一个端口号,在指定端口上等待客户端发起连接。
启动服务:ServerSocket ss = new ServerSocket(端口);
等待客户端发起连接,并建立连接通道:Sokcet socket = ss.accept();
5.4 客户端-Socket
新建Socket对象,连接指定ip的服务器的指定端口
Socket s = new Socket(ip, port);
从Socket获取双向的流
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
5.5 入门案例
服务器端接收客户端发来的hello,并给客户端响应hello。
5.5.1 服务器端
说明其中,server端的accept()是阻塞的,客户端不连接,服务器不执行后面流程。
in.read()也是阻塞的,读不到就死等。
package seday16;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
//1,在指定端口启动服务
ServerSocket server = new ServerSocket(8000);
//2,等待客户端发起连接,并建立通道
Socket socket = server.accept();
//3,取出双向的流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
//4,通信
/*
* 通信协议:
* 1,通信流程
* 2,数据格式
* 先接收hello,在发送world
*/
//接收客户端发来的hello
for(int i = 0 ; i < 5 ;i++) {
//一个一个字节从网络流中读取客户端发来的数据
char c = (char) in.read();
System.out.print(c);//一行展示收到的数据
}
//给客户端发送数据
out.write("world".getBytes());
out.flush();//刷出内存缓存
//释放资源
socket.close();//断开连接
server.close();//释放端口
}
}
5.5.2 客户端
package seday16;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws Exception, IOException {
//1,与服务器建立连接
//同桌启动服务器,你当客户端发送
Socket s = new Socket("127.0.0.1",8000);
//2,取出双向的流
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
//3,通信
/*
* 先发送Hello,再接收world
*/
//给服务器发送数据
out.write("hello".getBytes());
out.flush();
//接收服务器响应的数据
for (int i = 0; i < 5 ; i++) {
char c = (char) in.read();
System.out.print(c);//同一行展示
}
//释放资源
s.close();
}
}
5.6 服务器端的线程模型
上述案例中,存在两种阻塞状态:
1、 服务器端的accept(),一直等待客户端连接
2、 通信中的read(),死等数据
5.7 回声案例
接收客户端输入的一行数据,服务器再把数据回声给客户端。
5.7.1 服务器端
package seday16;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PipedWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
//回声服务:客户端发送的数据,原封不动发送给客户端
public class Server2 {
// 1、启动服务线程--负责-- 一直接收客户端连接
public void service() {
// 匿名内部类
new Thread() {
@Override
public void run() {
try {
ServerSocket ss = new ServerSocket(8000);
System.out.println("服务已启动!");
while (true) {// 接收连接
Socket s = ss.accept();
System.out.println("客户端连接成功!");
// 内部类通信线程--负责--分配一对一客服
TongXinThread t = new TongXinThread(s);
t.start();
}
} catch (IOException e) {
System.out.println("服务无法启动!");
e.printStackTrace();
}
}
}.start();
}
//内部类
// 创建通信线程-- 负责-- 处理每个连接
class TongXinThread extends Thread {
Socket s ;
public TongXinThread(Socket s) {
this.s=s;
}
@Override
public void run() {
try {
/*
* 通信协议:通信流程、数据格式
* 从客户端接收数据,把数据发回客户端
*
* 接收utf8编码的字符
* 一行字符串以换行结尾
*/
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
//接收客户端数据,并回声
String line ;
while( ( line=in.readLine() ) != null ) {
System.out.println("发来的数据:"+line);
//回声
out.println(line);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("客户端断开了一个连接!");
}
}
public static void main(String[] args) {
Server2 s = new Server2();
s. service ();
}
}
5.7.2 客户端
package seday16;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
public static void main(String[] args) throws Exception, IOException {
// 连接
Socket s = new Socket("127.0.0.1", 8000);
// 获取网络流
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
// 发送
while (true) {
System.out.println("请输入数据");
String input = new Scanner(System.in).nextLine();
//发送
out.println(input);
out.flush();
// 接收回声数据
String read = in.readLine();
if("exit".equals(line)) System.exit(0);
System.out.println("服务器回声的数据:"+read);
//客户端输入了exit就退出
if("exit".equals(line)) System.exit(0);
}
}
}
6 内部类
6.1 概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类。B类可以当做A类的一个成员看待。
6.2 特点
1、 内部类可以直接访问外部类中的成员,包括私有成员
2、 外部类要访问内部类的成员,必须要建立内部类的对象
3、 在成员位置的内部类是成员内部类
4、 在局部位置的内部类是局部内部类
6.2.1 成员内部类
被private修饰
package cn.tedu.inner;
//测试内部类被private修饰
public class Test5_InnerClass2 {
public static void main(String[] args) {
//TODO 创建内部类对象,并执行show()
// Outer2.Inner2 oi = new Outer2().new Inner2();//报错,Inner2已经被private了
//3,测试被private的内部类的资源能否执行!
new Outer2().test();
}
}
class Outer2{
//2,如果想要访问private的内部类,可以访问外部类提供的对应方法
public void test() {
//访问内部类方法
new Inner2().show();
}
//位置在类里方法外--成员内部类
//1,内部类可以被private修饰,但是外界无法直接创建对象了!
private class Inner2{
public void show() {
System.out.println("Inner2.show()");
}
}
}
被static修饰
package cn.tedu.inner;
//测试内部类被static修饰
public class Test6_InnerClass3 {
public static void main(String[] args) {
// 创建内部类对象测试show()
// Outer3.Inner3 oi = new Outer3().new Inner3();//报错,原因是Inner3是静态的内部类
Outer3.Inner3 oi = new Outer3.Inner3();//Outer3.Inner3通过类名.调用类中的静态资源
oi.show();
Outer3.Inner3.show2();//调用静态内部类里的静态方法
}
}
class Outer3{
//1,内部类被static修饰--随着类的加载而加载,会造成内存资源浪费,并不常用!
static class Inner3{
public void show() {
System.out.println("Inner3.show()");
}
static public void show2() {
System.out.println("Inner3.show2()");
}
}
}
6.2.2 局部内部类
package cn.tedu.inner;
//测试局部内部类
public class Test7_InnerClass4 {
public static void main(String[] args) {
//创建对象测试test()的执行
Outer4 out = new Outer4();
out.show();
}
}
class Outer4{
public void show() {
//位置如果在方法里--局部内部类--减少类的作用范围,提高效率节省内存,不常见!
class Inner4{
public void test() {
System.out.println("Inner4.test()");
}
}
//触发内部类的功能
new Inner4().test();
}
}
6.2.3 匿名内部类
匿名内部类属于局部内部类,并且是没有名字的内部类。
package cn.tedu.inner;
//测试匿名内部类
public class Test8_InnerClass5 {
public static void main(String[] args) {
new Hello() {// 匿名对象,本身接口不能new,这里new Hello()匿名对象,就相当于Hello接口的实现类
// 匿名内部类
@Override
public void save() {
System.out.println("save()..");
}
@Override
public void update() {
System.out.println("update()..");
}
}.update();// 触发指定的方法
new Hello2() {//抽象类的匿名内部类
@Override
public void show() { }
}.show();
new Animal() {//普通类的匿名内部类
@Override
public void eat() { }
};
}
}
//TODO 创建匿名对象+匿名内部类测试
class Animal{
public void eat() {}
}
abstract class Hello2 {
abstract public void show();
public void delete() { }
}
// 定义接口
interface Hello {
void save();
void update();
}
7 正则
7.1 概述
正确的字符串格式规则,常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。
如:输入邮箱,输入手机号,身份证号等。。。
7.2 常见语法
7.2.1 String提供了支持正则表达式的方法
matches(正则):当前字符串能否匹配正则表达式
replaceAll(正则,子串):替换子串
split(正则):拆分字符串
7.2.2 测试
package cn.tedu.regex;
import java.util.Scanner;
//测试正则表达式
public class Test3_Regex {
public static void main(String[] args) {
//接收用户输入的字符串
String input = new Scanner(System.in).nextLine();
//指定正则表达式
String regex = "1\\d{10}";//手机号
if(input.matches(regex)) {//如果输入的数据格式负责正则的规则返回true
System.out.println("ok..");
}else {
System.out.println("no ok..");
}
}
}
8 位运算符
package a;
public class c {
public static void main(String[] args) {
System.out.println(8>>1);//4? 把左面的数变小,除以2的n次幂,n就是右面的数。8/2^1=4
System.out.println(32>>2);//8? 32除以2的2次幂
System.out.println(3<<2);//12? 把左面的数变大,乘以2的n次幂,n就是右面的的数。3*2^2=12
System.out.println(5<<3);//40? 5*2^3=40
//最有效的运算2*8=?
System.out.println(2<<3);
}
}
9 枚举
9.1 概念
问大家一个问题,程序执行时,字符串快,还是整形快?
答案很肯定当然int整形执行快。
再问下,在实际开发时,我们常用到需要定义男女,这时应该如何表达呢?
男、女
1、0
用哪个?男女字符串的优点是非常易读,开发者一看就明白;0、1整数的好处执行快
可毕竟是两种方式啊,能否两个优点都具备呢?其实编程世界里早就有:枚举类型就支持,java一开始不支持,不知道怎么想的,但终于在1.5时,姗姗来迟。
春秋冬夏四季如何表示呢?
9.2 没有枚举时的样子
package javase.base.enumc;
public class OldSeason {
//传统java实现方式,通过static可以直接访问,通过final实现不可更改
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
}
9.3 Season.java
枚举后,是不特别简洁优美。用enum定义枚举类,直接定义枚举值就可以
package javase.base.enumc;
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
9.4 TestEnum.java
package X;
import java.lang.annotation.ElementType;
public class M {
public static void main(String[] args) {
System.out.println(G.SPRING);
//如果枚举没有初始化,即省掉"=整型常数"时, 则从第一个标识符开始,
//顺次赋给标识符0, 1, 2 ...
System.out.println(G.WINTER.ordinal());
System.out.println(ElementType.TYPE);
}
}
enum G{
SPRING,//静态常量,分配了标识符从0开始
SUMMER,
WINTER
}
10 冒泡排序算法
10.1 概念
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
10.2 形式
相邻位置比较,从小到大排序,如果小就往前换。i代表从头到尾遍历循环数据。
10.3 代码
package day0000;
import java.util.Arrays;
import java.util.Random;
public class TT {
public static void main(String[] args) {
// f();
int[] arr = new int[]{43, 36, 45, 18, 24,9,20,32};
int[] arrnew = f1(arr);
System.out.println(Arrays.toString(arrnew));
}
private static int[] f1(int[] a) {
//外循环控制循环次数,如果5个数字,循环4次就行
for (int i = 0; i < a.length-1; i++) {
//内循环控制比大小,循环次数和外循环一样
for (int j = 0; j < a.length-1; j++) {
if(a[j]>a[j+1]){
int t = a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
return a;
}
// 创建随机数组,长度是5
private static void f() {
// 创建数组
int[] a = new int[5];
// 遍历数组
for (int i = 0; i < a.length; i++) {
// 存入[1,101)以内的随机值
a[i] = 1 + new Random().nextInt(100);
}
// 打印数组
System.out.println(Arrays.toString(a));
}
}
11 IO进阶1
11.1 属性文件
什么是属性文件
properties文件一般用来作为程序的配置文件,例如,连接数据库时,需要知道数据库服务器的地址,用户名,密码等…
这些连接信息,可以存储在properties配置文件中,便于修改维护
java提供了专门的工具,来读写这种格式的数据
java.util.Properteis 类
11.2 jdbc.properties
#数据库链接配置
#\u6570\u636E\u5E93\u94FE\u63A5\u914D\u7F6E
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/yhmisdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
11.3 格式
属性文件是纯文本类型,本质就是txt文件
#后面代表单行注释,尽量不要在属性文件中出现中文
每行都是kv结构,中间用=号分割
key不能重复,文件不报错,读取后的集合中只有最后一个的值
11.4 测试
读取属性文件所有内容和某一个值
@Test
public void rprop() throws Exception {
//1,创建属性对象Properties
Properties ps = new Properties();
//2,读取当前类根目录(bin)下的属性文件
InputStream in = B.class.getResourceAsStream("/a.properties");
//3,读取文件
ps.load(in);
//4,关闭资源
in.close();
//5,获取内容
System.out.println(ps.getProperty("b"));//2
System.out.println(ps.getProperty("a"));//1
System.out.println(ps);//{b=2, a=1}
}
11.5 xml解析
11.6 什么是XML
可扩展标记语言,标准通用标记语言的子集,简称XML。是一种用于标记电子文件使其具有结构性的标记语言。
11.7 语法格式
1、 开始标签和结束标签匹配,如:<select></select>
2、 标签可以嵌套,如:<select><sql></sql></select>
3、 每个标签都可以设置属性,属性名和属性值用=连接,如:<select id="" resultType=""></select>
4、 想要获取xml中存在的数据,通常使用Dom4j技术实现,由于是第三方的开源技术,使用时需要单独导入jar包
上面就有<select></select>标签,它有属性id和resultType,还有值“SELECT * FROM tb_order”。而txt文件就是全部读入。
评价:xml被后期出现的json技术所取代,所以了解会用即可,不必深究。
11.8 什么是Dom4j
Dom4j,全称DOM API for Java。是一个Java的XML API,是jdom的升级品,用来读写XML文件的。dom4j是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom技术,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面还可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,所以可以知道dom4j无论在哪个方面都是非常出色的。如今可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这已经是必须使用的jar包。
Dom4j是第三方开源API,利用SAXReader对象把xml数据,读取在内存中,组织成一个树状结构,再用不同对象来表示其他节点。
Document
--Element
--getRootElement()
--element()
--attribute
11.9 练习1:创建spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="hello" class="com.spring.Hello"/>
<bean id="orderService" class="com.spring.service.OrderServiceImpl"/>
</beans>
11.10 解析xml
package tt.aa;
import java.io.InputStream;
import java.util.List;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
public class Xml3 {
@Test
public void read() throws Exception {
InputStream in = Xml3.class.getResourceAsStream("/test.xml");
SAXReader reader = new SAXReader();//读取xml,并解析成dom树,返回的是树状结构的树根对象
Document doc = reader.read(in);
Element root = doc.getRootElement();//得到xml的根元素,即<beans>元素
List<Element> list = root.elements("bean");
for (Element e : list) {//获取到每个bean
System.out.println(e.attributeValue("id"));
System.out.println(e.attributeValue("class"));
List<Attribute> attrs = e.attributes();//获取每个bean的所有属性
for (Attribute attr : attrs) {//获取bean的每个属性
//打印属性名和属性值
System.out.println(attr.getName()+"="+attr.getValue());
}
System.out.println();
}
}
}