1. 课前回顾:
  2. 1.进程:进入到内存执行的应用程序
  3. 2.线程:应用程序中的一个执行单元,CPU和内存之间的通道
  4. 3.并发:在同一个时刻,多条线程同时操作同一个资源
  5. 4.线程的创建:
  6. a.继承Thread 重写run方法,设置线程任务
  7. b.实现Runnable接口 重写run方法 设置线程任务
  8. c.匿名内部类: new Thread(new Runnable(){重写run})
  9. 5.Thread类中的方法
  10. a.getName()->获取线程名字
  11. b.Thread currentThread()->获取当前正在执行的线程对象
  12. c.setName()->给线程设置名字
  13. d.sleep(long time)->线程睡眠->不会释放锁,超时之后继续执行
  14. e.start()->开启线程,jvm自动执行run方法
  15. 6.解决线程安全我问题
  16. a.同步代码块:synchronized(任意对象)
  17. b.同步方法:
  18. 普通的:修饰符 synchronized 返回值类型 方法名(参数){}
  19. 默认锁:this
  20. 静态的:修饰符 static synchronized 返回值类型 方法名(参数){}
  21. 默认锁:类名.class
  22. c.注意:想要实现线程同步,多个线程使用的锁对象必须是同一个对象
  23. 7.死锁:锁嵌套
  24. 8.线程状态:
  25. 新建 可运行 锁阻塞 无限等待 计时等待 被终止
  26. 9.waitnotify
  27. wait:线程等待,会释放锁,需要其他的线程notify唤醒,唤醒之后重新抢锁,抢到了继续执行,抢不到,锁阻塞
  28. waitnotify需要锁对象调用,必须是同一把锁
  29. 今日重点:
  30. 1.会简单使用lock
  31. 2.会简单使用线程池
  32. 3.会使用Callable接口实现多线程
  33. 4.会使用Collection集合中的方法
  34. 5.会使用迭代器遍历集合
  35. 6.会使用ArrayList集合

第一章.多等待多唤醒

一.解决多生产多消费问题(if改为while,将notify改为notifyAll)

  1. public class BaoZiPu {
  2. //定义count,表示包子
  3. private int count;
  4. //定义flag,证明有么有包子
  5. private boolean flag;
  6. public BaoZiPu() {
  7. }
  8. public BaoZiPu(int count, boolean flag) {
  9. this.count = count;
  10. this.flag = flag;
  11. }
  12. //消费线程要调用的方法,证明消费包子
  13. public synchronized void getCount() {
  14. while (flag == false) {
  15. //证明没有包子,消费线程wait
  16. try {
  17. this.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. //出了if就要消费包子
  23. System.out.println("消费了第..." + count + "个包子");
  24. //改变flag状态,为false,证明消费完毕,没有包子了
  25. flag = false;
  26. //唤醒生产线程
  27. this.notifyAll();
  28. }
  29. //生产者要调用的方法,证明生产包子
  30. public synchronized void setCount() {
  31. while (flag == true) {
  32. //证明有包子,生产线程wait
  33. try {
  34. this.wait();
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. //出了if就要生产包子
  40. count++;
  41. System.out.println("生产了第........." + count + "个包子");
  42. //改变flag状态,为true,证明生产完毕,有包子了
  43. flag = true;
  44. //唤醒消费线程
  45. this.notifyAll();
  46. }
  47. public boolean isFlag() {
  48. return flag;
  49. }
  50. public void setFlag(boolean flag) {
  51. this.flag = flag;
  52. }
  53. }
  1. //消费者
  2. public class Consumer implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Consumer(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //消费
  16. baoZiPu.getCount();
  17. }
  18. }
  19. }
  1. //生产者
  2. public class Product implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Product(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //生产
  16. baoZiPu.setCount();
  17. }
  18. }
  19. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. BaoZiPu baoZiPu = new BaoZiPu();
  4. Product product = new Product(baoZiPu);
  5. Consumer consumer = new Consumer(baoZiPu);
  6. new Thread(product).start();
  7. new Thread(product).start();
  8. new Thread(consumer).start();
  9. new Thread(consumer).start();
  10. }
  11. }

第二章.Lock锁

