Java高级

1.多线程

1.1 基本概念:程序、进程、线程

程序:一段静态的代码
进程:正在运行的程序,是资源分配的基本单位
线程:是CPU执行和调度的最小单位,每个线程拥有独立的运行栈和程序计数器pc,线程切换的开销比较小。多个线程共享一个进程的资源(共享进程的方法区和堆,但存在安全隐患)。

在这里插入图片描述
在Java的内存区域中,栈区(虚拟机栈)和程序计数器是每个线程一份方法区和堆区,是每个进程一份,各个线程共享

使用多线程的优点
1.资源利用率提升,程序处理效率提高
2.改善程序结构,代码会相对简单
3.软件运行速度提升,提升应用程序的响应速度

何时需要多线程
1.程序需要同时执行两个或多个任务
2.程序需要实现一些需要等待的任务,如用户输入、文件读写操作、网络操作、搜索等
3.需要一些后台运行的程序时

Java程序至少有3个线程
1.主线程
2.垃圾回收线程
3.异常处理线程(会影响主线程)

1.2 线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现

Thread类的特性
1.每个线程都是通过特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
2.通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
3.Thread类实现了Runnable接口

start方法作用
1.启动当前线程
2.调用当前线程的run方法

注意
1.不能通过调用run方法来启动线程(仍然在主线程中执行)
2.额外再启动一个线程,需要重新创建一个线程的对象

两种创建线程的方法
1.继承Thread类,重写run方法
2.实现Runnable接口,实现run方法,在Thread构造器中传入一个Runnable接口实现对象

两种创建线程方法的比较
开发中,优先选择Runnable接口的方法
1.实现的方式没有类单继承性的限制
2.实现的方式更适合来处理多个线程共享数据的情况

注意,这里ticket没有加static,就实现了三个线程共享ticket

class Sell implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "卖" + ticket + "号票");
            ticket--;
        }
    }
}
/*
 * 死锁的四个必要条件
 * 1.互斥访问:共享资源互斥访问
 * 2.请求并保持:已经持有资源的情况下,申请别的资源未果,也不释放自己持有的资源
 * 3.不可剥夺:进程申请的资源除了完成任务释放,别人无法将其释放
 * 4.循环等待:系统中存在资源循环等待的链条*/

public class Demo {
    public static void main(String[] args) {
        Sell sell = new Sell();
        new Thread(sell).start();
        new Thread(sell).start();
        new Thread(sell).start();
    }

Thread类常用方法
1.void start():启动当前线程;调用当前线程的run()
2.run():通常需要重写Thread类中的此方法,将创建的线程需要执行的操作声明在此方法中
3.String getName()
4.void setName()
5.static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类。
6.yield():释放cpu的执行权(有可能紧接着又获取了cpu的执行权)
7.h.join():在线程a中执行h.join(),会阻塞当前线程a,直到h线程执行完,线程a才结束阻塞状态
8.stop():强制线程生命周期结束,deprecated 不推荐使用,因为不安全,过时的api
9.static void sleep(long millis):睡眠(阻塞)若干毫秒;静态方法,可以直接调用。
注意:如果是在run方法中使用sleep,则处理sleep的异常只能try-catch,无法throws;因为是重写Thread的run方法,其并无异常抛出,故子类重写也无法抛出异常。
10.bool isAlive():判断当前线程是否存活(是否执行完了),阻塞状态下返回值也是true。

获取线程名称
Thread.currentThread().getName();

更改线程名称
1.通过Thread.currentThread().setName();
2.通过构造器

class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }

    public static void main(String[] args) {
        Thread t1=new MyThread("一号线程");
    }
}

使用匿名对象执行开启线程

new Thread(){
     private  int count=0;
     @Override
     public void run(){
         while(true)
             System.out.println(count++);
     }
 }.start();

