(005)线程和IO流

摘要:

本周学习的是上一周结余部分集合的内容和线程,IO流,的内容,希望我们能够向着信念前行,每天都积极阳光;

集合部分

TreeSet集合

Set集合的子实现类

底层数据结构是一个红黑树结构, (自平衡的二叉树结构)
依赖于TreeMap<K, V>实现的,可以完成 自然排序以及比较器排序,取决于我们使用的构造方法
public TreeSet() 无参构造方法: 完成一种自然排序, 前提: 存储的数据类型必须要实现 Compareable接口里面的compareTo方法
public TreeSet(Comparator< E>comparator){}:实现比较器排序:存储的类型必须要实现 comparator接口里面的compare()方法
要进行比较的具体的方法 ,就模拟Integer类 jdk 提供的也实现了 Comareable接口完成数字的排序 里面使用三元运算符
集合的TreeSet< Student> 自定义对象,如何进行排序(自然排序—>TreeSet的无参构造方法) 按照学生的年龄从小到大排序
学生的姓名和年龄

一般这种:告诉你的排序主要条件,你自己分析次要条件 主要条件:年龄从小到大,
次要条件:年龄相等的,就是同一个人吗?
年龄相等还要判断姓名是否一样
Student s1 = new Student(“gaoyuanyuan”,36) ;
Student s2 = new Student(“zhangjianing”,36) ;

要实现自然排序,使用TreeSet集合的无参构造方法,
那么TreeSet< E>,里面的泛型E类型,必须要实现一个接口Compareable接口,
重写接口的comareTo方法,否则无法进行自然排序;意味着如果TreeSet< Student>,自定义类型,那么Student这个类必须要实现Comareable接口,否则报错;

匿名内部类(推荐方式)—本质(继承了该抽象类或者实现了该接口的子类对象)
new 抽象类名或者接口名(){
}

泛型的高级通配符

< ? >: 表示任意java类型包括Object

<? extends E>:向下限定:E类型及其他的子类,最起码和E类型保持一 致 <? super E>:向 上限定:E类型及其他的父类 public TreeSet (Comparator< E > comparator){}: 实现比较器排序:存储的类型必须要实现 comparator接口里面的compare()方法 使用TreeSet存储jdk提供的这个类型Integer包 含int类型的值;

Map集合

Map的基本功能:

V put(K key, V value)添加键值对,注意实现:如果键是第一次添加,返回值null;
如果键重复,后面的值将前面的值会覆盖掉,返回第一次键对应的值;
boolean containsKey(Object key):判断是否包含指定的键
boolean containsValue(Object value):判断是否包含指定的值
boolean isEmpty():判断集合是否为空,Map为空,true;否则,false
V remove(Object key):删除Map中的键,返回该键对应的值
Collection< V > values():获取Map集合中的所有值的集合—是Collection,里面将所有的Value集中起来
void clear():清空map集合

Map集合遍历1

获取Map集合中所有的键的集合,增强for遍历所有的键,通过键获取值
Set< K > keySet()—>获取Map集合中所有的键的集合
V get(Object key)------->通过键获取值

Map集合遍历方式2:不推荐 获取所有的键值对象 Set< Map.Entry < K,V>> entrySet()

Map.Entry< K,V>键值对象就有
K getKey():获取键
V getVvalue():获取值

HashMap< K,V>,

如果是自定义类型,保证元素唯一,键必须唯一!
K—>Student类型
V—>学生的地址String

HashSet的时候,的add方法依赖于HashMap的put方法---->间接依赖于Object的hashCode和equals()方法
默认比的地址值是否相同,equals,如果存储K键是String类型,jdk提供的----已经重写了
而现在的键是自定义类型,Student类型必须重写equals和hashCode,才能保证键必须唯一!
Map集合针对键有效,跟值无关,键必须唯一!(自定义类型,jdk提供的类型几乎重写过了equals和hashCode方法)

TreeMap< K,V>集合也是Map集合的子实现类
TreeSet< E>集合依赖于TreeMap实现

  • 保证元素必须进行排序的,使用TreeMap< K,V>,如果是自定义类型,要保证唯一而且还要排序
    底层是一种红黑树结构;

  • public TreeMap():自然排序,键的这个类型必须实现Comarepable接口

  • public TreeMap(Comparator< K> comparator):比较器排序,自定义一个类实现Comparator或者是使用接口匿名内部类
    HashSet 的底层是HashMap源码解析

  1. HashSet 底层是HashMap
  2. 添加一个元素时,先得到hash值会转成->索引值
  3. 找到存储数据表table,看这个索引位置是否已经存放的有元素
  4. 如果没有,直接加入,如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添
  5. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
  6. 在Java8中,如果一条链表的元素个数到达TREEIFY THRESHOLD(默认是8),并且table的大小 >=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

HashSet的底层源码

  1. 先执行的是HashSet()
 public HashSet(){

   map = new HashMap<>();

   }
  1. 执行 add()
