java基础(二)——多线程、常用类

第十章 多线程(基础)

10.1 相关的概念

1、程序(Program)

​ 为了实现一个功能,完成一个任务而选择一种编程语言编写的一组指令的集合。

2、进程(Process)

​ 程序的一次运行。操作系统会给这个进程分配资源(内存)。

​ 进程是操作系统分配资源的最小单位。

​ 进程与进程之间的内存是独立,无法直接共享。

​ 最早的DOS操作系统是单任务的,同一时间只能运行一个进程。后来现在的操作系统都是支持多任务的,可以同时运行多个进程。进程之间来回切换。成本比较高。

3、线程(Thread)

​ 线程是进程中的其中一条执行路径。一个进程中至少有一个线程,也可以有多个线程。有的时候也把线程称为轻量级的进程。

​ 同一个进程的多个线程之间有些内存是可以共享的(方法区、堆),也有些内存是独立的(栈(包括虚拟机栈和本地方法栈)、程序计数器)。

​ 线程之间的切换相对进程来说成本比较低。

4、并行: 多个处理器同时可以执行多条执行路径。

5、并发:多个任务同时执行,但是可能存在先后关系。

例子:

   顺序执行:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
   并发:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
   并行:你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行

理解:

解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行
并发和并行的意义:

并发和并行都可以处理“多任务”,二者的主要区别在于是否是“同时进行”多个的任务。
但是 涉及到任务分解(有先后依赖的任务就不能做到并行)、任务运行(可能要考虑互斥、锁、共享等)、结果合并

10.2 两种实现多线程的方式

1、继承Thread类

步骤:

(1)编写线程类,去继承Thread类

(2)重写public void run(){}

(3)创建线程对象

(4)调用start()

class MyThread extends Thread {
    public void run(){
        //...
    }
}
class Test{
    public static void main(String[] args){
        MyThread my = new MyThread();
        my.start();//有名字的线程对象启动
        
        new MyThread().start();//匿名线程对象启动
        
        //匿名内部类的匿名对象启动
        new Thread(){
            public void run(){
                //...
            }
        }.start();
        
        //匿名内部类,但是通过父类的变量多态引用,启动线程
        Thread t =  new Thread(){
            public void run(){
                //...
            }
        };
        t.start();
    }
}

2、实现Runnable接口

步骤:

(1)编写线程类,实现Runnable接口

(2)重写public void run(){}

(3)创建线程对象

(4)借助Thread类的对象启动线程

class MyRunnable implements Runnable{
    public void run(){
        //...
    }
}
class Test {
    public static void main(String[] args){
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        t1.start();
        t2.start();
        
        //两个匿名对象
        new Thread(new MyRunnable()).start();
        
        //匿名内部类的匿名对象作为实参直接传给Thread的构造器
        new Thread(new Runnable(){
            public void run(){
                //...
            }
        }).start();
            
    }
}

10.3 线程的生命周期

在这里插入图片描述

10.4 Thread的相关API

1、构造器

  • Thread()
  • Thread(String name)
  • Thread(Runnable target)
  • Thread(Runnable target, String name)

2、其他方法

(1)public void run()

(2)public void start()

(3)获取当前线程对象:Thread.currentThread()

(4)获取当前线程的名称:getName()

(5)设置或获取线程的优先级:set/getPriority()

优先级的范围:[1,10],Thread类中有三个常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)

优先级只是影响概率。

(6)线程休眠:Thread.sleep(毫秒)

(7)打断线程:interrupt()

(8)暂停当前线程:Thread.yield()

(9)线程要加塞:join()

xx.join()这句代码写在哪个线程体中,哪个线程被加塞,和其他线程无关。

(10)判断线程是否已启动但未终止:isAlive()

1、方法

import org.junit.Test;

/*
 * java.lang.Thread类的API:
 * (1)public void run():子类必须重写,它的方法体也称为线程体,即线程的任务代码
 * (2)public void start():线程启动必须用它
 * (3)public static void sleep(毫秒):休眠
 * (4)public String getName():线程的名称
 * 		主线程的名称:main
 * 		其他线程:默认是Thread-编号
 * (5)public static Thread currentThread() 
 * (6)线程优先级
 * getPriority() 
 * setPriority()
 * 		优先级的范围:MIN_PRIORITY - MAX_PRIORITY ,[1,10]
 * 		普通优先级:NORM_PRIORITY
 * 		一共10个等级。
 * 优先级高:被CPU调度的概率增加,不表示低的没有机会。
 * 所以:不能依赖于优先级来解决先后的任务问题。
 * 
 * (7)public void interrupt()
 * (8)public void join():加塞
 * (9)public static void yield() :暂停当前线程,让出本次的CPU资源,加入下一次CPU的抢夺中
 */