1.3 线程的调度

  • Java的调度方法
    1.同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    2.对于高优先级,使用优先调度的抢占式策略

  • 线程的优先级
    1.线程的优先级等级:MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5
    2.获取和设置当前线程的优先级:getPriority(),setPriority(int p)

注意:并不是优先级高的执行完再执行优先级低的,而是优先级高的获取CPU的概率更高

1.4 线程的分类

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。

Thread daemonTread = new Thread();
 
  // 设定 daemonThread 为 守护线程,default false(非守护线程)
 daemonThread.setDaemon(true);
 
 // 验证当前线程是否为守护线程,返回 true 则为守护线程
 daemonThread.isDaemon();

1.5 线程的生命周期

在这里插入图片描述

1.6 线程的同步

线程同步,主要用来解决线程的安全问题。

例:创建三个线程卖票,总共100张
1.问题:卖票过程中,出现了重票、错票(0,-1号票)–>出现了线程的安全问题
2.问题出现的原因:某线程操作车票时,别的线程也参与进来
3.如何解决:某线程操作车票时,即使出现阻塞(例如sleep(100)😉,别人也不能参与进来。
4.在Java中,我们通过同步机制,来解决线程的安全问题

解决方式

  1. 同步代码块
    a.同步监视器(又叫锁):任何一个对象都可以作为锁;要求多个线程必须要共用同一把锁。
    b.同步原理:当一个线程执行代码碰到一个锁时,如果已经有别的线程使用了锁并且没有归还,那么本线程就阻塞在这里。

继承Thread方式

public class p1 {
    public static void main(String[] args) {
        Sell s1 = new Sell();
        Sell s2 = new Sell();
        Sell s3 = new Sell();
        s1.start();
        s2.start();
        s3.start();
    }
}

class Sell extends Thread {
    public static int ticket = 10000;
    @Override
    public void run() {
        while (true) {
            synchronized(Sell.class) {
                if (ticket > 0) {
                    System.out.println(getName() + "出售第" + ticket + "张票");
                    ticket--;
                } else
                    break;
            }
        }
    }
}

这里的Sell.class充分说明了,在Java中类也是对象

实现Runnable接口方式

public class p2 {
    public static void main(String[] args) {
        Sell2 sell=new Sell2();
        Thread t1=new Thread(sell);
        Thread t2=new Thread(sell);
        Thread t3=new Thread(sell);
        t1.start();
        t2.start();
        t3.start();
    }
}

class Sell2 implements Runnable {
    private int ticket=10000;
    @Override
    public void run() {
        while(true){
            synchronized(this){
                if(ticket>0)
                    System.out.println(Thread.currentThread().getName()+"出售"+ticket--+"号票");
                else
                    break;
            }
        }
    }
}
  1. 同步方法
    如果操作共享数据的代码完整地声明在一个方法中,我们不妨将此方法声明为同步的。

实现Runnable接口方式

public class p3 {
    public static void main(String[] args) {
        Runnable r = new Window();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window implements Runnable {
    private int ticket = 10000;

    @Override
    public void run() {
        while (ticket > 0) {
            sell();
        }
    }

    private synchronized void sell() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "出售" + ticket-- + "号票");
        }
    }
}

继承Thread方式,注意这里的sell方法必须要加static,否则会出现安全问题(不加static,同步方法所用的锁,不是同一个)

同步方法不加static,使用的是this作为锁。加了static,使用的是类名.class作为锁

public class p4 {
    public static void main(String[] args) {
        Window4 w1=new Window4();
        Window4 w2=new Window4();
        Window4 w3=new Window4();
        w1.start();
        w2.start();
        w3.start();
    }
}


class Window4 extends Thread{
    private static int ticket=10000;
    @Override
    public void run(){
        while(ticket>0)
            sell();
    }

    private static synchronized void sell(){
        if(ticket>0)
            System.out.println(Thread.currentThread().getName()+"出售"+ticket--+"号票");
    }

}
  1. 使用同步锁
