JAVA高级笔记

一、多线程

程序、进程、线程

  • 了解

  • 优点

创建多线程方式一:继承Thread类

  1. 创建一个继承与Thread类的子类
  2. 重写Thread类的run() 方法 --> 将此线程执行的操作声明在run() 中
  3. 创建Thread类的子类的对象
  4. 通过此对象去调用Thread的start() 方法
public class ThreadTest {public static void main(String[] args) {MyThread myThread = new MyThread();// 不能使用myThread.run()来调用,这样就是单线程了myThread.start();for (int i = 0; i < 100; i++) {if (i % 2 == 0) {System.out.println(i + "abc");}}}
}class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {System.out.println(i);}}}
}
  • 要想启动线程,调用start() 方法,不能用run() 方法来调用
  • 同一个对象不能调用两次start() 方法,想用同一套线程方案,需要再重新创建一个对象

线程的常用方法

  • start() :启动当前线程,调用当前线程的run()

  • run() :通常需要重写Thread类中的此方法,将创建的线程要执行的操作

  • currentThread() :静态方法,返回执行当前代码的线程

  • getName() :获取当前线程的名字

    sout(Thread.currentThread().getName());
    
  • setName() :设置当前线程的名字

    对象名.setName("名称");
    // 给主线程命名
    Thread.currentThread().setName("主线程");
    

    还可以用构造器的方式给主线程命名

  • yield() :释放当前cpu的执行权,有可能在下一刻又会被该线程执行

  • join() :在线程a中,调用线程b的join() 方法,此时线程a就进入阻塞状态,知道线程b完全执行完之后,线程a才结束阻塞状态

    例如在线程a中,调用线程b.join();(本身会抛出异常,需要用try-catch),执行join方法时a线程将就会进入阻塞状态,等b线程执行完之后才继续执行a线程

  • sleep(long millitime) :让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线城市阻塞状态

  • isAlive() :判断当前线程是否存活

线程优先级

MAX_PRIORITY: 10 --> 较高优先级

MIN_PRIORITY: 1 --> 较低优先级

NORM_PRIORITY: 5 --> 默认优先级

  • getPriority() : 获取线程的优先级
  • setPriority(int priority) : 设置线程的优先级

高优先级的线程要抢占低优先级cpu的执行权。但是只是从概率上讲的。

高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完之后,低优先级的线程才执行。

卖票练习

/*** @ClassName ThreadTest2* @Description 卖票练习测试* @Author LiangHui* @Date 2020/7/16 8:42* @Version V1.0*/
public class ThreadTest2 {public static void main(String[] args) {Window t1 = new Window();Window t2 = new Window();Window t3 = new Window();// 线程命名t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}class Window extends Thread{private static int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {// 此处还存在线程安全问题,或同时有多个线程进入if中System.out.println(getName() + " 卖票,票号为:" + ticket);ticket--;}else {break;}}}
}

创建多线程方式二:实现Runnable接口

