java学习笔记3 -- 异常、多线程、字符串、常用类

目录

  • 1.异常
    • 1.1 概述
      • 1.1.1 定义
      • 1.1.2 异常分类
    • 1.2 常见的运行时异常
      • 1.2.1 NullPointerException
      • 1.2.2 ArrayIndexOutOfBoundsException
      • 1.2.3 ClassCastException
      • 1.2.4 NumberFormatException
      • 1.2.5 InputMismatchException
      • 1.2.6 ArithmeticException
    • 1.3 常见的编译时异常
    • 1.4 异常处理机制
      • 1.4.1 抓抛模型
      • 1.4.2 异常处理机制1:try-catch-finally
      • 1.4.3 finally的使用
      • 1.4.4 编译时异常和运行时异常的不同处理
      • 1.4.5 异常处理机制2:throws + 异常类型
    • 1.5 开发中如何选择异常处理方式
    • 1.6 throw手动抛出异常
      • 1.6.1 抛出运行时异常
      • 1.6.2 抛出编译时异常
    • 1.7 自定义异常类
    • 1.8 throw和throws的区别
    • 1.9 总结
  • 2.项目三
    • 2.1 NameListService
    • 2.2 TeamService
    • 2.3 继承和多态
  • 3.多线程
    • 3.1 概述
      • 3.1.1 程序
      • 3.1.2 进程
      • 3.1.3 线程
      • 3.1.4 并行和并发
      • 3.1.5 使用多线程的优点
      • 3.1.6 什么时候使用多线程
    • 3.2 创建线程方式1
      • 3.2.1 步骤
      • 3.2.2 start方法的作用
      • 3.2.3 细节
      • 3.2.4 实例
      • 3.2.5 Thread匿名子类
    • 3.2 创建线程方式2
      • 3.2.1 步骤
      • 3.2.2 实例
      • 3.2.3 Runnable匿名内部类
      • 3.2.4 细节
    • 3.3 线程相关的方法
      • 3.3.1 start() ---见上面
      • 3.3.2 run()
      • 3.3.3 static Thread currentThread()
      • 3.3.4 getName()
      • 3.3.5 setName(String name)
      • 3.3.6 static void yield() --线程让步
      • 3.3.7 join() -- 线程的插队
      • 3.3.8 static void sleep(long time)
      • 3.3.9 boolean isAlive()
    • 3.4 线程的优先级
      • 3.4.1 全局常量
      • 3.4.2 方法
      • 3.4.3 细节
    • 3.5 两种线程创建方式的区别
    • 3.6 线程的生命周期
    • 3.7 线程安全问题
      • 3.7.1 如何解决
      • 3.7.2 上面三种方法的优缺点
      • 3.7.3 同步和异步
    • 3.8 同步代码块
      • 3.8.1 格式
      • 3.8.2 同步代码块处理实现Runnable接口的线程安全问题
      • 3.8.3 同步代码块处理继承Thread类的线程安全问题
    • 3.9 同步方法
      • 3.9.1 格式
      • 3.9.2 同步方法处理实现Runnable接口的线程安全问题
      • 3.9.3 同步方法处理继承Thread类的线程安全问题
    • 3.10 线程安全的懒汉式单例模式
    • 3.11 释放锁的操作
    • 3.12 不会释放锁的操作
    • 3.13 线程的死锁
      • 3.13.1 定义
      • 3.13.2 解决方法
    • 3.14 Lock锁
    • 3.15 synchronized和Lock的异同
    • 3.16 线程通信
      • 3.16.1 三个方法
      • 3.16.2 细节
      • 3.16.3 sleep()和wait()的异同
    • 3.17 生产者消费者问题
    • 3.18 创建线程方式3
      • 3.18.1 步骤
      • 3.18.2 实现Callable接口比实现Runnable接口的有什么强大之处?
      • 3.18.3 实例
    • 3.19 创建线程方式4:线程池
      • 3.19.1 优点
      • 3.19.2 步骤
      • 3.19.3 实例
    • 2.30 坦克大战的多线程要点
      • 2.30.1 什么情况下使用多线程
      • 2.30.2 什么情况下启动线程
      • 2.30.3 什么情况下结束线程
      • 2.30.4 线程中static void sleep()的使用
  • 4. String字符串
    • 4.1 概述
      • 4.1.1 字符串的创建方式
      • 4.1.2 细节
      • 4.1.3 两种创建方式的区别
      • 4.1.4 String遍历
    • 4.2 ==String的不可变性==
    • 4.3 String拼接
      • 4.3.1 细节
      • 4.3.2 面试题--比较
      • 4.3.3 ==面试题--产生对象的个数==
    • 4.4 面试题
    • 4.5 String常用方法
      • 4.5.1 trim()
      • 4.5.2 int compareTo(String anotherString) -- 涉及字符串的排序
      • 4.5.3 String substring(int beginIndex)
      • 4.5.4 String substring(int beginIndex,int endIndex) ==前闭后开==
      • 4.5.5 boolean startsWith(String prefix, int toffset)
      • 4.5.6 boolean contains(CharSequence s)
      • 4.5.7 int indexOf(String str)
      • 4.5.8 int indexOf(String str,int fromIndex)
      • 4.5.9 int lastIndexOf(String str)
      • 4.5.10 int lastIndexOf(String str, int fromIndex)
      • 4.5.11 String replace(char oldChar, char newChar),String replace(CharSequence target, CharSequence replacement)
      • 4.5.12 String replaceAll(String regex, String replacement)
      • 4.5.13 String replaceFirst(String regex, String replacement)
      • 4.5.14 boolean matches(String regex)
      • 4.5.15 String[] split(String regex)
      • 4.5.16 String[] split(String regex, int limit)
    • 4.6 char[]与String之间的转换
      • 4.6.1 String转为char[]
      • 4.6.2 char[]转为String
    • 4.7 byte[]与String之间的转换(编码/解码)
    • 4.8 练习--字符串反转
    • 4.9 练习2-- 查找字符串出现次数
    • 4.10 练习3--获取两个字符串中最大相同子串
    • 4.11 String、StringBuilder、StringBuffer异同
      • 4.11.1 相同点
      • 4.11.2 不同点
      • 4.11.3 ==可变字符序列和不可变字符序列==
      • 4.11.4 三者效率比较
    • 4.12 StringBuffer源码分析(StringBuilder类似)
      • 4.12.1 创建对象
      • 4.12.2 如何实现扩容
      • 4.12.3 使用建议
    • 4.13 StringBuffer/StringBuilder类的常用方法
    • 4.13 面试题
  • 5.JDK8之前日期时间API
    • 5.1 System类
    • 5.2 Date类
      • 5.2.1 java.util.Date
      • 5.2.2 java.sql.Date(util.Date的子类)
      • 5.2.3 两种Date转换
    • 5.3 SimpleDateFormat
      • 5.3.1 作用
      • 5.3.2 空参构造器
      • 5.3.3 带参构造器
      • 5.3.4 练习:将字符串转为java.sql.Date
      • 5.3.5 练习:获取一个人的出生天数
    • 5.4 Calendar类
      • 5.4.1 创建对象
      • 5.4.2 get(int field)
      • 5.4.3 public void set(int field,int value)
      • 5.4.4 public void add(int field,int amount)
      • 5.4.5 public final Date getTime()
      • 5.4.6 public final void setTime(Date date)
  • 6.JDK8中新日期时间API
    • 6.1 java.time
      • 6.1.1 几个常用的类(==使用和Calendar类似==)
      • 6.1.2 now()方法
      • 6.1.3 of()方法
      • 6.1.4 getXxx()方法
      • 6.1.5 withXxx()方法
      • 6.1.6 plusXxx()方法
      • 6.1.7 minusXxx()方法
    • 6.2 Instant类
      • 6.2.1 作用
      • 6.2.2 实例
    • 6.3 DateTimeFormatter
      • 6.3.1 作用
      • 6.3.2 实例
    • 6.4 与传统日期处理的转换