public boolean add(E e){
    return map.put(e, PRESENT) == null;//(static) PRESENT = new object();
}
  1. 执行put(),该方法会执行hash(key) 得到key对应的hash值,算法h = key .hashCode()) ^(h >>> 16)–>异或无符号向右移动十六位
public V put(K key, V value) {
   return putVaL(hash(key), key, value, false, true);
   }
  1. 执行 putVal
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                      boolean evict) {
           Node<K,V>[] tab; Node<K,V> p; int n, i;//定义的辅助变量
            //table 就是HashMap 的一个数组,类型是Node [ ]
            //if语句表示如果当前table是null,或者大小=0
            //就是第一 次扩容,到16个空间.
           if ((tab = table) == null || (n = tab.length) == 0)
               n = (tab = resize()).length;//运行Node方法获取空间
            //1)根据key,得到hash去计算该key应该存放到tabLe表的哪个索引位置
            //并把这个位置的对象,赋给p
            //2)判断p是否为null
            //如果p为null, 表示还没有存放元素,就创建一个Node
            //就放在该位置tab[i] = newNode (hash, key, value, null);
           if ((p = tab[i = (n - 1) & hash]) == null)
               tab[i] = newNode(hash, key, value, null);
           else {
                //个开发技巧提示:在需要局部变量(辅助变量)时候,在创建
               Node<K,V> e; K k;
               if (p.hash == hash &&  
                //如果当前索引位置对应的链表的第。个元素和准备添加的key的hash值一样
                //并且满足 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象
                    //p指向的Node结点的key的equals() 和准备加入的key比较后相同
                //就不能加入
                   ((k = p.key) == key || (key != null && key.equals(k))))
                   e = p;
                //在判断 p 是不是一颗红黑树
                //如果是一颗红黑树,就调用putTreeVal,来进行添加
               else if (p instanceof TreeNode)
                   e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
               else {//如果table对应索引位置,已经是一个链表,就使用for循环比较
                    //那么就依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
                    //注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                    //就调用 treeifyBin() 对当前这个链表进行树化
                    // 注意 在转成红黑树时, 要进行判断 判断条件数组空间是否大于等于64
                    //如果上面俩个条件都成立,才进行转成红黑树
                   for (int binCount = 0; ; ++binCount) {
                       if ((e = p.next) == null) {
                           p.next = newNode(hash, key, value, null);
                           if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                               treeifyBin(tab, hash);
                           break;
                       }
                        //如果有相同的的情况,就直接break;
                       if (e.hash == hash &&
                           ((k = e.key) == key || (key != null && key.equals(k))))
                           break;
                       p = e;//俩个指针来回比较
                   }
               }
               if (e != null) { // existing mapping for key
                   V oldValue = e.value;
                   if (!onlyIfAbsent || oldValue == null)
                       e.value = value;
                   afterNodeAccess(e);
                   return oldValue;
               }
           }
           ++modCount;//size就是我们每加入一个结点Node(k, v, h, next), size++|
           if (++size > threshold)
               resize();
           afterNodeInsertion(evict);
           return null;
       }

jdk5提供很多新特性:

  • 增强for,自动拆装箱,静态导入(导入方法的级别,前提是这个类的方法静态的) 可变参数(当方法的形式参数有多个,可以使用…表示多个参数)

静态导入,前提这个类方法的是静态,导入方法的级别 import static 包名.类名.静态方法名;
可变参数 当方法的形式参数有多个,可以使用… 表示多个参数 注意:如果我们在使用静态导入的时候,导入方法级别,前提是自定义的方法名不能和它冲突,如果冲突,系统
不知道这个方法是自定义的还是jdk提供的这个类的方法,此时静态导入就不能用了,必须显示的指明是哪个类的方法

集合工具类Collections

Collections:是Jdk提供的针对集合操作的工具类
里面的很多静态功能,排序,求最值等等
public static T max(Collection< T> coll):求集合中最大值
public static T min(Collection< T> coll):求集合中最小值
public static void shuffle(List<?> list):随机置换 (针对List集合打乱元素)
public static <T extends Comparable<? super T>> void sort(List< T> list):针对List集合自然排序
public static <T extends Comparable<? super T>> void sort(List< T> list,Comparator< T> c):针对List集合比较器排序
TreeSet集合人家本身就可以排序 取决构造方法
而List集合要进行自然排序或者比较器排序需要使用Collections.sort(list集合对象)

Collections提供的功能:针对List集合按照条件可以排序 public static <T extends
Comparable<? super T>> void sort(List list):针对List集合自然排序 当前List
T的这个类型要实现Compareable接口,重写comareTo方法
public static <T extends Comparable<? super T>> void sort(List list,Comparator c):
针对List集合比较器排序

使用List集合存储自定义对象,保证排序,按照学生的年龄从小到大排序;
List集合本身可以重复,可以新建思想或者选择排序思想去重,现在为了演示按照条件排序,不加入重复数据了!

线程

线程的俩种创建方式

实现线程的创建方式第一种:继承关系
实现步骤
1)自定义一个类,继承自Thread类 (jdk提供的线程类), 这个类就是线程类
2)在自定义的类中重写Thread类的run方法
3)在main线程(主线程,或者用户线程) 创建当前自定义类对象—>就是线程对象,启动线程即可

  • 线程不知道哪一个线程?Thread类提供了一些方法,设置线程名称,获取线程名字