  1. 创建一个实现Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
public class ThreadTest3 {/*** @Author LiangHui* @Description main方法,主程序入口* @Date 2020/7/16 10:13* @param args* @return void*/public static void main(String[] args) {// 创建实现类的对象MyThread3 myThread3 = new MyThread3();// 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread t1 = new Thread(myThread3);// 通过Thread类的对象调用start()// 要看源码才知道为什么会执行MyThread类中的run()方法// 因为target不为null 所以调用了Runnable类型的target的run()方法t1.start();}
}class MyThread3 implements Runnable{/*** @Author LiangHui* @Description 实现run()抽象方法* @Date 2020/7/16 10:14* @param* @return void*/public void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {// currentThread() 获取当前线程System.out.println(Thread.currentThread().getName() + "偶数" + i);}}}
}

卖票练习

public class ThreadTest4 {public static void main(String[] args) {Window2 window2 = new Window2();Thread t1 = new Thread(window2);Thread t2 = new Thread(window2);Thread t3 = new Thread(window2);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}class Window2 implements Runnable {// 因为只创建了一个Windows2对象,所以不用添加staticprivate int ticket = 100;/*** @Author LiangHui* @Description 实现run()抽象方法,实现卖票功能* @Date 2020/7/16 10:40* @param* @return void*/@Overridepublic void run() {while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);ticket--;} else {break;}}}
}

比较两种创建线程的方式

  • 开发中,优先选择实现先Runnable接口的方式

    • 实现的方式没有类的单继承性的局限性
    • 实现的方式更适合来处理多个线程有共享数据的情况
  • 联系

    • Thread类也是实现了Runnable接口的

      public class Thread implements Runnable
      
    • 两种方法都需要重写run() 方法,将线程要执行的逻辑声明在run() 中

线程的生命周期

线程安全问题

  • 问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
  • 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

在Java中,我们通过同步机制,来解决线程的安全问题

方式一:同步代码块

synchronized(同步监视器){// 需要被同步的代码
}
  • 说明:

    • 操作共享数据的代码,即为需要被同步的代码。 --> 不能包含代码多了,也不能包含代码少了。
    • 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
    • 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
  • 要求:多个线程必须要共用同一把锁。即同步监视器必须是相同的对象
  • 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

同步代码块处理实现Runnable

public class ThreadTest5 {/*** @Author LiangHui* @Description main方法,主程序入口* @Date 2020/7/16 18:05 * @param args * @return void*/public static void main(String[] args) {Window3 window3 = new Window3();Thread t1 = new Thread(window3);Thread t2 = new Thread(window3);Thread t3 = new Thread(window3);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}}class Window3 implements Runnable{private int ticket = 100;/*** @Author LiangHui* @Description 实现run()方法,实现卖票功能* @Date 2020/7/16 17:30* @param* @return void*/@Overridepublic void run() {// 在操作共享变量的地方添加同步代码块// synchronized(同步监视器),同步监视器可以使任意的对象,前提是必须是公用的对象(锁)while (true) {synchronized (this) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);ticket--;} else {break;}}}}
}

同步代码块处理继承Thread类

class Window4 extends Thread {private static int ticket = 100;/*** @Author LiangHui* @Description 重写run()方法,实现卖票功能* @Date 2020/7/17 7:15* @param* @return void*/@Overridepublic void run() {// 类也是对象,所以Window4.class是对象,并且是唯一的while (true) {synchronized (Window4.class) {if (ticket > 0) {System.out.println(getName() + " 票号:" + ticket);ticket--;} else {break;}}}}
}

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的

同步方法块处理实现Runnable

class Window5 implements Runnable {private int ticket = 100;/*** @Author LiangHui* @Description 实现run()抽象方法,实现卖票功能* @Date 2020/7/16 10:40* @param* @return void*/@Overridepublic void run() { // 同步方法,不能在run()方法中添加同步监视器while (true) {buyTicket();}}/*** @Author LiangHui* @Description 创建同步方法,将操作共享变量的代码包含进去* @Date 2020/7/17 8:57* @param* @return void*/private synchronized void buyTicket(){if (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);ticket--;}}
}

同步代码块处理继承Thread类

class Window6 extends Thread{private static int ticket = 100;/*** @Author LiangHui* @Description 重写run()方法,实现卖票功能* @Date 2020/7/17 9:04* @param* @return void*/@Overridepublic void run() {while (true) {buyTicket();}}/*** @Author LiangHui* @Description 创建静态的同步方法,将操作共享变量的代码包含进去* @Date 2020/7/17 9:07 * @param  * @return void*/private static synchronized void buyTicket() {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 票号:" + ticket);ticket--;}}
}

使用同步方式的优劣

  • 同步的方式,解决了线程的安全问题。—好处

  • 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

解决单例模式的懒汉式实现中的线程安全问题

public class SingletonLazyTest {public static void main(String[] args) {Bank bank = Bank.getInstance();}
}class Bank {private Bank() {}// 私有化构造器// 内部创建类的对象,并且为空private static Bank instance = null;// 同步方法,锁(同步监视器)-->Bank.class// public static synchronized Bank getInstance(){public static Bank getInstance(){// // 存在线程安全问题// if (instance == null) {//     instance = new Bank();// }// return instance;// 同步代码块// 方式一:效率稍差,每次new对象的时候,都要排队拿锁,等候时间长,效率稍差// synchronized (Bank.class) {//     if (instance == null) {//         instance = new Bank();//     }// }// return instance;// 方式二:效率稍高,只要第一个进去了,后面的就不用拿锁,直接返回,双重检验if (instance == null) {synchronized (Bank.class) {if (instance == null) {instance = new Bank();}}}return instance;}
}

完整版

public class SingletonLazyTest {public static void main(String[] args) {Bank bank = Bank.getInstance();}
}class Bank {private Bank() {}private static Bank instance = null;public static Bank getInstance(){if (instance == null) {synchronized (Bank.class) {if (instance == null) {instance = new Bank();}}}return instance;}
}

线程死锁问题

演示死锁问题

public class ThreadTest9 {public static void main(String[] args) {StringBuffer str1 = new StringBuffer();StringBuffer str2 = new StringBuffer();new Thread(){@Overridepublic void run() {synchronized (str1) {str1.append("a");str2.append(1);try {sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (str2) {str1.append("b");str2.append(2);System.out.println(str1);System.out.println(str2);}}}}.start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (str2) {str1.append("c");str2.append(3);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (str1) {str1.append("d");str2.append(4);System.out.println(str1);System.out.println(str2);}}}}).start();}
}

线程a拿到str1的锁(同步监视器),因为有嵌套的同步代码块,所以要结束当前a线程,必须要拿到str2的锁。

而线程b与线程a同时进行,b线程拿到str2的锁,也是因为嵌套的同步代码块,所有要结束当前b线程,必须要拿到str1的锁

因为a线程拿到了str1的锁,所以b线程无法得到str1的锁,所以b线程阻塞,等待str1的锁释放

同理,b线程拿到str2的锁,所以a线程无法得到str2的锁,所以a线程阻塞,等待str2的锁释放

两个线程各自拿着对方的锁,所以造成线程死锁

解决方法

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

线程安全问题 — JDK5.0新增

方式三:Lock锁

public class ThreadCreateLockTest {public static void main(String[] args) {Window7 window7 = new Window7();Thread t1 = new Thread(window7);Thread t2 = new Thread(window7);Thread t3 = new Thread(window7);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}class Window7 implements Runnable{private int ticket = 100;// 实例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock();// 实例化ReentrantLock,参数为true,让他们公平竞争,当线程a出来之后,不会立马重新进行争夺,等后面的进程完成之后再重新争夺//private ReemtrantLock lock = new ReentrantLock(true);@Overridepublic void run() {while (true) {try {// 调用锁定方法lock()lock.lock();if (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 票号为:" + ticket);ticket--;} else {break;}} finally {// 调用解锁方法unlock()lock.unlock();}}}
}
  • 在线程中实例化ReentranLock对象,然后调用lock() 方法上锁,再调用unlock() 方法解锁。
  • 操作的代码部分要用try-finally包含,finally中写unlock();

synchronized与Lock对比

线程面试题1

  • synchronized与lock的异同

    • 相同:二者都可以解决线程安全问题
    • 不同
      • synchronized机制在执行完相应的同步代码之后,自动释放同步监视器
      • lock需要手动的启动同步监视器( lock() ),同时结束同步也需要手动的实现( unlock() )

线程的通信

线程之间的交互

使用wait()和notify()/notifyAll()

  • wait() 将线程进入阻塞状态,同时线程释放同步监视器

  • notify() 执行此方法,就会唤醒被wait() 的一个线程,如果有多个线程被wait() 那么就会根据优先级唤醒一个线程

  • notifyAll() 将所有被wait() 的线程唤醒

  • 说明

    • 这些方法代码块只能在同步代码块或同步方法当中
    • 这些方法的调用者,必须是同步代码块或同步方法中的同步监视器
    • 否则会出现IllegalMonitorStateException异常
    • 这些方法不是定义在Thread中,而是定义在java.lang.Object 中(因为调用者可以是任何对象)
// 两个线程交替打印1~100的数
while(true){synchronized(this){this.notify();//调用者和同步监视器相同,如果都是this可省略//notifyAll();if(number <= 100){sout(Thread.currentThread.getName() + "打印:" + number);number++;try{this.wait();//调用者和同步监视器相同,如果都是this可省略}catch(InterruptedException e){e.printStackTrace();}}else{break;}}
}

sleep()和wait() 异同

  • 相同点

    • 两个方法都可以使得当前的线程进入阻塞状态
  • 不同点
    • 两个方法声明的位置不同

      • Thread类中声明sleep() ,Object类中声明wait()
    • 调用的要求不同
      • sleep() 可以在任意需要的场景下调用
      • wait() 必须使用在同步代码块或同步方法中
    • 如果两个方法都声明在同步代码块或同步方法中,是否会释放同步监视器
      • sleep() 执行之后不会释放同步监视器
      • wait() 执行之后会释放同步监视器

线程通信经典例题:生产者与消费者问题

public class ThreadProducerConsumerTest {public static void main(String[] args) {Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);Thread pt1 = new Thread(p1);pt1.setName("生产者1");pt1.start();Consumer c1 = new Consumer(clerk);Thread ct1 = new Thread(c1);ct1.setName("消费者1");ct1.start();}
}/*** 店铺*/
class Clerk{private int productCount = 0;/*** @Author LiangHui* @Description 进货* @Date 2020/7/17 17:40 * @param  * @return void*/public synchronized void production() {if (productCount < 20) {productCount++;System.out.println(Thread.currentThread().getName() + "生产产品 " + productCount);// 每生产一个产品就可以将所有消费者线程唤醒,防止同类之间相互唤醒导致假死状态notifyAll();} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}/*** @Author LiangHui* @Description 卖出* @Date 2020/7/17 17:40* @param* @return void*/public synchronized void consumption() {if (productCount > 0) {System.out.println(Thread.currentThread().getName() + "消费产品 " + productCount);productCount--;// 每消费一个产品就可以将所有生产者线程唤醒,防止同类之间相互唤醒导致假死状态notifyAll();} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}/*** 生产者*/
class Producer implements Runnable {private Clerk clerk;public Producer(Clerk clerk) {this.clerk = clerk;}/*** @Author LiangHui* @Description 调用production方法进行生产* @Date 2020/7/17 17:47* @param* @return void*/@Overridepublic void run() {while (true) {clerk.production();}}
}/*** 消费者*/
class Consumer implements Runnable {private Clerk clerk;public Consumer(Clerk clerk) {this.clerk = clerk;}/*** @Author LiangHui* @Description 调用consumption方法进行消费* @Date 2020/7/17 17:48* @param* @return void*/@Overridepublic void run() {while (true) {clerk.consumption();}}
}
  • 生产者和消费者扶着负责生产和消费,而生产和消费的具体体现就是店铺进货和卖出,所以将具体的实现方法写在了店铺类中

  • 生产和消费无限进行,所以在run() 中写入while(true) 语句

  • 每生产或消费一个产品就可以将所有生产者或消费者线程唤醒,防止同类之间相互唤醒,导致假死状态,所以使用notifyAll()

创建多线程方式三:实现Callable接口 — JDK5.0新增

  1. 创建一个实现Callable接口的实现类

  2. 实现call() 方法,将此线程需要执行的操作声明在call() 方法中

  3. 创建Callable接口实现类的对象

  4. 将此Callable接口实现类的对象,作为参数传递到FutureTask构造器中,创建FutureTask的对象

  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() 方法

  6. 如果需要返回值:获取Callable中的call方法的返回值

    get() 返回值即为FutureTask构造器参数callable实现类重写的call() 的返回值

public class ThreadCreateCallableTest {public static void main(String[] args) {// 3.创建Callable接口实现类的对象MyThread4 myThread4 = new MyThread4();// 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask1 = new FutureTask(myThread4);FutureTask futureTask2 = new FutureTask(myThread4);// 5.将FutureTask的对象作为参数传递到Thread类中,创建Thread对象,并调用start()Thread t1 = new Thread(futureTask1);Thread t2 = new Thread(futureTask2);t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();// 6.获取Callable中call方法的返回值// get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值try {Object sum1 = futureTask1.get();Object sum2 = futureTask2.get();System.out.println("总和为:" + sum1 + "," + sum2);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
// 1.创建一个实现Callable的实现类
class MyThread4 implements Callable {/*** @Author LiangHui* @Description 2.重写call()方法 将100以内的偶数累加* @Date 2020/7/17 22:40* @param* @return java.lang.Object*/@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "偶数" + i);sum += i;}}return sum;}
}
  • 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

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

创建多线程方式四:使用线程池 — JDK5.0新增

开发中常用线程池

更多的是用框架来实现的

  • ExecutorService
  • Executors
  1. 根据需求创建线程池,提供指定线程数量的线程池

    ExecutorService service = Executors.newFixedThreadPool(10);

  2. 用实现Runnable或者实现Callable的方式写线程的操作

  3. 将实现类创建的对象放到线程池中

    1. Runnable使用:service.execute();
    2. Callable使用:service.submit(); 可以接收返回值,要用FutureTask接收,然后get()
  4. 关闭线程池:service.shutdown();

设置线程池属性

  • 因为创建线程池返回的对象赋给的是ExecutorService接口,属性方法少,所以要对接口对象进行强转,强转为接口的实现类

    ExecutorService service = Executors.newFixedThreadPool(10);
    ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
    // 设置属性
    service1.setCorePoolSize(15);
    ...
    

二、Java常用类

1、String类

概述

  • String声明为final的,不可被继承

  • String实现了Serializable接口:表示字符串是支持序列化的

    ​ 实现了Comparable接口:表示String可以比较大小

  • String内部定义了final char[] value用于存储字符串数据

  • String:代表不可变的字符序列。简称:不可变性。

    体现:

    • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
    • 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

    只要对字符串进行修改,都是重新在方法区(含字符串常量池)中新建

  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

  • 字符串常量池中是不会存储相同内容的字符串的。

String str1 = “abc”;与string str2 = new String(“abc”);的区别

练习1

面试题:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象

答:两个。一个是堆空间中的new的结构,另一个是char[]对应的常量池中的数据:“abc”。

字符串特性

  • intern()方法
  • 如果String声明为final 那么拼接的话再比较就是true

面试题1

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.println(ex.str); // goodSystem.out.println(ex.ch); // best}
}

String常用方法1

  • int length():返回字符串的长度: return value.length
  • char charAt(int index): 返回某索引处的字符return value[index]
  • boolean isEmpty():判断是否是空字符串:return value.length == 0
  • String toLowerCase()
  • String toUpperCase()
  • String trim()
  • boolean equals(Object obj):比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String anotherString)
  • String concat(String str)
  • int compareTo(String anotherString)
  • String substring(int beginIndex)
  • String substring(int beginIndex, int endIndex)

String常用方法2

  • boolean endsWith(String suffix)
  • boolean startsWith(String prefix)
  • boolean startsWith(String prefix, int toffset)
  • boolean contains(CharSequence s)
  • int indexOf(String str)
  • int indexOf(String str, int fromIndex)
  • int lastIndexOf(String str)
  • int lastIndexOf(String str, int fromIndex)

注:indexOf和lastIndexOf方法如果未找到都是返回-1

当indexOf(str)和lastIndexOf(str)相同时,要不字符串中没有,要不就只有一个

所以可以用这个来判断字符串中指定的子字符串是否只有一个

String常用方法3

  • 替换:
  • String replace(char oldChar, char newChar)
  • String replace(CharSequence target, CharSequence replacement)
  • String replaceAll(String regex, String replacement)
  • String replaceFirst(String regex, String replacement)
  • 匹配:
  • boolean matches(String regex)
  • 切片:
  • String[] split(String regex)
  • String[] split(String regex, int limit)

String常用方法整理 (没记牢的)

  • String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写

  • String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为

  • String trim():返回字符串的副本,忽略前导空白和尾部空白

  • 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):当字符串中包含此序列时,返回true

  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引

  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始

  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引

  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

  • String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。

  • String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  • String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

  • String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

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

  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

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

String 与 char[]之间的转换

String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器

String 与 byte[]之间的转换

编码: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);//没有出现乱码。原因:编码集和解码集一致!}

StringBuffer和StringBuilder

  • 异同

    • String:不可变的字符序列

    • StringBuffer:可变的字符序列:线程安全的,效率低;

    • StringBuilder:可变的字符序列:jdk5.0新增,线程不安全的,效率高;

      可变的字符序列,在调用方法时,改变的是自身

StringBuffer底层实现

  1. 创建StringBuffer时,是创建了一个char[16]的数组,是固定长度的

    而String创建时,是创建了一个char[0]的数组,是根据参数来选择长度的

  2. StringBuffer s1 = new StringBuffer(“abc”);

    底层:char[] value = new char[“abc”.length() + 16]。

    但是s1.length() 还是3

  3. 当需要扩容时,默认情况下,扩容为原来的2倍+2,同时将原有数组中的元素复制到新的数组中

在开发当中:

  1. 使用StringBuffer

  2. 当知道大概字符串长度时,创建StringBuffer对象时,最好规定长度,避免扩容复制,提高效率

    StringBuffer s = new StringBuffer(int capacity);

    StringBuilder s = new StringBuilder(int capacity);

StringBuffer常用方法

  • 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) : 更改索引位置的字符

String算法题-1

2、JDK8之前的日期时间API

System类中的currentTimeMillis()

long time = System.currentTimeMillis();
// 返回当前时间与1970年1月1日0时0分0喵=秒之间以毫秒为单位的时间差
// 称为时间戳
// 创建两个时间戳相差,得出运行时间,比较效率等

Date类

  • java.util.Date类

构造器一:Date()

Date date1 = new Date();
sout(date1.toString()); // Wed Jul 22 15:56:20 CST 2020
sout(date1.getTime()); // 获取时间戳(同currentTimeMillis)

构造器二:Date(long date)

将getTime获得的毫秒数转换成时间,将指定毫秒数换算成时间

  • java.sql.Date类,父类是java.util.Date类
java.sql.Date date1 = new java.sql.Date(231654489456L);
System.out.println(date1);
  • java.util.Date转为java.sql.Date
Date date1 = new Date();
java.sql.Date date2 = new java.sql.Date(date1.getTime());

SimpleDateFormat类

  • java.text.SimpleDateFormat

public void test3() throws ParseException {Date date = new Date();System.out.println("使用默认构造器");SimpleDateFormat sdf = new SimpleDateFormat();// 格式化String format1 = sdf.format(date);System.out.println("格式化:" + format1);// 解析Date date2 = sdf.parse("20-7-23 下午4:00");System.out.println("解析:" + date2);System.out.println();System.out.println("使用指定格式的构造器");SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");String format2 = sdf2.format(date);System.out.println("格式化:" + format2);Date date3 = sdf2.parse("2020-07-22 12:00:00");System.out.println("解析:" + date3);
}
  • 开发中常用指定格式的构造器
”2020-07-31“ 转换为 java.sql.Date类
public void test4() throws ParseException {String str = "2020-07-31";SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date = sdf.parse(str);java.sql.Date dateSql = new java.sql.Date(date.getTime());System.out.println(dateSql);
}

java.util.Calendar(日历)类,抽象类

public void testCalendar(){//1.实例化//方式一:创建其子类(GregorianCalendar)的对象//方式二:调用其静态方法getInstance()Calendar calendar = Calendar.getInstance();//        System.out.println(calendar.getClass());//2.常用方法//get()int days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//set()//calendar可变性calendar.set(Calendar.DAY_OF_MONTH,22);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);//add()calendar.add(Calendar.DAY_OF_MONTH,-3);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);//getTime():日历类---> DateDate date = calendar.getTime();System.out.println(date);//setTime():Date ---> 日历类Date date1 = new Date();calendar.setTime(date1);days = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(days);}

3、JDK8中的日期时间API

java.time API

  • LocalDate

  • LocalTime

  • LocalDateTime

    高频

方法
  • now ()

    获取当前的日期、时间、日期+时间

    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();
    
  • of ()

    设置指定的年、月、日、时、分、秒。没有偏移量

    LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
    
  • getXxx()

    获取相关的属性

    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.getDayOfWeek());
    System.out.println(localDateTime.getMonth());
    System.out.println(localDateTime.getMonthValue());
    System.out.println(localDateTime.getMinute());
    
  • withXxx()

    设置相关的属性

    LocalDate localDate1 = localDate.withDayOfMonth(22);
    LocalDateTime localDateTime2 = localDateTime.withHour(4);
    //不可变性
    LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
    LocalDateTime localDateTime4 = localDateTime.minusDays(6);
    

Instant

类似于java.util.Date类

//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//2019-02-18T07:29:41.719Z//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);//ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);

DateTimeFormat

类似于SimpleDateFormat

//方式一:预定义的标准格式。如: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);//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(localDateTime);
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);

4、比较器