1.异常

完整笔记

1.1 概述

1.1.1 定义

程序在运行过程中出现不正常的情况,导致运行终止。

1.1.2 异常分类

java学习笔记3 -- 异常、多线程、字符串、常用类_第1张图片

1.2 常见的运行时异常

1.2.1 NullPointerException

引用数据类型变量的值为null,使用变量调用属性或者方法时,出现NullPointerException。

    @Test
    public void testNullPointerException(){
//        空指针异常
        String s = null;
        System.out.println(s.toString());
    }

1.2.2 ArrayIndexOutOfBoundsException

    @Test
    public void testNullPointerException(){
//        数组角标越界异常
        int[] arr = {1,2,3};
        System.out.println(arr[3]);
    }

1.2.3 ClassCastException

一般发生在向下转型时。

    @Test
    public void testClassCaseException(){
        Object o = new Date();
        String s = (String)o;
    }

1.2.4 NumberFormatException

一般发生在将字符串转为包装类时。

    @Test
    public void testNumberFormatException(){
        String s = "abc";
        int i = Integer.parseInt(s);
    }

1.2.5 InputMismatchException

使用Scanner从键盘输入不匹配的类型时。

    @Test
    public void testInputMismatchException(){
        Scanner sc = new Scanner(System.in);
        int i = sc.nextInt();
    }

1.2.6 ArithmeticException

算术异常:除0

1.3 常见的编译时异常

编译时异常:编写代码后,在IDE中会自动标出来,不解决无法运行。

1.4 异常处理机制

1.4.1 抓抛模型

:程序在运行过程中出现异常,就会在异常代码处生成异常类的对象,并将此对象抛出,一旦发生异常,其后的代码就不再执行;
异常产生的方式:① 系统自动生成 ② 手动生成异常对象,并throw
:可以理解为异常的处理方式。① try-catch-finally ② throws

1.4.2 异常处理机制1:try-catch-finally

  • try代码块中是可能出现异常的代码,catch中是捕获异常后的处理方法,finally中是必须要执行的代码;
  • try中出现异常后的代码不再执行,如果有必须要执行的,放到finally中;
  • catch的形参是异常对象,有String getMessage()void printStackTrace()方法,表示异常描述信息,开发中常使用printStackTrace()方法;
  • 可以有多个catch语句,一个异常只能被一个catch捕获;
  • 并列catch结构中,父类异常对象不能声明在子类异常对象前面;
  • 在try结构中声明的变量,再出了try结构以后,就不能再被调用;
  • try-catch-finally可以进行嵌套;
  • try-catch-finally后面的代码会被执行,因为异常已经被处理了,程序会继续执行。
    @Test
    public void testInputMismatchException(){
        try {
            Scanner sc = new Scanner(System.in);
            int i = sc.nextInt();
        }catch (NumberFormatException e){
            System.out.println(e.getMessage());
        }catch (InputMismatchException e){
            e.printStackTrace();
        }catch(Exception e){
            System.out.println(e.getMessage());
        }finally {
            // 一定会执行的代码
        }
    }

