-----------android培训、java培训、java学习型技术博客、期待与您交流!---------
IO流
IO流:Input Ouput的缩写
概述
IO流用来处理设备之间的数据传输。
Java对数据的操作是通过流的方式来完成的。Java用于操作流的对象都在IO包中.
流按操作数据分为两种
字符流,字节流
流按流向分为
输入流,输出流
IO流常用基类
1. 字节流的抽象基类
InputString OutputStream
2. 字符流的抽象基类
Reader Writer
注:这4个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputString Reader的子类FileReader
字符流
字符流是IO流用于操作数据的,那么数据最常见的体现形式是文件
字符输出流(字符写入流):Writer
FileWriter
专门用于操作文件的Writer子类对象。(后缀是父类名,前缀是该流对象的功能)
这个类没有空参数构造函数,因为要有初始化动作,对象一初始化被操作的文件就要存在。
下面我们来定一个需求,按照需求讲解如何使用字符输出流
需求:在硬盘上创建一个文件并写入一些数据
步骤:
1. 创建一个FileWriter对象(要抛出IOException,后面会详细讲解)
该对象一被初始化就必须要明确被操作的文件,而且该文件会被创建到指定的目录下。如果已有同名文件,将被覆盖。
这个步骤就是在明确数据要存放的目的地。
2. 调用write方法,将数据写入到流中(数据为:字符串,字符数组或单个字符)
3. 调用flush方法刷新流对象中的数据,将数据刷新到目的地中。
4. 调用close方法关闭流资源
但关闭之前会刷新一次内部的缓冲区数据,并将数据刷新到目的地中。
close方法和flush方法的区别
flush方法刷新后流可以继续使用,close方法会刷新流,但会关闭流资源,使流不能继续被使用
为什么一定要关闭流资源?
因为java本身是不能往硬盘上写入数据的,因为每个系统写入数据的方式不一样,所以java要去调用系统内部的内容来完成数据的建立。
那么这些方式都在使用系统的资源,使用完成后必须要释放,所以才有了close方法,最终一定要做关闭流资源的动作。
文件内数据的续写
FileWrite类的构造函数有其中之一是 FileWriter(Filefile , boolean append);
如果在初始化对象时,传递了一个true参数,代表不覆盖已有文件,并在已有文件的末尾进行数据续写。
(如果目录下没有这个文件就创建新的文件)
回车符问题
因为系统不一样,回车符也不一样,Windows的回车符为\t\n,在Linux系统中回车符为\n。到后期我们会学到通用的回车符,不要着急呦~
下面根据上述需求来编写代码
/*
需求:在硬盘上创建一个文件并写入一些数据
思路
1.创建FileWriter对象,指定目的文件
2.使用write方法向目的文件中写入数据
3.调用flush方法刷新流。
4.调用close方法关闭流资源
*/
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)
{
//因为FileWriter对象创建时会抛出异常,所以要try catch处理一下
FileWriter fw1 = null;
FileWriter fw2 = null;
try
{
//创建FileWrite对象,并指定目的文件
fw1 = new FileWriter("我要去黑马.txt");
//调用write方法,往文件中写入一些数据
fw1.write("我会努力的!");
//调用flush刷新流,将流中数据冲入目的文件中
fw1.flush();
//再创建一个FileWriteer对象,续写 我要去黑马.txt 文件
//因为是续写,所以要多传入一个true参数
fw2 = new FileWriter("我要去黑马.txt",true);
//调用write方法,续写数据
fw2.write("有志者,事竟成");
//调用flush刷新流,将流中数据冲入目的文件中
fw2.flush();
}
//处理异常
catch (IOException e)
{
//抛出一个RuntimeException
throw new RuntimeException("输出流异常");
}
//finally代码块中的代码为必须执行的代码,一般用于关闭资源代码
finally
{
if(fw1!=null)
try
{
//调用close方法,关闭流资源
fw1.close();
}
catch (IOException e)
{
}
if(fw2!=null)
try
{
fw2.close();
}
catch (IOException e)
{
}
}
}
}
通过FileWriter对象创建出的文件,以及其中的内容
IO异常处理方式
使用try{}catch{}finally{}代码块来处理,其中要把close方法放在finally代码块中。
因为不放在finally代码块中,如果前面的代码出现异常,资源就不会关闭。
finally代码块中的内容是一定要执行的语句,所以一般用来方关闭资源的语句
不过当close语句放入finally中后,FileWriter对象的引用无法在别的代码块中访问,所以要在try代码块外建立FileWriter的引用,
在代码块内进行初始化。如我在前面程序中的代码。
fw.close(); 也是有问题的语句,所以要在finally内单独try catch一次。
在try内可能对象没有建立成功,但还要执行close语句,对象没有创建成功,FileWriter对象的引用就指向了null
close方法无法调用,还是会发生异常。所以要判断FileWriter对象的引用是否为null。就如我就前面程序中的代码那样写。
一定要对要被关闭的流的对象进行不等于null的判断,当有多个流对象时也要一个一个进行判断。
字符输入流(字符读取流):Reader
FileReader
用于读取字符文件的便捷类,有默认编码表,系统中的编码表。
读取文本文件
方法一
和FileWriter一样FileReader在初始化时要有被读取的文件,所以FileReader没有空参数构造函数。
1. 创建一个文件读取流对象,和指定名称的文件相关联。
例:FileReader fr = new FileReader(“我要去黑马.txt”);
要保证该文件已经存在,如果不存在会发生FileNotFoundException 文件没找到异常。
2. 调用读取流对象的read方法
例:int ch = fr.read();
read方法一次只读取一个字符,而且会自动往下读,如果已经到达流的末尾,则返回-1。
所以可以用循环的方法读取每一个字符,是否返回-1为循环条件。
3. 调用close方法关闭流资源。
代码演示
/*
字符输入流读取文件方法一
思路
1.创建FileReader对象,并指定要读取的文件
2.使用read方法读取文件中的单个字符
3.调用close方法关闭流资源
*/
import java.io.*;
class FileReaderDemo1
{
public static void main(String[] args)
{
//在try代码块外建立对象的引用
FileReader fr = null;
try
{
//创建FileReader对象,指定要读取的文件
fr = new FileReader("我要去黑马.txt");
//调用read方法读取文件中的字符
//定义int类型变量,用于记录read方法的返回值
int ch = 0;
//建立循环read方法的返回值不为-1作为循环条件
while((ch = fr.read())!=-1)
{
//输出文件中的字符,打印在控制台上
System.out.print((char)ch);
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
}
}
}
}
运行结果
方法二
1. 和方法一步骤一样,也是创建一个字符读取流对象,并指定要读取的文件。
2. 定义一个字符数组,用于存储读到的字符。
3. 调用int read(char[] cbuf) 方法,将读取的字符存入数组中。
因为数组个数有限,read(char[])读到最后一个字符之后会返回-1,可以用作循环,
然后再利用String构造函数,将字符数组从0角标位起,到read(char[])方法返回值数,将字符数组变为字符串,打印
new String(char[] , 角标数 , 个数)
代码演示
/*
字符输入流读取文件方法二
思路
1.创建FileReader对象,并指定要被读取的文件
2.创建一个char类型数组。
3.创建一个循环调用read(char[])方法,将读到的数据存入数组中
4.利用String的构造方法输出数组
5.关闭流资源
*/
import java.io.*;
class FileReaderDemo2
{
public static void main(String[] args)
{
//创建FileReader对象的引用
FileReader fr = null;
try
{
//创建FIleReader对象,并指定要读取的文件
fr = new FileReader("我要去黑马.txt");
//创建一个char类型数组,长度为1024
char[] arr = new char[1024];
//定义一个int类型变量,用于接收read(char[])方法的返回值
int len = 0;
//建立循环read(char[])方法返回值不为-1是循环条件
while((len = fr.read(arr))!=-1)
{
//利用String的构造函数将字符数组打印
System.out.println(new String(arr,0,len));
}
}
catch (IOException e)
{
throw new RuntimeException("读取流失败");
}
finally
{
if(fr!=null)
try
{
//关闭流资源
fr.close();
}
catch (IOException e)
{
}
}
}
}
运行结果
拷贝文本文件
步骤
1. 创建一个文本文件,用于存储拷贝过来的文件数据
2. 定义字符读取流和要被拷贝的文件关联。
3. 通过不断的读写完成数据存储
4. 关闭流资源。
从要被复制的文件读取信息有两个方法
方法一:读一个字符就写一个字符
a. 创建目的文件
FileWriter fw = new FileWriter (“目的地文件名”);
b. 创建字符读取流,与要被拷贝的文件相关联
FileReader fr = new FileReader(“被拷贝的文件名”);
c. 通过循环,从被拷贝的文件中读一个字符,就往目的文件中写一个字符
while((ch = fr . read())!= -1){ fw.write(); }
d. 关闭两个流的资源
方法二:读一点儿写一点儿
a. 创建目的地
b. 创建字符读取流,与要被拷贝的文件相关联
c. 定义一个字符数组,和int型变量
d. 通过循环,将字符输入流读取到的数据存储到数组中,字符输出流再把数组中的数据写入到目的文件中。
e. 关闭两个流的资源
代码演示
/*
通过IO流拷贝文本文件
方法一:读一个字符就写一个字符
a. 创建目的文件
b. 创建字符读取流,与要被拷贝的文件相关联
c. 通过循环,从被拷贝的文件中读一个字符,就往目的文件中写一个字符
d. 关闭两个流的资源
方法二:读一点儿写一点儿
a. 创建目的地
b. 创建字符读取流,与要被拷贝的文件相关联
c. 定义一个字符数组,和len变量
d. 通过循环将字符输入流读取到的数据存储到数组中,字符输出流把数组中的数据写入到目的文件中。
e. 关闭两个流的资源
需求
从c盘拷贝一个文本文件,到e盘
*/
import java.io.*;
class FileReaderTest
{
public static void main(String[] args)
{
//调用方法一
copyMethod_1();
//调用方法二
copyMethod_2();
}
//拷贝方法一
public static void copyMethod_1()
{
//字符输出流的引用
FileWriter fw = null;
//字符输出流的引用
FileReader fr = null;
//异常处理
try
{
//创建字符输出流对象,并指定目的地
fw = new FileWriter("e:\\我爱写博客_copy_1.txt");
//创建字符输入流对象,并指定要被拷贝的文件
fr = new FileReader("c:\\我爱写博客.txt");
//定义一个int类型变量,用于接收read方法的返回值
int ch = 0;
//建立循环,读取要被拷贝的文件数据,并写入到目的文件中
while((ch = fr.read())!=-1)
{
//把数据写入到目的文件中
fw.write(ch);
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败啦!!");
}
finally
{
if(fw!=null)
try
{
//关闭流资源
fw.close();
}
catch (IOException e)
{
}
if(fr!=null)
try
{
//关闭流资源
fr.close();
}
catch (IOException e)
{
}
}
}
//拷贝方法二
public static void copyMethod_2()
{
FileWriter fw = null;
FileReader fr = null;
try
{
//创建字符输出流对象,并指定目的地
fw = new FileWriter("e:\\我爱写博客_copy_2.txt");
//创建字符输入流对象,并指定要被拷贝的文件
fr = new FileReader("c:\\我爱写博客.txt");
//定义一个字符数组,用于存储读取到的信息
char[] arr = new char[1024];
//定义一个int类型变量len,用于接收read(char[])方法的返回值
int len = 0;
//通过循环,将读取到的数据存储到数组中,再把数组中的数据写入到目的文件中。
while((len = fr.read(arr))!=-1)
{
//将数组中的字符写入到目的文件中
fw.write(arr,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("读写怎老失败啊");
}
finally
{
if(fw!=null)
try
{
//关闭流资源
fw.close();
}
catch (IOException e)
{
}
if(fr!=null)
try
{
//关闭流资源
fr.close();
}
catch (IOException e)
{
}
}
}
}
拷贝出来的文件
字符流的缓冲区
缓冲区的出现提高了对数据读写的效率
对应的类
BufferedWriter BufferedReader
缓冲区要结合流才可以使用,在流的基础上对流的功能进行了增强。
缓冲区的出现是为了提高流的搞作效率,所以在创建缓冲区之前,必须要有流对象,所以缓冲区对应的类没有空参数构造函数。
缓冲技术原理:对象内封装了数组,先把数据存起来,再一次性写出去。把数组封装成对象,方便使用。
在实际开发中一般都会使用缓冲区,因为要对性能进行增强.
BufferedWriter
字符输出流缓冲区
使用步骤
1. 创建一个字符输出流对象,指定目的地
2. 为了提高字符输出流的效率,加入缓冲技术。只要将需要提高效率的流对象作为参数传递给缓冲区的构造函数即可。
例:BufferedWriter bufw = new BufferedWriter(new FileWriter(“目的地”));
3. 使用writer方法将流中数据写入到目的地中。
4. 刷新流。
记住:只要用到缓冲区,一定要刷新流,虽然关闭资源也能刷新,
但为了保证数据录入的准确性,每次往缓冲区中添加数据都要刷新。
5. 关闭缓冲区。
其实关闭缓冲区就是在关闭缓冲区的流对象,所以只关闭缓冲区即可。
BufferedWriter特有方法
void newline()
换行方法,在任何系统上都可以使用,是跨平台的,用作数据换行.
BufferedReader
字符输入流缓冲区
使用步骤
1. 创建一个字符输入流对象,和文件相关联
2. 为了提高效率加入缓冲技术,将字符输入流对象作为参数传递给缓冲区对象的构造函数。
例:BufferedReader bufr = new BufferedReader(new FileReader(“被读取的文件”));
3. 循环读取,使用BufferedReader特有的方法 String readLine() 读取一行文本。读取到文件末尾返回null。
返回的本行文本,不包含终止符。(返回的是有效数据,返回回车符之前的内容,不返回回车符)
4. 刷新流
5. 关闭缓冲区
readLine方法原理
无论是读取一行,还是读取多个字符,其实都是在硬盘上一个一个读取,所以最终使用的还是read方法。一次读一个字符的方法。
readLine方法就是把read读取到的一个一个的数据,存到缓冲区的一个字符串中,当读到\r\n时,这一行结束。不再继续往下读。
并且\r\n不进入缓冲区的字符串中,这时返回字符串。
readLine 比 read方法要高级很多。readLine的出现是在增强read方法的功能。
下面我们根据一个练习,来看一看字符流缓冲区如何使用
/*
字符流缓冲区的使用示例
需求
使用缓冲技术,拷贝一个纯文本文件
思路
1.使用字符输入流缓冲区,将被拷贝的文件数据读取到缓冲区中
2.使用字符输出流缓冲区,将缓冲区中数据写入目的文件中
3.每一次写入数据都要刷新字符输出流缓冲区。
4.关闭缓冲区
*/
import java.io.*;
class BufferedCopyTest
{
public static void main(String[] args)
{
//创建缓冲区的引用
BufferedReader bufr = null;
BufferedWriter bufw = null;
//处理异常
try
{
//创建字符输入流缓冲区对象,并把字符输入流对象作为参数传入
//并指定要被拷贝的文件
bufr =
new BufferedReader(new FileReader("c:\\论:为什么我一天只能写一篇博客.txt"));
//创建字符输出流缓冲区对象,并把字符输出流对象作为参数传入
//并指定目的文件
bufw =
new BufferedWriter(new FileWriter("e:\\论:为什么我一天只能写一篇博客_copy.txt"));
//定义String类型变量,用于接收readLine方法读取的数据
String line = null;
//建立循环读取文件中的数据,readLine方法的返回值作为循环条件
//如果返回null证明读取到了文件的末尾,循环也就结束。
//并用String类型变量line记录注读取到的一行数据
while((line=bufr.readLine())!=null)
{
//将读取到的数据line存入字符输出流缓冲区中
bufw.write(line);
//因为readLine方法不读取换行符,所以我们自己使用BufferedWriter对象中的newLine方法
//自己写回车符。
bufw.newLine();
//刷新字符输出流缓冲区,将数据冲入目的地中
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("缓冲区读写失败");
}
finally
{
try
{
if(bufr!=null)
//关闭缓冲区
bufr.close();
if(bufw!=null)
//关闭缓冲区
bufw.close();
}
catch (IOException e)
{
}
}
}
}
拷贝出的文件
装饰设计模式
当想要对已有的对象进行功能增强时,可以定义一个类:将已有对象传入,基于已有对象的功能,
并提供加强功能,那么自定义的类就称为装饰类。
定义装饰类要注意
它是为了增强而存在的,所以在建立对象的同时要有被增强的对象,
所以装饰类一定要有一个接收被强化对象的构造函数,而且没有空参数构造函数。
总的来说就是
装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能提供更强的功能。
装饰和继承的区别
装饰模式比继承更灵活,避免了集成体系的臃肿
而且降低了装饰类与被装饰类之间的关系,装饰类因为是增强已有对象,具备的功能和已有对象是相同的,只是提供了更强的功能。
所以装饰类和被装饰类都是属于一个体系中的,属于同一个父类,或者同一个接口。
自定义装饰类的构造函数中,可以传入被装饰类的子类。因为多态父类的引用指向子类对象。
建议:继承要写,因为父类时子类不断抽取出来的,可是仅仅为了几个功能而写很多子类并不建议。
体系会变得很臃肿,可以通过装饰类来扩展这些功能。
装饰类的好处
当代码过时之后需要增强,可以定义一个装饰类,把需要装饰的类的对象传入装饰类中,就可以起到增强的效果,
而且避免了继承重写方法可能会引起的错误。
自定义装饰类程序代码如下所示
/*
装饰设计模式示例
我们来写一个装饰设计模式
思路
1.定义一个被修饰的类,里面有它的方法
2.定义一个装饰类,为被修饰类的增加功能
3.创建修饰类的对象,将被修饰的类的对象传入,实现功能的增强
*/
//创建一个手机类
class Phone
{
//创建打电话功能,我们都知道原来的手机只能打电话,发短信都是后来的
public void phoneFunction()
{
System.out.println("打电话");
}
}
//创建一个手机的装饰类,智能手机
class Smartphone
{
private Phone p;
//构造函数,接收手机对象
Smartphone(Phone p)
{
this.p = p;
}
//创建手机类的增强方法,智能手机功能
public void smartphoneFunctiom()
{
//由原来的功能,打电话
p.phoneFunction();
//智能手机还能看电影,听音乐,上微博
System.out.println("看电影");
System.out.println("听音乐");
System.out.println("上微博");
}
}
class PhoneDemo
{
public static void main(String[] args)
{
//创建装饰类的对象,并把被增强类的对象作为参数传入它的构造函数中
Smartphone sp = new Smartphone(new Phone());
//调用增强方法
sp.smartphoneFunctiom();
}
}
运行结果
LineNumberReader
在BufferedReader中有个子类LineNumberReader,其中有特有的方法获取和设置行号:
void setLineNumber() 设置初始行号
int getLineNumber() 获取行号
代码演示
/*
LineNumberReader中方法演示
*/
import java.io.*;
class LineNumberReaderDemo
{
public static void main(String[] args)
{
//建立对象的引用
LineNumberReader lnr=null;
try
{
//创建LineNumberReader对象,并把FileReader对象作为参数传入
//指定被读取的文件
lnr=new LineNumberReader(new FileReader("c:\\论:为什么我一天只能写一篇博客.txt"));
//调用setLineNumber(int)方法,设置开始的行号
lnr.setLineNumber(100);
String line = null;
//建立循环,读取文件中的数据
while((line=lnr.readLine())!=null)
{
//调用getLIneNumber方法获取行号,并打印字符串
System.out.println(lnr.getLineNumber()+":"+line);
}
}
catch (IOException e)
{
throw new RuntimeException("读取数据失败");
}
finally
{
try
{
if(lnr!=null)
//关闭资源
lnr.close();
}
catch (IOException e)
{
}
}
}
}
运行结果
字节流
字节流和字符流的用法基本是相同的,但字节流可以操作其他媒体文件,比如Mp3文件,就需要用字节流来操作。
字节流基类
InputStream OutputStream
字节流在使用时,不需要刷新,字符流要刷新是因为它底层调用的是字节流的存储动作。
为什么字节流不用进行刷新动作?
如:汉字是两个字节也就是一个字符,所以需要先存一个字节,然后再存一个。两个字节在一起,凑一个汉字才刷新出去。
而字节是读一个字节写一个,所以不需要刷新。
InputStream:字节输入流
特有方法
int available() 得到的字节个数。
使用这个方法定义数组的长度,就是正好的长度不用循环。
但是这个方法慎用,文件过大可能会产生内存溢出,还是定义1024为倍数的数组方式来进行读取。
但是文件不大时,用available方法获得的数组大小可以使用。
OutputStream:字节输出流
没什么太大的特点,用法和字符输出流相似。
下面我们根据一个练习,来看看字节流如何使用。
/*
字节流使用示例
需求:拷贝图片文件
使用三个方法拷贝
1.定义长度正好的数组,使用available方法
2.定义长度为1024的数组,循环。
3.字节一个一个读
注意!!!!!!!!!!!
在这里我就不进行异常处理了,直接抛出异常,要不会显得程序很多,比较臃肿。
正常应该 trycatch处理! 正常应该 trycatch处理! 正常应该 trycatch处理!
重要的事情要说三遍
*/
import java.io.*;
class CopyPicDemo
{
public static void main(String[] args) throws IOException
{
copy_1();
copy_2();
copy_3();
}
//建立拷贝方法一方法:定义长度正好的数组
public static void copy_1()throws IOException
{
//创建字节输入流对象,并关联被拷贝文件。
FileInputStream fis = new FileInputStream("e:\\123.JPG");
//创建字节输出流对象,并关联目的文件
FileOutputStream fos = new FileOutputStream("e:\\jessica_1.JPG");
//定义一个字节数组,长度为文件的字节数,正好
byte[] arr = new byte[fis.available()];
//调用字节输入流中的read方法,将读取到的数组存入到arr数组中
fis.read(arr);
//调用字节输出流中的write方法,将数组中的数据写入到目的文件中
fos.write(arr);
//关闭资源
fis.close();
fos.close();
}
//建立拷贝方法二方法:定义长度为1024的数组,循环。
public static void copy_2()throws IOException
{
//创建字节输入流对象,并关联被拷贝文件。
FileInputStream fis = new FileInputStream("e:\\123.JPG");
//创建字节输出流对象,并关联目的文件
FileOutputStream fos = new FileOutputStream("e:\\jessica_2.JPG");
//定义一个字节数组,长度为1024
byte[] arr = new byte[1024];
//定义一个int类型变量,用于记录read方法的返回值
int len = 0;
//建立循环,read的返回值不为-1为循环条件,并且把数组存储到数组中
while((len = fis.read(arr))!=-1)
{
//调用字节输出流中的write方法,将数组中的数据写入到目的文件中
fos.write(arr,0,len);
}
//关闭资源
fis.close();
fos.close();
}
//建立拷贝方法三方法:字节一个一个读。
public static void copy_3()throws IOException
{
//创建字节输入流对象,并关联被拷贝文件。
FileInputStream fis = new FileInputStream("e:\\123.JPG");
//创建字节输出流对象,并关联目的文件
FileOutputStream fos = new FileOutputStream("e:\\jessica_3.JPG");
//定义一个int类型变量,用于记录read方法的返回值
int ch = 0;
//建立循环,read的返回值不为-1为循环条件,并把读取数据
while((ch = fis.read())!=-1)
{
//调用字节输出流中的write方法,将数据写入到目的文件中。
fos.write(ch);
}
//关闭资源
fis.close();
fos.close();
}
}
运行结果
到这里先说一个小题外话,我的第一个大笔记本到这里用完了,我开始想的是可能一个本就够用了,就特别省着地方写,
虽然也没少写东西,但是内容特别满,说实话,不是很方便阅读。其实这个本我用到一半就发现肯定不够了,但是我有一点儿点儿强迫症,
所以前面写的满满当当,后面也满满当当了。早知道就直接痛痛快快的写了。
其实说这么多就是想让大家知道,java基础已经学习到后半段了,就差一点儿了,再努力一把,胜利是咱们的!!!
纪念我慢慢当当的本
好了言归正传,我们来学习接下来的内容
字节流的缓冲区
提高了字节流的读写效率,字节流缓冲区和字符流缓冲区差不多。
我们通过一个例子来学习字节流缓冲区的使用
使用字节流缓冲区完成MP3文件的复制。
代码展示
/*
使用字节流缓冲区复制MP3文件
思路
1.创建字节输入流缓冲区,将字节流对象作为参数传入,并关联被拷贝的文件
2.创建字节输出流缓冲区,将字节流对象作为参数传入,并关联目标文件
3.使用read和write方法对数据进行读取
4.关闭资源
*/
import java.io.*;
class CopyMp3Demo
{
public static void main(String[] args)
{
//建立字节流缓冲区的引用
BufferedInputStream bufis = null;
BufferedOutputStream bufos = null;
//异常处理
try
{
//创建字节输入流缓冲区对象,将输入流作为参数传入
//并关联被拷贝的文件
bufis = new BufferedInputStream(new FileInputStream("c:\\Butterfly.mp3"));
//创建字节输出流缓冲区对象,将输出流作为参数传入
//并关联目的文件
bufos = new BufferedOutputStream(new FileOutputStream("e:\\1.mp3"));
//定义一个int类型变量,记录read方法的返回值
int byt = 0;
//建立循环read方法的返回值!= -1为循环条件
while((byt = bufis.read())! = -1)
{
//将read方法读取到的数据通过write方法写入目的文件中
bufos.write(by);
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufos!=null)
bufos.close();
if(bufis!=null)
bufis.close();
}
catch (IOException e)
{
throw new RuntimeException("缓冲区关闭失败");
}
}
}
}
运行结果
自定义字节流缓冲区
如果我们自定义的缓冲区在复制的时候有可能会出现没有复制到数据的情况。
因为往字节数组里面存的是字节,MP3文件的开头二进制有可能是1111-1111转换成十进制数是-1,所以循环判断就会停止。
那为什么FileInputStream中的read方法返回值是int类型的而不是byte类型的呢?
int在对字节数组中的返回值进行提升(小于0的字节提升)避免发生开始八个2进制位都取到1的情况。
如果我们直接进行类型提升,把byte类型转成int类型,还会是-1 在原有的二进制位前面补得还是1。所以类型提升之后还是辅负数
虽然-1进行类型提升之后的还是-1,但有一个方法又提升了,又变成整数了,又不会改变原有数据。那就是在前面补0!
在类型提升的时候int类型是32个二进制位,而byte类型是8个二进制位,要补24位。
byte类型-1为 1111-1111,在前面补24个0,提升为int类型就是
0000-0000 0000-0000 0000-0000 1111-1111
这样既保持了原数据没有变化,而且避免了-1的出现。
为什么会保持不变呢?
因为read把byte变成了int,而write在写入数据的时候又把int转回了byte。
就是保留int类型32位二进制的最后8位。所以,保持了原数据没有变化。
那么如何在自定义字节流缓冲区中,对byte进行补0呢?
只要让byte类型的数据&255即可。
255是int类型,所以是32位,最后8位为1111-1111 如图所示。
理解
本来MP3文件开始的字节为1111-1111,但在读取的时候读取一个字节也就是前八位,导致-1的出现,使循环结束,
所以我们要用字节&255得方式让字节变成int类型,前面有3组8个0。所以就可以取到255,循环就不会受阻。
结论
字节流的读一个字节的read方法返回值类型是int,而不是byte。因为有可能会读到连续8个二进制位都是1的情况。
1111-1111对应的十进制是-1,那么会发生数据还没有读完就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的,
所以为了避免这种情况的发生,将读取到的字节进行int类型的提升,并为了保留原字节数据的情况,前面补了24个0
变成了int类型的数值,而在写入数据时,write只写该int类型数据的最低8位。
自定义字节流缓冲区代码演示
/*
自定义字符读取流缓冲区代码演示
*/
import java.io.*;
class MyBufferedInputStream
{
//字节读取流属性
private InputStream in;
//定义一个字节数组,相当于缓冲区
private byte[] buf = new byte[1024];
//定义一个指针和一个计数器
private int pos = 0,count = 0;
//构造函数,一初始化的时候就要有被操作的字节流对象
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//定义一个myRead()方法,简单模拟实现BufferedInputStream中的read()方法
public int myRead()throws IOException
{
//如果计数器(字节数组的数据大小)为0,说明字节数组为空,没有数据,需要从硬盘上读取数据
if(count==0)
{
//调用FileInputStream类中的read方法,将硬盘上数据存储到数组中
count = in.read(buf);
//如果count小于0
if(count<0)
//说明硬盘上数据已被取完 返回-1
return -1;
//每次从硬盘上重新读取数据存储到字节数组中后都将指针重置
pos = 0;
//获得buf数组中角标为0,也就是第一个元素
byte b = buf[pos];
//每读取一个字节,代表数组中的字节少了一个
count--;
//指针向后挪一位
pos++;
//返回的byte类型数据提升为int类型,并且是前面补0的提升
return b&255;
}
//如果count大于0说明数组中还有数据
else if(count>0)
{
//将数组中数据取出
byte b = buf[pos];
//计数器自减
count--;
//指针向后挪一位
pos++;
//0xff十六进制 换算为十进制为255
return b&0xff;
}
//最后数据都读完了,返回-1
return -1;
}
//关闭资源方法。
public void myClose()throws IOException
{
//关闭流资源
in.close();
}
}
//自定义字节流缓冲区测试
//复制mp3文件
class CopyMp3Test
{
public static void main(String[] args)
{
//建立字节流缓冲区的引用
MyBufferedInputStream bufis = null;
BufferedOutputStream bufos = null;
//异常处理
try
{
//创建字节输入流缓冲区对象,将输入流作为参数传入
//并关联被拷贝的文件
bufis = new MyBufferedInputStream(new FileInputStream("c:\\Butterfly.mp3"));
//创建字节输出流缓冲区对象,将输出流作为参数传入
//并关联目的文件
bufos = new BufferedOutputStream(new FileOutputStream("e:\\2.mp3"));
//定义一个int类型变量,记录read方法的返回值
int by = 0;
//建立循环read方法的返回值!=1为循环条件
while((by = bufis.myRead())!=-1)
{
//将read方法读取到的数据通过write方法写入目的文件中
bufos.write(by);
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufos!=null)
bufos.close();
if(bufis!=null)
bufis.myClose();
}
catch (IOException e)
{
throw new RuntimeException("缓冲区关闭失败");
}
}
}
}
读取键盘录入
System.in:对应的是标准输入设备,键盘。
System.out:对应的是标准输出设备,控制台(屏幕)
System.in的类型是InputStream。
System.out的类型是PrintStream。
如果单单用InputStream类中的read方法对键盘录入进行读取,会发现只能一个一个字节进行读取。
影响效率,最好是可以用readLine方法来完成键盘录入一行数据的读取。
readLine方法是BufferedReadr类中的方法,而键盘录入的read方法是InputStream类中的方法,
一个是字符流,一个是字节流,那么能不能将节流转成字符流,再使用字符流缓冲区中的readLine方法呢?
可以,这时我们就用到了转换流
转换流
字符流与字节流之间的桥梁,方便了字符流与字节流之间的操作
InputStreamReader将字节流通向字符流
读取键盘录入步骤
1. 获取键盘录入对象。
InputStream in = System.in;
2. 将字节流对象转成字符流对象,使用转换流。
InputStreamReader isr = new InputStreamReader(in);
3. 为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReader br = new BufferedReader(isr);
键盘录入最常见写法
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter字符流通向字节流
字符通向字节:录入的是字符,存到硬盘上的是字节。
步骤和InputStreamReader转换流一样。
转换流代码演示
/*
转换流代码演示
需求
将键盘录入的数据,将小写字母变为大写,显示在控制台,当输入over时,表示录入结束
源:键盘录入。
目的:控制台。
为了使代码看着简单,就直接抛出异常了
正常情况下要trycatch处理!!正常情况下要trycatch处理!!正常情况下要trycatch处理!!
*/
import java.io.*;
class ReadInDemo
{
public static void main(String[] args) throws IOException
{
//键盘录入最常见写法,要记住!!
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
//字符流通向字节流
BufferedWriter bufw =new BufferedWriter(new OutputStreamWriter(System.out));
//定义String类型变量,用于接收readLine读取出的字符串
String line = null;
while((line=bufr.readLine())!=null)
{
//如果readLine读取的这个行字符串是over
if("over".equals(line))
//循环结束
break;
//将数据变为大写,写入
bufw.write(line.toUpperCase());
//人工换行
bufw.newLine();
//刷新缓冲区中的内容
bufw.flush();
}
//关闭流资源
bufw.close();
bufr.close();
}
}
运行结果
流操作的基本规律
流对象有很多,那么我们该用哪一个呢?通过两个明确来完成判断。
1. 明确源和目的地
a. 源:输入流 InputStream Reader 读取
b. 目的地:输出流 OutputStream Writer 写入
2. 明确操作的数据是否是纯文本文件
a. 是:字符流
b. 不是:字节流
3. 当体系明确后,再确定要使用哪个具体对象。通过设备来进行区分
a. 源设备:内存,硬盘,键盘
b. 目的设备:内存,硬盘(如文件),控制台(屏幕)
通过以上这些判断方法来判断下面这几个需求需要哪个对象
1. 将一个文本文件中数据存储到另一个文件中,复制文件
源:
a. 因为是源,所以要使用输入流,InputStream,Reader 。
操作的是文本文件,所以选择Reader。
b. 接下来明确要使用该体系中的哪个对象。
明确设备:硬盘上的文件。
Reader体系中可以操作的对象是FileReader
c. 是否要提高效率:是,就加入Reader体系中的缓冲区,BuffereReader,把FileReader的对象作为参数传递进去。
目的:
a. 因为是目的,所以使用输出流,OutputStream,Writer。
操作的是纯文本文件,所以选择Wrtier。
b. 明确设备:硬盘上的文件
该体系中可以操作文件的对象是FIleWriter
c. 是否需要提高效率:是,就加入Write而体系中的缓冲区,BufferedWrtier,把FileWriter的对象作为参数传递进去。
复制图片同理,用字节流即可
2. 将键盘录入的数据存到一个文件中
源:
a. InputStream,Reader。
因为键盘录入的是纯文本,所以用Reader
b. 设备:键盘,对应的对象是System.in,它对应的是字节流,为了操作键盘的文件数据最方便,转成字符流,
按照字符串操作是最方便的。那么就将System.in转成Reader,用Reader体系中的转换流InputStreamReader。
c. 需要提高效率,就是用BufferedReader。
目的:
a. OutputStream,Writer
纯文本文件,使用Writer
b. 设备:硬盘,是一个文件,所以使用FileWriter
c. 需要提高效率,使用BuffereWriter
扩展:想要把录入的数据按照指定的编码表,将数据存到文件中。(默认码表是GBK)
在存储时加入指定的编码表,而指定的编码表只有转换流可以指定。如图
因为是录入数据,所以使用OutputStreamWriter,而该转换流对象要接收一个字节输出流,
而且是可以操作文件的字节输出流,那么就使用FileOutputStream
例:OutputSreamWriter osw = new OutputStreamWriter(new FileOutputStream() , ”UFT-8”);
转换流什么时候使用
字符和字节之间的桥梁,通常涉及到字节转字符,字符转字节和字符编码转换时,需要用到转换流。
改变标准的输入和输出设备
static void serIn(InputStream in) 重新分配标准输入流
例:System.setIn(new FIleInputStream(“a.txt”)); //将源文件变成a.txt,a.txt中要有数据
static void serOut(Properties props) 重新分配标准输出流
例:System.setOut(new PrintStream(“b.txt”)); //将目的改成b.txt文件
小知识
将异常信息存到文件中
使用Throwable类中的
void printStackRtace(PrintStream s)
例:
e.printStackTrace(new PrintStream(“a.txt”)); //把异常信息存入a.txt文件中
也可用System.setOut(new PrintStream(“b.txt”)); //重新分配输出流,使目的不是控制台
在异常加入文件中时,写入时间
例:
Dated = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-ddHH:mm:ss ”);
友情小提示:记得导入text包
代码演示
/*
将异常信息存到文件中代码示例
*/
import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfo
{
public static void main(String[] args)
{
try
{
//现在写点儿异常,定义一个数组只有两个元素
int[] arr = new int[2];
//输出数组的第四角标位元素,但数组只有两个元素。会发生角标越界异常
System.out.println(arr[3]);
}
//捕捉异常
catch (Exception e)
{
//处理异常
try
{
//记录异常发生的时间
//创建Date对象
Date d = new Date();
//设置自定义模式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//根据获取现在的时间用自定义模式存储成字符串
String s = sdf.format(d);
//创建PrintStream对象,并指定目的文件
PrintStream ps = new PrintStream("exeception.log");
//将时间信息输入到目的文件中
ps.println(s);
//重新分配标准输出流,将目的改为exeception.log文件
System.setOut(ps);
}
catch (IOException ex)
{
throw new RuntimeException("日志文件创建失败");
}
//将异常信息写入标准输出流中,咱们给改啦!
e.printStackTrace(System.out);
}
}
}
异常信息在文件中的显示
利用IO输出流写出系统信息
Properties prop = System.getProperies();
prop.list(System.out);
谢谢大家的观看~!