public class Main {
    public static void main(String[] args) {
        Runnable r = new Window();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window implements Runnable {
    private int ticket = 10000;
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket > 0)
                    System.out.println(Thread.currentThread().getName() + "出售" + ticket-- + "号票");
                else
                    break;
            } finally {
                /*保证无论发生什么特殊情况,一定能够释放锁*/
                lock.unlock();
            }
        }
    }
}

可重入锁 ReentrantLock

一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;如果有两个不同的方法用到同一个锁,其中方法A里面又调用了方法B,那就不会死锁了

面试题:synchronized和Lock的区别?
synchronized在执行完同步代码以后,自动释放同步监视器
Lock方式手动加锁和释放锁,更加灵活。

1.7 改造懒汉单例模式使之线程安全

注意,写了两次判断instance==null,这样效率最高。只在首次访问的时候进行同步,后续已经有instance的情况下无需同步,直接返回instance,最大化效率。

//单例模式之懒汉式

class Tool {
    private static Tool instance = null;

    //私有化构造方法
    private Tool() {

    }

    public static Tool getInstance() {
        if (instance == null) {
            synchronized (Tool.class) {
                if (instance == null)
                    instance = new Tool();
            }
        }
        return instance;
    }
}

1.8 线程的死锁问题

死锁
1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
(操作系统中学到的定义:多个进程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去,这些永远都在互相等待的进程,称为死锁进程)

解决方法
1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步

死锁发生的四个必要条件
1.互斥:共享资源互斥访问
2.请求并保持:进程请求资源未果,不会释放自身已经占有的资源
3.不可剥夺:只有进程自身使用完资源后才会释放,别人无法将其释放
4.循环等待:多个进程之间存在资源请求的环路

预防死锁
1.破坏请求并保持条件:一次性请求所有需要的资源,否则就不申请
2.破坏不可剥夺条件:请求资源未果,释放自身占有的资源
3.破坏循环等待条件:可用资源线性排序,只能从低到高申请资源

避免死锁
银行家算法:已分配资源表、可用资源表、所需资源表;每次分配给一个能够满足其需求的进程,该进程执行完后返还所有的资源,不断循环。

在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。

预防死锁和避免死锁的区别
预防死锁是通过破坏死锁发生的必要条件中的一个,限制条件比较严格,可能造成系统资源利用率和系统吞吐率的下降。
避免死锁施加的条件比较宽松,在资源的动态分配中,避免系统出现不安全状态

1.9 线程的通信

例题:使用两个线程打印1~100。线程1,线程2交替打印

public class Main {
    public static void main(String[] args) {
        Runnable r = new Count();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
}

class Count implements Runnable {
    private int num = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();	//或者this.notify,  如果用的是某个obj,则obj.notify
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "数" + num--);
                } else
                    break;
                try {
                    wait(); /*自我阻塞,wait会自动释放锁(与sleep的区别)*/
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

涉及到的三个方法
1.wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器
2.notify():一旦执行此方法。就会唤醒被wait的一个线程。如果有多个,则唤醒优先级最高的
3.notifyAll():唤醒所有wait的线程

说明
1.wait(), notify(), notifyAll() 三个方法必须使用在同步代码块或者同步方法中
2.三者的调用者必须是同步代码块或同步方法中的同步监视器,否则会报异常,不加调用者默认是this.
3.三者是定义在java.lang.Object类中的

上述例题也可以使用公平锁来实现(存在一个等待队列,先进先出),注意要使用finally来保证unlock的执行

public class Main {
    public static void main(String[] args) {
        Runnable r=new Count();
        Thread t1=new Thread(r);
        Thread t2=new Thread(r);

        t1.start();
        t2.start();
    }
}

class Count implements Runnable {
    private int num = 100;
    ReentrantLock lock = new ReentrantLock(true);/*fair lock*/

    @Override
    public void run() {
        while(true){
            try{
                lock.lock();
                if(num>0)
                    System.out.println(Thread.currentThread().getName()+":"+num--);
                else
                    break;
            }
            finally {
                lock.unlock();
            }
        }
    }
}

生产者消费者问题
在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        new Thread(new Consumer(clerk)).start();
        new Thread(new Consumer(clerk)).start();
        new Thread(new Producer(clerk)).start();
    }
}