1.4.3 finally的使用

  • finally是可选的;
  • finally中的代码一定会被执行,即使catch中又出现异常了,try中有return语句,catch中有return语句等情况;
  • 如果finlly、try、catch中都有return语句,那么只会执行finally中的return语句;
  • 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
  • 在try中return执行之前始终会执行finally里面的代码,如果finally里面有return,则数据跟随finally改变并返回,不会再执行try中的return。如果没有return,则原数据不跟随finally里改变的数据改变!
  • finallly在一些特定情况下是不会执行的。参考文章
	@Test
	public void test2() {
		FileInputStream fis = null;
		try {
			File file = new File("hello1.txt");//文件可能不存在,而出现异常
			fis = new FileInputStream(file);

			int data = fis.read();
			while (data != -1) {
				System.out.print((char) data);
				data = fis.read();
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fis != null)
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

1.4.4 编译时异常和运行时异常的不同处理

  • 编译时异常使用try-catch-finally处理,使得其编译过程中不会报错,但是运行过程中仍可能会报错,相当于将编译时可能出现的异常延迟到运行时;有些语句本身就存在异常,是因为源码中的方法使用了throws声明了异常,我们必须要处理才能编译成功(例如FileInputStream的创建)
  • 开发中,运行时异常通常不需要try-catch-finally处理,因为有默认的处理方式;但是编译时异常一定要处理,不处理就无法运行。(可见1.6)

1.4.5 异常处理机制2:throws + 异常类型

格式:访问修饰符 返回值类型 方法名(形参) throws 异常类1,异常类2,…{ }

  • throws将当前方法中的异常抛给调用者,如果调用者不处理,仍然会有异常;
  • throws并没有真正解决异常,try-catch-finally才是真正把异常解决了;
  • 对于编译时异常如果不使用try-catch-finally处理,那么必须throws给调用者;
  • 异常后的代码不再执行;
  • 子类重写方法抛出的异常不能大于父类被重写方法抛出的异常,因为在多态情况下,我们对异常进行try-catch-finally处理,是以父类为准;

1.5 开发中如何选择异常处理方式

1.如果父类中被重写的方法没有使用throws抛异常,而子类重写的方法有异常,那么只能在子类中使用try-catch-finally处理异常;
2.如果方法a在运行的过程中需要调用方法b,而方法b需要调用方法c,那么方法b、c要使用throws抛出异常,在方法a中使用try-catch-finally处理;

1.6 throw手动抛出异常

  • 一些情况下,我们需要手动抛出异常,比如用户输入不符合规定的范围等等;
  • throw在方法内部使用,用来抛出一个异常对象,并结束当前方法的执行;
  • 只有异常类的对象才可以throw;

1.6.1 抛出运行时异常

运行时异常,可以不用处理。

    public void test1(int i) {
        if (i < 0) {
            throw new RuntimeException("输入不合规");
        }
    }
Exception in thread "main" java.lang.RuntimeException: 输入不合规
	at Exception.ThrowsTest.test1(ThrowsTest.java:30)
	at Exception.ThrowsTest.main(ThrowsTest.java:21)

1.6.2 抛出编译时异常

  • 编译时异常,要么throws让调用者处理,要么使用try-catch-finally处理;
    public void test1(int i) throws Exception {
        if (i < 0) {
            throw new Exception("输入不合规");
        }
    }

1.7 自定义异常类

要求:
1.继承于现有的异常结构:RuntimeException(运行时) 、Exception(运行时+编译时);
2.提供全局常量:serialVersionUID;
3.提供重载的构造器;
4.自定义的异常通过throw抛出;

public class MyException extends RuntimeException{
    static final long serialVersionUID = 123123213;
    public MyException() {
    }
    public MyException(String msg){
        super(msg);
    }
}

1.8 throw和throws的区别

1.throws是声明异常,throw是抛出异常;
2.throws是处理异常的一种方式,而throw是手动抛出异常;
3.位置不同:throws在方法声明处,后面时异常类名,throw在方法体中,后面是异常对象;
4.throws表示可能会出现异常,而throw一定会抛出一个异常对象;

1.9 总结

java学习笔记3 -- 异常、多线程、字符串、常用类_第2张图片

2.项目三

2.1 NameListService

  1. 有常量用常量,不用使用具体数据,不然修改很麻烦;
  2. 与前端交互的写在view层中,抛出异常的处理逻辑放在service层中;

2.2 TeamService

  1. 如果可以不用else,尽量只用if来写,这样代码结构更清晰;
  2. 下面第二种写法不对,如果是一名架构师但是人数够了,那么还会去执行下面的else if语句,因为架构师是设计师的子类,所以可能会进入第1个或者第2个else if中,导致程序出错;
  3. instanceOf的使用;

正确的:

        if(p instanceof Architect){
            if(t.getArchitectCount()==1)
                throw new AddException("团队中至多只能有一名架构师");

        }else if (p instanceof Designer){
            if(t.getDesignerCount()==2)
                throw new AddException("团队中至多只能有两名设计师");

        }else if (p instanceof Programmer){
            if(t.getProgrammerCount()==3)
                throw new AddException("团队中至多只能有三名程序员");
        }

错误的:

    if (p instanceof Architect && t.getArchitectCount() == 1) {
            throw new AddException("团队中至多只能有一名架构师");
        } else if (p instanceof Designer && t.getDesignerCount() == 2) {
            throw new AddException("团队中至多只能有两名设计师");
        } else if (p instanceof Programmer && t.getProgrammerCount() == 3) {
            throw new AddException("团队中至多只能有三名程序员");
        }

2.3 继承和多态

在前端页面输出实体类的信息,可以利用继承和多态的特性,重写toString方法,如果其中有不同的信息,可以在父类中提取出一个公共的方法,让所有子类的toString方法调用这个方法,而不用调用父类中的toString方法,也能实现代码复用。输出时,所有父子类对象都只要调用toString即可输出对应的信息,大大降低了代码的冗余。

3.多线程

3.1 概述

3.1.1 程序

为了完成特定任务,使用某种语言编写的一组指令。

3.1.2 进程

正在运行中的程序,是资源分配的最小单位。
生命周期:产生-运行-消亡

3.1.3 线程

  • 进程内部的执行单元,一个进程至少要有一个线程,线程是调度的基本单位。
  • 线程共享进程的堆空间,有自己独立的栈空间。
  • java程序中至少有三个线程,main方法主线程、垃圾回收线程和异常处理线程。

3.1.4 并行和并发

并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。

3.1.5 使用多线程的优点

  1. 提高应用程序的响应,增强用户的体验感。例如:用户可以边听音乐边聊天。
  2. 提高计算机系统cpu的利用率。
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
  4. 多线程在单核cpu上反而会降低程序执行的速度,因为线程切换也需要时间开销,但是对多核cpu来说,可以提升程序运行的速度。

3.1.6 什么时候使用多线程

  1. 需要多个任务同时执行的时候;
  2. 程序需要一些等待的任务,如用户输入、文件读写操作、网络操作等;
  3. 需要一些后台运行的程序;

3.2 创建线程方式1

3.2.1 步骤

  1. 创建Thread的子类;
  2. 重写run()方法;
  3. 实例化子类对象;
  4. 调用子类对象的start()方法启动线程;

3.2.2 start方法的作用

  1. 启动当前线程;
  2. 调用当前线程重写的run()方法,并不会立即执行,只是进入了就绪态,具体什么时候执行取决于cpu;

3.2.3 细节

  1. 如果直接调用run()方法,那么不会开启新线程,还是在当前线程中执行;
  2. 一个线程对象只能调用一次start()方法,启动一个新线程,如果多次调用,会出现java.lang.IllegalThreadStateException异常

3.2.4 实例

public class ThreadTest extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0)
                System.out.println(i);
        }
    }
}

class Test {
    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        t1.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("-------------------------");
        }
    }
}

3.2.5 Thread匿名子类

new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0)
                        System.out.println(Thread.currentThread().getName() + "," + i);
                }
            }
        }.start();

3.2 创建线程方式2

3.2.1 步骤

  1. 创建类来实现Runnable接口;
  2. 重写run()方法;
  3. 创建Thread对象,将实现类作为构造器的形参放进去;
  4. 使用Thread对象调用start()方法开启线程;

3.2.2 实例

class RunnableTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0)
                System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

class Test {
    public static void main(String[] args) {
        RunnableTest r = new RunnableTest();
        Thread t = new Thread(r, "线程1");
        t.start();
        }
  }

3.2.3 Runnable匿名内部类

   new Thread(new Runnable(){
       @Override
       public void run() {
           System.out.println(1);
       }
   },"线程1").start();

3.2.4 细节

因为不是继承的Thread类,所以调用Thread中的方法时,需要使用类名或者对象。

3.3 线程相关的方法

3.3.1 start() —见上面

3.3.2 run()

线程在被调度时执行的操作

3.3.3 static Thread currentThread()

表示当前正在执行中的线程

3.3.4 getName()

获取当前线程的名称

3.3.5 setName(String name)

设置当前线程的名称

3.3.6 static void yield() --线程让步

  1. 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
  2. 若队列中没有同优先级的线程,忽略此方法
  3. 让步不一定会成功,因为并不会阻塞当前线程,当前线程回到就绪态,仍然有可能被cpu执行

3.3.7 join() – 线程的插队

在线程a中调用线程b的join方法,会阻塞线程a,直到线程b执行完毕为止(在另一个线程执行完毕前都不会执行本线程)

3.3.8 static void sleep(long time)

使当前线程进入休眠状态,time以毫秒为单位

3.3.9 boolean isAlive()

判断当前线程是否还存活

3.4 线程的优先级

3.4.1 全局常量

MAX_PRIORITY:最高优先级(10)
MIN_PRIORITY:最低优先级(1)
NORM_PRIORITY:普通优先级(5)
线程的优先级默认为5。

3.4.2 方法

	线程对象.setPriority(Thread.MAX_PRIORITY);设置线程的优先级
	线程对象.setPriority(8); 设置线程的优先级
	线程对象.getPriority(); 获取线程的优先级

3.4.3 细节

优先级高的线程只是被cpu执行的概率更高,而并非一定在低优先级的线程之前执行;

3.5 两种线程创建方式的区别