public final void setName(String name):给线程设置名字
public final String getName():获取线程名称
public final void join() 线程礼让!
异常 throws InterruptedException
等待该线程终止(等待某个线程结束,才能执行其他线程互相抢占cpu执行权)
方法本身会抛出异常(中断异常),等会谁在调用这个方法可以抛throws(抛出到方法上)
或者捕获异常
(jvm内存解析代码,如果异常信息和它处理的信息异常,它会创建异常对象,将异常信息打印在控制台上,展示日志)
try{
//代码
}catch(异常类名 变量名){
//处理异常,跟踪底层原码 变量名.printStackTrace():将信息打印
}
开发中:使用捕获异常(业务逻辑层:捕获),自己写代码可以抛出异常 (给方法上抛throws)

public static void yield():暂停当前正在执行的线程,并执行其他线程,让多个线程执行更和谐,
但是不能保证在抢占CPU的执行权的时候,A/B线程,A一次,B一次,也就是说明线程的执行随机性大

Thread类常量表:设置优先级
下面自定义常量
public static final int MAX_PRIORITY 10 最大优先级
public static final int MIN_PRIORITY 1 最小优先级
public static final int NORM_PRIORITY 5 默认优先级
线程的优先级越大,只能说明它抢占到CPU的执行权的几率越大,但是不一定,因为线程的执行具有随机性!
public final void setPriority(int newPriority)设置优先级

创建线程另一种方式:是实现一个种接口方式 (推荐使用的,体现数据共享的概念)
谁实现了Runable接口的run方法,谁就是资源类!(多个线程必须同时操作同一个资源)

实现步骤:
1)自定义一个类实现Runnable接口,实现里面的run方法—“线程的具体执行的逻辑”
2)在主线程中(main)
创建实现Runable接口实现类—就是资源类对象
创建 Thread类对象
使用它完成public Thread(Runnable target,String name)
在Thread构造方法中,将资源类对象作为参数传递!(多个线程必须同时操作同一个资源)
3)启动线程 start()

实现线程的俩种方式哪一个更好

线程操作的时候:目前现在两种方式,继承关系,实现Runnable接口关系,为什么接口的方式要好一些?
1)java面向接口编程,接口的提供的抽象方法,实现类必须实现这个方法
2)实现Runable接口的方式,体现数据共享的概念
SellTicket st = new SellTicket() ;//栈内存–指向堆内存地址
//创建三个线程
Thread t1 = new Thread(st,“窗口1”) ; //三个线程 都在操作同一个资源类st对象
Thread t2 = new Thread(st,“窗口2”) ;
Thread t3 = new Thread(st,“窗口3”) ;

虽然方式2优于方式1,问题:刚才加入了网络延迟睡眠150毫秒,出现负票,也会出现同票(线程不安全!)

出现负票的原因:
因为加入了睡眠(延迟操作) 再去结合线程的会执行随机
不管是上面什么情况,都是不安全的体现!–就得要解决!

检验多线程安全的问题的标准----也是在告诉我们,多线程安全问题的思路怎么解决?
1)首先检查你的程序是否是多线程环境? 现在卖票:肯定时候多线程环境
2)你的程序中是有共享数据? 存在:100张票 ,整个SellTciket类都是资源类(共享)
3)你的程序中是否存在多条语句对共享数据操作? 是,它是突破口
Java提供了一个同步代码块,将上面的3)包裹起来,解决线程安全问题

设计模式概要

设计模式:Java中23种 —不是一种技术,它是一种"思想"(java编程语言中,根深蒂固的东西)

  • 分为三大类
    创建型设计模式(对象的创建):单例模式(重点),静态工厂方法模式(重点),工厂方法模式(重点)
    结构型设计模式(整个结构组成:抽象类,接口,…具体实现类…):代理模式(重点)
    行为型设计模式(功能型设计模式:使用具体的应用场景):适配器模式,装饰者设计模式(io流中就会用到)

静态代理属于代理设计模式

**代理模式:**核心思想:代理角色帮助真实角色完成一些事情 比如:过年回家排队买火车票
静态代理: 代理角色和真实角色都需要实现同一个接口
线程的创建方式2:就使用到了静态代理

class MyRunnable implements Runnable{--->真实角色
                        //重写run
                       //专注于run里面的真实内容
          }

 class Thread  implements Runnable{//代理角色:帮助我们MyRunnable完成事情
      private Runnable target;

                       public Thread(Runnable target, String name) {

                          //这里面完成 需要target赋值--->MyRunnable实现了Runnable接口
                       }

                  //重写run  public void run() {
                     if (target != null) {
                        target.run();//接口变量.run()
                      }
    }
}

动态代理
jdk动态代理(jdk提供的Proxy类) 反射中就会用到
cglib动态代理(第三方jar包)

举例:
分手这个事来说,每个人都会分手
代码实现:

