Java多线程与集合 笔记

165 阅读24分钟

注:笔记型文章

第一章 多线程

1.1 程序、进程、线程

  • 程序:是为了完成某个特定的任务,而用某种语言编写的一组指令的集合,即指的是一段静态的代码,静态对象。
  • 进程:是程序的一次执行过程,或是一个正在运行的程序。是一个动态的过程:有它自身的产生、存在、和消亡的过程——生命周期。
    • 程序是静态的,进程是动态的。系统在运行时会为每个进程分配不同的内存空间。
  • 线程:进程可进一步细化为线程,是一个程序内部的执行的路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,这就使得线程间的通信更简便、高效。但多个线程操作共享系统资源可能会带来安全隐患。

图片.png

1.2 并行、并发

  • 并行:多个CPU同时执行多个任务
  • 并发:单个CPU执行多个任务。

1.3 创建多线程

方法一:继承Thread类

  1. 创建Thread类的子类
  2. 重写Thread类的run(),将此线程执行的操作写到run()中
  3. 创建当前Thread类子类的对象
  4. 通过此对象调用start()。
package com.idealearn.java;
class MyClass extends Thread{
    public void run(){
        for(int i=0;i<=100;++i){
            if(i%2==0){
                System.out.println(i);
            }
            for(int j=0;j<=Math.random()*100;++j){
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        MyClass a = new MyClass();
        a.start();
        for(int i=0;i<=100;++i){
            if(i%2==1){
                System.out.println(i);
            }
            for(int j=0;j<=Math.random()*100;++j){
            }
        }
    }
}
  • start()方法的作用:开始执行当前线程,JAVA虚拟机执行当前线程的run()

    • 如果直接调用线程类的run(),那么不会开始当前线程。执行顺序仍是当前的单线程。
  • 不能让已经start()的线程再次start()。

    • 可以再新建一个线程,并start()
  • 匿名子类创建线程类

public class IdeaTest {
    public static void main(String[] args) {
        //匿名子类
        new Thread(){
            public void run(){
                System.out.println("哇塞!多线程!");
            }
        };
    }
}
  • Thread类常用方法:
    • start():启动线程,调用run()
    • run():执行对应语句
    • currentThread():静态方法,返回执行当前代码的线程(类)
    • getName():获取当前线程的名字
    • setName():设置当前线程的名字
    • yield():释放当前线程对CPU的执行权(转而交给其他线程)
    • join():在线程a调用线程b的join(),a阻塞,直到b运行完后a继续执行。
    • sleep(long minitime):线程“休眠”一段时间(单位:毫秒),并引发异常InterruptedException。(结合try-catch使用)
    • isAlive():判断当前线程是否存活。

线程的调度

  • CPU抢占式。
  • 线程的优先级:
    • MAX_PRIORITY:10
    • MIN_PRIORITY:1
    • NORM_PRIORITY:5
  • 获取/设置线程优先级
    • getPriority();
    • setPriority();
setPriority(Thread.MAX_PRIORITY);

方法二:实现Runnable接口

  1. 创建一个实现Runnable接口的类
  2. 实现Runnable接口中的方法:run()
  3. 创建实现类子类的对象
  4. 将此对象作为参数传递到Thread类构造器中,创建Thread类对象
  5. 通过Thread类的对象调用start()
package com.idealearn.java;

class AThread implements Runnable{
    public void run(){
        for(int i=0;i<=100;++i){
            if(i%2==0){
                System.out.println(i+":"+Thread.currentThread().getName());
            }
            for(int j=0;j<=Math.random()*100;++j){}
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        AThread runnable_thread = new AThread();
        Thread run_thread= new Thread(runnable_thread);
        run_thread.setName("多线程01");
        run_thread.start();
        //调用runnable里的target的run()
    }
}

1.4 线程的生命周期

  • 新建:一个Thread类或子类对象被创建
  • 就绪:start()后,还没有开始运行,等待CPU分配资源
  • 运行:CPU执行中
  • 阻塞:执行被阻塞,等待回到就绪
  • 死亡:执行结束或者出现错误或异常强制终止

1.5 线程的同步

  • 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
  • 问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
  • 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

1.6 解决线程同步问题

方式一:同步代码块

  • 操作共享数据的代码,即为需要被同步的代码。
  • 共享数据:多个线程共同操作的变量。
  • 同步监视器,俗称:。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
synchronized(同步监视器){
	//需要被同步的代码
}
  • 同步的方式,解决了线程的安全问题。
  • 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
class Window1 implements Runnable{
    private int ticket = 100;
    public void run() {
        while(true){
            synchronized (dog){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
class Dog{
}
  • 还可以使用this充当同步监视器(此时this对象只是一个)
class Window1 implements Runnable{
    private int ticket = 100;
    public void run() {
        while(true){
            synchronized (this){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
class Dog{
}
  • 甚至类也可以充当同步监视器(类是唯一的)
class Window1 implements Runnable{
    private int ticket = 100;
    public void run() {
        while(true){
            synchronized (Window1.class){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
class Dog{
}

方式二:同步方法。

  • 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
  • 使用Runnable
package com.idealearn.java;

class Window1 implements Runnable{
    private int ticket = 100;
    public void run() {
        while(show()){;}
    }
    //同步的方法
    private synchronized boolean show() {
    	//同步监视器:this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
            return true;
        } else {
            return false;
        }
    }
}
public class ScnBlock {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
  • 继承Thread
package com.idealearn.java;

class Window1 extends Thread{
    private static int ticket = 100;
    public void run() {
        while(show()){;}
    }
    private static synchronized boolean show() {
        //同步监视器:当前的类
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
            return true;
        } else {
            return false;
        }
    }
}
public class ScnBlock {
    public static void main(String[] args) {
        Window1 t1 = new Window1();
        Window1 t2 = new Window1();
        Window1 t3 = new Window1();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}

线程安全的懒汉式

class Bank{
	private Bank(){};
	private static Bank instance=null;
	public synchronized static Banck getInstance(){
		if(instance==null){
			instance=new Bank();
		}
	}
}
-- 效率较低
class Bank{
	private Bank(){};
	private static Bank instance=null;
	public static Banck getInstance(){
		synchronized(Bank.class){
			if(instance==null){
				instance=new Bank();
			}
			return instance;
		}
	}
}
-- 效率较高
class Bank{
	private Bank(){};
	private static Bank instance=null;
	public static Banck getInstance(){
		if(instance==null){
			synchronized(Bank.class){
				if(instance==null){
					instance=new Bank();
				}
			}
		}
		return instance;
	}
}

死锁

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

  • 解决方法

    • 专门的算法、原则
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步

方式二:Lock锁

class Window1 implements Runnable{
    private int ticket = 100;
    private ReentrantLock alock =new ReentrantLock();
    //ReentrantLock(true):公平竞争
    public void run() {
        while(true){
            try {
                //上锁
                alock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }finally{
                //解锁
                alock.unlock();;
            }
        }
    }
}
public class ScnBlock {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
  • synchronized是自动设置监视器
  • lock需要手动设置。

1.7 线程的通信

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
  • 注意
    1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
    2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
    3. wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
//使用两个线程打印 1-100。线程1, 线程2 交替打印
public class ThreadCommunication {
    public static void main(String[] args) {
        Number a = new Number();
        Thread t1=new Thread(a);
        Thread t2=new Thread(a);
        t1.start();
        t2.start();

    }
}

class Number implements Runnable{
    private int num=1;
    private Object obj=new Object();
    public void run(){
        while(true) {
            synchronized (obj){

                obj.notify();//唤醒wait()的线程
                //notifyAll(); 唤醒所有wait()的线程

                if (num <= 100) {
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;

                    try {
                        obj.wait();
                        //调用wait()进入阻塞状态
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                } else {
                    break;
                }
            }
        }
    }
}

  • 面试题:sleep() 和 wait()的异同?
    • 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
    • 不同点:
      1. 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
      2. 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
      3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

生产者与消费者问题

class Clerk{

    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");

            notify();

        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Producer extends Thread{//生产者

    private Clerk clerk;

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

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品.....");

        while(true){

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }

    }
}

class Consumer extends Thread{//消费者
    private Clerk clerk;

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

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品.....");

        while(true){

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();

    }
}

1.8 方式三:实现Callable接口

  1. 继承Thread
  2. 实现Runnable
  3. 实现Callable
  4. 使用线程池

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

1.9 方法四:使用线程池

  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般来执行Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
  • 好处:
    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3. 便于线程管理
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class NumberThread 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 NumberThread1 implements Runnable{

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

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

第二章 Java常用类

2.1 String

String基础

public final class String
	implements java.io.Serializable, Comparable<String>, CharSequence {
	/** The value is used for character storage. */
	private final char value[];
	/** Cache the hash code for the string */
	private int hash; // Default to 0
  • String声明为final,不可被继承
  • String实现了Serializable,表示字符串可以被序列化
  • String实现了Comparable,表示可以比较大小
  • private final char value[];:存储字符串数据
    • 不可变
String a = "123456";//不同于new,采用字面量的定义方式
String s1 = "123123";
String s2 = "123123";
System.out.println(s1==s2);//true
//地址相同
//通过字面量声明字符串,"123123"存储在方法区(含有字符串的常量池)
//常量池中不会存储内容相同的字符串
s2 = "123456";
//s2指向常量池中新字符串"123456"
String s3 = s2 + "abc";
//s3指向常量池中新字符串"123456abc"
//原来的字符串"123456"仍然保留,没有改
String s4 = s2.replace('c','z');
//s43指向常量池中新字符串"123456abz"

String的创建

String str = "hello";

//本质上this.value = new char[0];
String s1 = new String(); 
//new + 构造器 

//this.value = original.value;
String s2 = new String(String original); 
//此方式在内存中创建了两个对象
//s1指向堆中新建的对象
//对象中的属性value指向存储在常量池中的字符串

//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a); 
String s4 = new String(char[] a,int startIndex,int count);
String s1="123";
String s2="123";
String s3=new String("123");
String s4=new String("123");

System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s3==s4);//false
  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  • 只要其中有一个是变量,结果就在堆中
  • intern():强制要求值存在常量值中
    • 如果拼接的结果调用intern()方法,返回值就在常量池中
public void test4(){
    String s1 = "javaEEhadoop";
    String s2 = "javaEE";
    String s3 = s2 + "hadoop";
    System.out.println(s1 == s3);//false

    final String s4 = "javaEE";//s4:常量
    String s5 = s4 + "hadoop";
    System.out.println(s1 == s5);//true
}

图片.png

String常用方法

  • int length():返回字符串的长度: return value.length
  • char charAt(int index): 返回某索引处的字符return value[index]
  • boolean isEmpty():判断是否是空字符串:return value.length == 0
  • String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
  • String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
  • String trim():返回字符串的副本,忽略前导空白和尾部空白
  • boolean equals(Object obj):比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
  • String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
  • int compareTo(String anotherString):比较两个字符串的大小
  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
  • String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
  • boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
  • boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
    • 注:indexOf和lastIndexOf方法如果未找到都是返回-1

String与基本数据类型转换

  • String to 基本数据类型: parseXXX()
  • 基本数据类型 to String: String重载的 valueof(xxx)
  • String to char:str.toCharArray();
  • Char to String:new String(arr);调用String的构造器
  • 编码:String --> byte[]:调用String的getBytes()
    • 编码:字符串 -->字节 (看得懂 --->看不懂的二进制数据)
  • 解码:byte[] --> String:调用String的构造器
    • 解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
  • 说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
public void test3() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
    System.out.println(Arrays.toString(bytes));

    byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));

    System.out.println("******************");

    String str2 = new String(bytes);//使用默认的字符集,进行解码。
    System.out.println(str2);

    String str3 = new String(gbks);
    System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


    String str4 = new String(gbks, "gbk");
    System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}

常见算法题目

  • 实现trim方法
public class StringFunctions {
    public static void main(String[] args) {
        String s1="";
        String s2=" ";
        String s3="  ";
        String s4="   ";
        String s5="123   ";
        String s6="      1234";
        String s7="      12341212   ";
        String s8="a";
        System.out.println(StringFunc.trim_blank(s1));
        System.out.println(StringFunc.trim_blank(s2));
        System.out.println(StringFunc.trim_blank(s3));
        System.out.println(StringFunc.trim_blank(s4));
        System.out.println(StringFunc.trim_blank(s5));
        System.out.println(StringFunc.trim_blank(s6));
        System.out.println(StringFunc.trim_blank(s7));
        System.out.println(StringFunc.trim_blank(s8));
    }
}

class StringFunc{
    public static String trim_blank(String a){
//        char charAt(int index)
//        String substring(int beginIndex, int endIndex)
        if(a.length()==0) {
            return "";
        }
        int i=0,j=a.length()-1;
        while((a.charAt(i)==' ' || a.charAt(j)==' ') && i<j){
            if(a.charAt(i)==' ') {
                i++;
            }
            if(a.charAt(j)==' '){
                j--;
            }
        }
        if(a.charAt(i)==' '){
            return "";
        }
        else{
            return a.substring(i,j+1);
        }
    }
}
  • 获取一个字符串在另一个字符串中出现的次数。
    • 下面的不能解决“abgabgabg”与“abgabg”的重复问题
public class StringFunctions {
    public static void main(String[] args) {
        String s="fbgfbgfbgfbgfbgfbbgfbgffbgfbvgfbg";
        String a="fbg";
        System.out.println(StringFunc.find_str_num(s,a));
    }
}

class StringFunc{
    public static int find_str_num(String s,String a){
        if(s.length()==0){
            return 0;
        }
        int num=0,index=0;
        while(index<s.length()){
            index=s.indexOf(a,index);
            if(index==-1){
                break;
            }
            num++;
            index++;
        }
        return num;
    }
}
  • 将字符串中指定部分进行反转。
public class Main
{
    public static void main(String[] args) {
	    String s1="";
	    String s2="1";
	    String s3="0123456789";
	    System.out.println(StringFunc.StringReverse(s1,0,0));
	    System.out.println(StringFunc.StringReverse(s2,0,0));
	    System.out.println(StringFunc.StringReverse(s3,0,9));
	    System.out.println(StringFunc.StringReverse(s3,4,7));
	    System.out.println(StringFunc.StringReverse(s3,4,6));
    }
}

class StringFunc{
	public static String StringReverse(String s,int a,int b){
		if(s.length()==0){
			return s;
		}
		char[] c=new char[s.length()];
		char t;
		for(int i=0;i<s.length();i++){
			c[i]=s.charAt(i);
		}
		for(int i=a;i<=(b-a)/2+a;i++){
			t=c[i];
			c[i]=c[(a+b)-i];
			c[(a+b)-i]=t;
		}
		return new String(c);
	}
}
  • 获取两个字符串中最大相同子串
public class Main
{
    public static void main(String[] args) {
	    System.out.println(StringFunc.MaxSubstring("abcde3232efg","qqqqqbcdefg"));
    }
}

class StringFunc{
	public static String MaxSubstring(String a,String b){
		if(a==""&&b==""){
			return "";
		}
		if(a.length()<b.length()){
			String c = a;
			a = b;
			b = c;
		}
		int l=b.length(),offset;
		offset=0;
		while(offset<b.length()){
			for(int i=0;i<=offset;i++){
				String temp=b.substring(i,i+l-offset);
				if(a.indexOf(temp)!=-1){
					return temp;
				}
			}
			offset++;
		}
		return "No Found!";
	}
}

2.2 StringBuffer

比较

  • String:不可变的字符序列;底层使用char[]存储

  • StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储

  • StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

  • 源码分析:

String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
         默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。

        指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)

常用方法:

  • 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)

总结: 增:append(xxx) 删:delete(int start,int end) 改:setCharAt(int n ,char ch) / replace(int start, int end, String str) 查:charAt(int n ) 插:insert(int offset, xxx) 长度:length(); 遍历:for() + charAt() / toString()

2.3 时间日期 旧

currentTimeMillis()

  • 时间戳:返回1970年1月1号到现在的时间差(单位毫秒)

java.util.Date类

  • 构造器
    • new Date()
    • new Date(时间戳毫秒数)
  • 方法
    • toString():显示当前的年、月、日、时、分、秒
    • getTime():获取当前Date对象对应的毫秒数

java.sql.Date类

  • 数据库中用的。
  • 实例化
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
  • 将java.util.Date对象转换为java.sql.Date对象
//情况一:
Date date4 = new java.sql.Date(2343243242323L);
java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());

SimpleDateFormat

  • 格式化(转换成字符串)
import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.ParseException;

public class Main
{
    public static void main(String[] args) {
	    SimpleDateFormat simpleDateFormat1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		Date date2=simpleDateFormat1.parse("2022-11-22 11:48:27");
		System.out.println(date2);
    }
}
  • 解析(字符串转成日期)
public static void main(String[] args) throws ParseException{
    SimpleDateFormat simpleDateFormat1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	Date date2=simpleDateFormat1.parse("2020-05-29 11:48:27");
	System.out.println(date2);
	//Fri May 29 11:48:27 GMT 2020
	//要求字符串必须符合SimpleDateFormat识别的格式(构造器参数),否则会抛异常
}
  • 字符串转换java.sql.date
String s="2022-11-22";
SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd");
Date date=sim.parse(s);
java.sql.Date SqlDate=new java.sql.Date(date.getTime());
System.out.println(SqlDate);

java.util.Calendar;

  • Calender是个抽象类

  • 注意,一月、星期一是从0开始

  • 创建

//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用静态方法 getInstance()
Calendar c=Calendar.getInstance();
  • getTime()
//获取时间
Date date=c.getTime();
System.out.println(date);
Tue Nov 22 02:24:58 GMT 2022
  • setTime()
//设置时间
Date d=new Date();
c.setTime(d);
  • get()
//获取常用属性
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//22
System.out.println(c.get(Calendar.DAY_OF_YEAR));
//326
  • set()
//设置属性
c.set(Calendar.DAY_OF_MONTH,23);
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//23
  • add()
//添加
c.add(Calendar.DAY_OF_MONTH,3);
System.out.println(c.get(Calendar.DAY_OF_MONTH));
//26

2.4 时间日期 新

java.time

LocalDate、LocalTime、LocalDateTime

import java.time.*;

public class Main
{
    public static void main(String[] args){
	    LocalDate d=LocalDate.now();
	    LocalTime t=LocalTime.now();
	    LocalDateTime dt=LocalDateTime.now();
	    System.out.println(d);
		//2022-11-22
	    System.out.println(t);
		//02:45:55.305094

	    System.out.println(dt);
		//2022-11-22T02:45:55.305393


		//设置指定时间
	    LocalDateTime time = LocalDateTime.of(2006,9,10,12,3,50);
		System.out.println(time);
		//2006-09-10T12:03:50

		//getXXX()
		System.out.println(time.getDayOfMonth());
		//10
		System.out.println(time.getDayOfYear());
		//253
		System.out.println(time.getMonth());
		//SEPTEMBER
		System.out.println(time.getMinute());
		//3

		//设置 withXXX()
		LocalDate dd=d.withDayOfMonth(26);
		System.out.println(d);
		//2022-11-22
		System.out.println(dd);
		//2022-11-26

		//添加 plusXXX()
		LocalDate ddd = d.plusDays(1);
		System.out.println(ddd);
		//2022-11-23
    }
		
}

Instant

  • 时间线上的一个瞬时点
//now()
Instant i=Instant.now();
System.out.println(i);
//本初子午线时间
//2022-11-22T02:58:17.105656Z

//偏移时间
OffsetDateTime o = i.atOffset(ZoneOffset.ofHours(8));
System.out.println(o);
//2022-11-22T10:58:17.105656+08:00

//toEpochMilli:计算毫秒数(距离1970年)
System.out.println(i.toEpochMilli());
//1669085897105

//返回指定毫秒数(距离1970年)的对象
Instant ii=ofEpochMilli(900L)
System.out.println(ii);
//1970-01-01T00:00:00.900Z

java.time.format.DateTimeFormatter

  • 格式化解析日期时间
//方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime ldt = LocalDateTime.now();
String str1 = formatter.format(ldt);
System.out.println(ldt);
System.out.println(str1);//2019-02-18T15:42:18.797

//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);

//方式二:
//本地化相关的格式。如:ofLocalizedDateTime()
//FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(ldt);
System.out.println(str2);//2019年2月18日 下午03时47分16秒


//本地化相关的格式。如: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);//2019-2-18


//重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09

//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);

2.5 Java比较器

Comparable接口

  • 包装类、String等类实现了Comparable的借口,重写了compareTo()方法,给出了比较大小的规则

  • 重写compareTo()

    • 如果当前对象大于目标对象,返回正整数(1)
    • 如果当前对象等于目标对象,返回0(0)
    • 如果当前对象小于目标对象,返回负整数(-1)
  • 自定义类的比较

import java.util.Arrays;
import java.util.Comparator;

public class Main
{
    public static void main(String[] args) {
        Phone[] ps=new Phone[4];
        ps[0]=new Phone("小米",1900);
        ps[1]=new Phone("三星",2300);
        ps[2]=new Phone("金立",800);
        ps[3]=new Phone("华为",1600);
        Arrays.sort(ps);
        for(int i=0;i<4;i++){
	        System.out.print(ps[i].getName()+" ");
        }
        //金立 华为 小米 三星 
    }
}

class Phone implements Comparable{
	private String name;
	private int price;
	public Phone(String n,int p ){
		name=n;
		price=p;
	}
	public String getName(){
		return name;
	}
	public int compareTo(Object o){
		if(o instanceof Phone){
			Phone g=(Phone)o;
			if(this.price>g.price){
				return 1;
			}
			else if(this.price<g.price){
				return -1;
			}
			else{
				return 0;
			}
		}
		throw new RuntimeException("传入的数据不一致!");
	}
}
  • Comparator定制排序
    • 需要的时候临时创建一个Comparator类
import java.util.Arrays;
import java.util.Comparator;

public class Main
{
    public static void main(String[] args) {
        Phone[] ps=new Phone[4];
        ps[0]=new Phone("小米",1900);
        ps[1]=new Phone("三星",2300);
        ps[2]=new Phone("金立",800);
        ps[3]=new Phone("华为",1600);
        Arrays.sort(ps,new Comparator(){
        	public int compare(Object o1,Object o2){
        		if(o1 instanceof Phone && o2 instanceof Phone){
        			Phone p1=(Phone)o1;
        			Phone p2=(Phone)o2;
					if(p1.getPrice()>p2.getPrice()){
						return -1;
					}
					else if(p1.getPrice()<p2.getPrice()){
						return 1;
					}
					else{
						return 0;
					}
        		}

        		throw new RuntimeException("传入的数据不一致!");
        	}
        }
        );
        for(int i=0;i<4;i++){
	        System.out.print(ps[i].getName()+" ");
        }
		//三星 小米 华为 金立 
    }
}

class Phone{
	private String name;
	private int price;
	public Phone(String n,int p ){
		name=n;
		price=p;
	}
	public String getName(){
		return name;
	}
	public int getPrice(){
		return price;
	}
}

2.6 System类

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。

  • native long currentTimeMillis():

    • 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
  • void exit(int status):

    • 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
  • void gc()

    • 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
  • String getProperty(String key)

    • 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
public class Main
{
    public static void main(String[] args) {
        String javaVersion = System.getProperty("java.version");
        System.out.println("java的version:" + javaVersion);

        String javaHome = System.getProperty("java.home");
        System.out.println("java的home:" + javaHome);

        String osName = System.getProperty("os.name");
        System.out.println("os的name:" + osName);

        String osVersion = System.getProperty("os.version");
        System.out.println("os的version:" + osVersion);

        String userName = System.getProperty("user.name");
        System.out.println("user的name:" + userName);

        String userHome = System.getProperty("user.home");
        System.out.println("user的home:" + userHome);

        String userDir = System.getProperty("user.dir");
        System.out.println("user的dir:" + userDir);
    }
}
//java的version:11.0.7
//java的home:/usr/lib/jvm/java-11-openjdk
//os的name:Linux
//os的version:5.10.0-0.bpo.15-amd64
//user的name:repl
//user的home:/home/repl
//user的dir:/home/repl/6a1371c7-5f19-431d-aad9-0220e0149cd4

2.7 Math类

  • java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。

2.8 BigInteger与BigDecimal

BigInteger

  • BigInteger可以表示不可变的任意精度的整数。
  • BigInteger(String val):根据字符串构建BigInteger对象
  • public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
  • BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
  • BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
  • BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
  • BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
  • BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
  • BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
  • BigInteger pow(int exponent) :返回其值为 (this^exponent) 的 BigInteger。

BigDecimal

  • BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
  • public BigDecimal(double val) 
  • public BigDecimal(String val)
  • public BigDecimal add(BigDecimal augend)
  • public BigDecimal subtract(BigDecimal subtrahend)
  • public BigDecimal multiply(BigDecimal multiplicand)
  • public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

第三章 枚举类与注解

3.1 枚举类

  • 一组有限个数的常量
  • 如果枚举类中只有一个变量,可以作为单例模式实现。

3.2 定义枚举类

自定义枚举类

  1. 私有化构造器
  2. 声明对象的属性
  3. 提供枚举类的多个对象
  4. 自定义对象
class Season{
	private String seasonName;
	private String seasonInfo;
	private Season (String name,String info){
		seasonName=name;
		seasonInfo=info;
	}
	public static final Season SPRING = new Season("春天","春暖花开");
	public static final Season SUMMER = new Season("夏天","烈日炎炎");
	public static final Season AUTUMN = new Season("秋天","秋高气爽");
	public static final Season WINTER = new Season("冬天","冰天雪地");

	public String getName(){
		return seasonName;
	}

	public String getInfo(){
		return seasonInfo;
	}

	public String toString(){
		return seasonName+""+seasonInfo;
	}
}

public class Main
{
    public static void main(String[] args) {
        Season a =Season.WINTER;
        System.out.println(a);
		//冬天冰天雪地
    }
}

enum关键字

  • 默认继承于 java.lang.enum
  • toString():默认返回枚举常量的名称
  • values():显示所有枚举
  • valueOf(String s):返回叫做s的枚举对象

enum实现接口

  • 可以直接重写方法。也可以为每个枚举对象分别重写。
interface Info{
	void show();
}

enum Season implements Info{
	//多个对象逗号隔开
	SPRING("春天","春暖花开"){
		public void show(){
			System.out.println("美丽的春天");
		}
	},
	SUMMER("夏天","烈日炎炎"){
		public void show(){
			System.out.println("美丽的夏天");
		}
	},
	AUTUMN("秋天","秋高气爽"){
		public void show(){
			System.out.println("美丽的秋天");
		}
	},
	WINTER("冬天","冰天雪地"){
		public void show(){
			System.out.println("美丽的冬天");
		}
	};
	
	private String seasonName;
	private String seasonInfo;
	
	private Season (String name,String info){
		seasonName=name;
		seasonInfo=info;
	}

	public String getName(){
		return seasonName;
	}

	public String getInfo(){
		return seasonInfo;
	}

	//可以直接重写show方法
	//public void show(){
	//	System.out.println("多彩的四季");
	//}
	
	//一般不用重写枚举类
}

public class Main
{
    public static void main(String[] args) {
        Season a =Season.WINTER;
        System.out.println(a);
		//WINTER
        
        Season[] v=a.values();
        for(int i=0;i<v.length;i++){
            System.out.print(v[i]+" ");
        }
		//SPRING SUMMER AUTUMN WINTER 
        System.out.println();
        
        Season s=Season.valueOf("SPRING");
        System.out.println(s);
		//SPRING

		a.show();
		//美丽的冬天
		s.show();
		//美丽的春天
    }
}

3.3 注解(Annotation)

常用注解

@Override 重写的方法 @Deprecated 修饰过时的结构 @SupressWarning 抑制编译器警告

自定义注解

  1. 注解声明为:@interface
  2. 内部定义成员,通常使用value表示
  3. 可以指定成员的默认值,使用default定义
  4. 如果自定义注解没有成员,表明是一个标识作用。
public @interface MyAnnotation {
    String value() default "hello";
}
@MyAnnotation("Hi");

元注解

  • 元注解:对现有的注解进行解释说明的注解

Retention

  • Retention:指定所修饰的 Annotation 的生命周期
  • SOURCE:注解仅保留在源文件中,不写入.class文件
  • CLASS(默认行为):注解将写入.class文件,但运行时不会加载到内存中
  • RUNTIME:注解在运行时也会存在。
    • 只有声明为RUNTIME生命周期的注解,才能通过反射获取。

Target

  • Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(包、类、构造器等)

Documented

  • Documented:表示所修饰的注解在被javadoc解析时,保留下来。

Inherited

  • Inherited:被它修饰的 Annotation 将具有继承性。

可重复注解:

  • 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
  • MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

类型注解

class Generic<@MyAnnotation T>{
    public void show() throws @MyAnnotation RuntimeException{
        ArrayList<@MyAnnotation String> list = new ArrayList<>();
        int num = (@MyAnnotation int) 10L;
    }
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
    String value() default "hello";
}

第四章 集合

4.1 概述

  • 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
  • 数组
    • 初始化后长度不可变
      • ArrayList、LinkedList、Vector
    • 定义后只能存储指定类型的数据
      • HashSet、LinkedHashSet、TreeSet
    • 数据可以重复,存放时顺序存储
      • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

4.2 集合框架

  • Collection接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合(相当于动态数组)
    • Set:元素无序、不可重复的集合
  • Map接口:双列集合,存储键值对(key-value)

图片.png

4.3 Collection接口

Collection coll = new ArrayList();

//add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());

//size():获取添加的元素的个数
System.out.println(coll.size());//4

//addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);

System.out.println(coll.size());//6
System.out.println(coll);
//[AA, BB, 123, Mon Nov 28 06:15:02 GMT 2022, 456, CC]

//clear():清空集合元素
coll.clear();

//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());
//true

//contains():判断是否含有当前元素
coll.add(1234);
System.out.println(coll.contains(1234));
//true
coll.add(new String("12345"));
System.out.println( coll.contains(new String("12345")) );
//true
//contains调用参数相应类的equals()来比较

//containsAll(Collection c)
//判断c中所有元素是否在当前集合中
Collection c=Arrays.asList(1234,new String("12345"));
System.out.println(coll.containsAll(c));//true

//remove(a):移除a
//同样使用equals()
coll.remove("12345");
System.out.println( coll.contains(new String("12345")) );//false

//removeAll(Collection b):
//移除b与当前集合共有的元素
Collection b=Arrays.asList(1,12,123,1234,12345);
coll.removeAll(b);
System.out.println( coll.contains(1234) );//false

//retainAll(Collection b)
//与集合b求交集
// Collection c1= (1,2,3,4,5,6,7,8,9);
// Collection c2= (2,4,6,8,10,12,14);
// c1.retainAll(c2);
// System.out.println(c1);

//equals(Collection b)
//判断两个集合是否相等

//hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());

//toArray():集合→数组
coll.add(1);
coll.add(2);
coll.add(3);
Object[] arr=coll.toArray();
for(int i=0;i<arr.length;i++){
    System.out.print(arr[i]+" ");
}//1 2 3

//数组→集合:
//Arrays.asList()
List<String> list=Arrays.asList(new String[]{"A","B","C"});
System.out.println(list);//[A, B, C]

List arr1=Arrays.asList(new int[]{123,456});
System.out.println(arr1);//[[I@ea4a92b]

List arr2=Arrays.asList(new Integer[]{123,456});
System.out.println(arr2);//[123, 456]

//iterator():返回iterator接口的实例,用于遍历元素

4.4 Iterator 迭代器

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • 提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);

Iterator i=c.iterator();
System.out.println(i.next());
System.out.println(i.next());
System.out.println(i.next());
System.out.println(i.next());
//System.out.println(i.next());
// 异常
//Exception in thread "main" java.util.NoSuchElementException

i=c.iterator();
//hasNext()判断是否存在下一个元素
while(i.hasNext()){
    System.out.println(i.next());
    //1. 指针下移
    //2. 返回指向的元素
}

i=c.iterator();
while(i.hasNext()){
    Object obj=i.next();
    if(obj.equals(3)){
        i.remove();
        //删除当前元素
        break;
    }
}
System.out.println(c);
//[1, 2, 4]

foreach新特性遍历集合或数组

  • 集合数组都能用
//for(集合元素的类型 局部变量:集合对象)
for(Object obj:c){
    System.out.println(obj);
}

4.5 List接口

  • 存储有序的、可重复的数据
    • ArrayList:作为List接口的主要实现类,线程不安全,执行效率高;底层使用Object[]存储
    • LinkedList:底层使用双向链表——频繁地删除插入效率高
    • Vector:作为List接口的古老实现类,线程安全,执行效率低;底层使用Object[]存储

List常用方法

void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex(不包括toIndex)位置的子集合

总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:
① Iterator迭代器方式
② 增强foreach循环
③ 普通的循环

4.6 Set接口

  • HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值
    • LinkedHashSet:HashSet的子类,可以按照添加的数据遍历
  • TreeSet:可以按照添加对象指定的属性进行排序(存储同一类型的数据)
  • Set接口中没有额外定义的方法,使用的是Collection中的方法。

HashSet添加数据的过程

You can read this:https://www.cnblogs.com/missarain/articles/16025789.html

  • 首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
  • 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
    • 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
    • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
      • 如果hash值不相同,则元素a添加成功。--->情况2
      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        • equals()返回true,元素a添加失败
        • equals()返回false,则元素a添加成功。--->情况2

LinkedHashSet

  • 相当于在HashSet基础上添加了双向链表来记录添加时的顺序。

TreeSet

  • 存储同一类型的数据。
  • 数据按照指定的顺序排列。
    • 自然排序
      • 当前类 implements Comparable 并重写 compareTo()
      • 自然排序中,比较两个对象是否相同:compareTo()是否返回0
    • 定制排序
// 定制排序
public void test2(){
Comparator com = new Comparator() {
    //按照年龄从小到大排列
    @Override
    public int compare(Object o1, Object o2) {
        if(o1 instanceof User && o2 instanceof User){
            User u1 = (User)o1;
            User u2 = (User)o2;
            return Integer.compare(u1.getAge(),u2.getAge());
        }else{
            throw new RuntimeException("输入的数据类型不匹配");
        }
    }
};
TreeSet set = new TreeSet(com);

HashSet添加数据的过程

You can read this:https://www.cnblogs.com/missarain/articles/16025789.html

  • 首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
  • 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
    • 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
    • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
      • 如果hash值不相同,则元素a添加成功。--->情况2
      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        • equals()返回true,元素a添加失败
        • equals()返回false,则元素a添加成功。--->情况2

LinkedHashSet

  • 相当于在HashSet基础上添加了双向链表来记录添加时的顺序。

TreeSet

  • 存储同一类型的数据。
  • 数据按照指定的顺序排列。
    • 自然排序
      • 当前类 implements Comparable 并重写 compareTo()
      • 自然排序中,比较两个对象是否相同:compareTo()是否返回0
    • 定制排序
// 定制排序
public void test2(){
Comparator com = new Comparator() {
    //按照年龄从小到大排列
    @Override
    public int compare(Object o1, Object o2) {
        if(o1 instanceof User && o2 instanceof User){
            User u1 = (User)o1;
            User u2 = (User)o2;
            return Integer.compare(u1.getAge(),u2.getAge());
        }else{
            throw new RuntimeException("输入的数据类型不匹配");
        }
    } 
};
TreeSet set = new TreeSet(com);

总结

  • List 需要重写equals()方法
  • HashSet、LinkedHashSet需要重写equals()、hashCode()
  • TreeSet需要继承Comparable并重写compareTo(),或者写一个Comparator对象并使用TreeSet(Comparator )

4.7 MAP

  • MAP:键值对,存储key-value的数据
  • HashMap: 线程不安全,效率高,key或value允许存储null
    • LinkedHashMap: 链表结构,可以按照添加的顺序遍历
  • TreeMap: 根据key排序,底层使用红黑树
  • Hashtable:年代较远,线程安全,效率低,不允许存储null
    • Properties: 常用来处理配置文件

key与value

  • entry:一个键值对(key-value)构成一个entry对象
    • key:无序,不可重复,使用set存储(需要重写相应方法)
    • value:无序,可重复,使用Collection存储

HashMap底层原理

  • map.put(k,v):
    • 第一步首先将k,v封装到Node对象当中(节点)。
    • 第二步它的底层会调用K的hashCode()方法得出hash值。
    • 第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标。
      • 下标位置上如果没有任何元素,就把Node添加到这个位置上。
      • 如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。 - 如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。 - 如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
  • JDK8之后,如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。

Map常用方法

put(key,value)
// 添加数据
putAll(Map a)
// 将a中所有键值对添加到当前map中
remove(key)
// 移除键值对
clear()
// 清空map
isEmpty()
// 是否为空
equals(obj)
// 判断是否相等
containsKey(key)
// 是否存在key
containsValue(value)
// 是否存在value
  • Map的遍历

// 分开遍历
Set set=map.keyset();
Iterator it=se.iterator();
while(it.hasNext()){
	System.out.println(it.next());
}

Collection values = map.values();
for(Object obj:values){
	System.out.println(obj);
}

// 上面key与value输出的顺序对应一致
Set es=map.entrySet();
Iterator it=es.iterator();
while(it.hasNext()){
	Object k = it.next();
	Object v = map.get(k);
	System.out.println(k+" : "+v);
}

TreeMap

  • TreeMap需要设置排序。可以是自然排序,也可以是定制排序

Properties

  • Properties是Hashtable的子类,用于处理属性文件
  • 其key与value都是字符串类型
FileInputStream fis = null;
try {
    Properties pros = new Properties();

    fis = new FileInputStream("jdbc.properties");
    pros.load(fis);//加载流对应的文件

    String name = pros.getProperty("name");
    String password = pros.getProperty("password");

    System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if(fis != null){
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Collections

  • Collections是Collection的工具类
reverse(List)
// 反转List
shuffle(List)
// 对List中的元素进行随机排序
sort(List)
// List排序
max/min(Collection)
max/min(Collection,Comparator)
// 最大最小

copy(List dest,List scr)
// 复制 
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);

// Collections 类中提供了多个 synchronizedXxx() 方法,
// 该方法可使将指定集合包装成线程同步的集合,从而可以解决
// 多线程并发访问集合时的线程安全问题

List list1 = Collections.synchronizedList(list);
//返回的list1即为线程安全的List

第五章 泛型

5.1 集合中的泛型

  • 用泛型指名要存储的数据类型
ArrayList<Integer> a=new ArrayList<Integer>();
a.add(1);
a.add(2);
a.add(3);
System.out.println(a);

for(Integer o : a){
    int num=o;
    System.out.println(num);
}

Iterator<Integer> it=a.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
Map<Integer,String> m=new HashMap<Integer,String>();
m.put(1,"a");
m.put(2,"b");
m.put(3,"c");

Set<Map.Entry<Integer,String>> e = m.entrySet();
Iterator<Map.Entry<Integer,String>> it=e.iterator();

while(it.hasNext()){
    Map.Entry<Integer,String> p =it.next();
    Integer key = p.getKey();
    String value = p.getValue();
    System.out.println(key+":"+value);
}

5.2 自定义泛型结构

public class Main
{
    public static void main(String[] args) {
	    MyClass<String> m=new MyClass<String>(123,"aString");
	    System.out.println(m);
	    
	    HashMap<Integer,String> map=new HashMap<Integer,String>();
	    map.put(789,"Map");
	    BClass b=new BClass(123,456,map);
	    System.out.println(b);
	    
	    CClass c=new CClass("s123-C-class",123,new Integer(4578));
	    System.out.println(c);
    }
}

// 自定义类中的泛型 
class MyClass<T>{
	int num;
	T myT;

	public MyClass(){
	}

	public MyClass(int num,T t){
		this.num=num;
		this.myT=t;
	}

	public void setNum(int a){
		num=a;
	}

	public void setMyT(T t){
		myT=t;
	}

	//静态方法不能使用泛型
	//public static T getMyT(){
	//    return myT;
	//}

	public String toString(){
		return "MyClass:"+num+","+myT;
	}
}

//类继承已经指名类型的泛型类
class BClass extends MyClass<HashMap<Integer,String>>{
	int Bnum;

	public BClass(int bn,int num,HashMap m){
		super(num,m);
		Bnum=bn;
	}

	public String toString(){
		return "BClass:"+Bnum+","+num+","+myT;
	}
}

//类继承未指名类型的泛型类
class CClass<T> extends MyClass<T>{
	String s;

	public CClass(String cs,int num,T t){
		super(num,t);
		s=cs;
	}

	public String toString(){
		return "CClass:"+s+","+num+","+myT;
	}
}

5.3 泛型方法

public class Main
{
    public static void main(String[] args) {
	    M m=new M();
	    System.out.println(m.toString(new Integer(1234)));
    }
}

class M{
	public <T> String toString(T t){
		return "Func-toString:"+t;
	}

	// 泛型方法也可以写成静态方法
	//public static <T> String toString<T>(T t){
}

5.4 泛型继承问题与通配符

List<String> ls1=new ArrayList<String>();
List<Integer> ls2=new ArrayList<Integer>();

//ls2=ls1;
// 编译不通过

List<?> ls3;
ls3=ls1;
ls3=ls2;

有条件限制的通配符的使用

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.*;

public class Main
{
    public static void main(String[] args) {

	    List<A> la=null;
	    List<B_AChild> lb=null;
	    List<Object> lo=null;

	    List<? extends A> list1 = null;
	    list1=la;
	    list1=lb;
		// list2=lo; //错误
		// extends A 可以匹配A及其继承类


		List<? super A> list2 = null;
		list2=la;
		// list2=lb; //错误
	    list2=lo;
	    // super A 可以匹配A及其父类
    }
}

class A{}

class B_AChild extends A{}

2022年12月14日