  • Comparable接口的使用举例: 自然排序

    1. 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
    2. 像String、包装类重写compareTo()方法以后,进行了从小到大的排列
    3. 重写compareTo(obj)的规则:
      如果当前对象this大于形参对象obj,则返回正整数,
      如果当前对象this小于形参对象obj,则返回负整数,
      如果当前对象this等于形参对象obj,则返回零。
    4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
  • Comparator接口的使用:定制排序

    1. 背景:
      当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
      或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,
      那么可以考虑使用 Comparator 的对象来排序
    2. 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
      如果方法返回正整数,则表示o1大于o2;
      如果返回0,表示相等;
      返回负整数,表示o1小于o2。
/*** 一、说明:Java中的对象,正常情况下,只能进行比较:==  或  != 。不能使用 > 或 < 的*          但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。*          如何实现?使用两个接口中的任何一个:Comparable 或 Comparator** 二、Comparable接口与Comparator的使用的对比:*    Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。*    Comparator接口属于临时性的比较。*/
public class CompareTest {/*Comparable接口的使用举例:  自然排序1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列3. 重写compareTo(obj)的规则:如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序*/@Testpublic void test1(){String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};//Arrays.sort(arr);System.out.println(Arrays.toString(arr));}@Testpublic void test2(){Goods[] arr = new Goods[5];arr[0] = new Goods("lenovoMouse",34);arr[1] = new Goods("dellMouse",43);arr[2] = new Goods("xiaomiMouse",12);arr[3] = new Goods("huaweiMouse",65);arr[4] = new Goods("microsoftMouse",43);Arrays.sort(arr);System.out.println(Arrays.toString(arr));}/*Comparator接口的使用:定制排序1.背景:当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。*/@Testpublic void test3(){String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};Arrays.sort(arr,new Comparator(){//按照字符串从大到小的顺序排列@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof String && o2 instanceof  String){String s1 = (String) o1;String s2 = (String) o2;return -s1.compareTo(s2);}// return 0;throw new RuntimeException("输入的数据类型不一致");}});System.out.println(Arrays.toString(arr));}@Testpublic void test4(){Goods[] arr = new Goods[6];arr[0] = new Goods("lenovoMouse",34);arr[1] = new Goods("dellMouse",43);arr[2] = new Goods("xiaomiMouse",12);arr[3] = new Goods("huaweiMouse",65);arr[4] = new Goods("huaweiMouse",224);arr[5] = new Goods("microsoftMouse",43);Arrays.sort(arr, new Comparator() {//指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof Goods && o2 instanceof Goods){Goods g1 = (Goods)o1;Goods g2 = (Goods)o2;if(g1.getName().equals(g2.getName())){return -Double.compare(g1.getPrice(),g2.getPrice());}else{return g1.getName().compareTo(g2.getName());}}throw new RuntimeException("输入的数据类型不一致");}});System.out.println(Arrays.toString(arr));}
}

5、其他类

System

Math

BigInteger

BigDecimal

三、枚举类与注解

枚举类的使用

类的对象只有有限个,确定。

当需要定义一组常量时,强烈建议使用枚举类

如果枚举类中只有一个对象,则可以作为单例模式的实现方式

JDK5.0之前,自定义枚举类

public class SeasonTest1 {public static void main(String[] args) {Season spring = Season.SPRING;System.out.println(spring);}
}// 枚举类
class Season {// 1.声明Season对象的属性:private final修饰private final String seasonName;private final String seasonDesc;// 2.私有化类的构造器,并给对象属性赋值Season(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}// 3.提供当前枚举类的多个对象:public static final的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("冬天", "冬天冷");// 4.其他诉求1:获取枚举类对象的属性public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}// 4.其他诉求2:提供toString方法@Overridepublic String toString() {return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}
}

JDK5.0,使用enum关键字定义枚举类

public class EnumClass {public static void main(String[] args) {spring = EnumClass1.SPRING;System.out.println(spring.toString());}
}enum EnumClass1{// 1.提供当前枚举类的对象,多个对象之间用逗号隔开SPRING("春天", "温暖"),SUMMER("夏天", "炎热"),AUTUMN("秋天", "凉爽"),WINTER("冬天", "寒冷");private final String seasonName;private final String seasonDesc;EnumClass1(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}@Overridepublic String toString() {return "EnumClass1{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}
}
常用方法
toString()

返回枚举类对象的名称

sout(spring.toString); // 打印的是SPRING
values()

返回所有的枚举类对象构成的数组

EnumClass1[] values = EnumClass1.values();
for(int i = 0;i < values.length;i++){System.out.println(values[i]);values[i].show();
}
valueOf(String objName)

返回枚举类中对象名是objName的对象

EnumClass1 winter = EnumClass1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
使用enum定义的枚举类实现接口
  • 情况一:实现接口,在enum类中实现抽象方法
  • 情况二:让枚举类的对象分别实现接口中的抽象方法
public class EnumClass {public static void main(String[] args) {EnumClass1 spring = EnumClass1.SPRING;spring.show();}
}interface Info {void show();
}enum EnumClass1 implements Info{// 1.提供当前枚举类的对象,多个对象之间用逗号隔开SPRING("春天", "温暖") {@Overridepublic void show() {System.out.println("春暖花开");}},SUMMER("夏天", "炎热"){@Overridepublic void show() {System.out.println("夏日炎炎");}},AUTUMN("秋天", "凉爽"){@Overridepublic void show() {System.out.println("秋高气爽");}},WINTER("冬天", "寒冷"){@Overridepublic void show() {System.out.println("冰天雪地");}};private final String seasonName;private final String seasonDesc;EnumClass1(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}@Overridepublic String toString() {return "EnumClass1{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}// @Override// public void show() {//// }
}

注解的使用

Annocation的使用示例