1.Lock对象的介绍和基本使用

  1. 1.概述:锁对象 接口
  2. 2.使用:通过Lock的实现类 -> ReentrantLock
  3. 3.方法:
  4. void lock() -> 获取锁
  5. void unlock() ->释放锁
  1. public class Ticket implements Runnable {
  2. int ticket = 100;
  3. //创建Lock对象
  4. Lock lock = new ReentrantLock();
  5. @Override
  6. public void run() {
  7. while (true) {
  8. //获取锁
  9. lock.lock();
  10. if (ticket > 0) {
  11. try {
  12. Thread.sleep(100L);
  13. //买票
  14. System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
  15. ticket--;
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }finally {
  19. //释放锁
  20. lock.unlock();
  21. }
  22. }
  23. }
  24. }
  25. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket, "无忌");
  5. Thread t2 = new Thread(ticket, "三丰");
  6. Thread t3 = new Thread(ticket, "翠山");
  7. //开启三个线程
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

2.Lock和synchronized区别

2.1.Lock

  1. 1.Lock锁属于一种轻量级锁(乐观锁)
  2. 2.Lock底层实现利用了CAS机制(乐观锁)
  3. 3.乐观锁实现原理:CAS -> 代表的是 compare and swap(比较并交换)

day14[多线程_集合] - 图2

2.2.synchronized

  1. 属于悲观锁,一个线程在操作数据时,其他线程不能操作

2.3.Lock和synchronized区别(悲观锁和乐观锁的区别)

  1. a.
  2. Lock属于乐观锁,使用多线程操作的是同一个变量
  3. synchronized属于悲观锁,使用多线程操作一段代码
  4. b.
  5. 乐观锁:线程A在操作变量的时候,允许线程B操作,只是会判断,如果有问题,就放弃本次操作。判断如 果没有问题,则会正常执行本次操作
  6. 悲观锁:当线程A正在操作的时候,不允许B线程执行,要等A出来之后B才有可能进去。
  7. c.相对来说悲观锁效率更低,乐观锁效率高。

多个线程操作同一个数据时:很容易出现3个问题

可见性( 解决用一个关键字 volatile解决)

重排( 解决用一个关键字 volatile解决)

原子性(volatile解决不了原子性的问题,所以为了解决多线程操作同一个数据出现的原子性问题,我们可以使用原子类Atomicxxx类)

第三章.Condition(阻塞队列)

  1. 1.Condition:阻塞队列
  2. 2.获取:利用Lock对象中的方法:
  3. Condition newCondition()
  4. 3.方法:
  5. void await() -> 线程等待
  6. void signal()->线程唤醒
  7. 4.注意:
  8. Condition必须和Lock锁结合使用
  1. public class BaoZiPu {
  2. //定义count,表示包子
  3. private int count;
  4. //定义flag,证明有么有包子
  5. private boolean flag;
  6. Lock lock = new ReentrantLock();
  7. //创建生产者的阻塞队列
  8. Condition productCondition = lock.newCondition();
  9. //创建消费者的阻塞队列
  10. Condition consumerCondition = lock.newCondition();
  11. public BaoZiPu() {
  12. }
  13. public BaoZiPu(int count, boolean flag) {
  14. this.count = count;
  15. this.flag = flag;
  16. }
  17. //消费线程要调用的方法,证明消费包子
  18. public void getCount() {
  19. //获取锁
  20. lock.lock();
  21. while (flag == false) {
  22. //证明没有包子,消费线程wait
  23. try {
  24. consumerCondition.await();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. //出了if就要消费包子
  30. System.out.println("消费了第..." + count + "个包子");
  31. //改变flag状态,为false,证明消费完毕,没有包子了
  32. flag = false;
  33. //唤醒生产线程
  34. productCondition.signal();
  35. //释放锁
  36. lock.unlock();
  37. }
  38. //生产者要调用的方法,证明生产包子
  39. public void setCount() {
  40. //获取锁
  41. lock.lock();
  42. while (flag == true) {
  43. //证明有包子,生产线程wait
  44. try {
  45. productCondition.await();
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. //出了if就要生产包子
  51. count++;
  52. System.out.println("生产了第........." + count + "个包子");
  53. //改变flag状态,为true,证明生产完毕,有包子了
  54. flag = true;
  55. //唤醒消费线程
  56. consumerCondition.signal();
  57. //释放锁
  58. lock.unlock();
  59. }
  60. public boolean isFlag() {
  61. return flag;
  62. }
  63. public void setFlag(boolean flag) {
  64. this.flag = flag;
  65. }
  66. }
  1. //生产者
  2. public class Product implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Product(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //生产
  16. baoZiPu.setCount();
  17. }
  18. }
  19. }
  1. //消费者
  2. public class Consumer implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Consumer(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //消费
  16. baoZiPu.getCount();
  17. }
  18. }
  19. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. BaoZiPu baoZiPu = new BaoZiPu();
  4. Product product = new Product(baoZiPu);
  5. Consumer consumer = new Consumer(baoZiPu);
  6. new Thread(product).start();
  7. new Thread(product).start();
  8. new Thread(consumer).start();
  9. new Thread(consumer).start();
  10. }
  11. }