一般优先使用实现Runnable接口的方式

  1. 继承Thread类有单继承的局限,而实现Runnable接口更加灵活;
  2. 实现Runnable接口可以更好地满足数据共享的需求;

3.6 线程的生命周期

java学习笔记3 -- 异常、多线程、字符串、常用类_第3张图片

3.7 线程安全问题

多个线程共享一个数据时,一个线程操作共享数据时被另一个线程抢占了CPU,从而造成了数据不同步的问题。
共享数据:多个线程共同操作的属性。

3.7.1 如何解决

  1. 同步代码块
  2. 同步方法
  3. Lock锁

3.7.2 上面三种方法的优缺点

优点:解决了线程安全的问题。
缺点:执行同步代码时,相当于是单线程,降低了程序运行的速度。

3.7.3 同步和异步

同步:某个时间段内只能执行一件事,加锁实现同步。
异步:某个时间段内多件事同时执行。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

3.8 同步代码块

3.8.1 格式

同步监视器:可以是任意对象,但使用同一资源的所有线程必须使用同一对象
操作共享数据的代码不能包含的太多,也不能包含的太少。

synchronized(同步监视器){
	操作共享数据的代码
}

3.8.2 同步代码块处理实现Runnable接口的线程安全问题

对于同步监视器,可以使用当前对象this,因为所有线程都是使用的同一个对象。

class PocketTest1 implements Runnable {
    private int count = 100;
    @Override
    public void run() {
        while (true) {
            synchronized(this){
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出1张,还有" + count + "张");
                    count--;
                }else
                    break;
            }
        }
    }
}

3.8.3 同步代码块处理继承Thread类的线程安全问题

对于同步监视器,不能使用this,可以使用静态对象,也可以使用当前类(类名.class)

class PocketTest extends Thread {
    private static int count = 100;
    public static Object o = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized(o){
            synchronized(PocketTest.class){
                if (count > 0) {
                    System.out.println(getName() + "卖出1张,还有" + --count + "张");
                } else
                    break;
            }
        }
    }

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

3.9 同步方法

3.9.1 格式

同步方法中也有同步监视器,只是不用显示声明。
非静态方法的同步监视器是this,静态方法的同步监视器是类(类名.class)

    public synchronized static SingleTon getSingleTon() {
        if (singleTon == null) {
            singleTon = new SingleTon();
        }
        return singleTon;
    }

3.9.2 同步方法处理实现Runnable接口的线程安全问题

非静态方法的同步监视器是this,静态方法的同步监视器是类

class PocketTest1 implements Runnable {
    private int count = 1000;
    @Override
    public void run() {
        while (true) {
            test();
        }
    }
    public synchronized void test(){
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出1张,还有" + count + "张");
                count--;
            }
    }
}

3.9.3 同步方法处理继承Thread类的线程安全问题

必须将同步方法声明为static,本类对象才能共用一个同步监视器

class PocketTest extends Thread {
    private static int count = 1000;

    @Override
    public void run() {
        while (true) {
            test();
        }
    }
    public synchronized static void test(){
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出1张,还有" + count + "张");
            count--;
        }
    }
    public PocketTest(String name) {
        super(name);
    }
}

3.10 线程安全的懒汉式单例模式

方式1:同步方法

public class SingleTon {
    private SingleTon() {

    }

    private static SingleTon singleTon;

    public synchronized static SingleTon getSingleTon() {
        if (singleTon == null) {
            singleTon = new SingleTon();
        }
        return singleTon;
    }

}

方式2:同步代码块

public class SingleTon {
    private SingleTon() {

    }

    private static SingleTon singleTon;
        public static SingleTon getSingleTon(){
            synchronized (SingleTon.class){
                if (singleTon == null) {
                singleTon = new SingleTon();
                }
                return singleTon;
            }
        }
}

方式3:效率更高的同步代码块

public class SingleTon {
    private SingleTon() {

    }

    private static SingleTon singleTon;
        public static SingleTon getSingleTon(){
            if (singleTon == null) {
                synchronized (SingleTon.class){
                    if (singleTon == null) {
                        singleTon = new SingleTon();
                    }
                }
            }
            return singleTon;
        }
}

3.11 释放锁的操作

  1. 同步代码块和同步方法执行完毕;
  2. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致线程结束;
  3. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁;
  4. 当前线程在同步代码块、同步方法中出现了return或break语句终止了该代码块或方法的执行;

3.12 不会释放锁的操作

  1. 当前线程在同步代码块、同步方法中调用yield()、sleep()方法,不会释放锁;
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器);

3.13 线程的死锁

3.13.1 定义

不同线程占用对方所需的资源而不放弃,都在等待对方放弃自己所需的资源,从而形成了死锁。

3.13.2 解决方法

  1. 专门的算法
  2. 避免使用同步资源
  3. 避免嵌套同步

3.14 Lock锁

  • 如果同步代码有异常,一定要将unlock()方法放在finally代码块中;
class PocketTest1 implements Runnable {
    private int count = 100;
//    1.创建ReentrantLock对象
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
//                2.上锁
                lock.lock();
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出1张,还有" + --count + "张");
                }else{
                    break;
                }
            } finally {
//                3.解锁
                lock.unlock();
            }
        }
    }
}

3.15 synchronized和Lock的异同

相同点:都可以解决线程安全问题
不同点:synchronized执行完相应的同步代码后,自动释放同步监视器;Lock需要手动上锁和解锁。

3.16 线程通信

3.16.1 三个方法

wait():阻塞当前线程并且释放同步监视器;
notify():唤醒一个被wait()的线程,如果有多个线程,具体唤醒顺序由jvm决定 详解;
notifyAll():唤醒所有被wait()的线程;

3.16.2 细节

  1. wait(),notify(),notifyAll() 只能在同步代码块或同步方法中使用,不能用在Lock中;
  2. 三个方法都要使用同步监视器调用;
  3. 三个方法都是java.lang.Object中的方法;

3.16.3 sleep()和wait()的异同

相同点:都可以使线程进入阻塞状态;
不同点:1.在同步代码块或者同步方法中调用sleep()不会释放同步监视器,而调用wait()会释放同步监视器
2.声明位置不同:sleep是Thread中的静态方法,wait是Object中的方法;
3.调用位置不同:sleep可以在多线程的任意场景中调用,wait只能在同步代码块或同步方法中调用;

3.17 生产者消费者问题

/**
 * @Description: 生产者消费者问题
 * @author:zhou
 * @create: 2022-02-04 15:54
 */
class ProduceTest{
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        new Thread(new Producer(clerk),"生产者1").start();
        new Thread(new Customer(clerk),"消费者1").start();
        new Thread(new Customer(clerk),"消费者2").start();
    }
}

public class Producer implements Runnable {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
        }
    }
}

class Customer implements Runnable {
    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.custome();
        }
    }
}

class Clerk {
    private int count = 0;