class Clerk {
    private int products = 0;

    public synchronized void produce() {
        //notity(); 也可以写这里
        if (products < 20) {
            System.out.println("生产者生产第" + (++products) + "个商品");
            this.notify();
        } else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consume() {
        if (products > 0) {
            System.out.println("消费者消费第" + products-- + "个商品");
            if (products < 20)
                this.notify();
        } else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable {
    private Clerk clerk;

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

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

class Consumer implements Runnable {
    private Clerk clerk;

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

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

这里有个疑问:如果把sleep写在两个同步方法中,while中只有调用,则会出现每次生产者生产满20个消费者才去消费,每次消费者消费完了生产者才开始生产。即每轮while循环结束同步方法都不释放锁。(在while中随意加入一个语句如print,则此现象消失,正常交替执行)

1.10 JDK5.0新增线程创建方式

新增方式一:实现Callable接口
与Runnable相比,Callable功能更强大
1.相比run()方法,可以有返回值
2.方法可以抛出异常(重写Runnable中的run不能抛出异常,因为Runnable接口中的run没有抛 )
3.支持泛型的返回值
4.需要借助FutureTask类,比如获取返回值

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

public class Demo {
    public static void main(String[] args) {
        Count count = new Count();
        FutureTask futureTask = new FutureTask(count);
        new Thread(futureTask).start();
        Object ans = null;
        try {
            ans = futureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(ans);
    }
}

class Count implements Callable {
    @Override
    public Integer call() {
        int i = 100000000;
        int sum = 0;
        while (i-- > 0)
            sum += i;
        return sum;
    }
}

使用泛型

public class Demo {
    public static void main(String[] args) {
        Count count = new Count();
        FutureTask<Integer> futureTask = new FutureTask<>(count);
        new Thread(futureTask).start();
        int ans = 0;
        try {
            ans = futureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(ans);
    }
}

class Count implements Callable<Integer> {
    @Override
    public Integer call() {
        int i = 100000000;
        int sum = 0;
        while (i-- > 0)
            sum += i;
        return sum;
    }
}

新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
3.便于线程管理:
corePoolSize核心池的大小
maximumPoolSize最大线程数
keepAliveTime 线程没有任务时最多保持多长时间后会终止

线程池相关API
1.JDK5.0起提供了线程池相关API:ExecutorServiceExecutors
2.ExecutorService 真正的线程池接口.常见子类ThreadPoolExecutor

  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • <T>Future<T>submit(Callable<T>task):执行任务,有返回值,一般用来执行Callable
  • void shutdown():关闭连接池

3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可以安排在给定延迟后运行命令或者定期地执行
public class Main {
    public static void main(String[] args) {
        /*Executors是线程池的工厂类*/
        ExecutorService server= Executors.newFixedThreadPool(10);
        server.execute(new Count());
        server.submit(new Calculate());
        /*或者使用FutureTask包装Calculate()以获取线程返回值*/
        FutureTask<Integer> task = new FutureTask<>(new Calculate());
        server.submit(task);
        try {
            System.out.println(task.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Count implements Runnable{
    @Override
    public void run() {
        int i=0;
        while(i++<100)
            System.out.print(i+" ");
    }
}

class Calculate implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum=0;
        for(int i=0;i<100;i++)
            sum+=i;
        return sum;
    }
}


练习题

  1. 谈谈你对程序、进程、线程的理解
    程序:一段静态代码
    进程:关于进程我想谈两点,一个是为什么需要进程这个概念,另一个是进程的作用。首先,现代操作系统的一大特点就是多道程序设计,那么在此之前是单道程序设计,一个程序运行时占有所有的资源,那么就无需资源的分配和调度,也就无需进程这个概念。到了多道程序设计中,多个程序并发执行,这就涉及到了系统资源的分配和调度,诸如CPU资源、存储资源、IO资源、文件资源,那么进程这个概念就应运而生。因此进程的作用有两大点,第一,它是资源分配和调度的基本单位。第二,它是程序独立运行的载体,保障程序正常执行。
    线程:线程是CPU调度的基本单位,多个线程之间共享进程的资源;线程包含在进程之中,是进程中实际运行工作的单位。

操作系统四大特性:虚拟、异步、共享、并发。

  1. 对比两种线程的创建方式
  2. sleep和wait方法的异同?
    相同点:都能使当前线程阻塞
    不同点:1.sleep声明在Thread类中,wait声明在Object类中 2.sleep()可以在任何地方使用,wait()需要在同步方法或者同步代码块中调用 3.如果都使用在同步代码块或者同步方法中,sleep方法不会释放锁,wait会释放锁

2.常用类

2.1 字符串相关的类

String类

String特性
1.String类:代表字符串.Java程序中,所有字符串的字面值(如"abc")都是String类的实例
2.String是一个final类,不可以继承
3.字符串是常量,值在创建之后不可改变,字符内容是存储在一个字符数组value[]中的
4.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
5.字符串常量池(位于方法区中,后来改叫meta space)中是不会存储相同内容的字符串的
在这里插入图片描述
String对象的创建
1.字面量的方式,String str="abc";
2.String s=new String()
3.String s=new String(String s2)
4.String s=new String(char[] arr)
5.String s=new String(char[] arr,int startIndex, int count)

字符串常量池存储在字符串常量池,目的是共享; 字符串非常量的对象存储在堆中

面试题1:下面的p1.name==p2.name?

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("yfx");
        Person p2 = new Person("yfx");
        System.out.println(p1.name == p2.name); //true 地址相同
    }
}

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }
}

面试题2:String s=new String("abc"); 在内存中创建了几个对象? 答:如果常量池中没有abc,则创建了两个,有的话则创建一个(现在常量池创建"abc",然后在堆中创建一个String对象,内容fianl char[]value指向常量池的"abc")

面试题3:下面几种字符串连接后,在内存中实际是怎么样的?

public static void main(String[] args) {
    String s1 = "java", s2 = "docker";

    String s3="javadocker";
    String s4="java"+"docker";	//编译阶段会直接合成"javadocker"
    String s5=s1+"docker";
    String s6="java"+s2;

    System.out.println(s3==s4); //true
    System.out.println(s3==s5); //false
    System.out.println(s3==s6); //false
    System.out.println(s5==s6); //false

	String s7=s5.intern();	
//intern():如果常量池中没有s5的字符串值,那么就在常量池中创建该字符串常量,并返回其引用;如果常量池中已经有,则直接返回引用
    System.out.println(s3==s7); //true
    
    final String s8="java";	//final修饰的变成常量
    String s9=s8+"docker";
    System.out.println(s3==s9); //true
}

涉及到对象的连接,则都在堆中产生新的String对象;

面试题4:java的值传递

public class Main {
    String str = new String("good");

    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        str = "test ok";	//将常量池中"test ok"的地址赋值给str
        ch[0] = 'b';
    }

    @Test
    public void test() {
        Main obj = new Main();
        obj.change(obj.str, obj.ch);
        System.out.println(obj.str);    //good
        System.out.println(obj.ch);     //best
    }
}

String类常用方法

1.int length()
2.char charAt(int index)
3.boolean isEmpty()
4.String toLowerCase()
5.String toUpperCase()
6.String trim():返回字符串的副本,忽略前导空白和尾部空白
7.boolean equalsIgnoreCase(String s)
8.String concat(String str):将指定字符串连接到此字符串的结尾,等价于+
9.int compareTo(String s):比较两个字符串字典序
10.String substring(int beginIndex):
11.String substring(int beginIndex, int endIndex):按照下标截取字符串,左闭右开
12.boolean endsWith(String suffix):是否以指定的后缀结束
13.boolean startsWith(String prefix):是否以指定的前缀开始
14.boolean startsWith(String prefix,int offset):测试此字符串从指定索引开始的子串是否以指定前缀开始

15.boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
16.int indexOf(String str):返回子串第一次出现的索引,未找到返回-1
17.int indexOf(String s,int fromIndex):返回子串从偏移下标开始首次出现的索引
18.int lastIndexOf(String s):反向搜索
19.int lastIndexOf(String s,int fromIndex),从指定偏移量开始反向搜索

20.String replace(char oldChar,char newChar):替换所有指定字符
21.String replace(CharSequence target, CharSequence replacement):替换所有指定子串
22.String replaceAll(String regex, String replacement):使用给定的子串替换所有匹配正则表达式的子串
23.String replaceFirst(String regex, String replacement):

24.boolean matches(String regex):告知此字符串是否匹配给定的正则表达式

25.String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
26.String[] split(String regex, int limit):根据给定正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部放到最后一个元素中

String与基本数据类型转换

1.String转基本数据类型、包装类: 调用包装类的静态方法:parseXXX(str)
2.基本数据类型、包装类转Stirng:String.valueOf(xxx)
3.String转char[]:str.toCharArray()
4.char[]转String:new String(char[] arr)
5.String转byte[]:str.getBytes()
6.byte[]转String:new String(bytes[] arr) 重载函数new String(bytes[]arr,字符集)

public class Main {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s="abc123中国";
        byte[] bytes = s.getBytes();//使用默认的字符集
        System.out.println(Arrays.toString(bytes));
        //[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]  utf-8编码一个汉字3个字节
        byte[] gbks = s.getBytes("gbk");
        System.out.println(Arrays.toString(gbks));
        //[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]       gbk编码一个汉字2个字节

        System.out.println(new String(bytes));
        //abc123中国
        System.out.println(new String(gbks));
        //abc123�й�  字符集指定错误,出现乱码
        System.out.println(new String(gbks,"gbk"));
        //abc123中国
    }
}

StringBuffer,StringBuilder

String:
1.不可变字符序列

StringBuffer:
1.可变字符序列
2.线程安全,效率低 (除了构造方法,所有方法都加了synchronized)

StringBuilder:
1.可变字符序列
2.线程不安全,效率高

三者底层都是用char[]存储

StringBuffer和StringBuilder的扩容
默认情况下,扩容为原来的2倍+2,同时将原有数组中的元素复制到新的数组中
指导意义: 如果需要对字符串进行频繁的添加, 建议使用此构造器StringBuffer(int capacity)StringBuilder(int capacity),提前指定容量,以免频繁扩容,降低效率

三者效率对比

    @Test
    public void testTimeConsume() {
        String s = "";
        StringBuffer buffer = new StringBuffer();
        StringBuilder builder = new StringBuilder();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            buffer.append(i);
        }
        System.out.println("StringBuffer use " + (System.currentTimeMillis() - startTime) + " ms");

        for (int i = 0; i < 100000; i++) {
            builder.append(i);
        }
        System.out.println("StringBuilder use " + (System.currentTimeMillis() - startTime) + " ms");

        for (int i = 0; i < 100000; i++) {
            s += i;
        }
        System.out.println("String use " + (System.currentTimeMillis() - startTime) + " ms");
    }

在这里插入图片描述

2.2 JDK8之前的时间API

System静态方法

System.currentTimeMillis(): 返回1970年1月1日0点到现在经历了多少毫秒,返回值是long类型

Date类

有两个Date类
java.util.Date和java.sql.Date(继承前边的类)

java.util.Date
1.构造器一:Date() 创建了一个对应当前时间的Date对象

2.构造器二Date(long time) 创建指定毫秒数的Date类

java.sql.Date
对应着数据库中的日期

public class Main {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date);
        //Wed Aug 25 10:49:42 CST 2021
        System.out.println(date.getTime());
        //1629859782884
        Date date2=new Date(1629859782884L);
        System.out.println(date2);
        //Wed Aug 25 10:49:42 CST 2021
        Date date3=new java.sql.Date(2131243242334L);
        System.out.println(date3);
        //2037-07-15
        /*java.util.Date转换为java.sql.Date*/
        //情形一 多态,对象本身就是sql下的Date
        Date date4=new java.sql.Date(2315432424345L);
        java.sql.Date date5= (java.sql.Date) date4;
        //情形二 对象本身就是util下的Date,无法直接强制转换.但是他们拥有一个共同的东西,就是毫秒数
        Date date6=new Date();
        java.sql.Date date7=new java.sql.Date(date6.getTime());
    }
}