第四章.线程池

  1. 1.原始线程实现方案:频繁的创建线程,频繁的销毁线程,很消耗资源

day14[多线程_集合] - 图3

  1. 1.表示线程池的对象:
  2. Executors
  3. 2.创建线程池,指明线程池中最多有多少个线程
  4. Executors中有一个静态方法:static ExecutorService newFixedThreadPool(int nThreads)
  5. nThreads:代表的是线程池中最多能有多少条线程对象
  6. ExecutorService:用于管理线程池中的线程对象
  7. 3.ExecutorService类中的方法:
  8. Future<?> submit(Runnable task)->提交线程任务
  9. void shutdown() -> 关闭线程池
  10. 4.Future接口:用于接收Runnablerun方法的返回值
  11. run方法没有返回值的,所以我们调用submit的时候,不用Future去接收
  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println(Thread.currentThread().getName()+"执行了");
  5. }
  6. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. //创建线程池对象
  4. ExecutorService es = Executors.newFixedThreadPool(2);
  5. //调用ExecutorService中的submit方法,提交线程任务
  6. es.submit(new MyRunnable());
  7. es.submit(new MyRunnable());
  8. es.submit(new MyRunnable());
  9. //关闭线程池
  10. //es.shutdown();
  11. }
  12. }

第五章.Callable接口

  1. <T> Future<T> submit(Callable<T> task)
  2. 1.概述:Callable接口,用于实现多线程
  3. 2.Callable中有一个方法:
  4. v call()->设置线程任务
  5. call的返回值类型取决于Callable<类型>
  6. 3.call方法和run方法有什么区别:
  7. 相同点:
  8. 都是设置线程任务
  9. 不同点:
  10. run() 没有返回值,不能直接throws异常
  11. call() 有返回值,能throws异常
  12. 4.提交线程任务:
  13. <T> Future<T> submit(Callable<T> task)
  14. 5.Future接口:接收run方法的返回值或者call方法的返回值
  15. Future接口中的方法:
  16. V get()->获取的是call方法返回的数据
  1. public class Test01 {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. ExecutorService es = Executors.newFixedThreadPool(2);
  4. Future<String> future = es.submit(new MyCallable());
  5. System.out.println(future.get());
  6. }
  7. }
  1. public class MyCallable implements Callable<String> {
  2. @Override
  3. public String call() throws Exception {
  4. return "hia hia hia";
  5. }
  6. }

1.创建线程池:Executors类中的方法newFixedThreadPool(2)确定线程池中最多有几个线程,返回ExecutorService

2.使用ExecutorService中的submit方法,提交线程任务

submit(Runnable),无需用Future接受返回值,因为Runnable中的run没有返回值

submit(Callable) ,需要用Future接收返回值,因为Callable中的call方法是有返回值的

3.如何获取到call的返回值

用Future中的get方法获取

第六章.Timer定时器

  1. 1.作用:让一个线程任务,每隔一段时间就执行一次
  2. 2.构造:
  3. Timer()
  4. 3.方法:
  5. void schedule(TimerTask task, Date firstTime, long period)
  6. task:设置线程任务
  7. firstTime:从什么时间开始计算
  8. period:隔多长时间执行一次线程任务
  1. public class Demo01Timer {
  2. public static void main(String[] args) {
  3. Timer timer = new Timer();
  4. timer.schedule(new TimerTask() {
  5. @Override
  6. public void run() {
  7. System.out.println("柳岩对涛哥说:涛哥快起床了!");
  8. }
  9. },new Date(),2000L);
  10. }
  11. }