public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();
        you.mary();
        System.out.println("--------");
        BreakUpOccasions b = new BreakUpOccasions(you);
        b.mary();
    }
}
//定义接口
interface Mary{
    void mary();
}
//定义具体实现的个体,实现接口
class You implements Mary{

    @Override
    public void mary() {
        System.out.println("我分手了,哎,开心起来");
    }
}
//代理角色,分手场合,它的目的就是为了 "增强的真实角色的功能
class BreakUpOccasions implements Mary{
    //定义接口类型成员变量
    private Mary mary;

    public BreakUpOccasions(Mary mary) {
        this.mary = mary;
    }
    //重写方法实现了分手
    @Override
    public void mary() {
        System.out.println("分手公司布置分手场地");
        mary.mary();
        System.out.println("分手公司帮忙完成分手环节");
    }
}

线程安全问题

虽然方式2优于方式1,问题:刚才加入了网络延迟睡眠150毫秒,出现负票,也会出现同票(线程不安全!)

出现负票的原因:
因为加入了睡眠(延迟操作) 再去结合线程的会执行随机
不管是上面什么情况,都是不安全的体现!–就得要解决!

检验多线程安全的问题的标准----也是在告诉我们,多线程安全问题的思路怎么解决?
1)首先检查你的程序是否是多线程环境? 现在卖票:肯定时候多线程环境
2)你的程序中是有共享数据? 存在:100张票 ,整个SellTciket类都是资源类(共享)
3)你的程序中是否存在多条语句对共享数据操作? 是,它是突破口
Java提供了一个同步代码块,将上面的3)包裹起来,解决线程安全问题
线程创建的方式2优于方式1的最大好处:使用到了静态代理以及数据共享概念
说明了这个多线程环境不安全,如何解决呢?
出现同票是为什么?
因为线程的执行具有原子性操作,代码中最简单的操作(变量++或变量的–),而且线程的执行随机

线程的6种状态

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

在这里插入图片描述

同步锁

同步代码块

synchronized(锁对象){  //同步代码块
多条语句对共享数据操作;
}
 同步锁----悲观锁  当一个线程持有资源的时候,别的线程是进不来的
                     synchronized(锁对象){  //同步代码块
                     多条语句对共享数据操作;
         }

注意:锁对象:可以是任意Java类对象,包括 Object,包括自定义的类都行
但是.必须是同一个对象; (锁只能有一个)
就相当于火车上上厕所,厕所的钥匙只有乘务员有!这个钥匙能够给每个人发一把吗?不行

在实际开发中,有的时候同步代码块很少用,如果一个方法中的第一句话就是
synchronzied(锁对象){
多条语句对共享数据的操作;
}

同步方法
将这个synchronized提到方法声明上,形成了同步方法(一般说的非静态的)

面试题
1)同步方法的锁对象是谁呢? (一般说的都是非静态的)
非静态同步方法的锁对象是 this:代表当前类对象的地址引用
2)静态的同步方法的锁对象是谁呢?—>静态的东西和类相关
跟反射有关系,是当前类的字节码文件对象
之前讲Object类---->Class getClass():获取字节码文件对象 方式1
任意java类型的class属性—>第二种方式获取字节码文件对象

lock锁

JdK提供一个接口Lock java.util.concurrent.locks 包下的
比synchronzied更具体的锁定操作!
syncronzied(锁对象){ 同步代码块 —或者同步方法的锁对象 —>this
}
提供了一些方法
获取锁对象 void lock()获得锁。
释放锁对象 void unlock()

Lock是一个接口:具体的实现类
ReentrantLock:可重入的互斥锁
电影卖票三个窗口,核心代码现在使用lock---->相当于synchronzied同步代码块
底层原码用到的Lock,我们自己写代码—使用synchronzied同步代码块或者同步方法

死锁

在多线程环境下,首选考虑安全,安全问题使用同步代码块解决(同步机制:synchronzied代码块)安全问题,效率低了,可能会产生一种现象 “死锁”.
在使用的多线程的时候,可能使用到了synchronzied,但是能够解决安全问题,效率低,而且当前多个线程操作不是同一个资源,可能出现一种现象"你在等我,我在等你",这种互相等待的情况,“死锁”

解决方案------------->举例 “生产者和消费者模式思想” 需要多个线程访问的是同一个资源

比如:
用学生(生产者),使用数据(消费者)
中国人和美国人吃饭
中国人一双筷子,美国人一把刀,一个叉
如果中国人一根筷子,一个叉
美国人一把刀,一个一个筷子
美国人需要等待中国人给叉子,中国人需要等待美国人给一个根筷子

等待唤醒机制

引出线程的相互通信
模拟 生成者和消费思想模式,让线程之间的进行互相通信

