System类
System:类中的方法和属性都是静态的。
out:标准输出,默认是控制台
in :标准输入,默认键盘
描述系统的一些信息。
获取系统属性信息:Propertise getPropertises();
//获取系统信息的方法演示
import java.util.*;
public class SystemDemo {
public static void main(String[] args)
{
Properties prop = System.getProperties();
//Properties 是一个双列集合。
//因为Properties是Hashtable的子类,也就是Map的子类对象
//那么可以通过map集合的取出方式取出该集合
//该集合存储的都是字符串,没有泛型定义
for(Object obj:prop.keySet())
{
String s = (String)obj;
System.out.println(s+"::"+prop.get(s));
}
}
}
运行结果:
既然能获取,就可以设置
setProperty(String key,String value)
//获取系统信息的方法演示
import java.util.*;
public class SystemDemo {
public static void main(String[] args)
{
//自定义添加系统信息
System.setProperty("mykey","myvalue");
//获取所有的系统信息
Properties prop = System.getProperties();
//打印键值对
for(Object obj:prop.keySet())
{
String s = (String)obj;
System.out.println(s+"::"+prop.get(s));
}
}
}
获取单个系统信息的方法:String getProperty(String key);
//获取系统信息的方法演示
import java.util.*;
public class SystemDemo {
public static void main(String[] args)
{
//获取单个的系统信息:
String s = System.getProperty("os.name");
System.out.println("os.name="+s);
}
}
如果参数输入一个不存在的键的话,输出的就是null
import java.util.*;
public class SystemDemo {
public static void main(String[] args)
{
//获取单个的系统信息:
String s = System.getProperty("hahaha");
System.out.println("value="+s);
}
}
那么怎么样在jvm启动的时候就动态的加载一些系统信息呢?
java命令中有这样的指令:
-Dname = value 设置一个系统属性
命令行这样写:
java -Dhahaha=qqqqqq SystemDemo
//就是指定一个键为hahaha 值为qqqqqq的系统信息。
再次执行上边的代码,就会出现这样的结果:
结果发现,在查看hahaha对应的值的时候,可以打印出来该键对应的值。
Runtime类
每一个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。
该类中并没有提供构造函数。说明不可以new对象,那么会直接想到该类中的方法都是静态的。
发现该类中还有非静态方法
说明该类肯定会提供了方法获取本类对象,而且该方法是静态的,并且返回值类型是本类对象。
由这个特点,可以看出该类使用了单例设计模型
下面看一个练习
//用Runtime对象打开一个本地的记事本程序,然后再关掉
//Runtime是虚拟机和运行环境建立的一个连接,也就是说Runtime的对象可以调用操作系统底层的功能
public class RuntimeDemo {
public static void main(String[] args) throws Exception
{
//因为没有办法创建对象,使用java提供的获取对象的方法,返回一个本类的对象
Runtime r = Runtime.getRuntime();
//有了对象就可以调用方法了
//exec("这里写上命令,虚拟机就可以执行该命令")
Process p = r.exec("notepad");//调用记事本的命令,会返回一个执行的进程对象p
//如果在这里传递的参数写错了,会抛出io异常
//线程睡眠4秒钟
Thread.sleep(4000);
//然后记事本关闭
//由java开启的调用底层功能的线程,java可以获取到线程的对象,就可以关闭该线程
p.destroy();
}
}
运行结果为:
运行结果可以看到,程序运行时打开一个空白的记事本程序,4秒之后,记事本程序自动关闭。
然而Runtime类的强大之处还在于:
//用Runtime对象打开一个本地的记事本程序,记事本程序默认打来一个文本文件
public class RuntimeDemo {
public static void main(String[] args) throws Exception
{
Runtime r = Runtime.getRuntime();
Process p = r.exec("notepad SystemDemo.java");//中间用空格隔开,写上一个文本文档文件,
}
}
运行结果:
运行结果显示,打开的记事本程序自动打开了一个SystemDemo.java的文件。
同样的道理,使用Runtime对象如果打开了一个播放器后边跟上一个视频文件,运行程序的时候就会直接播放视频了。
以上代码中写的记事本程序没有写路径,是因为在path中有System32,jvm会先在当前目录下找,如果找不到就在path路径下找,如果再找不到,就会抛出异常,
所以在使用Runtime对象打开本地程序的时候最好加上完整路径。
由Runtime对象开启的本地程序,java可以获取到他的进程对象Process对象,并且关闭掉他,
如果进程不是java打开的,java是拿不到他的进程对象的,也关不掉进程。
Date类
在util包中。
可以获取当前的时间
import java.text.*;
import java.util.*;
public class DateDemo {
public static void main(String[] args)
{
Date d = new Date();
//System.out.println(d);//打印结果为Tue Jul 28 21:31:47 CST 2015
//打印出来的时间看着不爽,怎么办呢?
//将简单的时间模式封装到SimpleDateFormat对象中
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日E hh:mm:ss");
//调用foemat方法让模式格式化指定Date对象。
String time = sdf.format(d);
System.out.println(time);
}
}
运行结果为:
想要得到自己想要的时间模式,需要创建简单时间格式(SimpleDateFormat)对象,
在构造函数的时候就给定想要的时间的格式,
再用该时间模式的对象调用format 方法,将时间对象d传进去,
就得到了想要的时间。
Calendar类
我们想要获取当前的年,用Date类的方法是这样的
//使用Date对象获取年份
import java.util.*;
import java.text.*;
public class CalendarDemo {
public static void main(String[] args)
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年");
String year = sdf.format(d);
System.out.println(year);//2015年
}
}
以上代码中,获取到的年份的值已经变成了一个字符串,没有办法再进行有关年的其他运算,(严格来说也有,Integer.parseInt(),)和调用年的方法。
方法二:
//使用Calendar对象获取年份月份日,还有星期
import java.util.*;
import java.text.*;
public class CalendarDemo {
public static void main(String[] args)
{
//Calendar是抽象的,所以使用它的静态方法获取一个本类对象
Calendar c = Calendar.getInstance();
//获取对象中的值用get方法,传入给定的字段,返回值,记住前边要标识Calendar
sop(c.get(Calendar.YEAR)+"年");
//同样的获取月份和日
//sop(c.get((Calendar.MONTH))+1+"月");
//获取月份的时候要注意,他这里的月份总比我们的月份少1,所以要有一个加1的动作
//但是有了这个动作太麻烦了,我们可以直接定义一个字符串的数组,每一次获取的值直接来数组中取值打印就好了
String[] mons = {"一月","二月","三月","四月"
,"五月","六月","七月","八月"
,"九月","十月","十一月","十二月"
};
int index = c.get((Calendar.MONTH));//因为他这里返回的是一个数字,所以就直接当做角标了。
sop(mons[index]);
sop(c.get(Calendar.DAY_OF_MONTH)+"日");
//打印星期也是一样的原理
String[] weeks = {"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
int week = c.get(Calendar.DAY_OF_WEEK);
sop(weeks[week]);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
运行结果:
演示set和add方法
//将时间随意前移或者后退的方法,add方法
import java.util.*;
import java.text.*;
public class CalendarDemo2 {
public static void main(String[] args)
{
Calendar c = Calendar.getInstance();
//使用set方法可以设置当前的时间值
//c.set(1991,03,25);
//使用add方法,可以调整指定的字段值进行前移或者后移
c.add(Calendar.YEAR,5);//将年份,向后推5年
printCalendar(c);
//将月份向前移5个月
c.add(Calendar.MONTH,-5);
printCalendar(c);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void printCalendar(Calendar c)
{
sop(c.get(Calendar.YEAR)+"年");
String[] mons = {"一月","二月","三月","四月"
,"五月","六月","七月","八月"
,"九月","十月","十一月","十二月"
};
int index = c.get((Calendar.MONTH));
sop(mons[index]);
sop(c.get(Calendar.DAY_OF_MONTH)+"日");
String[] weeks = {"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
int week = c.get(Calendar.DAY_OF_WEEK);
sop(weeks[week]);
}
}
运行结果:
练习1:算出指定年份的2月一共有多少天?
//计算出指定的年份的二月有多少天
import java.util.*;
import java.text.*;
public class CalendarTest1{
public static void main(String[] args)
{
//获取一个日历对象
Calendar c = Calendar.getInstance();
sop(getErYue(c,2016));
}
public static int getErYue(Calendar c,int year)
{
//将时间设定在某一年的3月1号,这里切记月份是按照数组这样排序的,第一个是0月
c.set(year,2,1);
//3月1号向前推一天,也就是二月的最后一天了
c.add(Calendar.DAY_OF_MONTH,-1);
//获取到该最后一天是该月份中的第几天
int erYue = c.get(Calendar.DAY_OF_MONTH);
return erYue;
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
运行结果:
练习2:算出昨天的现在的时间点?
import java.util.*;
import java.text.*;
public class CalendarTest2 {
public static void main(String[] args)
{
Calendar c = Calendar.getInstance();
getYestoday(c);
}
public static void getYestoday(Calendar c)
{
//先把日期向前推一天
c.add(Calendar.DAY_OF_MONTH,-1);
//把年月日都打印出来
printCalendar(c);
//再获取并打印其中的时分秒
sop(c.get(Calendar.HOUR));
sop(c.get(Calendar.MINUTE));
sop(c.get(Calendar.SECOND));
}
public static void sop(Object obj)
{
System.out.println(obj);
}
//这是之前用过的打印年月日的方法,直接拿过来用
public static void printCalendar(Calendar c)
{
sop(c.get(Calendar.YEAR)+"年");
String[] mons = {"一月","二月","三月","四月"
,"五月","六月","七月","八月"
,"九月","十月","十一月","十二月"
};
int index = c.get((Calendar.MONTH));
sop(mons[index]);
sop(c.get(Calendar.DAY_OF_MONTH)+"日");
String[] weeks = {"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
int week = c.get(Calendar.DAY_OF_WEEK);
sop(weeks[week]);
}
}
运行结果为:
Math类
java.lang包中的类。
里边都是静态方法。
//ceil方法的使用:返回大于该参数的最小整数
public class MathDemo {
public static void main(String[] args)
{
double d1 = Math.ceil(12.34);
sop(d1);
double d2 = Math.ceil(16.34);
sop(d2);
double d3 = Math.ceil(-12.34);
sop(d3);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
Math.floor();返回小于该参数的最大整数
//floor方法:返回小于该参数的最大整数
public class MathDemo {
public static void main(String[] args)
{
double d1 = Math.floor(12.34);
sop(d1);
double d2 = Math.floor(16.34);
sop(d2);
double d3 = Math.floor(-12.34);
sop(d3);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
Math.round(double d);四舍五入,返回值为long类型的。
public class MathDemo {
public static void main(String[] args)
{
//round方法:四舍五入
long l1 = Math.round(12.34);
sop("l1="+l1);
long l2 = Math.round(12.54);
sop("l2="+l2);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
Math.pow(double a,double b);求a的b次方,返回值为double
public class MathDemo {
public static void main(String[] args)
{
//pow();求幂的方法,第一个参数为底数,第二个参数为指数
double d1 = Math.pow(2,3);
sop("d1="+d1);
double d2 = Math.pow(3,3);
sop("d2="+d2);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
random方法:随机的返回一个大于等于0,且小于1的数。double类型的。
public class MathDemo {
public static void main(String[] args)
{
//random方法,随机的返回一个大于等于0,且小于1的double类型的数
for (int x = 0;x<10 ;x++ )
{
//获取10个这样的数
double d1 = Math.random();
sop(d1);
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
如果想要获取从1到10范围内的十个随机整数呢?
简单:
public class MathDemo {
public static void main(String[] args)
{
for (int x = 0;x<10 ;x++ )
{
//只要在原来的小数的基础上乘以10再加1就行了
//因为之前的数是0~0.9,乘10后变成0~9,加1变为1~10
int d1 = (int)((Math.random()*10)+1);
sop(d1);
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
其实,想要获得以上者样的数字组合,还有另一中方法就是使用util包中的Random类。
import java.util.*;
public class RandomDemo {
public static void main(String[] args)
{
for (int x =0;x<10 ;x++ )
{
Random r = new Random();//创建一个random对象
sop(r.nextInt(10)+1);//获取0到指定数之间的随机整数
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
运行结果为:
练习:给定一个小数,要求保留小数的后两位。
//给定一个小数,要求保留小数的后两位
public class MathTest {
public static void main(String[] args)
{
double d = Math.random()*10+1;
sop(d);
getTwo(d,2);
}
//这里定义一个保留小数后几位的方法,既然是方法,为了提高复用性,将要保留几位也作为一个参数传进来。
public static void getTwo(double d,int base)
{
/*
整体思路是:
保留后的小数最后一位需要四舍五入,
我们在这里就先把小数点后移两位(具体后移几位按需求来说)
然后四舍五入取到值,(是一个long类型的,后边没有小数点。)
在把小数点前移两位。打印出来
*/
double num = Math.pow(10,base);
long l = Math.round(d*num);
sop(l/num);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
IO流
- IO流用来处理设备之间的数据传输
- Java对数据的操作是通过流的方式
- Java用于操作流的对象都在IO包中
- 流按照操作数据分为两种:字节流与字符流
- 流按流向分为:输入流,输出流。
IO流常用的基类
- 字节流的抽象基类
- InputStream,OutputStream
- 字符流的抽象基类:
- Reader,Writer
- 注意:由这四个类派生出来的子类名称都是以其父类作为子类名的后缀。
- 如:InputStream的子类FileInputStream.
- 如:Reader的子类FileReader
既然IO流是用于操作数据的,那么数据最常见的体现形式就是“文件”
那么先以操作文件为主来演示。
需求:在硬盘上,创建一个文件并写入一些文字数据。
找到一个专门操作文件的Writer子类对象,FileWriter。
后缀名是父类名,前缀名是该流对象的功能
下面以FileWriter为例演示一下IO流的基本写入的方法。
//文件写入流的简单操作
import java.io.*;
public class FileWriterDemo {
public static void main(String[] args) throws IOException
{
//创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件。
//而且该文件会被创建到指定的目录下,如果该目录下已经有同名文件,将被覆盖。
//其实该步就是再明确数据要存放的目的地。
FileWriter fw = new FileWriter("demo.txt");
//调用write方法,将字符串写入到流中。
fw.write("ni hao shi jie");
//刷新流对象中的缓冲中的数据。
//将数据刷到目的地中,
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据
//将数据刷到目的地中。
//和flush的区别,flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
}
运行结果:
IO异常的处理
//IO异常的处理
import java.io.*;
public class FileWriterDemo2 {
public static void main(String[] args)
{
FileWriter fw = null;//因为fw对象要放在多个代码块中使用,所以在最外边建立一个空的引用
try
{
fw = new FileWriter("k:\\demo.txt");//这一步会发生异常
fw.write("hello world");//这一步也会发生异常
//两行代码都会发生异常,而且都有相关的,所以放在同一个try中处理
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally//关闭资源的动作必须要执行,所以放在finally中
{
try
{
//因为该步骤涉及到了fw,假如在上方代码中fw没有创建成功,这里的fw就是空指针了
//会发生空指针异常,所以这里先要进行判断
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
编译可以通过,当流对象创建成功了,程序正常运行,并且结束。
当出现错误的值的时候,流对象创建不成功,打印出异常信息。
在硬盘上原有的文件中续写数据
分析,应该先考虑构造函数,因为只要一new新的对象,新创建的文件就会覆盖掉原来的文件。所以从构造函数下手。
查阅API发现:
//演示对已有文件的数据的续写
import java.io.*;
public class FileWriterDemo3 {
public static void main(String[] args)
{
FileWriter fw = null;
try
{
//需要在创建流对象的时候传入布尔型的值,true表示如果文件已经存在则不覆盖已有的文件
//在原有的文件上续写数据
fw=new FileWriter("demo.txt",true);
fw.write("nihao shijie");
}
catch (IOException e)
{
System.out.println("流对象创建失败啦");
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
}
}
运行结果:
如果想要在写入数据的时候加上换行的话,需要在数据中间加上\r\n
Ps:Windows系统中认识的换行就是\r\n
//演示对已有文件的数据的续写
import java.io.*;
public class FileWriterDemo3 {
public static void main(String[] args)
{
FileWriter fw = null;
try
{
//覆盖掉原有的文件进行重新写入
fw=new FileWriter("demo.txt");
fw.write("nihao shijie\r\nhello world");
}
catch (IOException e)
{
System.out.println("流对象创建失败啦");
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
}
}
运行结果:
结果显示、操作成功。
使用write方法进行写数据的时候,不仅可以写字符串,还可以写字符,字符数组之类的数据。
文件读取方式一
FileReader对象,的read方法。
//文件读取流方法一演示
import java.io.*;
public class FileReaderDemo {
public static void main(String[] args)
{
//先创建一个文件,里边写入数据nihao
FileWriter fw = null;
try
{
//覆盖掉原有的文件进行重新写入
fw=new FileWriter("demo.txt");
fw.write("nihao");
}
catch (IOException e)
{
System.out.println("流对象创建失败啦");
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
//开始创建读取流对象,读取数据
FileReader fr = null;
try
{
//同样初始化的时候要指定要操作的文件
//传入的文件名称如果存在,继续运行,如果不存在,抛出FileNotFoundException
fr = new FileReader("demo.txt");
//read方法只能一个字符一个字符的读,返回的是读取到的字符的ASCII码
//所以在打印的时候需要强转
//read方法读取到字符的末尾的时候,会返回-1
//所以读取方法可以这样写
int ch = 0;
while ((ch=fr.read())!=-1)
{
System.out.println((char)ch);
}
}
catch (IOException e)
{
System.out.println("读取流创建失败");
}
finally
{
//最后一步,关闭资源:
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
}
}
运行结果为:
文件读取成功。
文件读取方式二
read方法还有重载方法:
上一个是一次读一个字符,这个可以一次读多个字符,
演示一下:
//读取文件的另外一种办法
import java.io.*;
public class FileReaderDemo2 {
public static void main(String[] args)
{
//先创建一个文件,里边写入数据nihao
FileWriter fw = null;
try
{
//覆盖掉原有的文件进行重新写入
fw=new FileWriter("demo.txt");
fw.write("nihao");
}
catch (IOException e)
{
System.out.println("流对象创建失败啦");
}
finally
{
try
{
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
//开始创建读取流对象,读取数据
FileReader fr = null;
try
{
//同样初始化的时候要指定要操作的文件
fr = new FileReader("demo.txt");
//read(char[] chs)方法可以将读取到的字符存储到数组中去,然后返回读取到的数组的个数。
//所以先要创建一个数组:数组的长度创建多大比较合适呢?
//如果长度小了,只能读取到长度为数组长度的数据,想要获取到所有的数据还要读取多次
//于是我们一般都把长度定为1024
char[] chs = new char[1024];
int num = fr.read(chs);
//然后将所读到的数据打印出来,将数组的一部分转变成字符串,用new String(数组,起始位置,结束位置)
System.out.println(new String(chs,0,num));
}
catch (IOException e)
{
System.out.println("读取流创建失败");
}
finally
{
//最后一步,关闭资源:
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
}
}
运行结果为:
接下来一个练习,获取一个.java文件,并且打印在控制台上。
//获取一个.java文件,并且打印在控制台上
import java.io.*;
public class FileReaderTest {
public static void main(String[] args)
{
FileReader fr = null;
try
{
//获取到文件对象
fr = new FileReader("DateDemo.java");
char[] chs = new char[1024];
int num = 0;
while ((num = fr.read(chs))!=-1)
{
System.out.println(new String(chs,0,num));
}
}
catch (IOException e)
{
System.out.println("读取流创建失败");
}
finally
{
//最后一步,关闭资源:
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
System.out.println("关闭流资源失败");
}
}
}
}
运行结果为:
对文本文件进行拷贝:
第一种方法,
读一个字符,写一个字符:
//对文本文件进行拷贝
import java.io.*;
public class FileCopyDemo {
public static void main(String[] args)
{
copy_1();
}
public static void copy_1()
{
//建立读取流对象的引用
FileReader fr = null;
FileWriter fw = null;
try
{
//创建写入流对象,并制定文件名称
fw = new FileWriter("RuntimeDemo.txt");
}
catch (IOException e)
{
throw new RuntimeException("写入流开启失败啦");
}
try
{
//创建读取流对象,并且开始读取数据
fr = new FileReader("RuntimeDemo.java");
int num = 0;//num为读取到的字符的ASCII值
while ((num = fr.read())!=-1)
{
fw.write((char)num);//将读到的ASCII值强转成char类型,然后使用写入流对象,写入到目标文件中
}
}
catch (IOException e)
{
throw new RuntimeException("读取流开启失败啦");
}
finally
{
//最后的动作:关闭资源
//一定要先判断,然后处理,这样两个异常可以都被处理掉,
//切记,两个异常不能同时处理。要分开处理
if(fr!=null)
{
try
{
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败啦");
}
}
if(fw!=null)
{
try
{
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败啦");
}
}
}
}
}
运行结果,拷贝功能可以实现。
这种方法效率很低,因为读取流一次只能读取一个字符,写入流一次也能写一个字符。一点效率都没有,
第二种方法,效率就比较高了。
//拷贝文本文件的第二中方法
import java.io.*;
public class FileCopyDemo2 {
public static void main(String[] args)
{
copy_2();
}
public static void copy_2()
{
//建立两个流对象的引用
FileWriter fw = null;
FileReader fr = null;
try
{
//创建写入流对象,并指定文件名称
fw = new FileWriter("SystemDemo.txt");
}
catch (IOException e)
{
//如果流对象创建失败,抛出运行时异常
throw new RuntimeException("写入流开启失败啦");
}
try
{
fr = new FileReader("SystemDemo.java");
int num = 0;
char[] chs = new char[1024];
//开始读取数据,并写入数据
while ((num = fr.read(chs))!=-1)
{
fw.write(chs,0,num);
//write方法有自己的重载方法,可以写入一个数组,从指定位置开始,到指定位置结束
}
}
catch (IOException e)
{
throw new RuntimeException("读取流开启失败啦");
}
//最后的关流动作
finally
{
if(fr!=null)
{
try
{
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败啦");
}
}
if(fw!=null)
{
try
{
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败啦");
}
}
}
}
}
运行结果为:
这样的效率比较高一些。