第七章.集合框架(单列集合)

  1. 1.集合分类:
  2. 1.单列集合:Collection接口为首
  3. 存储元素的: list.add("abc")
  4. 2.双列集合:Map接口为首
  5. map.put("涛哥","柳岩")
  6. 2.概述:容器
  7. 3.作用:存储数据
  8. 4.特点:
  9. 1.长度可变
  10. 2.只能存储引用数据类型(即使存储了基本类型到了集合中也会自动转成对应的引用类型(包装类))

day14[多线程_集合] - 图4

day14[多线程_集合] - 图5

第八章.Collection接口

  1. 1.概述:单列集合的顶级接口
  2. 2.使用:
  3. Collection<E> coll = new ArrayList<E>();
  4. 3.<E>是啥意思:
  5. 泛型,规定集合能存储什么类型的元素
  6. <Integer> -> 代表的是集合中存储的元素为整数
  7. <String> -> 代表的是集合中存储的元素为字符串
  8. 如果不写泛型-> 元素默认类型Object-->不建议这么写
  9. 4.方法:
  10. - public boolean add(E e): 把给定的对象添加到当前集合中
  11. add方法调用之后,我们一般不用返回值接收(add是一定能添加成功的)
  12. - public boolean addAll(Collection<? extends E>)将另一个集合元素添加到当前集合中。
  13. - public void clear() :清空集合中所有的元素。
  14. - public boolean remove(E e): 把给定的对象在当前集合中删除。
  15. - public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
  16. - public boolean isEmpty(): 判断当前集合是否为空。
  17. - public int size(): 返回集合中元素的个数。
  18. - public Object[] toArray(): 把集合中的元素,存储到数组中。
  1. public class Demo01Collection {
  2. public static void main(String[] args) {
  3. //创建集合
  4. Collection<String> collection = new ArrayList<String>();
  5. //- public boolean add(E e): 在集合尾部添加元素
  6. collection.add("张三");
  7. collection.add("李四");
  8. collection.add("王五");
  9. collection.add("赵六");
  10. collection.add("田七");
  11. collection.add("猪八");
  12. collection.add("猪八");
  13. System.out.println(collection);
  14. //- public boolean addAll(Collection<? extends E>)将另一个集合元素添加到当前集合中。->集合合并
  15. Collection<String> collection1 = new ArrayList<String>();
  16. collection1.add("林黛玉");
  17. collection1.add("贾宝玉");
  18. collection1.add("张曼玉");
  19. collection.addAll(collection1);
  20. System.out.println(collection);
  21. //- public void clear() :清空集合中所有的元素。
  22. /*collection.clear();
  23. System.out.println(collection);*/
  24. //- public boolean remove(E e): 把给定的对象在当前集合中删除。
  25. boolean b = collection.remove("张三");
  26. System.out.println(b);
  27. System.out.println(collection);
  28. //- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
  29. boolean b1 = collection.contains("李四");
  30. System.out.println(b1);
  31. //- public boolean isEmpty(): 判断当前集合是否为空(集合中是否有元素)。
  32. //collection.clear();
  33. boolean empty = collection.isEmpty();
  34. System.out.println(empty);
  35. //- public int size(): 返回集合中元素的个数。
  36. System.out.println(collection.size());
  37. //- public Object[] toArray(): 把集合中的元素,存储到数组中。
  38. Object[] objects = collection.toArray();
  39. System.out.println(Arrays.toString(objects));
  40. }
  41. }

第九章.迭代器

1.迭代器基本使用

  1. 1.作用:将集合中的元素逐一地获取出来
  2. 2.接口:
  3. Iterator
  4. 3.获取:
  5. Collection接口中有一个方法:
  6. Iterator<E> iterator()
  7. 4.Iterator中的方法:
  8. boolean hasNext()->判断集合中有没有下一个元素
  9. E next()-> 获取下一个元素
  1. public class Test01 {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("张三");
  5. list.add("李四");
  6. list.add("王五");
  7. list.add("赵六");
  8. //获取Iterator对象
  9. Iterator<String> iterator = list.iterator();
  10. while(iterator.hasNext()){
  11. String element = iterator.next();
  12. System.out.println(element);
  13. }
  14. }
  15. }