Student类—>学生数据(看成—>包子)
name/age 姓名和年龄
SetThread类---->生产者线程操作的资源类—>给学生的信息赋值
GetThrad类----->消费者线程操作的资源类---->输出学生的信息
ThreadDemo类—>主线程main(用户线程)
需求1:先在SetThread类产生学生对象数据,在GetThrad类输出学生数据
问题: 发现数据是null 0 ,为什么呢?不同资源类对数据的操作应该同一个数据
在SetThread里面new 一个学生对象
在GetThread里面new 一个学生对象 ,不是同一个学生对象,没有数据!
解决方案,通过构造方法进行数据传递;必须使用同一个学生对象!
需求2:生产者不断的产生数据,消费者不断的使用数据!—加入循环操作
问题:数据紊乱,部分数据的名字和年龄不对应
数据一打印一大片,是因为线程的执行的时候,cpu的一点点时间片足够线程执行很多次,数据可能一打印一大片数据紊乱,线程的执行具有随机性
说明程序不安全------>上午讲了---->同步代码块 (多线程的同步机制 synchronized)
校验多线程安全问题的标准:
是否是多线程环境 是
是否存在共享数据 是
是否有多条语句对共享数据的操作 有---->使用同步代码块将它包起来
问题:数据不会紊乱,但是一打印一大片,cpu的一点点时间片足够线程执行很多次
解决方案: 依次打印------------>使用Java的等待唤醒机制(重点)

public class Student {

    String name;//姓名
    int age;//年龄
    boolean flag;//条件变量

}
//生产者资源类
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (s){
                if (s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (x%2==0){
                    s.name="张杨";
                    s.age=23;
                }else{
                    s.name="章若楠";
                    s.age=26;
                }
                x++;
                //修改标记
                s.flag = true;
                //唤醒通知消费者线程,
                //锁对象调用者这些方法
                s.notify();
            }
        }
    }
}

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        //不断的使用数据(消费数据)
        while (true) {
            //调用学生类的同步方法:产生学生数据
            synchronized (s) {
                //如果有数据,等待消费者先使用数据
                if (!s.flag) {
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.print(s.name + " ");
                System.out.println(s.age);
                //消费完了,没有数据了,修改标记
                s.flag = false;
                //唤醒(通知)生产者线程,
                s.notify();
            }
        }

    }
}

public class ThreadDemo{
    public static void main(String[] args) {
        Student s = new Student();
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        t1.start();
        t2.start();
    }
}

运行结果

章若楠 26
张杨 23
章若楠 26
张杨 23
章若楠 26
张杨 23
章若楠 26
张杨 23

方法递归的解决问题的思想

拆分思想
将一个的问题拆分很小问题,里面找规律
1)需要有一个方法
2)方法需要有出口条件,否则就是死递归
3)需要有一定递归
构造方法不存在方法递归,我们所说的递归都是普通方法

wait()和notify()也是同步机制的一种,这两个必须一块使用的 (解决线程通信)
同步机制:synchonzied(锁对象){或者是同步方法}
创建线程的方式有前两种了
1)继承关系
2)实现Runnable接口的方式,使用到了静态代理
为什么要有第三种方式呢?线程池

线程池(重点)

使用步骤
1)创建线程池 Executors:工厂类(提供一些静态的方法) 专门创建线程池

public static ExecutorService newFixedThreadPool(int nThreads)创建
一个固定的可重用的线程数的线程池
返回值是一个接口---->底层原码—肯定返回的接口的子实现类对象

2)提交异步任务 ExecutorService:线程池—接口里面的方法都要被ThreadPoolExecutor子实现类实现

< T> Future submit(Callable task):提交异步任务,并返回结果处理
这个方法的返回值Future接口—代表的具体的处理结果,
如果仅仅是看一下多个线程互相抢占CPU执行权的效果,返回值可以不写;
如果要进行线程计算结果,必须有返回结果
参数:Callable: 接口,需要接口的子实现类对象(执行的任务—类似于Runnable接口)
callable里面的方法call---->类似于Runnable的run 方法—耗时的操作,线程执行的业务代码

3)关闭线程池

void shutdown():关闭之后,不会接受任何新任务

线程池7大参数 Executors里面的

public static ExceutorService newFixedThreadPool(int nThreads) //2条
件线程
返回值ExceutorService 接口----实现类 ThreadPoolExecutor :规定了线程池
的参数
1)核心线程数量
//volatile 关键字: 和synchronized 语义上都是解决同步安全的
private volatile int corePoolSize;
2)最大线程数量
private volatile int maximumPoolSize;
3) 线程工厂 是一个接口
private volatile ThreadFactory threadFactory;
作用给线程池中创建线程的
ThreadFactory–>里面的子实现类DefaultThreadFactory实现了
public Thread newThread(Runnable r) {
//创建线程
Thread t = new Thread(xxxx) ;
}
4)线程池的拒绝策略
private volatile RejectedExecutionHandler handler;
当线程池中的线程数量已经达到最大了,创建线程不不能放在线程池中,线程池
的这个参数是启用拒绝策略
5)工作队列(阻塞队列):当一个线程被使用的时候,其他线程在队列中等待另一
个用户操作这个线程,本身就是集合,集合可以存储很多的线程对象
private final BlockingQueue< Runnable> workQueue;
6)当超过最大线程数量,线程池的存活时间 耗时间的计量单位
private volatile long keepAliveTime;
7) 是否允许最大线程的超时等待
private volatile boolean allowCoreThreadTimeOut;