Calendar类

Calendar是一个抽象基类,主要用于完成日期字段之间相互操作的功能

获取Calendar实例的方法
1.使用Calendar.getInstance()方法 (抽象类无法实例化,这里返回的是子类GregorianCalendar的对象,多态,用Calendar接收)
2.调用它的子类GregorianCalendar的构造器

一个Canlendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息.比如YEAR, MONTH, DAY_OF_WEEK, HOUR_OF_DAY,MINUTE, SECOND
1.public void set(int field, int value)
2.public void add(int field, int amount)
3.public final Date getTime()
4.public final void setTime(Date date)

注意
1.获取月份时:一月是0, 二月是1, 以此类推, 12月是11
2.获取星期时,周日是1,周二是2,…,周六是7

public class Main {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance(); //多态
        System.out.println(calendar.getClass());    //java.util.GregorianCalendar

        //get()
        System.out.println(calendar.get(Calendar.YEAR));
        System.out.println(calendar.get(Calendar.MONTH) + 1);
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
        System.out.println(calendar.get(Calendar.DAY_OF_YEAR));

        //add()
        calendar.add(Calendar.DAY_OF_MONTH, -10);   //往前倒10个月

        //getTime()
        Date date=calendar.getTime();

        //setTime()
        calendar.setTime(date); //用date来设置calendar
        calendar.setTimeInMillis(23214325798L); //用毫秒数设置calendar

    }
}