经验值:

  1. 切记,使用迭代器的时候不要连续使用next()方法
  1. public class Test02 {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("张三");
  5. list.add("李四");
  6. list.add("王五");
  7. //获取Iterator对象
  8. Iterator<String> iterator = list.iterator();
  9. while(iterator.hasNext()){
  10. String element = iterator.next();
  11. String element1 = iterator.next();
  12. System.out.println(element);
  13. System.out.println(element1);
  14. }
  15. }
  16. }

day14[多线程_集合] - 图6

2.迭代器迭代原理

day14[多线程_集合] - 图7

3.迭代器底层原理

  1. 1.问题: Iterator<String> iterator = list.iterator(); 获取迭代器,用Iterator接收,而Iterator是一个接口,获取之后肯定指向的是Iterator的实现类对象,Iterator指向的是哪个实现类?
  2. Iterator指向的是ArrayList的内部类(Itr)

day14[多线程_集合] - 图8

注意:Iterator接口指向了Itr,仅仅局限于使用的是ArrayList集合

4.并发修改异常

  1. 需求:
  2. 定义集合,存储 孙悟空 猪八戒 沙和尚 白龙马 唐僧
  3. 遍历集合,如果遍历到了猪八戒,添加一个"涛哥"
  1. public class Test03 {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("孙悟空");
  5. list.add("猪八戒");
  6. list.add("唐僧");
  7. list.add("沙和尚");
  8. list.add("白龙马");
  9. Iterator<String> iterator = list.iterator();
  10. while (iterator.hasNext()){
  11. String element = iterator.next();
  12. if ("猪八戒".equals(element)){
  13. list.add("涛哥");
  14. }
  15. }
  16. System.out.println(list);
  17. }
  18. }
  1. 出现的异常:
  2. Exception in thread "main" java.util.ConcurrentModificationException->并发修改异常
  3. at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
  4. at java.util.ArrayList$Itr.next(ArrayList.java:859)
  5. at com.atguigu.h_iterator.Test03.main(Test03.java:17)
  1. String element = iterator.next();底层源码
  2. modCount:实际操作次数
  3. expectedModCount:预期操作次数
  4. public E next() {
  5. checkForComodification();
  6. }
  7. final void checkForComodification() {
  8. if (modCount != expectedModCount)
  9. throw new ConcurrentModificationException();
  10. }
  11. ========================================================
  12. list.add("涛哥");底层源码:
  13. public boolean add(E e) {
  14. ensureCapacityInternal(size + 1); // Increments modCount!!
  15. }
  16. private void ensureCapacityInternal(int minCapacity) {
  17. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  18. }
  19. private void ensureExplicitCapacity(int minCapacity) {
  20. modCount++;
  21. }
  22. 总结:出现并发修改异常的原因:
  23. 当调用add时,底层给modCount++了
  24. 但是modCount改变之后没有重新给expectedModCount赋值,导致了modCountexpectedModCount不相等了
  25. 所以add后再调用next方法时.next方法底层做了一个判断
  26. modCountexpectedModCount不相等时,抛出ConcurrentModificationException
  27. 在使用迭代器的时候,不要随意修改集合长度

扩展:ListIterator接口

获取: ListIterator listIterator()

ListIterator接口中的方法:add(E e)

  1. public class Test03 {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("孙悟空");
  5. list.add("猪八戒");
  6. list.add("唐僧");
  7. list.add("沙和尚");
  8. list.add("白龙马");
  9. ListIterator<String> iterator = list.listIterator();
  10. while (iterator.hasNext()){
  11. String element = iterator.next();
  12. if ("猪八戒".equals(element)){
  13. iterator.add("涛哥");
  14. }
  15. }
  16. System.out.println(list);
  17. }
  18. }

第十章.数据结构

1.栈

  1. 1.有栈顶 有栈低
  2. 2.先存进去的元素在栈低,后存进去会往上压
  3. 3.特点:
  4. 先进后出

day14[多线程_集合] - 图9