IO流

java.io.File这个类

代表的文件或者文件夹(目录)的一种抽象路径表现形式
常用的构造方法:
File(String pathname):创建File对象指定路径
基本功能:
boolean creatNewFile():创建文件
boolean mkdir():(创建文件夹单级目录)
boolean mkdirs():创建多级目录,父目录不存在的时候则创建
boolean isFile():是否是一个文件
boolean isDirectory():是否是文件夹
高级功能:
File[] listFiles() :获取表达某个文件夹中的所有 文件以及文件夹的名称fiile数组

:在不同的设备之间进行数据传输

  • I–Input—>输入 读数据
  • O–Output—>输出 写数据

常说的IO—就读写操作
实际场景:文件上传/下载(二阶段 前端+后端)
普通的文件复制
后端给前端展示数据—>也要使用IO流

IO流的分类

按流的方向划分:
输入流 读
输出流 写

在流的方向上,继续按类型划分
字节流—先出现,由于操作的字节,一些中文可能出现乱码,所以才有字符流(针对文本文件)
字节输入流 —> 读 —> 抽象类 InputStream
具体类 FileInputStream:针对文件操作(文件字节输入流)
高效字节输入流(字节缓冲输入流):BufferedInputStream

字节输出流 —> 写 —> 抽象类 OutputStream
具体类 FileOutputStream:针对文件操作(文件字节输出流)
高效的字节输出流(字节缓冲输出流):BufferedOutputStream

字符流
字符输入流—> 读 —>抽象类 Reader
具体类:转换字符输入流:InputStreamReader
便捷类:优化上面格式:FileRreader—>直接操作文件
高效的字符输入流(字符缓冲输入流):BufferedReader

字符输出流 —> 写 —> 抽象类 Writer
具体类:转换字符输出流:OutputStreamWriter
便捷类:优化上面的格式:FileWriter—>直接输出文件
高效的字符输出流(字符缓冲输出流):BufferedWriter

字节流

字节输入流具体的子类:FileInputStream
字节输出流具体的子类:FileOutputStream

写入数据的时候,如何实现换行呢?
第一个细节:
在windows系统中,流中写入的数据实现换行的换行符号"\r\n"

第二个细节:
使用FileOutputStream文件字节输出流写入数据的时候,如何实现文件自动不会被覆盖,而是文件内容
自动上一次的基础上自动追加!
FileOutputStream的构造方法:实现自动追加效果
public FileOutputStream(String name,boolean append)throws FileNotFoundException
参数2:就是自动追加:true;表示启动自动追加到上一次的文件内容的末尾

第三个细节:
io流中处理异常:自己玩的时候可以throws抛出到方法上而且抛出异常
实际开发中,捕获异常try…catch…finally(释放资源)
选中代码----ctrl+alt+t—>选第6个 try…catch…

字节缓冲流

字节缓冲流不能直接操作文件,操作文件需要底层字节流

字节缓冲输入流BufferedInputStream的构造方法使用默认缓冲区大小
public BufferedInputStream(InputStream in)形式参数是一个抽象类,需要抽象类的子类对象(装饰者模式)
为什么会出现字节缓冲输入或者字节缓冲输出流
因为读写文件进行复制操作的时候,使用一次读取一个字节数组的方式相对一次读取一个字节更高效,
但是这个字节数组也是自定义的;字节流它想更高效的去读写复制,jdk提供了字节缓冲流(字节高效流)
BufferedInputStream:字节缓冲输入流
BufferedOutputStream:字节缓冲输出流

这些流底层源码----给我们给定一个缓冲区 缓冲区默认值足够大,8192个长度;只是提供字节缓冲区,文件的读写还是依赖于 FileInputStreamFileOutputStream来操作
public BufferedOutputStream(OutputStream out):提供默认缓冲区大小的字节缓冲流对象
方法的形式参数是一个抽象类,需要抽象类的子类对象 符合装饰者设计模式

文本文件(.java/.css/.js/.html/.md/.txt/.xml…,打开之后能读懂的)的读或者写,就直接使用字符流;
什么使用字节流:当使用记事本打开文件,读不懂,乱码—此时使用字节流

字符流

使用InputStreamReaderOutputStreamWriter
字符转换流对普通文本文件进行读写复制,不能直接操作文件(但是这个流的类型属于字符流)
借助包装的底层字节流来完成文件读写操作
将当前项目的下的copy.java文件—复制到
D://EE_2208//day27_code_resource//my.java文件中
分析:使用字符转换输入流读当前项目下的copy.java,一次读取一个字符/一次读取一个字符数组
使用字符转换输出流:将读取的内容写入到流中,输出到D://EE_2208//day27_code_resource//my.java中

这个实际场景很少用,比较麻烦,等会使用他们转换输入流/转换输出流的便捷类,直接操作文件!

字符缓冲流

BufferedWriter字符缓冲输出流:它的出现为了提高高效写入,里面存在缓冲区,默认缓冲区大小已经足够大了
构造方法
BufferedWriter(Writer out) :创建默认缓冲区大小的字符缓冲输出流

