一、输入输出流概述
1、流的分类:
字符流基类:Reader Writer
字节流基类:InputStream OutputStream
2、流的操作规律:
源:键盘---System.in 文件---FileInputStream/FileReader 内存---ArrayInputStream
目的:控制台---System.out 文件---FileOutputStream/FileWriter 内存---ArrayInputStream
当需要流具有更多的功能,可以将一个已经存在的流传递给另一个流的构造器方法,将两个流结合起来,结合后的流就被称为过 滤流。例如为了能够从文件中读取数值,可以将 FileInputStream与DataInputStream结合起来:
FileInputStream fis = new FileInputStream("f.txt");
DataInputStream dis = new DataInputStream(fis);
double d = dis.readDouble();
二、转换流
1、InputStreamReader 是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。为了达到最高效率,可要考虑在BufferedReader内包装 InputStreamReader。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
2、OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集 可以由名称指定或显式给定,否则将接受平台默认的字符集。每次调用write()方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给write()方法的字符没有缓冲。为了获得最高效率,可考虑将OutputStreamWriter包装到BufferedWriter中,以避免频繁调用转换器。例如:
Writer out= new BufferedWriter(new OutputStreamWriter(System.out));
三、具体流案例分析
1、BufferedReader的readLine()方法的原理分析与模仿
无论是读取一行还是读取多个字符其实都是你在硬盘上一个一个读取,所以最终使用的还是read()方法一次读一个来完成。readLine()读取一个文本行。通过下列字符之一即可认为某行已终止:换行('\n')、回车('\r')或回车后直接跟着换行。也就是说,该方法缓冲的内容里并不包含换行('\n')、回车('\r')或回车后直接跟着换行这些内容,所以如果要将这些内容进行输出,需要使用BufferedWriter的newLine()方法再加上这些结束换行符。
public class MyBufferedReader extends Reader{ //装饰类模式
private Reader fr ;
public MyBufferedReader(Reader fr) { //面向接口
this.fr = fr;
}
public String readLine() throws IOException{
StringBuilder sb = new StringBuilder();
int c = 0;
while((c = fr.read())!= -1){
if((char)c == '\r')
continue;
if((char)c == '\n')
return sb.toString();
sb.append((char)c);
}
if(sb.length()>0){
return sb.toString();
}
return null;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {//子类实现Reader类的抽象方法
return fr.read(cbuf, off, len);
}
@Override
public void close() throws IOException{子类实现Reader类的抽象方法
fr.close();
}
public static void main(String[] args) {
FileReader fr ;
FileWriter fw;
MyBufferedReader mbr = null;
BufferedWriter bw = null;
try {
fr = new FileReader("E:\\demo.txt");
fw = new FileWriter("E:\\demo1.txt");
mbr = new MyBufferedReader(fr);
bw = new BufferedWriter(fw);
String line = null;
while((line = mbr.readLine()) != null){
bw.Write(line);
bw.newLine(); //这里用来补全源文件内容的换行符,
bw.flush(); //将流缓冲区内疏浚刷新入底层
}
} catch (IOException e) {
throw new RuntimeException("文件读取异常");
}finally{
try {
if(mbr != null)
mbr.close();
} catch (Exception e) {
throw new RuntimeException("关闭输入流异常");
}
try {
if(bw != null)
bw.close();
} catch (Exception e) {
throw new RuntimeException("关闭输出流异常");
}
}
}
}
2、对音视频文件进行读取时的底层细节注意点,通过一个自定义的具有缓冲功能的字节输入流来分析
public class MyBufferedInputStream {
private InputStream is = null;
private int count = 0 , pos = 0;
private byte[] bytes = new byte[1024];
public MyBufferedInputStream(InputStream is) {
this.is = is;
}
public int read() throws IOException{
int bt = 0;
if(count==0){
count = is.read(bytes); //①
if(count<0)
return -1;
pos = 0;
}
bt = bytes[pos];
pos++;
count--;
return bt&255; //②
//③ return bt;
}
public void close() throws IOException{
is.close();
}
public static void main(String[] args) {
MyBufferedInputStream mbis = null;
BufferedOutputStream bos = null;
try {
mbis = new MyBufferedInputStream(new FileInputStream(new File("E:\\你的背包.mp3")));
bos = new BufferedOutputStream(new FileOutputStream(new File("E:\\你的背包2.mp3")));
int elem = 0;
while ((elem = mbis.read()) != -1) { //④
bos.write(elem);
}
} catch (IOException e) {
throw new RuntimeException("文件读取异常");
}finally{
try {
if (mbis != null)
mbis.close();
} catch (IOException e) {
throw new RuntimeException("输入流关闭异常");
}
try {
if (bos != null)
bos.close();
} catch (IOException e) {
throw new RuntimeException("输出流关闭异常");
}
}
}
}
(1)、在上面程序①处是为了模仿缓冲更能。
(2)、如果将②处语句注释并取消③处注释可能会出现文件无法读取完整的异常情况,这与音视频文件二进制编码有关,如果
出现连续8个1(即为-1)的字节,则读取该字节时即使被自动提升为int后仍然为-1,那这时候在④处就会判断并结束文件读取的操作,从而造成文件损坏。为了避免这种情况就要进行一些特殊的处理,运算情况如下:
四、File
1、用来将文件或者文件夹封装成对象,从而方便对文件即及文件夹的操作,也可以作为参数传递给流的构造器方法。
2、File类的一些特殊方法的列举与解析
(1)、构造方法及其重载形式:
File(File parent, String child)根据parent抽象路径名和child路径名字符串创建一个新File实例。
File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新File实例。
File(String parent, String child)根据parent路径名字符串和child路径名字符串创建一个新File实例。
File(URI uri)通过将给定的file: URI转换为一个抽象路径名来创建一个新的File实例。
(2)、createNewFile()当且仅当不存在具有此抽象路径名指定名称的文件时,创建一个新的空文件。如果指定的文件不存在并成功地创建,则返回 true;如果指定的文件已经存在,则返回 false 。
(3)、deleteOnExit()在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 文件(或目录)将以与注册相反的顺序删除。调用此方法删除已注册为删除的文件或目录无效。根据 Java 语言规范中的定义,只有在虚拟机正常终止时,才会尝试执行删除操作。一旦请求了删除操作,就无法取消该请求。所以应小心使用此方法。
(4)、exists()测试此抽象路径名表示的文件或目录是否存在。
(5)、isDirectory()测试此抽象路径名表示的文件是否是一个目录。
(6)、isFile()测试此抽象路径名表示的文件是否是一个标准文件。
(7)、getParent()返回此抽象路径名父目录的路径名字符串,如果此路径名没有指定父目录,则返回 null。抽象路径名的父路径名由路径名的前缀(如果有),以及路径名名称序列中最后一个名称以外的所有名称组成。如果名称序列为空,那么该路径名没有指定父目录。getParentFile()返回抽象路径名。
(8)、getAbsolutePath()返回此抽象路径名的绝对路径名字符串。 如果此抽象路径名已经是绝对路径名,则返回该路径名字符串,这与getPath()方法一样。如果此抽象路径名是空抽象路径名,则返回当前用户目录的路径名字符串,该目录由系统属性user.dir指定。否则,使用与系统有关的方式解析此路径名。
(9)、list()返回一个字符串数组(或者抽象路径名数组),这些字符串指定此抽象路径名表示的目录中的文件和目录。如果此抽象路径名不表示一个目录,那么此方法将返回 null。否则返回一个字符串数组,每个数组元素对应目录中的每个文件或目录。表示目录本身及其父目录的名称不包括在结果中。每个字符串是一个文件名,而不是一条完整路径。其有一重载形式
list(FilenameFilter filter)当且仅当在此抽象路径名及其表示的目录中的文件名或目录名上调用过滤器的
FilenameFilter.accept(java.io.File, java.lang.String)方法返回true时,该名称才满足过滤器。
(10)、两个特殊字段:
pathSeparator与系统有关的路径分隔符,被表示为一个字符串,此字符串只包含一个字符,即pathSeparatorChar。
separator与系统有关的默认名称分隔符,被表示为一个字符串,此字符串只包含一个字符,即separatorChar。
3、通过list()方法结合递归可以实现文件的遍历与层次输出
public class FileBrowse {
private int num = 0;
public static void main(String[] args){
new FileBrowse().fileBroswer(new File("D:\\EditPlus\\workspace"));
}
public void fileBroswer(File file){
StringBuilder sb = new StringBuilder("|---");
File[] fs = file.listFiles();
if(num==0){
System.out.println(file.getName());
num++;
}
for(int count = num*5 ; count>0 ; count--){
sb.insert(0, " ");
}
for(File f : fs){
System.out.println(sb.toString()+f.getName());
if(f.isDirectory()){
num++; //递归深入一层加一次
fileBroswer(f);
}
}
num--; //递归退出一层减一次
}
}
4、文件的切割与合并4、文件的切割与合并
public class SplitFile {
public static void main(String[] args) {
new SplitFile().splitFile();
new SplitFile().mergeFile();
}
//文件合并方法
public void mergeFile() {
File f = new File("E:\\一丝不挂.mp3");
FileOutputStream fos = null;
SequenceInputStream sis = null;
List<InputStream> l = new ArrayList<InputStream>();
int len =0;
byte[] buf = new byte[1024];
try {
for (int i = 1; i <= f.length() / (1024 * 1024) + 1; i++) {
l.add(new FileInputStream("E:\\一丝不挂" + i + ".part"));
}
final Iterator<InputStream> it = l.iterator();
//此处基于Iterator实现Enumeration匿名子类并
Enumeration<InputStream> ei = new Enumeration<InputStream>(){
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public InputStream nextElement() {
return it.next();
}
};
sis = new SequenceInputStream(ei); //SequenceInputStream表示其他输入流的逻辑串联
fos = new FileOutputStream("E:\\一丝不挂2.mp3");
while ((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("文件处理异常!");
}finally{
try {
if (sis != null)
sis.close();
} catch (IOException e2) {
throw new RuntimeException("序列流关闭异常!");
}
try {
if (fos != null)
fos.close();
} catch (IOException e2) {
throw new RuntimeException("文件输出流关闭异常!");
}
}
}
//文件切割方法
public void splitFile() {
FileInputStream fis = null;
FileOutputStream fos = null;
byte[] buf = new byte[1024];
int len = 0,count = 0;
try {
fis = new FileInputStream("E:\\一丝不挂.mp3");
while ((len = fis.read(buf)) != -1) {
if(count%1024 == 0){ //此处实现每1Mb切割一次并读取
if(fos != null)
fos.close(); //关闭前一部分的切割流
fos = new FileOutputStream("E:\\一丝不挂"+(count/1024+1)+".part");
}
count++;
fos.write(buf, 0, len);
}
fos.close(); //关闭最后一次切割流
} catch (Exception e) {
throw new RuntimeException("文件处理异常!");
}finally{
try {
if (fis != null)
fis.close();
} catch (Exception e) {
throw new RuntimeException("文件输入流关闭异常!");
}
}
}
}