2.队列

  1. 特点:先进先出
  2. 排队

day14[多线程_集合] - 图10

3.数组

  1. 1.特点:
  2. 查询快,增删慢
  3. 2.查询快:因为数组是有索引的,我们查询元素可以直接根据索引定位到这个元素上
  4. 增删慢:由于数组定长,所以如果添加元素,删除元素,我们首先得创建一个新的数组,将老数组的元素复制到新数组中

4.链表

  1. 1.特点:
  2. 查询慢,增删快
  3. 2.分类:
  4. 单向链表:不能保证元素有序
  5. 双向链表:能保证元素有序

4.1单向链表

  1. 一个节点分两部分:
  2. 数据域:存储的元素
  3. 指针域:存储的是下一个节点的地址
  4. 前面的节点记录后面节点的地址,但是后面节点的地址不记录前面节点的地址
  5. 如果集合底层数据结构为单向链表,无法保证元素有序

day14[多线程_集合] - 图11

4.2双向链表

  1. 1.一个节点分三部分:
  2. 第一个部分:记录上一个节点地址
  3. 第二部分:数据域
  4. 第三部分:记录下一个节点地址
  5. 2.特点:
  6. 查询慢,增删快
  7. 前面的节点知道后面的节点
  8. 后面的节点知道前面的节点

day14[多线程_集合] - 图12

第十一章.List接口

  1. 1.概述:List extends Collection接口
  2. 2.特点:
  3. 有索引
  4. 元素有序
  5. 元素可重复

第十二章.List集合下的实现类

1.ArrayList集合

  1. 1.概述: List接口下的实现类
  2. 2.特点:
  3. a.底层数据结构:数组
  4. b.元素有序
  5. c.有索引
  6. d.元素可重复
  7. 3.使用:
  8. ArrayList<E> list = new ArrayList<E>()
  9. 4.常用方法:
  10. - boolean add(E e) ->将元素添加到集合末尾(针对于ArrayList集合add方法一定能添加成功,所以我们不用返回值接收)
  11. - public void add(int index,E element):在指定索引位置上添加元素
  12. - public boolean remove(Object o):删除指定的元素,返回删除是否成功
  13. - public E remove(int index) :删除指定索引位置上的元素,返回的是被删除的元素
  14. - public E set(int index,E element):将指定索引位置上的元素,修改成后面的element
  15. - public E get(int index) :根据索引获取元素
  16. - public int size() :获取集合元素个数

1.1.ArrayList集合使用

  1. public class Demo01ArrayList {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("张三");
  5. list.add("李四");
  6. list.add("王五");
  7. //- public void add(int index,E element):在指定索引位置上添加元素
  8. list.add(0,"二郎神");
  9. System.out.println(list);
  10. //- public boolean remove(Object o):删除指定的元素,返回删除是否成功
  11. /* boolean b = list.remove("张三");
  12. System.out.println(b);
  13. System.out.println(list);*/
  14. //- public E remove(int index) :删除指定索引位置上的元素,返回的是被删除的元素
  15. /*String remove = list.remove(0);
  16. System.out.println(remove);
  17. System.out.println(list);*/
  18. //- public E set(int index,E element):将指定索引位置上的元素,修改成后面的element
  19. String s = list.set(0, "涛哥");
  20. System.out.println(s);
  21. System.out.println(list);
  22. //- public E get(int index) :根据索引获取元素
  23. String s1 = list.get(0);
  24. System.out.println(s1);
  25. //- public int size() :获取集合元素个数
  26. System.out.println(list.size());
  27. System.out.println("==============================");
  28. Iterator<String> iterator = list.iterator();
  29. while (iterator.hasNext()){
  30. System.out.println(iterator.next());
  31. }
  32. System.out.println("====================");
  33. for (int i = 0;i<list.size();i++){
  34. System.out.println(list.get(i));
  35. }
  36. System.out.println("======================");
  37. for (int i = 0; i < list.size(); i++) {
  38. System.out.println(list.get(i));
  39. }
  40. }
  41. }

集合名.fori