SimpleDateFormat类

用于对Date类的格式化和解析

Date类的API不易于国际化,大部分都被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类

它允许进行格式化: 日期->文本, 解析: 文本->日期

1.格式化
SimpleDateFormat(): 默认的模式和语言环境创建对象
public SimpleDateFormat(String pattern) 该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用
public String format(Date date): 方法格式化时间对象date

2.解析
public Date parse(String source): 从给定字符串的开始解析文本,以生成一个日期

public class Main {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        /*格式化:date转String*/
        String format = sdf.format(new Date());
        System.out.println(format); //2021-08-25 04:00:54
        /*解析*:String转Date*/
        Date date = sdf.parse("2021-08-25 16:30:20");
        System.out.println(date);   //Wed Aug 25 16:30:20 CST 2021
    }
}

如果用SimpleDateFormat类解析字符串成java.sql.Date,就先解析为java.util.Date,在用毫秒数转换为java.util.Date

例题
1990-01-01开始,三天打鱼两天晒网,请问给定日期在做什么事情?
答:用SimpleDateFomart类解析两个日期,算出毫秒值之差,然后向上整除1000*3600*24(这里用向上除法 ( m + n − 1 ) / n (m+n-1)/n (m+n1)/n), 然后结果+1(代表一共有多少天), 然后对5取模. 0~2在打渔,3~4在晒网