    public void product() {
            synchronized (this) {
                if (count < 20) {
                    count++;
                    System.out.println(Thread.currentThread().getName() + "生产了第" + count + "件产品");
                    notify();
                } else {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    }

    public void custome() {
        synchronized (this) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + "消费了第" + count + "件产品");
                count--;
                notify();
            }else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.18 创建线程方式3

3.18.1 步骤

  1. 创建Callable接口的实现类;
  2. 重写Callable中的call()方法;
  3. 创建实现类的对象;
  4. 将实现类对象作为FutureTask的构造器参数,创建FutureTask对象;
  5. 将FutureTask对象作为Thread的构造器参数,创建新线程,并调用start方法开启线程(FutureTask实现了Runnable接口);
  6. 调用FutureTask对象的get()方法获取call()方法中的返回值;

3.18.2 实现Callable接口比实现Runnable接口的有什么强大之处?

  1. call()方法有返回值;
  2. call()方法可以抛出异常;
  3. Callable支持泛型;

3.18.3 实例

public class CallableTest implements Callable {
//    重写call()方法
    @Override
    public Object call() throws Exception {
        System.out.println(":12312");
        return null;
    }
}
class Test3{
    public static void main(String[] args) {
//        创建Callable实现类对象
        CallableTest callableTest = new CallableTest();
//        创建FutureTask对象
        FutureTask future = new FutureTask(callableTest);
//        创建并启动线程
        new Thread(future,"Thread-1").start();
        try {
//            获取call()方法的返回值
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

3.19 创建线程方式4:线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。(开发中优先使用这种方法

3.19.1 优点

  1. 不需要频繁创建销毁线程,减少了系统开销;
  2. 提高了响应速度;(减少了创建新线程的时间)
  3. 便于线程的管理;

3.19.2 步骤

  1. 创建线程池;
  2. 创建要提交的线程任务;
  3. 提交并执行任务;
  4. 关闭线程池;

3.19.3 实例

public class ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        1. 创建线程池对象
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
//        设置线程池的属性
        threadPoolExecutor.setCorePoolSize(5);
        threadPoolExecutor.setMaximumPoolSize(10);
//        2. 创建线程任务
        RunnableTest1 runnableTest1 = new RunnableTest1();
        CallableTest1 callableTest = new CallableTest1();
//        3.提交并执行任务
//        submit适用于实现Runnable接口和实现Callable接口的线程
//        返回Future对象,可以调用get()方法的到call()方法的返回值
        Future submit = executorService.submit(callableTest);
        System.out.println(submit.get());
        executorService.submit(runnableTest1);

//        execute只适用于实现Runnable接口的线程
        executorService.execute(runnableTest1);
//        4.关闭线程池
        executorService.shutdown();
    }
}
class RunnableTest1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
class CallableTest1 implements Callable {

    @Override
    public Object call() throws Exception {
        for (int i = 100; i > 0; i--) {
            System.out.println(i);
        }
        return null;
    }
}

2.30 坦克大战的多线程要点

2.30.1 什么情况下使用多线程

需要多个任务同时执行的时候。例如我方坦克需要键盘控制移动,而发射的子弹需要按一定速度移动,要求两者可以同时移动,所以需要创建一个线程用于子弹位置的计算。

2.30.2 什么情况下启动线程

一般是在创建Runnable实现类的同时启动线程。
在这里插入图片描述

2.30.3 什么情况下结束线程

当不满足条件时,关闭线程。例如子弹位置超出画布边界或者敌方坦克被击毁。

2.30.4 线程中static void sleep()的使用

为了显示动态效果,一般在操作后需要使用sleep方法休眠一段时间,以便将当前的状态绘制出来。
java学习笔记3 -- 异常、多线程、字符串、常用类_第4张图片

4. String字符串

4.1 概述

4.1.1 字符串的创建方式

字面量直接创建:String s1 = "abc";
通过new创建:String s2 = new String("abc");
  1. 通过字面量创建的字符串的内容位于常量池中,并且常量池中不能存在相同内容的字符串;
  2. 通过new创建的字符串会在堆中开辟空间,变量指向堆中内存地址,而字符串的内容也是存放在常量池中;
    java学习笔记3 -- 异常、多线程、字符串、常用类_第5张图片
    java学习笔记3 -- 异常、多线程、字符串、常用类_第6张图片

4.1.2 细节

  1. String被声明为final的,不可以被继承;
  2. String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小;
  3. String内部定义了final char[] value 数组,用于存放字符串数据;
  4. String不可以通过对象[index]的形式获取
  5. 字符串比较内容使用的是equals();

4.1.3 两种创建方式的区别

  1. 字符串常量存储在字符串常量池,目的是共享;
  2. 字符串非常量对象存储在堆中;
       String s1 = "abc";
       String s2 = "abc";
       String s3 = new String("abc");
       String s4 = new String("abc");
        System.out.println(s1==s2); // true
        System.out.println(s1==s3); // false
        System.out.println(s4==s3); // false

4.1.4 String遍历

通过charAt方法获取str的各位字符

        String str = "abcd";
        for (int i = 0; i < str.length(); i++) {
            System.out.println(str.charAt(i));
        }

4.2 String的不可变性

  1. 如果String变量重新赋值,那么不会更改现有的字符串内容,而是重新指定内存区域赋值;
  2. 对现有的字符串进行连接操作时,也需要重新指定内存区域赋值;
  3. 调用字符串的replace()方法修改字符或者字符串时,也需要重新指定内存区域赋值;

4.3 String拼接

4.3.1 细节

  1. 常量与常量的拼接结果在常量池中,常量池中只会存在拼接的结果,且常量池中不会存在相同内容的常量;(final修饰的也是常量)
  2. 只要其中一个是变量,结果就在堆中;
  3. 如果拼接的结果调用intern()方法,返回值就在常量池中;

4.3.2 面试题–比较

     String s1 = "hello";
     String s2 = "world";
     String s3 = "helloworld";
     String s4 = "hello" + "world";
     String s5 = new String("hello") + "world";
     String s6 = s1 + "world";
     String s7 = s1 + s2;
     System.out.println(s3 == s4); //true
     System.out.println(s3 == s5); //false
     System.out.println(s3 == s6); //false
     System.out.println(s3 == s7); //false
     System.out.println(s3 == s7.intern()); //true
	 System.out.println(s3 == s5.intern()); //true
     s1 += "world";
     System.out.println(s1 == s3); //false
     System.out.println(s1.intern() == s3); //true

	final String s8 = "hello";
	String s9 = s8 + "world";
	 System.out.println(s9 == s3); //true

4.3.3 面试题–产生对象的个数

以下创建对象的个数都是基于常量池不存在创建的字符串情况下:

  1. String s1 = "a"; 创建了1个对象
    说明:在字符串常量池中创建了一个字面量为"a"的字符串。
  2. s1 = s1 + "b"; 创建了两个对象:堆中和常量池各一个,相当于s1 = new String("ab")
    说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串对象s1+“b”(也就是"ab")。
    如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。
    如果这样的操作放到循环中,会极大影响程序的性能。
  3. String s2 = "ab"; 创建了1个对象
    说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。
  4. String s3 = "a" + "b"; 创建了1个对象,等价于String s2 = "ab";
    说明:编译阶段会直接将“a”和“b”结合成“ab”,这时如果字符串常量池已存在“ab”,则将s3的引用指向该字符串,如不存在,则在方法区中生成字符串“ab”对象,然后再将s3的引用指向该字符串,并不会创建a和b字符串。
  5. String s4 = s1.intern();
    说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。
  6. 我觉得是4个对象

4.4 面试题

虽然String是引用数据类型,参数传递的是地址值,但是由于字符串的不可变性,会在内存中重新开辟空间存放"test ok"并赋值给局部变量str,而属性str的值并没有改变;
字符数组也是引用数据类型,但是其内容是可变的,所以字符数组的值会改变。

public class StringTest {
	String str = new String("good");
	char[] ch = { 't', 'e', 's', 't' };
	public void change(String str, char ch[]) {
		str = "test ok";
		ch[0] = 'b'; 
		}
	public static void main(String[] args) {
		StringTest ex = new StringTest();
		ex.change(ex.str, ex.ch);
		System.out.print(ex.str); //good
		System.out.println(ex.ch); //best
} }

4.5 String常用方法

4.5.1 trim()

去除字符串的首尾空格,中间的不管。

4.5.2 int compareTo(String anotherString) – 涉及字符串的排序

比较两个字符串的大小:逐位比较,如果对应位置的字符不同,返回字符的差值;如果对应位置字符都相同,返回字符串长度的差值。

        String s1 = "abcddsfaf";
        String s2 = "abc";
        System.out.println(s1.compareTo(s2)); // 6
        String s3 = "dbcdds";
        String s4 = "abcaa";
        System.out.println(s3.compareTo(s4)); // 3

4.5.3 String substring(int beginIndex)

从指定索引处开始截取字符串直至末尾。

4.5.4 String substring(int beginIndex,int endIndex) 前闭后开

此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

4.5.5 boolean startsWith(String prefix, int toffset)

判断此字符串从给定的索引处toffset开始的子字符串是否以前缀prifix开始。

        String s3 = "dbcdds";
        System.out.println(s3.startsWith("db")); // true
        System.out.println(s3.startsWith("db",1)); // false

4.5.6 boolean contains(CharSequence s)

判断此字符串中是否包含子串s,s可以传入字符串。
严格区分大小写。

        String s3 = "dbcdds";
        System.out.println(s3.contains("bcd")); //true
        System.out.println(s3.contains("bcD"));//false

4.5.7 int indexOf(String str)

返回str在此字符串中第一次出现的索引,没有返回-1。

4.5.8 int indexOf(String str,int fromIndex)

返回str在此字符串指定索引开始的子串中第一次出现的索引,没有返回-1。

        String s3 = "dbcdds";
        System.out.println(s3.indexOf("d",4)); // 4

判断题:方法IndexOf((char ch,-1)返回字符ch在字符串中最后一次出现的位置。❌
IndexOf只会从前向后查找,不会反向查找。

4.5.9 int lastIndexOf(String str)

返回str在此字符串中最后一次出现的索引,没有返回-1。

什么时候indexOf和lastIndexOf返回值相同?
str在字符串中出现了1次或0次。

4.5.10 int lastIndexOf(String str, int fromIndex)

返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索,没有返回-1。

String s3 = "dbcdds";
System.out.println(s3.lastIndexOf("d",2)); // 0

4.5.11 String replace(char oldChar, char newChar),String replace(CharSequence target, CharSequence replacement)

字符串的替换:可以替换单个字符,也可以替换字符串。

4.5.12 String replaceAll(String regex, String replacement)

使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

将所有的数字变成逗号,然后去除首位的逗号
  String str = "12hello34world5java7891mysql456";
  为什么需要\\,因为只有一个\是把d给转义了,在前面加一个\表示将第二个\转义成一个普通的字符,所以说\\d表示的就是普通字符\d。
  System.out.println(str.replaceAll("\\d+", ",").replaceAll("^,|,$", ""));//hello,world,java,mysql
  正则表达式中"."表示所有的字符,而"\."表示英文句号,而\在java中表示转义,所以需要用\\将\进行转义。
  String str = ".12.hello34world5java7891mysql456.";
  System.out.println(str.replaceAll("\\.", ","));  //,12,hello34world5java7891mysql456,

4.5.13 String replaceFirst(String regex, String replacement)

使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

4.5.14 boolean matches(String regex)

告知此字符串是否匹配给定的正则表达式。

4.5.15 String[] split(String regex)

根据给定正则表达式的匹配拆分此字符串。

   String str = "hello|world|java";
   String[] str1 = str.split("\\|");
   for (int i = 0; i < str1.length; i++) {
       System.out.println(str1[i]);
   }
hello
world
java

4.5.16 String[] split(String regex, int limit)

根据匹配给定的正则表达式来拆分此字符串,limit影响生成的数组长度,该模式最多应用limit-1

  String str = "hello|world|java";
  String[] str1 = str.split("\\|",2);
  for (int i = 0; i < str1.length; i++) {
      System.out.println(str1[i]);
  }
hello
world|java

4.6 char[]与String之间的转换

4.6.1 String转为char[]

调用String的toCharArray()

    String s2 = "abc123";
    char[] chars1 = s2.toCharArray();

4.6.2 char[]转为String

使用String的构造器

   char[] chars3 = {'a','b','c'};
    String str = new String(chars3);
    System.out.println(str); // abc

4.7 byte[]与String之间的转换(编码/解码)

编码:String转为byte[], 调用String的getBytes() 。(看得懂 -> 看不懂)
解码:byte[]转为String,使用String的构造器。(看不懂 -> 看得懂)
注意:编码解码使用的字符集要一致,不然会发生乱码。

    public void testByte() throws UnsupportedEncodingException {
        String s1 = "abcdef中国";
//        使用getBytes()无参方法,将使用编辑器的字符集进行编码
        byte[] bytes = s1.getBytes();
//        指定字符集进行编码,解码时也要使用此字符集,不然会产生乱码
//        存在异常,需要处理(try或者throws)
        byte[] gbks = s1.getBytes("gbk");

        String s2 = new String(bytes);
        System.out.println(s2); // abcdef中国 编码解码字符集一致,不会乱码
        String s3 = new String(gbks);
        System.out.println(s3); // abcdef�й�  编码解码字符集不一致,发生乱码
        String s4 = new String(gbks,"gbk");
        System.out.println(s4); // abcdef中国 编码解码字符集一致,不会乱码

    }

4.8 练习–字符串反转

将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”

双指针同步移动
    public void testReverse() {
        String str = "abcdefg";
        char[] chars = str.toCharArray();
        int start = 0;
        int end = 6;
        for (int i = start, j = end; i < j; i++, j--) {
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
        System.out.println(chars);
    }

4.9 练习2-- 查找字符串出现次数

获取一个字符串在另一个字符串中出现的次数。
比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数

    public void testIndexOf() {
        String str = "abcabdabeabfbab";
        int count = 0;
        int index = 0;
//        index是从找到的ab索引位置+2
        while ((index = str.indexOf("ab", index)) != -1) {
            index += 2;
            count++;
        }
        System.out.println(count);
    }

4.10 练习3–获取两个字符串中最大相同子串

获取两个字符串中最大相同子串。比如:
str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”
提示:将短的那个串进行长度依次递减的子串与较长的串比较。

  1. 只存在一个最大相同子串
    public String findSame(String str1, String str2) {
        if (str1 == null || str2 == null)
            return null;
        if (str1.length() == 0 || str2.length() == 0)
            return "";
        String minStr = str1.length() >= str2.length() ? str2 : str1;
        String maxStr = str1.length() < str2.length() ? str2 : str1;
        for (int i = 0; i < minStr.length(); i++) {
            for (int start = 0, end = minStr.length() - i; end <= minStr.length(); start++, end++) {
                String str = minStr.substring(start,end);
                if(maxStr.contains(str)){
                    return str;
                }
            }
        }
        return "";
    }
  1. 如果存在多个长度相同的最大相同子串。 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
    public String[] findSame1(String str1, String str2) {
        if (str1 == null || str2 == null)
            return null;
        if (str1.length() == 0 || str2.length() == 0)
            return new String[]{};
        String minStr = str1.length() >= str2.length() ? str2 : str1;
        String maxStr = str1.length() < str2.length() ? str2 : str1;
        StringBuilder s = new StringBuilder();
        // 每一轮减少一个字母,内层循环判断所有的组合是否符合题意
        for (int i = 0; i < minStr.length(); i++) {
            for (int start = 0, end = minStr.length() - i; end <= minStr.length(); start++, end++) {
                String str = minStr.substring(start, end);
                if (maxStr.contains(str)) {
                    s.append(str + ",");
                }
            }
       // 如果存在多个长度相同的最大相同子串,内层for循环可以全部找出来,找出来之后就不用再进行下一轮外层循环了
            if (s.length() > 0)
                break;
        }
        String[] split = s.delete(s.length() - 1, s.length()).toString().split(",");
        return split;
    }

4.11 String、StringBuilder、StringBuffer异同

4.11.1 相同点

底层都是使用字符数组存储

4.11.2 不同点

  1. String是不可变的字符序列;final char value[];
  2. StringBuffer是可变字符序列,线程安全但是效率低;char[] value;
  3. StringBuilder是可变字符序列,jdk1.5新增,线程不安全但效率高;char[] value;
  4. StringBuilder和StringBuffer的最大区别就是线程是否安全;

4.11.3 可变字符序列和不可变字符序列

可变字符序列:修改字符串的内容后不会产生新的对象;
不可变字符序列:修改字符串的内容后会产生新的对象;

4.11.4 三者效率比较

StringBuilder(线程不安全)>StringBuffer(线程安全)>String

4.12 StringBuffer源码分析(StringBuilder类似)

4.12.1 创建对象

//        方式1:底层创建了长度为16的char[]
//        char[] value = new char[16];
        StringBuffer stringBuffer = new StringBuffer();
        System.out.println(stringBuffer.length());
        stringBuffer.append('a'); // value[0] = 'a'
        stringBuffer.append('b'); // value[1] = 'b'
        
//      方式2 底层:char[] value = new char["abc".length + 16];
        StringBuffer stringBuffer1 = new StringBuffer("abc");
        System.out.println(stringBuffer1.length());
        
//      方式3:char[] value = new char[30];
        StringBuffer stringBuffer2 = new StringBuffer(30);

4.12.2 如何实现扩容

如果数组容量满了,会创建一个新的数组,长度一般为原来数组长度的2倍+2,并且将原来数组中的内容复制到新数组中。

4.12.3 使用建议

开发中如果需要频繁对字符串进行添加、修改,那么建议使用StringBuffer(int capacity)和StringBuilder(int capacity),在创建对象时指定容量,避免后面扩容导致效率低下。

4.13 StringBuffer/StringBuilder类的常用方法

方法链的原理:这些方法虽然都有返回值,但是返回值都是当前对象本身,也就意味着调用这些方法会改变当前对象。

 * StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
 * StringBuffer delete(int start,int end):删除指定位置的内容
 * StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
 * StringBuffer insert(int offset, xxx):在指定位置插入xxx
 * StringBuffer reverse() :把当前字符序列逆转
 * public int indexOf(String str)
 * public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
 * public int length()
 * public char charAt(int n )
 * public void setCharAt(int n ,char ch)
      StringBuffer stringBuffer2 = new StringBuffer(30);
        stringBuffer2.append(-1); // 添加元素
        stringBuffer2.append(2);
        stringBuffer2.append(3);
        stringBuffer2.append(4);
        System.out.println(stringBuffer2);
        stringBuffer2.delete(2,4);  // 删除[2,4)之间的元素
        stringBuffer2.replace(2,4,"hello"); // 将[2,4)之间的元素替换成hello
        stringBuffer2.reverse(); // 反转字符串
        stringBuffer2.insert(2,"c"); // 在索引为2的位置插入c,其他元素向后移
        System.out.println(stringBuffer2);
		stringBuffer2.setCharAt(1,'a'); // 只能替换指定索引处的单个字符

4.13 面试题

        String str = null;
        StringBuffer sb = new StringBuffer();
        sb.append(str);
        System.out.println(sb.length());// 4
        System.out.println(sb);// null
        StringBuffer sb1 = new StringBuffer(str);
        System.out.println(sb1);// java.lang.NullPointerException

5.JDK8之前日期时间API

5.1 System类

System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
时间戳可以用来充当订单号。

5.2 Date类

5.2.1 java.util.Date

两种构造器:
new Date(); 使用无参构造器创建的对象可以获取本地当前时间。
new Date(long l);自定义毫秒时刻。

方法:
toString():输出具体的年月日时分秒
getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数(无参构造器表示到本地当前时间为止的毫秒数,有参构造器表示传入的参数)

5.2.2 java.sql.Date(util.Date的子类)

构造器:
new Date(long l); 自定义毫秒时刻。

方法:
toString(): 输出年月日

5.2.3 两种Date转换

方法1:多态,向下转型

  Date d  = new java.sql.Date(1231221321l);
  java.sql.Date date2 = (java.sql.Date)d;

方法2:构造器传入

Date date = new Date(1234211545142l);
java.sql.Date date1 = new java.sql.Date(date.getTime());

5.3 SimpleDateFormat

5.3.1 作用

因为Date类的API不易于国际化,大部分被废弃了,所以使用SimpleDateFormat对Date类进行格式化和解析。
格式化: Date -> String public String format(Date date)
解析:String -> Date public Date parse(String source)

5.3.2 空参构造器

空参输出默认格式

		Date date3 = new Date();
 		SimpleDateFormat sdf = new SimpleDateFormat();
//        格式化:Date -> String
        String format = sdf.format(date3);
        System.out.println(format);  //22-2-7 下午12:46
//        解析:String -> Date
        try {
            Date d = sdf.parse("22-2-7 下午12:42");
            System.out.println(d); // Mon Feb 07 12:42:00 CST 2022
        } catch (ParseException e) {
            e.printStackTrace();
        }

5.3.3 带参构造器

开发中一般使用这种方式,指定格式化和解析的格式。对字符串进行解析时,参数格式和字符串格式一定要相匹配。

y
M
d
H
m
s
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format1 = sdf1.format(date3);
        System.out.println(format1); // 2022-02-07 12:46:51

        try {
            Date d = sdf1.parse(format1);
            System.out.println(d); // Mon Feb 07 12:46:51 CST 2022
        } catch (ParseException e) {
            e.printStackTrace();
        }

5.3.4 练习:将字符串转为java.sql.Date

方式1:

        String str = "2021-09-08";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 参数的格式要与解析的字符串格式相同
        try {
            Date d = sdf.parse(str);
            java.sql.Date date = new java.sql.Date(d.getTime());
            System.out.println(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }

方式2:

        String str = "2018-01-10";
        java.sql.Date date = java.sql.Date.valueOf(str);
        System.out.println(date);

5.3.5 练习:获取一个人的出生天数

    public void test123() {
        String date1 = "1990-01-01";
        Date d1 = getDate(date1); // 把字符串转为Date
        Date d2 = new Date(); // 获取当前日期
        long l = d2.getTime() - d1.getTime();
        long l1 = l / (24 * 60 * 60 * 1000) + 1;
        System.out.println(l1);
    }
    
    public Date getDate(String str) {
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
      Date d = null;
      try {
          d = sdf.parse(str);
      } catch (ParseException e) {
          e.printStackTrace();
      }
      return d;
    }

5.4 Calendar类

Calendar对象具有可变性,具体表现在可以通过set方法直接更改自身的值

5.4.1 创建对象

方式1:调用静态方法getInstance(); Calendar instance = Calendar.getInstance();
方式2:通过子类GregorianCalendar构造器创建对象;

5.4.2 get(int field)

用于获取年份、月份、星期、某年的第几天、某星期的第几天等等。

5.4.3 public void set(int field,int value)

将当前Calendar 对象中的字段设置为给定值。

Calendar instance = Calendar.getInstance();
System.out.println(instance.get(Calendar.YEAR)); // 2022
instance.set(Calendar.YEAR,2011);
System.out.println(instance.get(Calendar.YEAR)); // 2011

5.4.4 public void add(int field,int amount)

在当前基础上进行偏移

        instance.add(Calendar.YEAR,10); // +10年
        System.out.println(instance.get(Calendar.YEAR)); // 2032
        instance.add(Calendar.YEAR,-10); // -10年
        System.out.println(instance.get(Calendar.YEAR)); // 2022

5.4.5 public final Date getTime()

将Calendar对象转为Date对象

Date time = instance.getTime();
System.out.println(time);

5.4.6 public final void setTime(Date date)

将Date对象转为Calendar对象,可以用于获取Date中的年月日

  Date d = new Date();
  instance.setTime(d);
  System.out.println(instance.get(Calendar.YEAR)); // 2022

6.JDK8中新日期时间API

6.1 java.time

6.1.1 几个常用的类(使用和Calendar类似)

LocalDate:本地日期
LocalTime:本地时间
LocalDateTime:本地日期时间(使用频率最高)

6.1.2 now()方法

静态方法,根据当地时间创建对象。

     LocalDateTime now = LocalDateTime.now();
     System.out.println(now); // 2022-02-07T16:17:33.847

6.1.3 of()方法

静态方法,根据指定日期/时间创建对象

   LocalDateTime of = LocalDateTime.of(2010, 10, 31, 10, 10, 10);
   System.out.println(of); // 2010-10-31T10:10:10

6.1.4 getXxx()方法

获取年月日时分秒

        System.out.println(now.getDayOfMonth()); // 7 获得月份天数(1-31)
        System.out.println(now.getDayOfYear()); // 38 获得年份天数(1-366)
        System.out.println(now.getDayOfWeek()); // MONDAY 获得星期几(返回一个 DayOfWeek 枚举值)
        System.out.println(now.getMonth()); // FEBRUARY 获取月份
        System.out.println(now.getMonthValue()); // 2  获取本年的第几个月
        System.out.println(now.getHour()); //获得当前对象对应的小时
        System.out.println(now.getMinute()); // 分钟
        System.out.println(now.getSecond()); // 秒

6.1.5 withXxx()方法

设置年月日时分秒,是不可变的,即调用方法后会返回一个新的变量,原来的值不会改变。
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()

 	LocalDateTime now = LocalDateTime.now();
   LocalDateTime localDateTime = now.withDayOfMonth(28);
   System.out.println(localDateTime.getDayOfMonth()); // 28
   System.out.println(now.getDayOfMonth()); // 7

6.1.6 plusXxx()方法

向当前对象添加几天、几周、几个月、几年、几小时,是不可变的。
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()

        LocalDateTime localDateTime1 = now.plusDays(5);
        System.out.println(localDateTime1.getDayOfMonth());

6.1.7 minusXxx()方法

向当前对象减少几天、几周、几个月、几年、几小时,是不可变的。

        LocalDateTime localDateTime2 = now.minusDays(6);
        System.out.println(localDateTime2.getDayOfMonth());

6.2 Instant类

6.2.1 作用

与java.lang.Date类似,用于获取毫秒数或通过毫秒数获取对象

6.2.2 实例

//        now:获取本初子午线的时间
        Instant i = Instant.now();
        System.out.println(i);
//        atOffset 添加时间偏移量,获取本地时间
        OffsetDateTime offsetDateTime = i.atOffset(ZoneOffset.ofHours(8));
        System.out.println(offsetDateTime);

//        toEpochMilli 获取时间戳,相当于Date类的getTime()方法
        long l = i.toEpochMilli();
        System.out.println(l);
//      ofEpochMilli 通过给定毫秒数获取Instant对象,相当于Date类的有参构造器new Date(long millis)
        Instant instant = i.ofEpochMilli(1644282265743l);
        System.out.println(instant);

6.3 DateTimeFormatter

6.3.1 作用

和SimpleDateFormat类似,对时间进行解析和格式化。

6.3.2 实例

方式3:最重要的一种方法
//        通过静态方法ofPattern创建DateTimeFormatter对象,相当于SimpleDateFormat的有参构造器
        DateTimeFormatter d = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//        格式化
        String format = d.format(LocalDateTime.now());
        System.out.println(format);

//        解析
        TemporalAccessor d1= d.parse(format);
        System.out.println(d1);
  //方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        //格式化:日期-->字符串
        LocalDateTime localDateTime = LocalDateTime.now();
        String str1 = formatter.format(localDateTime);
        System.out.println(localDateTime);
        System.out.println(str1);//2020-05-10T18:26:40.234

        //解析:字符串 -->日期
        TemporalAccessor parse = formatter.parse("2020-05-10T18:26:40.234");
        System.out.println(parse);

        //方式二:
        //本地化相关的格式。如:ofLocalizedDateTime()
        //FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        //格式化
        String str2 = formatter1.format(localDateTime);
        System.out.println(str2);//2020年5月10日 下午06时26分40秒

        //本地化相关的格式。如:ofLocalizedDate()
        //FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
        DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
        //格式化
        String str3 = formatter2.format(LocalDate.now());
        System.out.println(str3);//2020-5-10

6.4 与传统日期处理的转换

java学习笔记3 -- 异常、多线程、字符串、常用类_第7张图片

你可能感兴趣的:(java,java,开发语言,后端)