1.2.底层源码分析(扩展,扩展)

  1. 1.ArrayList底层数据结构:数组 查询快,增删慢
  2. 2.特点:
  3. 有序
  4. 有索引
  5. 元素可重复
  6. 3.构造:
  7. new ArrayList(); //默认长度为10
  8. 不是指的一new底层数组就是10,第一次添加(add)数据时,底层数组长度会变为10
  9. new ArrayList(int initialCapacity); //指定底层数组长度
  10. 4.问题:假如创建出来的ArrayList集合底层数组长度为10,如果我们要添加第11个,那么底层长度为10的数组就要扩容了,扩容多少倍?-->1.5
  1. 结论:
  2. 1.new出来的ArrayList集合,底层数组长度为0,我们第一次add的时候数组才会由0扩容为10
  3. 原因是底层的elementData = Arrays.copyOf(elementData, newCapacity);
  4. 2.为什么ArrayList底层是数组,但是我们说ArrayList长度可变?
  5. 原因是底层的elementData = Arrays.copyOf(elementData, newCapacity);->底层实现数组复制
  6. 3.如果使用有参构造创建ArrayList集合,底层直接就在有参构造中创建了指定长度的数组
  7. 4.如果扩容的话:扩的是1.5
  8. int newCapacity = oldCapacity + (oldCapacity >> 1);
  9. 新的容量 = 老的容量+(老的容量/2)
  1. 1.ArrayListadd方法底层源码分析:刚new出来的ArrayList集合,底层数组长度为0,我们第一次add的时候数组才会由0扩容为10
  2. ArrayList<String> list = new ArrayList<>();
  3. list.add("a");
  4. elementData->数组
  5. DEFAULTCAPACITY_EMPTY_ELEMENTDATA->数组,默认为0
  6. public ArrayList() {
  7. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  8. }
  9. public boolean add(E e) {
  10. ensureCapacityInternal(size + 1); // 1
  11. }
  12. private void ensureCapacityInternal(int minCapacity) {minCapacity-->1
  13. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//elementData数组 minCapacity->1
  14. 10
  15. }
  16. private static int calculateCapacity(Object[] elementData, int minCapacity) {//minCapacity 1
  17. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  18. return Math.max(DEFAULT_CAPACITY, minCapacity);//minCapacity 1 DEFAULT_CAPACITY->10
  19. }
  20. return minCapacity;
  21. }
  22. ensureExplicitCapacity参数走完返回的是10,接下来开始走ensureExplicitCapacity方法
  23. private void ensureExplicitCapacity(int minCapacity) {//minCapacity->10
  24. if (minCapacity - elementData.length > 0)
  25. grow(minCapacity);//10
  26. }
  27. private void grow(int minCapacity) {//10
  28. int oldCapacity = elementData.length;//0
  29. int newCapacity = oldCapacity + (oldCapacity >> 1);//右移为除以2 左移为乘以2 newCapacity为0
  30. if (newCapacity - minCapacity < 0)
  31. newCapacity = minCapacity; //newCapacity 10
  32. if (newCapacity - MAX_ARRAY_SIZE > 0)
  33. newCapacity = hugeCapacity(minCapacity);
  34. // minCapacity is usually close to size, so this is a win:
  35. elementData = Arrays.copyOf(elementData, newCapacity);//newCapacity 10
  36. }
  37. elementData = Arrays.copyOf(elementData, newCapacity);//决定了ArrayList第一次add的时候底层数组长度由0扩容为了10
  38. 这句话也解释了ArrayList底层为数组但是长度可变->数组复制
  39. ==============================================================
  40. ArrayList的有参构造->可以指定底层数组长度
  41. ArrayList<String> list = new ArrayList<>(10);
  42. public ArrayList(int initialCapacity) {//10
  43. if (initialCapacity > 0) {
  44. this.elementData = new Object[initialCapacity];
  45. } else if (initialCapacity == 0) {
  46. this.elementData = EMPTY_ELEMENTDATA;
  47. } else {
  48. throw new IllegalArgumentException("Illegal Capacity: "+
  49. initialCapacity);
  50. }
  51. }

经验值:

  1. public class Test01 {
  2. public static void main(String[] args) {
  3. ArrayList<Integer> list = new ArrayList<>();
  4. list.add(2);
  5. System.out.println(list);
  6. //调用删除remove
  7. list.remove(2);//错误写法
  8. System.out.println(list);
  9. }
  10. }