  • 示例一:生成文档相关的注解

  • 示例二:在编译时进行格式检查(JDK内置的三个基本注解)

    • @Override: 限定重写父类方法, 该注解只能用于方法
    • @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
    • @SuppressWarnings: 抑制编译器警告
  • 示例三:跟踪代码依赖性,实现替代配置文件功能

元注解

修饰其他注解的注解

对现有的注解进行解释说明的注解

通常都会有Retention和Target元注解

  • Retention:指定所修饰的 Annotation 的生命周期:SOURCE \ CLASS(默认行为)\ RUNTIME

    只有声明为RUNTIME生命周期的注解,才能通过反射获取。

  • Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(可以用来修饰结构,例如修饰类、接口、枚举类、构造器等)

Documented和Inherited出现的频率较低

  • Documented:表示所修饰的注解在被javadoc解析时,保留下来。
  • Inherited:被它修饰的 Annotation 将具有继承性。

四、*集合

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。

说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

数组在存储方面的特点

特点

  • 一旦初始化以后,其长度就确定了。

  • 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。

    比如:String[] arr;int[] arr1;Object[] arr2;

缺点

  •  一旦初始化以后,其长度就不可修改。
    
  •  数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
    
  •  获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
    
  •  数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
    

集合框架

Collection接口

单列集合,用来存储一个一个的对象

List接口

存储有序的,可重复的数据。(动态数组)

通常使用List代替数组

ArrayList

ArrayList向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().

常用方法
public class CollectionTest1 {@Testpublic void test1() {Collection coll = new ArrayList();// 1.add(Object e);将元素e添加到集合coll中coll.add(123);coll.add("abc");coll.add('c');coll.add(false);coll.add(new Date());// 2.size():获取添加的元素个数System.out.println(coll.size()); // 4// 3.addAll():将coll2中的元素添加到当前的集合coll中Collection coll2 = new ArrayList();coll2.add("***");coll2.add("&&&");coll.addAll(coll2);System.out.println(coll.size()); // 6System.out.println(coll);// 4.clear():清空集合元素// coll.clear();// 5.isEmpty():判断集合是否为空,空为true// System.out.println(coll.isEmpty());// 6.contains(Object obj):判断当前集合中是否包含obj,包含为true// 我们在判断时会调用obj对象所在类的equals()。自定义的类要重写equals()// 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().boolean contains1 = coll.contains(1234);boolean contains2 = coll.contains(123);System.out.println(contains1); // falseSystem.out.println(contains2); // true// 7.containsAll(Collection coll):判断形参coll3中的所有元素是否都存在与当前集合中Collection coll3 = Arrays.asList(123, "abc");System.out.println(coll.containsAll(coll3)); // true// 8.remove(Object obj):从当前集合中移除obj元素,不存在返回false,移除成功返回true// 所以移除的元素也需要重写equals()System.out.println(coll.remove(123)); // true// 9.removeAll(Collection coll):从当前集合中移除coll4中所有的元素,只要有一个能够移除,那就就返回trueCollection coll4 = Arrays.asList("***", "&&");System.out.println(coll.removeAll(coll4)); // true// 10.retainAll(Collection coll):交集,获取coll和coll3集合的交集,并返回给coll(删掉不同的,保留一样的)// System.out.println(coll);// coll.retainAll(coll3);// System.out.println(coll); // 将coll和coll3相同的返回到coll中// 11.equals(Object obj):比较两个对象是否相等,每一个都得是一样的,ArrayList是有序的,所以比较的时候也要是有序的Collection coll5 = new ArrayList();coll5.add(123);coll5.add('c');System.out.println(coll.equals(coll5)); // false// 12.hashCode():返回当前对象的哈希值System.out.println(coll.hashCode());// 13.toArray():集合 --> 数组的转换Object[] arr = coll.toArray();for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}// 13拓展Arrays.asList(arr):数组 --> 集合的转换List obj = Arrays.asList(arr);System.out.println(obj);}
}
Iterator迭代器接口

主要用于遍历Collection集合中的元素

@Test
public void testIterator() {Collection coll = new ArrayList();coll.add(123);coll.add(456);coll.add("abc");coll.add("def");coll.add("***");// iterator():返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中测试Iterator iterator = coll.iterator();// 方式一,不用// System.out.println(iterator.next());// System.out.println(iterator.next());// 方式二,不用// for (int i = 0; i < coll.size(); i++) {//     System.out.println(iterator.next());// }// 方式三,推荐使用,hasNext()和next()搭配使用while (iterator.hasNext()) { // 有元素就进入,没有退出System.out.println(iterator.next());}iterator = coll.iterator();// 删除集合中指定数据while (iterator.hasNext()) {Object obj = iterator.next(); // 迭代if ("***".equals(obj)) {iterator.remove();}}// 重新遍历集合iterator = coll.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}
}
foreach循环迭代 jdk5.0新增

用于遍历集合、数组

将集合、数组中的元素取出来赋给定义的局部变量

@Test
public void testForeach() {Collection coll = new ArrayList();coll.add(123);coll.add(456);coll.add("abc");coll.add("def");coll.add("***");// for(集合中元素的类型 局部变量 : 集合对象)for (Object obj : coll) {System.out.println(obj);}// for(数组中元素的类型 局部变量 : 数组对象)int[] arr = new int[]{1,2,3,4,5};for (int i : arr) {System.out.println(i);}
}
LinkedList
Vector
List接口常用方法总结
增: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
源码分析
ArrayList
  • jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