2.3 JDK8中新日期的API

JDK1.0包含了一个java.util.Date类,但是它的大多数方法已经在JDK1.1引入Calendar类后被弃用了.而Calendar类并不比Date号多少.它们面临的问题是:
1.可变性:像日期和事件这样的类,应该是不可变得
2.偏移性:Date中的年份是从1900开始的,而月份是从0开始的
3.格式化:格式化只对Date有用,Calendar则不行
此外,它们也不是线程安全的; 不能处理润秒等

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一

第三次引入的API是成功的,并且Java8中引入的java.timeAPI已经纠正了过去的缺陷,将在很长一段时间内它都会为我们服务

LocalDate,LocalTime,LocalDateTime

其中LocalDateTime最常用

1.now(): 当前的日期、时间
2.of(): 设置指定的年、月、日、时、分、秒 无偏移量,方便
3.getXxx(): 获取某项时间信息
4.with(): 修改日期,返回修改后的时间对象,不修改原来的时间对象
5.plus()/minus(): 增减日期

    public void test(){
        /*now(): 当前的日期、时间*/
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();  //LocalDateTime用的最频繁
        System.out.println(localDate);
        System.out.println(localTime);
        System.out.println(localDateTime);

        /*of(): 设置指定的年、月、日、时、分、秒   无偏移量,方便*/
        LocalDateTime time1 = LocalDateTime.of(2021, 8, 25, 20, 34);
        System.out.println(time1);

        /*getXxx()*/
        System.out.println(localDateTime.getYear());
        System.out.println(localDateTime.getMonth());
        System.out.println(localDateTime.getDayOfMonth());
        System.out.println(localDateTime.getDayOfWeek());
        System.out.println(localDateTime.getDayOfYear());
        System.out.println(localDateTime.getMinute());

        /*with(): 修改日期,返回修改后的时间对象,不修改原来的时间对象*/
        LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(1); //不修改原来的日期,重新返回一个修改后的日期
        System.out.println(localDateTime1);
        System.out.println(localDateTime1);

        LocalDateTime localDateTime2 = localDateTime1.withHour(4);
        System.out.println(localDateTime2);

        /*plus()/minus(): 增减日期*/
        LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
        System.out.println(localDateTime3);
        LocalDateTime localDateTime4 = localDateTime.minusYears(2);
        System.out.println(localDateTime4);
    }