写:write(String str):推荐 写字符串进去
特有功能:

public void newLine() throws IOException :写入一个行终止符号,就是换行
之前使用字节流进行换行的时候用的什么? windows系统流写入换行符号"\r\n"

BufferedReader:字符缓冲输入流,提供写的高效
构造方法:一般默认缓冲区大的字符缓冲输入流对象
BufferedReader(Reader in)

读: 一次读取一个字符数组(普通的方式而已)
特有功能:
public String readLine() throws IOException
一次读取一行内容,它如何判断文件是否到达末尾呢?返回值为null,表示文件读取完毕

使用字符缓冲流进行文件读写复制

  • 将当前项目下的BufferedReaderDemo.java复制到当前项目下的Demo.java文件中
  • 文本文件复制---------优先使用字符流实现—而字符流有很多,使用BufferedReader的readLine()一次读取一行
  • 使用BufferedWriter的newLine()可以实现换行 来完成操作

关于BufferedReader它的另一种用法
键盘录入

键盘录入 构造方法 Scanner(InputStream in)
—细节:先录入int,在录入String出现字符串漏掉了,因为回车符号的原因—>本身就当字符串
int a = sc.nextInt() ;
String b = sc.nextLine() ; //nextLine();一行
b值就会漏掉
使用next()方法就可以
现在可以使用BufferedReader的构造方法完成键盘录入
BufferedReader(Reader in)

IO流复制文件视频 及相关代码用法全部代码

@SuppressWarnings({"all"})
public class BufferOutputStreamDemo {
    public static void main(String[] args) throws Exception {
        //BufferOutputStreamTest();
        //BufferedInputStreamTest();
        //long l = System.currentTimeMillis();
        //CopyMP4();
        //long l1 = System.currentTimeMillis();
//        System.out.println("字节拷贝视频用时:"+(l1-l)+"毫秒");//字节拷贝视频用时:44646毫秒
//        long l2 = System.currentTimeMillis();
//        BCopyMP4();
//        long l3 = System.currentTimeMillis();
//        System.out.println("字节数组拷贝视频用时:"+(l3-l2)+"毫秒");//字节数组拷贝视频用时:85毫秒
        //Test03();
        //CopyFile();
        //CopyFile1();
        //BufferedWriter1();
        //BufferedReader1();
        CopyFileDemo();
        Demo();
    }

    //写,输入流
    public static void BufferOutputStreamTest() throws IOException {
        //创建默认缓冲区大小的字节缓冲流对象
        //一层包装一层:里面需要的是具体的子类对象
        BufferedOutputStream bfo = new BufferedOutputStream(new FileOutputStream("bos.txt"));
        //缓冲流的在使用的时候,关闭之前,刷新---将缓冲区的字节数刷新一下
        bfo.flush();
        //图片文件本身就是缓存的,可能将部分字节缓存这个缓冲区中,最好关闭之前刷新
        bfo.write("hello word".getBytes());//写数据
        bfo.close();//释放资源

    }

    //字节缓冲输入流BufferedInputStream的构造方法使用默认缓冲区大小
    public static void BufferedInputStreamTest() throws IOException {
        //创建字节缓冲输入流对象
        BufferedInputStream bfi = new BufferedInputStream(new FileInputStream("bos.txt"));

        byte[] bytes = new byte[1024];
        int by;
        //将读出来的展示控制台
        //两种方式:
        //一次读取一个字节
        while ((by = bfi.read(bytes)) != -1) {

            System.out.println(new String(bytes, 0, by));
        }
        //释放资源
        bfi.close();
    }

    public static void CopyMP4() throws IOException {
        //创建默认缓冲区大小的字节缓冲流对象
        //创建字节缓冲输入流对象
        //创建字节缓冲输出流对象
        BufferedInputStream bfi = new BufferedInputStream(new FileInputStream("D:\\哦吼咋了撒.mp4"));
        BufferedOutputStream bfo = new BufferedOutputStream(new FileOutputStream("E:\\咋了撒.mp4"));
        int by = 0;
        //遍历读取每一个字节
        while ((by = bfi.read()) != -1) {
            bfo.write(by);//获取字节
            bfo.flush();//刷新
        }
        //释放资源
        bfo.close();
        bfo.close();
    }

    public static void BCopyMP4() throws IOException {
        //创建默认缓冲区大小的字节缓冲流对象
        BufferedInputStream bfi = new BufferedInputStream(new FileInputStream("D:\\哦吼咋了撒.mp4"));
        BufferedOutputStream bfo = new BufferedOutputStream(new FileOutputStream("E:\\咋了撒.mp4"));
        byte[] bytes = new byte[1024];
        int by = 0;
        //遍历读取每一个字节数组
        while ((by = bfi.read(bytes)) != -1) {
            bfo.write(bytes, 0, by);
            bfo.flush();//刷新资源
        }
        //释放资源
        bfo.close();
        bfo.close();
    }