public class TestMethod {
	@Test
	public void testJoin() {
		MyRunnable my = new MyRunnable();
		Thread t = new Thread(my);
		t.start();
		
		MyRunnable my2 = new MyRunnable();
		Thread t2 = new Thread(my2);
		t2.start();
		
		
		for (int i = 1; i <= 10; i++) {
			System.out.println("main:" + i);
			if(i==3){
				try {
					t.join();//当main线程打印到3后,被t线程加塞,main线程就不能继续,main被阻塞了,main要等到t线程结束才能继续了
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
//				Thread.yield();//作用不大,让出CPU,但下次有可能还是它抢到,象征性让一下
			}
		}
	}
	
	@Test
	public void testInterrupt(){
		MyThread my1= new MyThread();
		my1.start();
		
		//主线程休眠3秒后,中断MyThread线程
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		my1.interrupt();
	}
	
	@Test
	public void testPriority(){
		Thread t = Thread.currentThread();
		System.out.println(t.getPriority());
		
		MyThread my1= new MyThread();
		System.out.println(my1.getPriority());
		
		System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
		System.out.println("最低优先级:" +Thread.MIN_PRIORITY);
		System.out.println("普通优先级:" + Thread.NORM_PRIORITY);
	}
	
	@Test
	public void testName2(){
		Thread t = Thread.currentThread();
		System.out.println(t.getName());
		
		MyThread my1= new MyThread();
		System.out.println(my1.getName());
		
		MyThread my2 = new MyThread();
		System.out.println(my2.getName());
		
		MyThread my3 = new MyThread("线程3");
		System.out.println(my3.getName());
	}
	
	@Test
	public void testName(){
		Thread t = Thread.currentThread();
		System.out.println(t.getName());
	}
	
	@Test
	public void testSleep2(){
		//获取明天的当前时间
		try {
			Thread.sleep(24*60*60*1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(System.currentTimeMillis());
	}
	
	@Test
	public void testSleep(){
		for (int i = 10; i>=1; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);//毫秒   1000毫秒= 1秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class MyThread extends Thread{
	
	public MyThread() {
		super();
	}

	public MyThread(String name) {
		super(name);
	}

	public void run(){
		System.out.println("自定义线程");
		try {
			Thread.sleep(10000);//休眠10秒
		} catch (InterruptedException e) {
			System.out.println("自定义线程被打断");
			e.printStackTrace();
		}
		System.out.println("自定义线程休眠结束");
	}
}

class MyRunnable implements Runnable{
	public void run(){
		for (int i = 10; i>=1; i--) {
			System.out.println(Thread.currentThread().getName() + "run:" + i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
    

2、龟兔赛跑1


/*
2、案例:编写龟兔赛跑多线程程序,设赛跑长度为30米
兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒
乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒
要求,
(1)每跑1米,显示一下结果:xxx跑了几米,
	  休息时,显示一下:xxx在休息...
(2)要求必须等乌龟和兔子都跑完了,然后显示结果谁赢了,用时xx时间
 */
public class TestExer07 {
	public static void main(String[] args) {
		Sportman t = new Sportman("兔子",30,100,10000);
		Sportman w = new Sportman("乌龟",30,1000,1000);
		
		t.start();
		w.start();
		
		try {
			//等t和w线程结束后,才能运行下面的代码
			t.join();//t阻塞了main
			w.join();//w阻塞了main
			//不加上这两句的话,两个动物还没跑完主线程就往下运行了
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//在这里想要获取运动员跑全程的时间
		long tTime = t.getTotalTime();
		long wTime = w.getTotalTime();
		if(tTime == wTime){
			System.out.println(t.getName() + "," + w.getName() + "平局");
		}else if(tTime < wTime){
			System.out.println(t.getName() + "赢了");
		}else{
			System.out.println(w.getName() + "赢了");
		}
		
		System.out.println(t.getName() + "用时:" + tTime);
		System.out.println(w.getName() + "用时:" + wTime);
	}
}
class Sportman extends Thread{
	private int distance;//距离
	private long runMillsPerMeter;//每米的时间,毫秒
	private long restMillsPerTenMeter;//每10米休息的时间,毫秒
	private long totalTime;
	
	public Sportman(String name, int distance ,long millsPerMeter, long restPerTenMeter) {
		super(name);
		this.distance = distance;
		this.runMillsPerMeter = millsPerMeter;
		this.restMillsPerTenMeter = restPerTenMeter;
	}

	public void run(){
		long start = System.currentTimeMillis();
		for (int i = 1; i <= distance; i++) {
			try {
				Thread.sleep(runMillsPerMeter);//模拟跑1米的时间
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//用线程名称代替运动员的名称
			System.out.println(getName() + "跑了" + i + "米");
			
			if(i<distance && i%10==0){
				System.out.println(getName() + "在休息....");
				try {
					Thread.sleep(restMillsPerTenMeter);//休息n秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		long end = System.currentTimeMillis();
		totalTime =  end - start;
	}

	public long getTotalTime() {
		return totalTime;
	}
	
}
    

10.5 关键字:volatile

volatile:易变,不稳定,不一定什么时候会变

修饰:成员变量

作用:当多个线程同时去访问的某个成员变量时,而且是频繁的访问,再多次访问时,发现它的值没有修改,Java执行引擎就会对这个成员变量的值进行缓存。一旦缓存之后,这个时候如果有一个线程把这个成员变量的值修改了,Java执行引擎还是从缓存中读取,导致这个值不是最新的。如果不希望Java执行引擎把这个成员变的值缓存起来,那么就可以在成员变量的前面加volatile,每次用到这个成员变量时,都是从主存中读取。

原子性问题,可见性问题,有序性问题,volatile深入刨析

单例模式双重检查为什么用volatile

10.6 关键字:synchronized(同步)

1、什么情况下会发生线程安全问题?

(1)多个线程

(2)共享数据

(3)多个线程的线程体中,多条语句再操作这个共享数据时

2、如何解决线程安全问题?同步锁

形式一:同步代码块

形式二:同步方法

3、同步代码块

synchronized(锁对象){
    //一次任务代码,这其中的代码,在执行过程中,不希望其他线程插一脚
}

锁对象:

(1)任意类型的对象

(2)确保使用共享数据的这几个线程,使用同一个锁对象

如类中的变量,类.class

4、同步方法

synchronized 【修饰符】 返回值类型  方法名(【形参列表】)throws 异常列表{
    //同一时间,只能有一个线程能进来运行
}

锁对象:

(1)非静态方法:this(谨慎)

(2)静态方法:当前类的Class对象


public class Test11 {
	public static void main(String[] args) {
		Ticket t1 = new Ticket("窗口一");
		Ticket t2 = new Ticket("窗口二");
		Ticket t3 = new Ticket("窗口三");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class Ticket extends Thread{
	private static int total = 10;
	
	public Ticket(String name) {
		super(name);
	}

	public void run(){
		while(total>0){//程序停止的条件
			saleOneTicket();
		}
	}
	
	public synchronized static void saleOneTicket(){
		if(total > 0){//线程安全问题的条件
			System.out.println(Thread.currentThread().getName() + "卖出一张票");
			total--;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("剩余:" + total);
		}
	}
	
	//同步方法,锁的是方法的一次调用过程
	//非静态方法的锁对象是this,这里使用this,不是合格的锁对象
	/*public synchronized void saleOneTicket(){
		if(total > 0){//线程安全问题的条件
			System.out.println(getName() + "卖出一张票");
			total--;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("剩余:" + total);
		}
	}*/
}
    

5、死锁


/*
 * 死锁:
 *   两个线程,互相持有,占有对方想要的锁,不放手。
 */
public class TestDeadLock {
	public static void main(String[] args) {
		Object g = new Object();
		Object m = new Object();
		
		Boy b = new Boy(g,m);
		Bang bang = new Bang(g,m);
		
		b.start();
		bang.start();
	}
}
//男朋友
class Boy extends Thread{
	private Object girl;
	private Object money;
	
	public Boy(Object girl, Object money) {
		super();
		this.girl = girl;
		this.money = money;
	}

	public void run(){
		synchronized (money) {
			System.out.println("你放了我女朋友,我给你500万");
			synchronized (girl) {
				System.out.println("给了对方500万");
			}
		}
	}
}
//绑匪
class Bang extends Thread{
	
	private Object girl;
	private Object money;
	
	public Bang(Object girl, Object money) {
		super();
		this.girl = girl;
		this.money = money;
	}

	public void run(){
		synchronized (girl) {
			System.out.println("你给我500万,我放了你女朋友");
			synchronized (money) {
				System.out.println("放人");
			}
		}
	}
}
    

10.7 线程通信

1、为了解决“生产者与消费者问题”。

当一些线程负责往“数据缓冲区”放数据,另一个线程负责从“数据缓冲区”取数据。

问题1:生产者线程与消费者线程使用同一个数据缓冲区,就是共享数据,那么要考虑同步

问题2:当数据缓冲区满的时候,生产者线程需要wait(), 当消费者消费了数据后,需要notify或notifyAll

​ 当数据缓冲区空的时候,消费者线程需要wait(), 当生产者生产了数据后,需要notify或notifyAll

2、java.lang.Object类中声明了:

(1)wait():必须由“同步锁”对象调用

(2)notfiy()和notifyAll():必须由“同步锁”对象调用

3、面试题:sleep()和wait的区别

(1)sleep()不释放锁,wait()释放锁

(2)sleep()在Thread类中声明的,wait()在Object类中声明

(3)sleep()是静态方法,是Thread.sleep()

​ wait()是非静态方法,必须由“同步锁”对象调用

(4)sleep()方法导致当前线程进入阻塞状态后,当时间到或interrupt()醒来

​ wait()方法导致当前线程进入阻塞状态后,由notify或notifyAll()

4、哪些操作会释放锁?

(1)同步代码块或同步方法正常执行完一次自动释放锁

(2)同步代码块或同步方法遇到return等提前结束

(3)wait()

(4)遇到异常,但是没有处理,这个线程死了,会释放锁

5、不释放锁

(1)sleep()

(2)yield()

(3)suspend()

6、消费者、生产者
(1)一个生产者、一个消费者

    package com.atguigu.test14;

/*
 * 线程通信是用来解决生产者与消费者问题。
 * 
 * 生产者与消费者问题:
 * 	  有两个或者多个线程。
 * 	 其中一个/一部分线程,生产“数据”,称为生产者线程;
 * 	另一个/一部分线程,消耗“数据”,称为消费者线程。
 * 	这些数据放在一个“共享”区域。
 *  那么就会出现:
 *    当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
 *    当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
 *    
 *  生产者与消费者问题:
 *  (1)共享数据:    就会有线程安全问题,就需要同步
 *  (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
 *  
 *  Object类中有:
 *  (1)wait():必须由锁对象(线程的监视器对象)来调用。
 *  (2)notify():必须由锁对象(线程的监视器对象)来调用。
 *  notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
 */
public class Test14 {
	public static void main(String[] args) {
		Workbench tai = new Workbench();
		
		Cook c = new Cook("崔志恒", tai);
		Waiter w = new Waiter("翠花", tai);
		
		c.start();
		w.start();
	}
}
class Workbench{
	//假设工作台上最多能够放10盘
	private static final int MAX = 10;
	private int count;
	
	//同步方法,非静态方法来说,锁对象就是this
	public synchronized void put(){//往工作台上放一盘菜
		if(count >= MAX){
			try {
				//生产者停下来,等待
				wait();//默认是this.wait()
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count++;
		
		System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
		this.notify();
	}
	
	public synchronized void take(){//从工作台上取走一盘菜
		if(count<=0){
			try {
				//工作台没有菜,消费者应该停下来
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count--;
		System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
		this.notify();
	}
}
class Cook extends Thread{
	private Workbench tai;

	public Cook(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.put();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class Waiter extends Thread{
	private Workbench tai;
	
	public Waiter(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.take();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
    

(2)多个生产者与消费者

在这里插入图片描述

如果用if,有可能此时消费者秋香和翠花都在wait()等待,且count=0。
生产者放了一个后,count=1,并唤醒了翠花。
翠花取了一个后count=0,接着翠花唤醒了秋香,秋香直接向下执行,就会导致count=-1

所以应该改为用while

在这里插入图片描述
如果为notify()

①当MAX=1,count=1,两个生产者全部wait
②消费者秋香取一个,count=0,并唤醒小崔,同时秋香再次抢到锁并wait
③消费者如花拿到锁,直接wait()
生产者小崔放一个,count=1,并唤醒小甄,同时再次抢到锁,wait
④小甄拿到锁直接wait()

四个全部wait卡住

所以需要notifyAll()

    package com.atguigu.test16;

/*
 * 线程通信是用来解决生产者与消费者问题。
 * 
 * 生产者与消费者问题:
 * 	  有两个或者多个线程。
 * 	 其中一个/一部分线程,生产“数据”,称为生产者线程;
 * 	另一个/一部分线程,消耗“数据”,称为消费者线程。
 * 	这些数据放在一个“共享”区域。
 *  那么就会出现:
 *    当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
 *    当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
 *    
 *  生产者与消费者问题:
 *  (1)共享数据:    就会有线程安全问题,就需要同步
 *  (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
 *  
 *  Object类中有:
 *  (1)wait():必须由锁对象(线程的监视器对象)来调用。
 *  (2)notify():必须由锁对象(线程的监视器对象)来调用。
 *  notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
 *  (3)notifyAll():唤醒所有和我是同一个监视器对象的正在等待的线程
 */
public class Test16 {
	public static void main(String[] args) {
		Workbench tai = new Workbench();
		
		Cook c1 = new Cook("崔志恒", tai);
		Cook c2 = new Cook("甄玉禄", tai);
		Waiter w1 = new Waiter("翠花", tai);
		Waiter w2 = new Waiter("如花", tai);
//		Waiter w3 = new Waiter("秋香", tai);
		
		c1.start();
		c2.start();
		w1.start();
		w2.start();
//		w3.start();
	}
}
class Workbench{
	//假设工作台上最多能够放10盘
	private static final int MAX = 1;
	private int count;
	
	//同步方法,非静态方法来说,锁对象就是this
	public synchronized void put(){//往工作台上放一盘菜
		while(count >= MAX){
			try {
				//生产者停下来,等待
				wait();//默认是this.wait()
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count++;
		System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
		this.notify();
	}
	
	public synchronized void take(){//从工作台上取走一盘菜
		while(count<=0){
			try {
				//工作台没有菜,消费者应该停下来
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count--;
		System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
//		this.notify();
		this.notifyAll();
	}
}
class Cook extends Thread{
	private Workbench tai;

	public Cook(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.put();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class Waiter extends Thread{
	private Workbench tai;
	
	public Waiter(String name, Workbench tai) {
		super(name);
		this.tai = tai;
	}

	public void run(){
		while(true){
			tai.take();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
    

第十一章 常用类

11.1 包装类

11.1.1 包装类

当要使用只针对对象设计的API或新特性(例如泛型),那么基本数据类型的数据就需要用包装类来包装。

序号基本数据类型包装类
1byteByte
2shortShort
3intInteger
4longLong
5floatFloat
6doubleDouble
7charCharacter
8booleanBoolean
9voidVoid

11.1.2 装箱与拆箱

JDK1.5之后,可以自动装箱与拆箱。

注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。

Integer i = 1;
Double d = 1;//错误的,1是int类型

装箱:把基本数据类型转为包装类对象。

转为包装类的对象,是为了使用专门为对象设计的API和特性

拆箱:把包装类对象拆为基本数据类型。

转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等

总结:对象(引用数据类型)能用的运算符有哪些?

(1)instanceof

(2)=:赋值运算符

(3)==和!=:用于比较地址,但是要求左右两边对象的类型一致或者是有父子类继承关系。

(4)对于字符串这一种特殊的对象,支持“+”,表示拼接。

11.1.3 包装类的一些API

1、基本数据类型和字符串之间的转换

(1)把基本数据类型转为字符串

int a = 10;
//String str = a;//错误的
//方式一:
String str = a + "";
//方式二:
String str = String.valueOf(a);

(2)把字符串转为基本数据类型

int a = Integer.parseInt("整数的字符串");
double a = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");

2、数据类型的最大最小值

Integer.MAX_VALUE和Integer.MIN_VALUE
Long.MAX_VALUE和Long.MIN_VALUE
Double.MAX_VALUE和Double.MIN_VALUE

3、转大小写

Character.toUpperCase('x');
Character.toLowerCase('X');

4、转进制

Integer.toBinaryString(int i) 
Integer.toHexString(int i)
Integer.toOctalString(int i)

11.1.4 包装类对象的缓存问题

包装类缓存对象
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float没有
Double没有
Character0~127
Booleantrue和false
Integer i = 1;
Integer j = 1;
System.out.println(i == j);//true

Integer i = 128;
Integer j = 128;
System.out.println(i == j);//false

Integer i = new Integer(1);//新new的在堆中
Integer j = 1;//这个用的是缓冲的常量对象,在方法区
System.out.println(i == j);//false

Integer i = new Integer(1);//新new的在堆中
Integer j = new Integer(1);//另一个新new的在堆中
System.out.println(i == j);//false

Integer i = new Integer(1);
int j = 1;
System.out.println(i == j);//true,凡是和基本数据类型比较,都会先拆箱,按照基本数据类型的规则比较

11.2 字符串

11.2.1 字符串的特点

1、字符串String类型本身是final声明的,意味着我们不能继承String。

2、字符串的对象也是不可变对象,意味着一旦进行修改,就会产生新对象

我们修改了字符串后,如果想要获得新的内容,必须重新接受。

如果程序中涉及到大量的字符串的修改操作,那么此时的时空消耗比较高。可能需要考虑使用StringBuilder或StringBuffer。

3、String对象内部是用字符数组进行保存的

JDK1.9之前有一个char[] value数组,JDK1.9之后byte[]数组

4、String类中这个char[] values数组也是final修饰的,意味着这个数组不可变,然后它是private修饰,外部不能直接操作它,String类型提供的所有的方法都是用新对象来表示修改后内容的,所以保证了String对象的不可变。

5、就因为字符串对象设计为不可变,那么所以字符串有常量池来保存很多常量对象

常量池位置:

(1)JDK1.6及其之前:永久代(方法区的一种实现)

(2)JDK1.7:字符串常量池单独在堆,其他在永久代

(3)JDK1.8:字符串常量池单独在堆,其他在元空间

PermGen(永久代)----->替换为Metaspace(元空间)(本地内存中)

方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过设置一些参数来指定元空间的大小

11.2.2 字符串对象的比较

1、==:比较是对象的地址

只有两个字符串变量都是指向字符串的常量对象时,才会返回true

String str1 = "hello";
String str2 = "hello";
str1 == str2//true

2、equals:比较是对象的内容,因为String类型重写equals,区分大小写

只要两个字符串的字符内容相同,就会返回true

String str1 = new String("hello");
String str2 = new String("hello");
str1.equals(strs) //true

3、equalsIgnoreCase:比较的是对象的内容,不区分大小写

String str1 = new String("hello");
String str2 = new String("HELLO");
str1.equalsIgnoreCase(strs) //true

4、compareTo:String类型重写了Comparable接口的抽象方法,自然排序,按照字符的Unicode编码值进行比较大小的,严格区分大小写

String str1 = "hello";
String str2 = "world";
str1.compareTo(str2) //小于0的值

5、compareToIgnoreCase:不区分大小写,其他按照字符的Unicode编码值进行比较大小

String str1 = new String("hello");
String str2 = new String("HELLO");
str1.compareToIgnoreCase(str2)  //等于0

11.2.3 空字符的比较

1、哪些是空字符串

String str1 = "";
String str2 = new String();
String str3 = new String("");

空字符串:长度为0

2、如何判断某个字符串是否是空字符串

if("".equals(str))

if(str!=null  && str.isEmpty())

if(str!=null && str.equals(""))

if(str!=null && str.length()==0)

11.2.4 字符串的对象的个数

1、字符串常量对象

String str1 = "hello";//1个,在常量池中

2、字符串的普通对象

String str2 = new String();
String str22 = new String("");
//两个对象,一个是常量池中的空字符串对象,一个是堆中的空字符串对象

3、字符串的普通对象和常量对象一起

String str3 = new String("hello");
//str3首先指向堆中的一个字符串对象,然后堆中字符串的value数组指向常量池中常量对象的value数组

11.2.5 字符串拼接结果

原则:

(1)常量+常量:结果是常量池

(2)常量与变量 或 变量与变量:结果是堆

(3)拼接后调用intern方法:结果在常量池

	@Test
	public void test06(){
		String s1 = "hello";
		String s2 = "world";
		String s3 = "helloworld";
		
		String s4 = (s1 + "world").intern();//把拼接的结果放到常量池中
		String s5 = (s1 + s2).intern();
		
		System.out.println(s3 == s4);//true
		System.out.println(s3 == s5);//true
	}
	
	@Test
	public void test05(){
		final String s1 = "hello";
		final String s2 = "world";
		String s3 = "helloworld";
		
		String s4 = s1 + "world";//s4字符串内容也helloworld,s1是常量,"world"常量,常量+ 常量 结果在常量池中
		String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是常量,常量+ 常量 结果在常量池中
		String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
		
		System.out.println(s3 == s4);//true
		System.out.println(s3 == s5);//true
		System.out.println(s3 == s6);//true
	}
	
	@Test
	public void test04(){
		String s1 = "hello";
		String s2 = "world";
		String s3 = "helloworld";
		
		String s4 = s1 + "world";//s4字符串内容也helloworld,s1是变量,"world"常量,变量 + 常量的结果在堆中
		String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是变量,变量 + 变量的结果在堆中
		String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
		
		System.out.println(s3 == s4);//false
		System.out.println(s3 == s5);//false
		System.out.println(s3 == s6);//true
	}

11.2.6 字符串的API

(1)boolean isEmpty()

(2)int length()

(3)String concat(xx):拼接,等价于+

(4)boolean contanis(xx)

(5)int indexOf():从前往后找,要是没有返回-1

(6)int lastIndexOf():从后往前找,要是没有返回-1

(7)char charAt(index)

(8)new String(char[] ) 或new String(char[] ,int, int)

(9)char[] toCharArray()

(10)byte[] getBytes():编码,把字符串变为字节数组,按照平台默认的字符编码进行编码

​ byte[] getBytes(字符编码方式):按照指定的编码方式进行编码

(11)new String(byte[] ) 或 new String(byte[], int, int):解码,按照平台默认的字符编码进行解码

​ new String(byte[],字符编码方式 ) 或 new String(byte[], int, int,字符编码方式):解码,按照指定的编码方式进行解码

(12)String substring(int begin):从[begin]开始到最后

String substring(int begin,int end):从[begin, end)

(13)boolean matchs(正则表达式)

(14)String replace(xx,xx):不支持正则

String replaceFirst(正则,value):替换第一个匹配部分

String repalceAll(正则, value):替换所有匹配部分

(15)String[] split(正则):按照某种规则进行拆分

(16)boolean startsWith(xx):是否以xx开头

boolean endsWith(xx):是否以xx结尾

(17)String trim():去掉前后空白符,字符串中间的空白符不会去掉

(18)String toUpperCase():转大写

(19)String toLowerCase():转小写

面试题:字符串的length和数组的length有什么不同?

字符串的length(),数组的length属性

11.3 可变字符序列

1、可变字符序列:StringBuilder和StringBuffer

StringBuffer:老的,线程安全的(因为它的方法有synchronized修饰)

StringBuilder:线程不安全的

2、面试题:String和StringBuilder、StringBuffer的区别?

String:不可变对象,不可变字符序列

StringBuilder、StringBuffer: 可变字符序列

3、常用的API,StringBuilder、StringBuffer的API是完全一致的

(1)append(xx):拼接,追加

(2)insert(int index, xx):插入

(3)delete(int start, int end)

deleteCharAt(int index)

(4)set(int index, xx)

(5)reverse():反转

… 替换、截取、查找…

11.4 和数学相关的

1、java.lang.Math类

(1)sqrt():求平方根

(2)pow(x,y):求x的y次方

(3)random():返回[0,1)范围的小数

(4)max(x,y):找x,y最大值

​ min(x,y):找最小值

(5)round(x):四舍五入

​ ceil(x):进一

​ floor(x):退一

2、java.math包

BigInteger:大整数

BigDecimal:大小数

运算通过方法完成:add(),subtract(),multiply(),divide()…

11.5 日期时间API

11.5.1 JDK1.8之前

1、java.util.Date

new Date():当前系统时间

long getTime():返回该日期时间对象距离1970-1-1 0.0.0 0毫秒之间的毫秒值

new Date(long 毫秒):把该毫秒值换算成日期时间对象

2、java.util.Calendar:

(1)getInstance():得到Calendar的镀锡

(2)get(常量)

3、java.text.SimpleDateFormat:日期时间的格式化

y:表示年

M:月

d:天

H: 小时,24小时制

h:小时,12小时制

m:分

s:秒

S:毫秒

E:星期

D:年当中的天数

	@Test
	public void test10() throws ParseException{
		String str = "2019年06月06日 16时03分14秒 545毫秒  星期四 +0800";
		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		Date d = sf.parse(str);
		System.out.println(d);
	}
	
	@Test
	public void test9(){
		Date d = new Date();

		SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒  E Z");
		//把Date日期转成字符串,按照指定的格式转
		String str = sf.format(d);
		System.out.println(str);
	}
	
	@Test
	public void test8(){
		String[] all = TimeZone.getAvailableIDs();
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i]);
		}
	}
	
	@Test
	public void test7(){
		TimeZone t = TimeZone.getTimeZone("America/Los_Angeles");
		
		//getInstance(TimeZone zone)
		Calendar c = Calendar.getInstance(t);
		System.out.println(c);
	}
	
	@Test
	public void test6(){
		Calendar c = Calendar.getInstance();
		System.out.println(c);
		
		int year = c.get(Calendar.YEAR);
		System.out.println(year);
		
		int month = c.get(Calendar.MONTH)+1;
		System.out.println(month);
		
		//...
	}
	
	@Test
	public void test5(){
		long time = Long.MAX_VALUE;
		Date d = new Date(time);
		System.out.println(d);
	}
	
	@Test
	public void test4(){
		long time = 1559807047979L;
		Date d = new Date(time);
		System.out.println(d);
	}
	@Test
	public void test3(){
		Date d = new Date();
		long time = d.getTime();
		System.out.println(time);//1559807047979
	}
	
	@Test
	public void test2(){
		long time = System.currentTimeMillis();
		System.out.println(time);//1559806982971
		//当前系统时间距离1970-1-1 0:0:0 0毫秒的时间差,毫秒为单位
	}
	
	@Test
	public void test1(){
		Date d = new Date();
		System.out.println(d);
	}

11.5.2 JDK1.8之后

java.time及其子包中。

1、LocalDate、LocalTime、LocalDateTime

(1)now():获取系统日期或时间

(2)of(xxx):或者指定的日期或时间

(3)运算:运算后得到新对象,需要重新接受

plusXxx():在当前日期或时间对象上加xx

minusXxx() :在当前日期或时间对象上减xx

方法描述
now() / now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHours()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
with(TemporalAdjuster t)将当前日期时间设置为校对器指定的日期时间
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
plus(TemporalAmount t)/minus(TemporalAmount t)添加或减少一个 Duration 或 Period
isBefore()/isAfter()比较两个 LocalDate
isLeapYear()判断是否是闰年(在LocalDate类中声明)
format(DateTimeFormatter t)格式化本地日期、时间,返回一个字符串
parse(Charsequence text)将指定格式的字符串解析为日期、时间

2、DateTimeFormatter:日期时间格式化

该类提供了三种格式化方法:

预定义的标准格式。如:ISO_DATE_TIME;ISO_DATE

本地化相关的格式。如:ofLocalizedDate(FormatStyle.MEDIUM)

自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

	@Test
	public void test10(){
		LocalDateTime now = LocalDateTime.now();
		
//		DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);//2019年6月6日 下午04时40分03秒
		DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);//19-6-6 下午4:40
		String str = df.format(now);
		System.out.println(str);
	}
	@Test
	public void test9(){
		LocalDateTime now = LocalDateTime.now();
		
		DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;//2019-06-06T16:38:23.756
		String str = df.format(now);
		System.out.println(str);
	}
	
	@Test
	public void test8(){
		LocalDateTime now = LocalDateTime.now();
		
		DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒  SSS毫秒  E 是这一年的D天");
		String str = df.format(now);
		System.out.println(str);
	}
	
	@Test
	public void test7(){
		LocalDate now = LocalDate.now();
		LocalDate before = now.minusDays(100);
		System.out.println(before);//2019-02-26
	}
	
	@Test
	public void test06(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		LocalDate go = lai.plusDays(160);
		System.out.println(go);//2019-10-20
	}
	
	@Test
	public void test05(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		System.out.println(lai.getDayOfYear());
	}
	
	
	@Test
	public void test04(){
		LocalDate lai = LocalDate.of(2019, 5, 13);
		System.out.println(lai);
	}
	
	@Test
	public void test03(){
		LocalDateTime now = LocalDateTime.now();
		System.out.println(now);
	}
	
	@Test
	public void test02(){
		LocalTime now = LocalTime.now();
		System.out.println(now);
	}
	
	@Test
	public void test01(){
		LocalDate now = LocalDate.now();
		System.out.println(now);
	}

11.6 Leetcode常用补充

void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) src为原数组, dest为目标数组

long System.currentTimeMillis() 当前时间(毫秒)

Arrays.sort(T[] a, Comparator<? super T> c) c可以为lambda
Arrays.toString(Object[] a)
Arrays.copyOf(T[] original, int newLength)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值