Instant

类似于java.util.Date

方法描述
now()静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970年1月1日0时0分0秒基础上指定毫秒数后的Instant类对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个OffsetDateTime
toEpochMilli()返回1970年1月1日0时0分0秒(UTC)开始的毫秒数 即时间戳
    public void test2(){
        /*now(): 获取本初子午线对应的时间标准*/
        Instant time1 = Instant.now();//默认是 本初子午线 的时区

        /*atOffset()结合即时的偏移来创建一个OffsetDateTime量*/
        OffsetDateTime time2 = time1.atOffset(ZoneOffset.ofHours(8));//改成东八区
        System.out.println(time1);
        System.out.println(time2);

        /*toEpochSecond():返回1970年1月1日0时0分0秒(UTC)开始的毫秒数 即时间戳*/
        long l = time2.toEpochSecond();
        System.out.println(l);

        /*ofEpochMilli()静态方法,返回在1970年1月1日0时0分0秒基础上指定毫秒数后的Instant类对象*/
        Instant instant = Instant.ofEpochMilli(1532324124325L);
        System.out.println(instant);
    }

DateTimeFormatter

1.方式一:预定义的标准格式

2.本地化相关的格式
在这里插入图片描述

3.自定义的格式(一般都用这个)

public void test3(){
    //自定义格式
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    //格式化
    String format = formatter.format(LocalDateTime.now());
    System.out.println(format);
    //解析
    TemporalAccessor parse = formatter.parse("2021-08-25 09:34:46");   //LocalDateTime,LocalDate,LocalTime都实现了TemporalAccessor接口
    System.out.println(parse);
}

其他类

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

2.4 Java比较器

Comparable接口

Comparator接口

2.5 System类

2.6 Math类

2.7 BigInteger和BigDecimal类

3.枚举类 & 注解

4.Java集合

5.泛型

6.IO流

7.网络编程

8.反射

9.Java8新特性

10.Java9 & 10 & 11 新特性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值