目录
- 多线程和Stream流
- 1. 多线程
- 1.1 进程
- 1.2 线程
- 1.3 进程和线程的关系
- 1.4 多线程优势
- 1.5 多线程代码实现的两种方式
- 1.6 Thread 涉及到的方法
- 1.7 同步机制
- 1.7.1 同步代码块
- 1.7.2 同步方法
- 2. Stream 流式操作
- 2.1 Stream 基本涉及到的方法
- 2.2 基本演示
- 2.3 skip 跳过 和 limit 限制
- 2.4 filter 过滤
- 2.5 distinct 去重
- 2.6 sorted 排序
- 2.7 map 转换方法
- 2.8 count 计数和 forEach 最终处理
多线程和Stream流
1. 多线程
1.1 进程
进程是一个独立的程序,每一个程序相对互斥,互不干扰。进程所需的资源(CPU, 内存,硬盘,网络,显卡)是由操作系统分配。
Windows 操作系统是一个多任务,多线程操作系统吗???
不是一个多任务,多线程的操作系统。
时间片 ==> 操作系统 + CPU 执行一次任务的完整时间。
例如: 1s ==> 100 份 10个程序
A 15 B 5 C 12 D 8
注意:
1. 进程是一个独立的程序,需要操作系统分配资源
2. 操作系统不是一个多任务,多线程的操作系统,是单位时间片多个任务切换给使用者的一个体验
1.2 线程
多线程
电脑管家:可以同时执行病毒查杀,垃圾清理,电脑检查,权限管理,软件更新....
每一个功能模块都是一个【线程】
线程需要进程环境,因为线程使用的资源都是当前进程提供的!!!提供的资源有(CPU, 内存,硬盘,网络,显卡),并且线程之前有一定的抢占操作。也可以认为在抢占进程的执行时间片。
一个进程至少有一个有效线程,如果当前进程没有任何一个线程存在,整个进程退出关闭
Java程序你认为最少有几个线程???分别是哪些线程???
至少两个线程
1. main线程【主线程】
2. JVM 的 GC (Garbage Collector) 机制
1.3 进程和线程的关系
对比 | 进程 | 线程 |
资源分配 | CPU,操作系统分配 | 当前进程分配资源 |
组成 | 是由多个线程组成 | 线程关注任务 |
独立性 | 进程是相对独立的 | 不可能独立存在,需要进程环境 |
1.4 多线程优势
物极必反!!!多线程的优势即劣势
优势:
1. 提高资源利用率
2. 提升用户体验
3. 提高执行效率
劣势:
1. 资源利用过高,导致计算机压力大。
2. 导致用户体验降低
3. 单个线程执行效率降低
4. 极易导致死锁
1.5 多线程代码实现的两种方式
方式一:
继承 Thread 类,重写 run 方法,run 方法中是目标线程代码。【What will be run】
利用 Thread 类内的 start 开启线程。
方式二:
遵从 Runnable 接口,实现 run 方法,run 方法中是目标线程代码、
实例化 Thread 类对象,调用 Thread(Runnable) 构造方法,利用 start 开启线程
推荐方式二
1. Java 中的类都是单继承类,如果继承 Thread 类之后无法继承其他类,影响代码的继承结构
2. 遵从接口不影响继承过程,同时对当前代码是一个增强,耦合度低
package com.qfedu.a_thread;
/*
* 两种方式实现 线程代码
* 1. 继承 Thread 类
* 2. 遵从 Runnable 接口
*/
/*
* 方式一 继承 Thread 类
*/
class MyThread1 extends Thread {
/*
* run 方法是线程执行目标的核心方法, what will be run。
* 当前线程对象启动,会执行 run 方法内容,如果是单独调用 run 方法
* 实际上非线程代码。
*/
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("继承 【Thread】 类, 实现线程代码");
}
}
}
/*
* 方式二 遵从 Runnable 接口
*/
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("遵从 【Runnable】接口, 实现线程代码");
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 方式一线程启动
new MyThread1().start();
// 方式二线程启动
new Thread(new MyThread2()).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程【main 线程】代码!!!");
}
}
}
1.6 Thread 涉及到的方法
构造方法:
Thread();
Thread 无参数构造方法
Thread(Runnable target);
使用 Runnable 接口实现类作为 Thread 构造方法参数对象,明确当前 Thread 线程执行目标是什么。
Thread(Runnable target, String name);
使用 Runnable 接口实现类作为 Thread 构造方法参数对象,明确当前 Thread 线程执行目标是什么,同时使用
name 设置当前线程的名称
成员方法:
void setName(String name);
设置线程的名称
String getName();
获取线程名
void setPriority(int newPriority);
设置线程优先级,
tips
1. 线程优先级从 1 开始,最大为 10, 默认为 5。
2. 线程优先级只可以提示线程的执行概率,不明确首先执行,第一个执行,本次时间片一定执行。
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5
Thread.MAX_PRIORITY = 10
int getPriority();
获取线程优先级
void setDaemon(boolean daemon);
设置当前线程为守护线程,参数如果为 true 表示当前线程为守护线程。
boolean isDaemon();
判断当前线程是否为守护线程
static Thread currentThread();
【静态方法】
在哪一个线程代码中执行,获取当前代码对应线程对象
static void sleep(int ms);
【静态方法】
在哪一个线程代码中执行,当前线程进入阻塞状态,休眠指定的毫秒数
package com.qfedu.a_thread;
/*
构造方法:
Thread();
Thread 无参数构造方法
Thread(Runnable target);
使用 Runnable 接口实现类作为 Thread 构造方法参数对象,明确当前 Thread 线程执行目标是什么。
Thread(Runnable target, String name);
使用 Runnable 接口实现类作为 Thread 构造方法参数对象,明确当前 Thread 线程执行目标是什么,同时使用
name 设置当前线程的名称
成员方法:
void setName(String name);
设置线程的名称
String getName();
获取线程名
void setPriority(int newPriority);
设置线程优先级,
tips
1. 线程优先级从 1 开始,最大为 10, 默认为 5。
2. 线程优先级只可以提示线程的执行概率,不明确首先执行,第一个执行,本次时间片一定执行。
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5
Thread.MAX_PRIORITY = 10
int getPriority();
获取线程优先级
void setDaemon(boolean daemon);
设置当前线程为守护线程,参数如果为 true 表示当前线程为守护线程。
boolean isDaemon();
判断当前线程是否为守护线程
static Thread currentThread();
【静态方法】
在哪一个线程代码中执行,获取当前代码对应线程对象
static void sleep(int ms);
【静态方法】
在哪一个线程代码中执行,当前线程进入阻塞状态,休眠指定的毫秒数
*/
public class Demo2 {
public static void main(String[] args) {
Thread thread1 = new Thread();
Thread thread2 = new Thread(() -> {
System.out.println("lambda 表达式实现线程执行目标");
/*
* 在哪一个线程代码中执行,获取当前代码对应线程对象
*/
Thread ct = Thread.currentThread();
System.out.println(ct);
});
Thread thread3 = new Thread(() -> {
System.out.println("lambda 表达式实现线程执行目标");
}, "河霖想要周三交抄写String+反射十遍线程");
/*
* Thread[Thread-0,5,main]
* Thread[threadName, threadPriority, threadGroup]
* Thread[线程名, 线程优先级, 线程组]
*/
System.out.println(thread1);
System.out.println(thread2);
System.out.println(thread3);
System.out.println();
// 设置和获取线程名
thread2.setName("吉祥祥哥想要周二(2022年3月29日)交作业");
System.out.println(thread2);
System.out.println(thread1.getName());
System.out.println();
// 设置和获取线程优先级
thread2.setPriority(Thread.MAX_PRIORITY);
System.out.println(thread2.getPriority());
/*
* 当前方法获取的哪一个线程对象
* 在哪一个线程代码中执行,获取当前代码对应线程对象
*/
Thread ct = Thread.currentThread();
System.out.println(ct);
thread2.start();
}
}
1.7 同步机制
1.7.1 同步代码块
案例:
电影院 <<钢铁侠>> 100 票
猫眼,淘票票,美团
第一个问题:
100 张票是不是共享资源???
是
第二个问题:
三个销售途径是否可以认为是三个线程???
是
同步代码块
在同步代码块中,有且只允许一个线程进入,线程进入【锁生效】。线程执行完毕【锁开启】。锁开启 之后允许其他线程进入抢占执行。
synchronized (锁对象) {
同步代码块
}
锁对象要求:
1. 必须是符合 Java 规格的一个类对象,不能是【包装类】
2. 要求限制的线程,必须使用同一个锁对象。
3. 常用锁对象
线程之前的共享资源,类锁(考虑当前类有且只有一组相关线程)
package com.qfedu.a_thread;
/*
* 100 张票作为三个线程对象的共享资源
*/
class SaleTicket implements Runnable {
/*
* static 修饰的静态成员变量在对应当前类的所有对象是一个共享资源
* 同时使用 private 限制操作形式,针对于 ticket 操作全部由设定
* 的 方法来完成,初始化数据为 100
*/
private static int ticket = 100;
/*
* @Override 是开启代码格式严格检查,确定子类重写父类方法,或者子类实现父类 or 接口
* 中方法,方法声明格式是否一致
*/
@Override
public void run() {
/*
* 获取当前线程对应的名称
*/
String threadName = Thread.currentThread().getName();
while (true) {
synchronized (SaleTicket.class) {
if (ticket > 0) {
System.out.println(threadName + "售出了 编号为 " + ticket + " 电影票" );
ticket--;
} else {
System.out.println(threadName + "售罄");
break;
}
}
}
}
}
public class Demo4 {
public static void main(String[] args) {
Thread t1 = new Thread(new SaleTicket(), "淘票票");
Thread t2 = new Thread(new SaleTicket(), "美团");
Thread t3 = new Thread(new SaleTicket(), "猫眼");
t1.start();
t2.start();
t3.start();
}
}
1.7.2 同步方法
package com.qfedu.a_thread;
class SaleTicket2 implements Runnable {
/*
* static 修饰的静态成员变量在对应当前类的所有对象是一个共享资源
* 同时使用 private 限制操作形式,针对于 ticket 操作全部由设定
* 的 方法来完成,初始化数据为 100
*/
private static int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
sale();
}
}
/*
* synchronized 【静态】同步方法,要求当前方法体执行有且只允许一个线程进入。
* 可以认为作为所标记的对象 ==> SaleTicket2.class
*
* 一个类内有多个 synchronized 修饰的同步方法,某一个方法执行,其他方法
* 都无法执行。可以保证执行目标的唯一性,保证数据安全,但是效率低!!!
*/
public static synchronized void sale() {
String threadName = Thread.currentThread().getName();
if (ticket > 0) {
System.out.println(threadName + "售出了 编号为 " + ticket + " 电影票" );
ticket--;
} else {
System.out.println(threadName + "售罄");
}
}
}
public class Demo5 {
public static void main(String[] args) {
Thread t1 = new Thread(new SaleTicket2(), "淘票票");
Thread t2 = new Thread(new SaleTicket2(), "美团");
Thread t3 = new Thread(new SaleTicket2(), "猫眼");
t1.start();
t2.start();
t3.start();
}
}
2. Stream 流式操作
2.1 Stream 基本涉及到的方法
一般用于处理集合数据。可以基于 Stream 流完成流水线操作,提高开发效率,满足开发要求
Collection<E>
Stream<E> stream();
通过集合对象调用,可以获取对应当前集合的 Stream 流对象,同时在 Stream 中对应处理的数据类型为当前集合
存储数据类型
中间方法:
Stream<E> skip(int n);
跳过指定数据个数
Stream<E> limit(int n);
限制当前 Stream 流对应的数据总数
Stream<E> filter(Predicate<E> pre);
根据提供的 Predicate 接口过滤器,限制 Stream 流中数据存储内容
Stream<E> distinct();
去重操作,Stream 流中重复元素仅保留一个
Stream<E> sorted(Comparator<E> com);
Stream 流数据内容按照 Comparator 比较器规则排序
Stream<R> map(Function<E, R> fun);
Stream 数据内容根据 Function 接口限制规则进行数据类型转换,转换为对应 R 类型
Stream<String> ==> Function ==> Stream<Person>
最终方法:
int count();
当前 Stream 流对应有多少元素
void forEach(Consumer<E> handler);
当前 Stream 流对应元素的最终处理方式
2.2 基本演示
package com.qfedu.b_stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Demo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("酱牛肉");
list.add("羊肉烩面");
list.add("羊肉泡馍");
list.add("烤羊排");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
/*
* 1. 去除前两个元素
* 2. 仅保留 5 个元素
* 3. 要求元素带有 "肉"
*/
// 原始方式
List<String> subList = list.subList(2, list.size());
List<String> subList2 = subList.subList(0, 5);
ArrayList<String> list3 = new ArrayList<String>();
for (int i = 0; i < subList2.size(); i++) {
if (subList2.get(i).contains("肉")) {
list3.add(subList2.get(i));
}
}
System.out.println(list3);
// Stream 流 JDK1.8 新特征
List<String> list2 = list
.stream()
.skip(2)
.limit(5)
.filter(s -> s.contains("肉"))
.collect(Collectors.toList());
System.out.println(list2);
}
}
2.3 skip 跳过 和 limit 限制
package com.qfedu.b_stream;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("酱牛肉");
list.add("羊肉烩面");
list.add("羊肉泡馍");
list.add("烤羊排");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
/*
* 集合对象获取 Stream 流对象
*/
Stream<String> stream = list.stream();
/*
* Stream<E> skip(long n);
* 跳过用户指定个数的数据内容
*/
Stream<String> skip = stream.skip(3);
/*
* Stream<E> limit(long maxSize);
* 允许当前 Stream 流对象中最大容量是多少
*/
Stream<String> limit = skip.limit(3);
limit.forEach(s -> System.out.println(s));
}
}
2.4 filter 过滤
package com.qfedu.b_stream;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("酱牛肉");
list.add("羊肉烩面");
list.add("羊肉泡馍");
list.add("烤羊排");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
Stream<String> stream = list.stream();
/*
* Stream<T> filter(Predicate<T> predicate);
* 过滤方法,过滤规则由 Predicate 过滤器接口决定,要求完成的方法
* boolean test(T t);
* 当前 Stream 流对应处理的数据类型为 字符串类型 String 类型。
* boolean test(String str);
* 可以利用 Lambda 表达式,完成一个处理参数为 String 类型返回值结果为
* boolean Lambda 表达式
*/
Stream<String> filter = stream.filter(s -> s.contains("肉"));
filter.forEach(s -> System.out.println(s));
}
}
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
2.5 distinct 去重
package com.qfedu.b_stream;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("酱牛肉");
list.add("羊肉烩面");
list.add("羊肉泡馍");
list.add("烤羊排");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
list.add("羊杂汤");
list.add("牛肉汤");
list.add("罐焖牛肉");
list.add("辣子鸡丁");
Stream<String> stream = list.stream();
/*
* Stream<T> distinct();
* 当前 Stream 流对应数据内容,重复元素仅保留一个
*/
Stream<String> distinct = stream.distinct();
distinct.forEach(s -> System.out.println(s));
}
}
2.6 sorted 排序
package com.qfedu.b_stream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class Demo6 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(2);
list.add(7);
list.add(6);
list.add(3);
list.add(1);
list.add(4);
list.add(10);
list.add(8);
list.add(9);
Stream<Integer> stream = list.stream();
/*
* Stream<T> sorted();
* 当前Stream 流处理元素有自然顺序或者有比较方式(遵从Comparable接口)
* 默认为升序方式
*/
// Stream<Integer> sorted = stream.sorted();
/*
* Stream<T> sorted(Comparator<? super T> comparator);
* 要求提供可以处理当时 Stream 流处理元素的 Comparator 比较器
* 可以采用 Lambda 方式来完成
* int compare(T o1, T o2);
* 目前 Stream 流处理的数据类型为 integer 类型
* int compare(Integer o1, Integer o2);
* Lambda 表达式 两个参数都是 Integer 类型,返回值要求为 int 类型
*/
Stream<Integer> sorted = stream.sorted((o1, o2) -> o2 - o1);
/*
sorted.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
*/
sorted.forEach(t -> System.out.println(t));
}
}
2.7 map 转换方法
package com.qfedu.b_stream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.stream.Stream;
import com.qfedu.util.BeanUtils;
public class Demo7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("id=1,name=王某平,age=95,gender=男,score=0.5");
list.add("id=2,name=王乾,age=99,gender=男,score=0.2");
list.add("id=3,name=王豪豪,age=25,gender=男,score=99.5");
list.add("id=4,name=王乐,age=16,gender=女,score=100");
Stream<String> stream = list.stream();
/*
* 执行 map 方法,同时提供类型转换器
* <R> Stream<R> map(Function<? super T, ? extends R> mapper);
* 参数是一个 Function 类型转换器接口
* R apply(T t);
* Stream 流中目前处理的数据类型为 String 类型
* R apply(String t)
* R ==> Student 类型
* Student apply(String t);
* Lambda 表达式需要在返回值中明确返回类型为 Student 类型
*/
Stream<Student> map = stream.map(t -> {
// 每一行字符串对应一个 Student 类对象
Student stu = new Student();
// t ==> id=1,name=王某平,age=95,gender=男,score=0.5 按照 , 切割
String[] split = t.split(",");
for (int i = 0; i < split.length; i++) {
// 案例 id=1
int index = split[i].indexOf("=");
// fieldName = "id"
String fieldName = split[i].substring(0, index);
// value = "1"
String value = split[i].substring(index + 1);
// System.out.println(fieldName + " " + value);
try {
BeanUtils.setProperty(stu, fieldName, value);
} catch (NoSuchFieldException | SecurityException | NoSuchMethodException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return stu;
});
map.forEach(System.out::println);
}
}
package com.qfedu.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class BeanUtils {
/**
* 给予符合 JavaBean 规范类对象赋值指定成员变量 fieldName,赋值数据为 String 字符串 value
*
* @param bean 符合 JavaBean 规范类对象
* @param fieldName 指定赋值对应的成员变量名称
* @param value 对应当前成员变量的数据,类型为 String 类型
* @throws SecurityException 安全异常
* @throws NoSuchFieldException 没有对应成员变量异常
* @throws NoSuchMethodException 没有对应成员方法异常
* @throws InvocationTargetException 执行目标异常
* @throws IllegalArgumentException 非法参数异常
* @throws IllegalAccessException 非法权限异常
*/
public static void setProperty(Object bean, String fieldName, String value) throws NoSuchFieldException, SecurityException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 1. 确定当前 JavaBean 对象对应的是哪一个数据类型,获取对应的 Class 对象
Class<? extends Object> cls = bean.getClass();
// 2. 根据成员变量名称获取对应的成员变量类 Field 对象
Field field = cls.getDeclaredField(fieldName);
// 3. 给予对应的成员变量操作权限
field.setAccessible(true);
// 4. 获取成员变量对应数据类型
Class<?> fieldType = field.getType();
/*
* 1. String 直接用
* 2. char or Character 类型 charAt(0)
* 3. int or Integer 类型 Integer.parseInt(String)
* 4. Byte Short Long Float Double Boolean
* static Byte parseByte(String)
* static Short parseShort(String)
* static Long parseLong(String)
* static Float parseFloat(String)
* static Double parseDouble(String)
* static Boolean parseBoolean(String)
*/
// 5. 字符串数据类型转换
Object parseValue = null;
if (fieldType == String.class) {
// String字符串
parseValue = value;
} else if (fieldType == char.class || fieldType == Character.class) {
// 字符类型
parseValue = value.charAt(0);
} else if (fieldType == int.class || fieldType == Integer.class) {
// int 类型
parseValue = Integer.parseInt(value);
} else {
// Byte Short Long Float Double Boolean
// 获取类型名称 得到的是完整的包名.类名 例如 java.lang.Float
String typeName = fieldType.getName();
// 例如 java.lang.Float ==> Float
typeName = typeName.substring(typeName.lastIndexOf('.') + 1);
// 拼接解析方法 parseXXX 方法名 例如 parseFloat
String parseMethodName = "parse" + typeName;
// 从当前成员变量对应数据类型中,获取 parse 系列方法
Method parseMethod = fieldType.getMethod(parseMethodName, String.class);
// 执行 parseMethod 方法 转换 value 到目标数据类型 因为 parse 系列方法都是 static 方法,静态方法执行无需对象,可以给予参数 null
parseValue = parseMethod.invoke(null, value);
}
// 6. 成员变量对象调用 set 方法,明确给予对应类对象赋值数据
field.set(bean, parseValue);
}
}
2.8 count 计数和 forEach 最终处理
package com.qfedu.b_stream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo8 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("id=1,name=王某平,age=95,gender=男,score=0.5");
list.add("id=2,name=王乾,age=99,gender=男,score=0.2");
list.add("id=3,name=王豪豪,age=25,gender=男,score=99.5");
list.add("id=4,name=王乐,age=16,gender=女,score=100");
/*
* long count();
* 得到当前 Stream 流中处理数据个数有多少个,一旦调用当前 Stream 终止
* 再次使用会提示 异常
* Exception in thread "main" java.lang.IllegalStateException:
* stream has already been operated upon or closed
*/
Stream<String> stream = list.stream();
long count = stream.count();
System.out.println(count);
Stream<String> stream2 = list.stream();
/*
* void accept(String)
*/
stream2.forEach(s -> {
// 文件操作字节输出流
FileOutputStream fos = null;
try {
// 文件路径是当前工作目录./data/student.txt 采用追加写方式
fos = new FileOutputStream("./data/student.txt", true);
// 字符串转换 byte[] 数组写入
s = "[" + s + "];";
fos.write(s.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 关闭资源
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}