摘要:
本周学习的是上一周结余部分集合的内容和线程,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源码解析
- HashSet 底层是HashMap
- 添加一个元素时,先得到hash值会转成->索引值
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入,如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY THRESHOLD(默认是8),并且table的大小 >=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
HashSet的底层源码
- 先执行的是HashSet()
public HashSet(){
map = new HashMap<>();
}
- 执行 add()
public boolean add(E e){
return map.put(e, PRESENT) == null;//(static) PRESENT = new object();
}
- 执行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);
}
- 执行 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种状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(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个长度;只是提供字节缓冲区,文件的读写还是依赖于 FileInputStream和FileOutputStream来操作
public BufferedOutputStream(OutputStream out):提供默认缓冲区大小的字节缓冲流对象
方法的形式参数是一个抽象类,需要抽象类的子类对象 符合装饰者设计模式
文本文件(.java/.css/.js/.html/.md/.txt/.xml…,打开之后能读懂的)的读或者写,就直接使用字符流;
什么使用字节流:当使用记事本打开文件,读不懂,乱码—此时使用字节流
字符流
使用InputStreamReader和OutputStreamWriter
字符转换流对普通文本文件进行读写复制,不能直接操作文件(但是这个流的类型属于字符流)
借助包装的底层字节流来完成文件读写操作
将当前项目的下的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就学完了,应该进入到难点了,不过还是要一往无前,痛改前非,继续学习,上号上号;