  • jdk 8情况下
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
...
后续的添加和扩容操作与jdk 7无异。
  • 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList

LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null

list.add(123);//将123封装到Node中,创建了Node对象。

其中,Node定义为:体现了LinkedList的双向链表的说法

private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next){this.item = element;this.next = next;this.prev = prev;}
}
Vector

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。

在扩容方面,默认扩容为原来的数组长度的2倍。

面试题:ArrayList、LinkedList、Vector异同
相同点
  • 三个类都实现了List接口,存储数据的特点相同:有序的,可重复的数据
不同点
  • ArrayList

    • 作为List接口的主要实现类(默认使用)
    • 线程不安全的,效率高
    • 底层使用Object[] elementData存储
  • LinkedList
    • 对于频繁的插入、删除操作,使用此类效率比ArrayList高
    • 底层使用双向链表存储
  • Vector
    • 作为List接口古老实现类(jdk1.0),不会使用到
    • 线程安全的,效率低
    • 底层使用Object[] elementData存储

Set接口

存储无序的,不可重复的数据。(数学中的集合->无序不重复)

Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

  • 无序性:无序性不等于随机性(遍历的顺序还是按照添加的顺序打印)。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

  • 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

  • HashSet、LinkedHashSet需要重写equals()、hashCode()

  • TreeSet需要重写Comparable:compareTo(Object obj)、Comparator:compare(Object o1,Object o2)

HashSet

作为Set接口的主要实现类;线程不安全的;可以存储null值,底层用的是HashMap

LinkedHashSet

作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历。

对于频繁的遍历操作,LinkedHashSet效率高于HashSet.

LinkedHashSet是在HashSet的基础上,新增一个链表,在添加数据的同时,按顺序将数据先后用链表链接,记录此数据的前一个数据和后一个数据,提高遍历效率。

TreeSet

可以按照添加对象的指定属性(例如指定int等),进行排序。

Map接口

双列集合,用来存储一对(key, value)的数据。(数学中的函数->y=f(x), 映射

不同的key可以对应多个value,多个value不能对应多个key

HashMap

作为Map的主要实现类,线程不安全,效率高;存储null的key和value

LinkedHashMap

保证在遍历map元素时,可以按照添加的顺序实现遍历

原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素

对于频繁的遍历操作,此类执行效率要高于HashMap

TreeMap

保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或者定制排序

底层使用的是红黑树

Hashtable

作为古老的实现类,线程安全,效率低;不能存储null的key和value

Properties

常用来处理配置文件。key和value都是String类型

Map结构的理解
  • Map中的key使用Set存储 ----> key所在的类要重写equals()和hashCode()
  • Map中的value使用Collection存储 ----> value所在的类要重写equals()
  • 一个键值对(key-value)构成了一个Entry对象
Map常用方法

put, putAll, remove, clear, size, get, containsKey, containsValue, isEmpty, equals

@Test
public void test1() {Map map = new HashMap();// put 添加map.put("AA", 123); // Key和Value各自的类型都是确定的(泛型)map.put("BB", 456);map.put("CC", 789);// 修改map.put("AA", 102);Map map2 = new HashMap();map2.put("AA", 103);map2.put("DD", 1112);map2.put("EE", 1314);// putAllmap.putAll(map2);// remove(Object obj)Object value = map.remove("AA");map.remove(value);map.remove("CC");// clear(),清空Map中的数据,但是Map不会为nullmap.clear();// size(),Map的大小System.out.println(map.size());System.out.println(map);
}@Test
public void test2() {Map map = new HashMap();map.put("AA", 123);map.put("BB", 456);map.put("CC", 789);// get(Object key),返回key的value值System.out.println(map.get("AA"));// containsKey(Object key),判断Map中是否有这个keyboolean isExist = map.containsKey("AA");System.out.println(isExist);// containValue(Object value),判断Map中是否有这个valueisExist = map.containsValue("123");System.out.println(isExist);// isEmpty(),判断Map是否为空System.out.println(map.isEmpty());Map map2 = new HashMap();map2.putAll(map);// equals(Object obj),判断当前Map和参数对象obj是否相等System.out.println(map.equals(map2));
}
遍历
@Test
public void test3() {Map map = new HashMap();map.put("AA", 123);map.put("BB", 456);map.put("CC", 789);// 遍历所有的key集:keySet(),key构成SetSet set = map.keySet();// 获得Set之后,再使用迭代器Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}// 遍历所有的value集:values()Collection values = map.values();// Iterator iterator1 = values.iterator();// while (iterator1.hasNext()) {//     System.out.println(iterator1.next());// }for (Object object : values) {System.out.println(object);}// 遍历所有的key-value集:entrySet()// 方式一:entrySet()Set entrySet = map.entrySet();Iterator iterator2 = entrySet.iterator();while (iterator2.hasNext()) {Object obj = iterator2.next();// System.out.println(iterator2.next()); // 得到的是entryMap.Entry entry = (Map.Entry)obj;System.out.println(entry.getKey() + "--->" + entry.getValue());}// 方式二:先取得key,再用key得到value
}
面试题
1、HashMap的底层实现原理(JDK 7.0)
  • HashMap map = new HashMap();

    在实例化以后,底层创建了长度是16的一维数组Entry[] table

  • map.put(key1,value1);

    首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

    如果此位置上的数据为空,此时的key1-value1添加成功。

    如果此位置上的数据不为空 (意味着此位置上存在一个或多个数据,以链表形式存在),比较key1和已经存在的一个或多个数据的哈希值:

    ​ 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功

    ​ 如果key1的哈希值与已经存在的某一个数据的哈希值相同,继续比较:调用key1所在类的equals()方法,比较:

    ​ 如果equals()返回false:此时key1-value1添加成功

    ​ 如果equals()返回true:使用value1替换相同key的value值

    扩容为原来的两倍,并将原来的数据复制过来

JDK 8.0相比较JDK7.0的不同:

  • new HashMap():底层没有创建一个长度为16的数组

  • 底层数组是:Node[],而非Entry[]

  • 首次调用put()方法时,底层创建长度为16的数组

  • JDK7底层结构只有:数组 + 链表。JDK8底层结构有:数组 + 链表 + 红黑树

    当数组的某一个索引位置上的元素以链表形式存在的数据个数: > 8 , 且当前数组的长度:> 64 时。

    此时该索引位置上的所有数据改为使用红黑树存储。

2、HashMap和Hashtable的异同
3、CurrentHashMap与Hashtable的异同

Collections工具类

操作Collection、map工具类

@Test
public void test1() {List list = new ArrayList();list.add(123);list.add(1);list.add(456);list.add(43);list.add(43);System.out.println("原顺序:" + list);// reverse(List):反转List中的元素顺序Collections.reverse(list);System.out.println("反转后的顺序:" + list);// shuffle(List):对List中的元素进行随机排序Collections.shuffle(list);System.out.println("随机排序后:" + list);// sort(List):根据元素的自然顺序对指定List集合元素按升序排序// sort(List, Comparator):定制排序// 按自然顺序排序,集合中的元素需要全是Integer类型Collections.sort(list);System.out.println("自然排序:" + list);// swap(List, int, int):交换指定List集合中的i处元素和j处元素Collections.swap(list, 1, 2);System.out.println("交换后的排序" + list);// max(Collection):根据元素的自然排序,返回给定集合中的最大元素// max(Collection, Comparator), min 同理System.out.println(Collections.max(list));// frequency(Collection, Object):返回指定集合中指定元素的出现次数System.out.println(Collections.frequency(list, 43));// copy(List dest, List src):将src中的内容复制到dest中//错误的写法,抛异常:IndexOutOfBoundsException: Source does not fit in dest// List dest = new ArrayList();// Collections.copy(dest, list);List dest = Arrays.asList(new Object[list.size()]);Collections.copy(dest, list);System.out.println("复制后的dest" + dest);// Collections中有多个synchronizedXxx()方法// 该方法可将指定集合包装成线程同步的集合// 从而解决多线程并发访问集合时的线程安全问题// 返回的list1就是线程安全的List list1 = Collections.synchronizedList(list);}

五、泛型

  • 有泛型,都要用

public class GenericTest {/*** @Author LiangHui* @Description 测试没有泛型可能出现的问题* @Date 2020/9/13 8:54 * @param  * @return void*/@Testpublic void test1() {ArrayList list = new ArrayList();// 需求:存放学生成绩list.add(78);list.add(32);list.add(45);list.add(90);// 问题一:类型不安全// list.add("Tom");for (Object score : list) {// 问题二:强转出问题,ClassCastExceptionint stuScore = (Integer) score;System.out.println(stuScore);}}/*** @Author LiangHui* @Description 使用泛型* @Date 2020/9/13 8:58* @param* @return void*/@Testpublic void test2() {ArrayList<Integer> list = new ArrayList<>();list.add(78);list.add(32);list.add(45);list.add(90);// 编译时就会进行类型检查,保证数据的安全// list.add("Tom");for (Integer score : list) {// 避免了强转操作int stuScore = score;System.out.println(stuScore);}Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}/*** @Author LiangHui* @Description 以HashMap为例* @Date 2020/9/13 9:04* @param* @return void*/@Testpublic void test3() {Map<String, Integer> map = new HashMap<>();map.put("Tom", 90);map.put("Jack", 80);map.put("Kevin", 70);map.put("Jerry", 100);// 泛型的嵌套Set<Map.Entry<String, Integer>> entry = map.entrySet();Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();while (iterator.hasNext()) {Map.Entry<String, Integer> e = iterator.next();String key = e.getKey();Integer value = e.getValue();System.out.println(key + "===>" + value);}}/*** @Author LiangHui* @Description 自定义泛型的使用* @Date 2020/9/13 9:53* @param* @return void*/@Testpublic void test4() {// 如果定义了泛型类,实例化没有指明类的方形,则认为此泛型类型为Object类型// 要求:如果定义了类是带泛型的,建议在实例化时要指明类的泛型OrderTest orderTest = new OrderTest();orderTest.setOrderT(123);orderTest.setOrderT("tse");OrderTest<String> order = new OrderTest<>();order.setOrderT("Test");}
}
/*** @ClassName OrderTest* @Description 自定义泛型* @Author LiangHui* @Date 2020/9/13 9:30* @Version V1.0*/
@Data // lombok
public class OrderTest<T> {private String orderName;private int orderId;// 类的内部结构就可以使用类的泛型private T orderT;
}

通配符

当类A是类B的父类,G和G是没有关系的不能相互赋值,二者的共同父类是G<?>,G和G都可以赋值给G<?>

List<String> list1 = null;
List<Object> list2 = null;List<?> list = null; // 此时的<?>就是泛型的通配符,这样就将不同的泛型类型赋值给list了list = list1;
list = list2;// 使用
public void print(List<?> list){Iterator<?> iterator = list.iterator();while(iterator.hasNext()){Object obj = iterator.next();System.out.println(obj);}
}// list<?>不能添加数据,即list.add("a");会报错,只能添加null
// list<?>可以读取数据,读取到list2中的数据
sout(list.get(0));

有限制条件的通配符

六、IO流

File类

File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法。

并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。

后续File类的对象常会作为参数传递到流的构造器中,指明读写的"终点"

File类的创建以及常用方法

/*** @Author LiangHui* @Description 实例化File的三种方式* @Date 2020/9/13 21:46* @param* @return void*/
@Test
public void test1() {// 构造器一:File(String filePath)File file = new File("E:\\系统文件夹\\桌面\\hello.txt");// 构造器二:FIle(String parentPath,String childPath)File file2 = new File("E:\\系统文件夹", "桌面");// 构造器三:File(String parentFile,String childPath)File file3 = new File(file2, "hello.txt");
}/*** @Author LiangHui* @Description File类的常用方法(仅操作文件,不包含文件内容)* @Date 2020/9/13 22:13* @param* @return void*/
@Test
public void test2() {File file = new File("E:\\系统文件夹\\桌面\\hello.txt");// getAbsolutePath:获取绝对路径System.out.println(file.getAbsolutePath());// getPath:获取路径System.out.println(file.getPath());// getName:获取名称System.out.println(file.getName());// getParent:获取上层文件目录路径,若无,返回nullSystem.out.println(file.getParent());// length:获取文件长度(字节数),不能获取目录长度System.out.println(file.length());// lastModified:获取最后一次的修改时间,毫秒值System.out.println(file.lastModified());// String[] list():获取指定目录下的所有文件或者文件目录的名称数组File file1 = new File("E:\\系统文件夹\\桌面");String[] list = file1.list();for (String s : list) {System.out.println(s);}// File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组File[] files = file1.listFiles();for (File f : files) {System.out.println(f);}}/*** @Author LiangHui* @Description file的重命名,把文件重命名为指定的文件路径,相当于移动* @Date 2020/9/13 22:18* @param* @return void*/
@Test
public void test3() {File file1 = new File("E:\\系统文件夹\\桌面\\hello.txt");File file2 = new File("test1.txt");// 要想保证返回的true,需要file1在硬盘中存在,且file2不能在硬盘中存在boolean renameTo = file1.renameTo(file2);System.out.println(renameTo);
}
/*** @Author LiangHui* @Description file常用的判断方法* @Date 2020/9/14 9:29* @param* @return void*/
@Test
public void test4() {File file = new File("test1.txt");// isDirectory():判断是否是文件目录System.out.println(file.isDirectory());// isFile():判断是否是文件System.out.println(file.isFile());// exists():判断是否存在System.out.println(file.exists());// canRead():判断是否可读System.out.println(file.canRead());// canWrite():判断是否可写System.out.println(file.canWrite());// isHidden():判断是否隐藏System.out.println(file.isHidden());
}
/*** @Author LiangHui* @Description 创建、删除硬盘中对应的文件或文件目录* @Date 2020/9/14 9:36* @param* @return void*/
@Test
public void test5() throws IOException {File file1 = new File("test2.txt");// 文件的创建和删除if (!file1.exists()) {file1.createNewFile(); // 若存在,则不创建,返回falseSystem.out.println("创建成功");} else {file1.delete(); // 文件和文件目录均可删除System.out.println("删除成功");}File file2 = new File("mkdir");// mkdir文件目录的创建,上层目录不存在,则不会创建if (file2.mkdir()) {System.out.println("文件目录创建成功");} else {file2.delete();System.out.println("文件目录删除成功");}File file3 = new File("mkdir\\mkdirs");// mkdirs如果创建的上一层目录是不存在的,则会一并创建if (file3.mkdirs()) {System.out.println("递归创建");}
}

流的分类

  • 操作数据单位:字节流、字符流

    • 字节流处理01二进制数(图片音视频等),字符流处理文本(.txt等)
    • 字节流处理:.jpg, .mp3, .mp4, .doc, .ppt等
    • 字符流处理:.txt, .java, .c, .cpp等
  • 数据的流向:输入流、输出流
  • 流的角色:节点流、处理流

流的体系结构

抽象基类

  • InputStream -> 字节输入流
  • OutputStream -> 字节输出流
  • Reader -> 字符输入流
  • Writer -> 字符输出流

节点流(或文件流)

  • FileInputStream
  • FileOutputStream
  • FileReader
  • FIleWriter

开发中不会单独使用这四个,因为效率差一点,一般还会使用缓冲流

处理字节流

和处理字符流类似

  • 对于复制操作,使用字节流可以复制txt文本,但是如果txt文本有中文,使用字节流输入的话,可能会出现乱码。复制就没有影响
  • 字符流不能复制字节流
处理字符流
FileReader
/*** @Author LiangHui* @Description FileReader字符节点输入流测试* @Date 2020/9/14 13:57* @param  * @return void*/
@Test
public void testFileReader1() {/* 不使用throws处理异常因为当出现异常时,将异常抛出后,流不会关闭所以适用try-catch来处理,一定要执行流的关闭操作*/FileReader fr = null;try {// 1、实例化File类的对象,指明要操作的文件,读入的文件一定要存在,否则报错File file1 = new File("test1.txt");// 2、提供具体的流fr = new FileReader(file1);// 3、数据的读入// read():返回读入的一个字符。如果达到文件末尾,返回-1int data;while ((data = fr.read()) != -1) {System.out.print((char) data);}} catch (IOException e) {e.printStackTrace();} finally {try {if (fr != null) { // 有可能fr没有实例化就抛出了异常,所以需要判断是否为null// 4、流的关闭操作fr.close();}} catch (IOException e) {e.printStackTrace();}}
}/*** @Author LiangHui* @Description FileReader字符节点输入流测试 2.0,使用read的重载方法* @Date 2020/9/14 14:20* @param* @return void*/
@Test
public void testFileReader2() {FileReader fr = null;try {// 1、File类的实例化File file1 = new File("test1.txt");// 2、FileReader流的实例化(根据需要操作的文件来确定流)fr = new FileReader(file1);// 3、读入的操作(或者写出)// read(char[] cbuf):返回每次读入cbuf数组中的字符个数,如果达到文件末尾,返回-1char[] cbuf = new char[2];int len;while ((len = fr.read(cbuf)) != -1) {// 方式一// 错误的写法,最后一次未装满的还是保留着上一次的字符// for (int i = 0; i < cbuf.length; i++) {//     System.out.println(cbuf[i]);// }// 正确的写法// for (int i = 0; i < len; i++) {//     System.out.println(cbuf[i]);// }// 方式二String str = new String(cbuf, 0, len); // 添加条件System.out.println(str);}} catch (IOException e) {e.printStackTrace();} finally {try {if (fr != null) {// 4、资源的关闭fr.close();}} catch (IOException e) {e.printStackTrace();}}}
FileWriter
/*** @Author LiangHui* @Description FileWriter字符节点输出流测试,从内存中写出数据到硬盘的文件里* @Date 2020/9/14 14:53* @param  * @return void*/
@Test
public void testFileWriter1() {FileWriter fw = null;try {// 1、File类的实例化// 不存在的文件自动创建,已存在的文件覆盖写// 要追加写,在实例化流的时候使用不同的构造器,加参数(true为追加),不加参数默认为falseFile file1 = new File("test1.txt");// 2、提供FileWriter的对象,用于数据的写出fw = new FileWriter(file1);// FileWriter fw = new FileWriter(file1, true); // 追击写// 3、写出操作fw.write("I have a dream!\n");fw.write("you need to have a dream!");} catch (IOException e) {e.printStackTrace();} finally {if (fw != null) {// 4、流资源的关闭try {fw.close();} catch (IOException e) {e.printStackTrace();}}}
}/*** @Author LiangHui* @Description 输入和写出结合,读出test1.txt文件中的数据,写入到test2.txt中* @Date 2020/9/14 15:06* @param* @return void*/
@Test
public void testFileReaderWriter() {FileReader fileReader = null;FileWriter fileWriter = null;try {// 1、创建File类的对象,指明读入和写入的文件File srcFile = new File("test1.txt");File destFile = new File("test2.txt");// 2、创建输入流和输出流的对象fileReader = new FileReader(srcFile);fileWriter = new FileWriter(destFile);// 3、数据的读入和写出操作char[] cbuf = new char[5];int len; // 记录每次读入到cbuf数组中的字符个数while ((len = fileReader.read(cbuf)) != -1) {fileWriter.write(cbuf, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {// 4、流资源关闭try {if (fileReader != null) {fileReader.close();}} catch (IOException e) {e.printStackTrace();} // 将fileWriter.close() 写入到finally中也可以try {if (fileWriter != null) {fileWriter.close();}} catch (IOException e) {e.printStackTrace();}}
}

缓冲流(处理流中的一种)

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

开发中常用缓冲流,提高节点流的读取和写入速度

提高速度的原因:内部提供了一个缓冲区

BufferedInputOutputStream
/*** @Author LiangHui* @Description 复制非文本文件* @Date 2020/9/14 17:20* @param* @return void*/
@Test
public void testBufferedStream() {BufferedInputStream bis = null;BufferedOutputStream bos = null;try {// 1 造文件File srcFile = new File("1989.png");File destFile = new File("1989-2.png");// 2 造流// 2.1 造节点流FileInputStream fis = new FileInputStream(srcFile);FileOutputStream fos = new FileOutputStream(destFile);// 2.2 造缓冲流bis = new BufferedInputStream(fis);bos = new BufferedOutputStream(fos);// 3 复制的细节:读取、写入byte[] buffer = new byte[10];int len;while ((len = bis.read(buffer)) != -1) {bos.write(buffer, 0, len);// 刷新缓冲区:缓冲区满了就清空// bos.flush(); // 不用再写,调用的时候源码里有写}} catch (IOException e) {e.printStackTrace();} finally {// 4 资源关闭:先关闭外层的流,再关闭内层的流if (bos != null) {try {bos.close();} catch (IOException e) {e.printStackTrace();}}if (bis != null) {try {bis.close();} catch (IOException e) {e.printStackTrace();}}// 关闭外层流的同时,内层流也会自动关闭。所以内层流的关闭,可以省略// fos.close();// fis.close();}
}
BufferedReaderWriter
/*** @Author LiangHui* @Description 使用BufferedReader和BufferedWriter实现文本复制* @Date 2020/9/15 7:53* @param* @return void*/
@Test
public void testBufferedReaderWriter() {BufferedReader br = null;BufferedWriter bw = null;try {// 将一二步整合到一步br = new BufferedReader(new FileReader(new File("test1.txt")));bw = new BufferedWriter(new FileWriter(new File("test3.txt")));// 读写操作// 方式一:使用char[]数组// char[] cbuf = new char[1024];// int len;// while ((len = br.read(cbuf)) != -1) {//     bw.write(cbuf, 0, len);// }// 方式二:使用StringString data;while ((data = br.readLine()) != null){ // 一次读一行// bw.write(data); // data中不包含换行符// bw.write(data + "\n"); // 方式一:手动添加换行符bw.write(data);bw.newLine(); // 换行 方式二:使用newLine方法}} catch (IOException e) {e.printStackTrace();} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}if (bw != null) {try {bw.close();} catch (IOException e) {e.printStackTrace();}}}
}

总结

抽象基类
InputStream
OutputStream
Reade
Writer
----------------------
节点流(或文件流)
FileInputStream (read(byte[] buffer))        FileOutputStream (write(byte[] buffer,0,len)  FileReader (read(char[] cbuf))                 FileWriter (write(char[] cbuf,0,len)
----------------------
缓冲流(处理流的一种)
BufferedInputStream (read(byte[] buffer))
BufferedOutputStream (write(byte[] buffer,0,len) / flush()
BufferedReader (read(char[] cbuf) / readLine())
BufferedWriter (write(char[] cbuf,0,len) / flush()

FileInputStream fis = new FileInputStream(“hello.txt”);

在字节流中可以直接写路径(有这样的构造器)

转换流(处理流中的一种)

转换流提供了再字节流和字符流之间的转换。

属于字符流

  • InputStreamReader:将一个字节的输入流,转换为字符的输入流
  • OutputStreamWriter:将一个字符的输出流,转换为字节的输出流

/*** @Author LiangHui* @Description 使用转换流,将utf-8文件复制成gbk文件* @Date 2020/9/15 14:37* @param* @return void*/
@Test
public void test2() {InputStreamReader isr = null;OutputStreamWriter ows = null;try {// 1 创建file、字节流、转换流File file1 = new File("test1.txt");File file2 = new File("test4.txt");FileInputStream fis = new FileInputStream(file1);FileOutputStream fos = new FileOutputStream(file2);isr = new InputStreamReader(fis, "utf-8");ows = new OutputStreamWriter(fos, "gbk");// 2 具体的写入写出操作char[] cbuf = new char[20];int len;while ((len = isr.read(cbuf)) != -1) {ows.write(cbuf, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {// 3 关闭流if (ows != null ){try {isr.close();} catch (IOException e) {e.printStackTrace();}}if (isr != null) {try {ows.close();} catch (IOException e) {e.printStackTrace();}}}
}

JAVA高级学习笔记相关推荐

  1. 尚学堂JAVA高级学习笔记_1/2

    尚学堂JAVA高级学习笔记 文章目录 尚学堂JAVA高级学习笔记 写在前面 第1章 手写webserver 1. 灵魂反射 2. 高效解析xml 3. 解析webxml 4. 反射webxml 5. ...

  2. JAVA基础与高级学习笔记

    JAVA基础与高级学习笔记 /记录java基础与高级,除了较简单的内容,没有必要记录的没有记录外,其余的都记录了/ java初学者看这一篇就够了,全文 6万+ 字. JAVA基础 java会出现内存溢 ...

  3. Java 虚拟机学习笔记 | 类加载过程和对象的创建流程

    前言 创建对象是 Java 语言绕不开的话题,那么对象是如何创建出来的呢?我们今天就来聊一聊.对象创建第一步就是检查类是否加载,而类的加载又牵扯到类的加载过程.如果单说对象的创建而绕开类的加载过程,感 ...

  4. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

  5. java mail 学习笔记

    JAVA MAIL 学习 笔记 电子邮件协议的简介 SMTP 简单邮件传输 SMTP是Simple Mail Transfer Protocol的简称,即简单邮件传输协议.该协议定义了邮件客户端软件和 ...

  6. MySQL高级学习笔记(四)

    文章目录 MySQL高级学习笔记(四) 1. MySql中常用工具 1.1 mysql 1.1.1 连接选项 1.1.2 执行选项 1.2 mysqladmin 1.3 mysqlbinlog 1.4 ...

  7. Java入门学习笔记——郝斌(一)概述及面向对象

    Java入门学习笔记--郝斌 1.Java概述 java的起源和发展 java的特点 java的应用领域 java学习目标 环境变量的设置 为什么要设置path? path的设置 有关classpat ...

  8. 【Java】学习笔记2——从小白到入门(技术提升篇)

    写在前面 [Java]学习笔记1--从小小白到小白 (基础知识篇)里记录了Java中最最基础的知识,在对基础知识有了基本了解之后,就可以开始着手技术提升了.本篇博客也将延续第一篇,继续记录我的Java ...

  9. Java高级技术笔记

    Java高级技术笔记 URL地址 HTTP协议 开发工具 Java开发工具包(JDK) JSP引擎 MyEclipse IDEA 工具集成 C/S架构是Client/Server的简写,也就是客户机/ ...

最新文章

  1. Design Pattern - Flyweight(C#)
  2. C/C++中extern关键字
  3. sizeof 是关键字不是函数!使用sizeof需要注意?
  4. ios业务模块间互相跳转的解耦方案
  5. android菜鸟学习笔记27----Fragment的简单使用
  6. 服务器排障 之 nginx 499 错误的解决
  7. ajax返回数据类型为JSON数据的处理
  8. 深入浅出 - Android系统移植与平台开发(十三)- Android的对象管理
  9. php 异步进度条,PHP学习:PHP+Ajax异步带进度条上传文件实例
  10. angularjs 模块化
  11. timeout 和 deadline
  12. [Algorithm NLP] 文本深度表示模型——word2vecdoc2vec词向量模型
  13. 【问题解决】nuget 打包 Unable to find “****.nupkg”.make sure the project has been built. 问题参考
  14. javascript函数传参
  15. Axure软件页面介绍
  16. js动画 无缝轮播 进度条 文字页面展示 div弹窗遮罩效果
  17. android死锁解决方案,【线程死锁】Android多线程死锁产生的原因以及如何避免
  18. git命令 之 切糕大全
  19. IE浏览器 请求报304,解决办法 设置页面禁止缓存
  20. JAVA计算机毕业设计民航售票管理系统(附源码、数据库)

热门文章

  1. C++处理点在椭圆上的问题
  2. 2021北大软微计算机考研感想——从另一角度看考研
  3. 电商移动Web实战项目(5)
  4. 2021年电工(初级)考试题库及电工(初级)考试资料
  5. Kali安装和使用oneforall和EHole
  6. centos查找和替换字符串
  7. RSA算法(加密与解密)
  8. oracle索引的创建与删除,Oracle关于索引创建中断无法删除的问题
  9. 微信小程序尺寸及外边距设置调整技巧
  10. Keil 官网下载PACK包的地址