    //字符转换输出流: OutputStreamWriter : 字节输出流通向字符输出流的桥梁
    public static void Test03() throws IOException {
        //字符转换输出流,读入数据
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("asb.txt"));
        char[] chars = {'a', 'b', 'c', ' ', 'l', 'o', 'n', 'g'};
        osw.write(99);
        osw.write(chars, 0, chars.length);
        String s = "hello world 章若楠";
        osw.write(s, 12, 3);
        osw.write("hello word");
        osw.flush();//刷新资源
        osw.close();//释放资源
    }
    //
    public static void Test04() throws Exception{
        InputStreamReader isr = new InputStreamReader(new FileInputStream("abs.txt"));

        //定义字符数组大小 1k
        char[] chars = new char[1024];
        //定义文件字符数组的数量
        int len;
        //遍历字符数组获取里面的字符转化成字符串打印到控制台上
        while ((len=isr.read(chars))!=-1){
            //打印的是字符数组的从0开始到最后一个数组
            System.out.println(new String(chars,0,len));
        }
        //释放资源
        isr.close();
    }
    public static void CopyFile() throws IOException, InterruptedException {
        //创建字符转换输入流对象--封装源文件
        InputStreamReader ri = new InputStreamReader(new FileInputStream("D://MyCallable.java"));
        //创建字符转换输出流文件--封装目的地文件
        OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("MyCallable.java"));
        //一次读取一个字符数组
        char[] chars = new char[1024];
        int len;//实际读取的字符数
        while ((len= ri.read(chars))!=-1){
            ow.write(chars,0,len);//读取一个字符放到文件中

        }
        //释放资源
        ow.close();
        ri.close();
    }
    public static void CopyFile1() throws Exception{
        //创建字符输入流的便捷类对象
        FileReader fr = new FileReader("MyCallable.java");
        //创建字符输出流的便捷类对象
        FileWriter fw = new FileWriter("D://MyCallable.java");
        //一次读取一个字符数组
        char[] chars = new char[1024];
        //实际字符数
        int len;
        //方法:阻塞式方法,只要文件没有读完,一直读!
        while((len= fr.read(chars))!=-1){
            fw.write(chars,0,len);
        }
        //释放资源
        fr.close();
        fw.close();
    }


    public static void BufferedWriter1() throws Exception{
        //BufferedWriter字符缓冲输出流:
        // 它的出现为J提高高效写入,里面存在缓冲区,默认缓冲区大小已经足够大了
        BufferedWriter bf = new BufferedWriter(new FileWriter("aaa.txt"));

        bf.write("hello");//在文件中写入字符串
        bf.newLine();//换行
        bf.write("world");
        bf.newLine();
        bf.write("章若楠");
        bf.newLine();
        bf.write("你好美");
        bf.close();//释放资源
    }
    public static void BufferedReader1() throws Exception{
        //创建一个字符缓冲输入流对象
        BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
        /*//一次读取一个字符数组或者一次读取读取一个字符
        char[] chars = new char[1024];
        int len;//
        while ((len = br.read(chars))!=-1){//第一种阻塞式方式,只要文件没有读完就一直读
            //在控制台打印文件中的内容
            System.out.println(new String(chars,0,len));
        }*/
        //声明变量用来结束循环
        String line = null;
        //第二种阻塞式方法,只要没有null值,就一直读(判断,赋值,调用方法一块用)
        //如果文件中有内容就一直读,或者文件中存在null为单独一行,或者文件没有内容了,程序猜停止
        while ((line= br.readLine())!=null){
            System.out.println(line);//打印在控制台
        }
        br.close();//释放资源
    }
    public static void CopyFileDemo() throws Exception{
        //封装源文件
        BufferedReader br = new BufferedReader(new FileReader("MyCallable.java"));
        //封装目的地文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("D://aaa.java"));
        //定义变量
        String line=null;
        //第二种阻塞式方法,只要没有null值,就一直读(判断,赋值,调用方法一块用)
        //如果文件中有内容就一直读,或者文件中存在null为单独一行,或者文件没有内容了,程序猜停止
        while ((line = br.readLine())!=null){
            //按行写入
            //写入到文件中
            bw.newLine();
            //刷新
            bw.flush();
        }
        //释放资源
        bw.close();
        br.close();
    }
    public static void Demo() throws Exception {
        //创建键盘录入对象
        InputStream in = System.in;//标准输入流
        Scanner sc = new Scanner(in);

        //录入字符串
        System.out.println("请输入一个字符串:");
        String s1 = sc.nextLine();
        System.out.println(s1);

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

        //分步写
        InputStream ni = System.in;
        Reader reader = new InputStreamReader(ni);
        BufferedReader br = new BufferedReader(reader);
        System.out.println("请输入字符串:");
        String s2 = br.readLine();
        System.out.println(s2);

        //一步写
        BufferedReader rb = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入字符串:");
        //利用BufferedReader的读取一行
        String s = rb.readLine();
        System.out.println(s);
    }
}

本周的总结就到这里了,好累,马上javaSE就学完了,应该进入到难点了,不过还是要一往无前,痛改前非,继续学习,上号上号;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java_houduan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值