黑马程序员-javaSE学习之IO流

来源:互联网 发布:ubuntu apt get本地源 编辑:程序博客网 时间:2024/05/16 11:30

------- android培训、java培训、期待与您交流! ----------

1)字节流和字符流的由来:

在编程语言发展初期,ASCII码表中的数据的输入输出使用的是字节流,由于后期的发展需要,我们需要统一所有国家的文字,创造国际标准的编码表-UNICODE码表,在发展的过程中,我们用字节流读取文字,但不直接操作,而是先查指定的编码表,获取对应的文字,再进行操作。此时,字节流与码表一起就形成了字符流。简而言之:字符流=字节流+码表

输入流和输出流相对于内存设备而言,将外设中的数据读取到内存中,我们称之为输入流;将内存中的数据读出到外部设备中,我们称之为输出流。 

字节流的顶层父类:1.InputStream 2.OutputStream

字符流的顶层父类:1.Writer     2.Reader

2)  字符流:

FileWriter():是Writer的子类。使用的过程中有以下几点需要注意:

①构造方法中都是有参数的构造方法,一般而言传入的参数是文件的绝对路径。创建对象的过程中,如果文件存在,则会被覆盖;文件不存在,程序会自动创建文件。

②写文件时,其实是将写入的内容暂时存储在输出流(存储区域)中,当使用该类的flush方法进行刷新后,才会将输出流的内容存入到文件中去。如果构造方法的参数只有文件的路径,当不断的writer方法写文件时,此时文件中的内容其实是在不断的被覆盖。而当构造方法中传入的参数不仅有路径还有追加的模式(将该模式置为null即可,如FileWriter f=new FileWriter(“d:\\123.txt,true))的时候,此时写入的东西不是以一种覆盖的方式,而是以一种追加的方式,即写入的东西都会存在在一个文件中。

③对于flushclose而言,flush方法是将写入到写入流的内容保存到文件中,又叫做刷新,就相当于在文件中写入东西按下保存键的原理类似;close方法是将写入流的资源释放,此时就不能再往文件中写入内容了,就相当于将保存好的文件关闭一样。所以在写入文件的时候,flush可以用多次,而close只可以用一次,一旦关闭就不能再进行任何操作。

④记事本程序是微软的产品,记事本中的换行在java中的表示时\r\n,java虚拟机中的换行符就是\n。一般我们可以直接调用line.separator(移植性非常好)获取系统的换行符,此时就不用纠结这一个问题了。

FileReader类(读):Reader的子类。使用的过程中有以下几点需要注意:

①在读文件的时候,必须指定文件的路径,并且该文件必须存在,否则或抛出FileNotFindException的异常。同理,读文件时,也是将文件内容从文件中读取到读取流中,所以在读完文件的结尾处,也必须进行关闭资源(读取流)的操作。

②在用该类的Read()方法读取文件的时候,返回值是0-65535之间的数,所以在读取文件后要进行转换的操作;其次文件内容的开始和结尾处其实都有始标识和尾标识,当文件读取到最后的时候,已经没有可读的内容时,此时会读取文件的尾标识(具体都不太一样),然后返回-1来表示文件已经读取结束。

③用该类的Readchar[] c)读取文件的时候,传入的参数是一个字符数组,返回值是读到了该数组中的所有字符的长度。即:

FileReader f=new FileReader(“d:\\123.txt”);

char[] c= new char[3];

int num=0;;

While((num=f.read(c))!=-1)

{

System.out.println(new String(buf,0,num));//此处是读取从0到该数组中拥有的字符的长度之间读取该数组

}

④对于②和③这两种读取方式的区别:②是一个一个读取的而③是按照数组长度一部分一部分读取。③的效率更高。

对于FileWriterFileReader的使用,分别如以下所示:

FileWriterDemo.java

import java.io.FileWriter;

import java.io.IOException;

 

public class FileWriterDemo {

private static final String LINE_SEPARATOR = System.getProperty("line.separator");//获取系统的换行符

 

public static void main(String[] args)  {

FileWriter fw=null;//这里必须给fw赋初值为null,因为流对象的创建一般是在外部声明并且初始化,在try内部进行创建实例。

try {

fw = new FileWriter("d:/file.txt",true);//追加的模式写入文件

int line=0;

String[] f=new String[]{"你好","世界","我叫","小明"};

while(line<4)

{

fw.write(f[line]+LINE_SEPARATOR);

line++;

}

fw.flush();

catch (IOException e) {

// TODO Auto-generated catch block

System.out.println(e+"文件写入失败");

finally

{

try {

if(fr!=null)

fw.close();

catch (IOException e) {

// TODO Auto-generated catch block

throw new RuntimeException("文件关闭失败");

}

}

}

}

FileReaderDemo.java

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

 

public class FileReaderDemo {

public static void main(String[] args)  {

FileReader fr=null;

try {

fr = new FileReader("d:/file.txt");

char[] buf=new char[1024];//一般而言从硬盘中读文件是按照KB为单位读取的,所以建议使用1024作为数组长度。

int len=0;

try {

while((len=fr.read(buf))!=-1)

{

System.out.println(new String(buf,0,len));

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally

{

try {

if(fr!=null)

 

fr.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

BufferedWriter类(写入流缓冲区):本身就是一个封装了数组的对象,为了提高写入的效率。

BufferedReader类(读取流缓冲区):本身就是一个封装了数组的对象,为了提高写入的效率。

以写入流为例,带有缓冲区的写入流的步骤过程如下:

将写入的内容(包括换行符即readline方法)通过写入流传入到内存的缓冲区中,此时缓冲区会按行读取内容,然后将读到的每一行内容都变成字符串按行写入到文件中去。

对于这两个对象,必须与FileWriter和FileReader配合使用。

装饰设计模式:对一组对象的功能进行增强时,就可以使用该模式。字符流的缓冲区的设计原理就是这个。

特点:装饰类与被装饰类都必须所属同一个接口或者父类。

装饰设计模式与继承的区别:

装饰设计模式与继承都能对一组对象进行功能的扩展,但是装饰设计模式比继承更为灵活。

 

3) 字节流:除了能够存储字符,文字等等外,还能存储音频,视频等媒体文件,这一点是字符流无法比拟的。

它涉及的类的使用方式与字符流基本一致,具体参考API文档
以下字节流的应用范例:这两种应用,传递数据的效率都比较高,比较好。

StreamDemo.java

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

 

public class StreamDemo {

public static void main(String[] args) {

//IOmethod_1();

IOmethod_2();

}

 

public static void IOmethod_2() {

// 字节流的第一种表现方式

FileInputStream fis=null;

FileOutputStream fos=null;

try {

fis = new FileInputStream("d:/樊凡 - 回味.mp3");

fos=new FileOutputStream("d:/回味2.mp3");

byte[] buf=new byte[1024];

int leng=0;

try {

while((leng=fis.read(buf))!=-1)

{

fos.write(buf,0,leng);

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

try {

fis.close();

fos.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

 

public static void IOmethod_1() {

//字节流的第二种表现方式

FileInputStream fis;

BufferedInputStream bis=null;

FileOutputStream fos;

BufferedOutputStream bos=null;

try {

fis = new FileInputStream("d:/樊凡 - 回味.mp3");

bis=new BufferedInputStream(fis);

fos=new FileOutputStream("d:/回味.mp3");

bos=new BufferedOutputStream(fos);

int ch=0;

try {

while((ch=bis.read())!=-1)

{

bos.write(ch);

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

try {

if(bis!=null)

bis.close();

bos.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

4) 转换流:InputStreamReader是字节流转换为字符流的桥梁;OutputStreamWriter是字符流转换为字节流的桥梁。当想要指定编码表时,只能用转换流来做。

总结:IO流操作的基本规律:即“四个明确”

①明确源和目的:源(读取流):InputStream Reader

目的(输出流):OutputStream Writer

②明确数据是否是纯文本数据:源:是:Reader

    否:InputStream

目的:是:Writer

  否:OutputStream

③明确具体的设备:源设备:硬盘:File

  键盘:System.in

  内存:数组

  网络:Socket流

目的设备:硬盘:File

  控制台:System.out

  内存:数组

  网络:Socket流

④明确是否需要其他额外功能:

①是否需要高效:缓冲区

②是否需要转换:转换流

 

 

5) File类:IO流中的类只能操作文件中的数据,而不能操作文件,而File是操作文件以及文件夹的类。

案例1:文件夹的深度遍历

FileDemo.java

import java.io.File;

import com.io.filesplit.SufixFilter;

/*需求:1.删除一个拥有文件以及文件文件夹的文件夹

   2.深度遍历一个文件夹,包括其内部的所有文件与文件夹

   */

public class FileDemo {

public static void main(String[] args) {

File f=new File("d:/file");

DeepErgodicFile(f,1);

//removeFiles(f);

}

 

public static void removeFiles(File f) {

// 删除一个文件夹下所有的文件以及文件夹

//File f2=f;

File[] files=f.listFiles();

for(File file:files)

{

if(file.isDirectory())

{

removeFiles(file);

}

else

{

System.out.println(file.getName()+file.delete());

}

}

System.out.println(f.getName()+f.delete());

}

 

public static void DeepErgodicFile(File f,int count) {

// 深度遍历一个文件夹

//System.out.println(getSpace(count)+f.getName());

File[] files=f.listFiles();

count++;

for(File file:files)

{

if(file.isDirectory())

{

DeepErgodicFile(file,count);

}

else

{

System.out.println(getSpace(count)+file.getName());

}

}

}

 

public static String getSpace(int count) {

//文件夹与文件缩进

StringBuilder sb=new StringBuilder();

for(int i=0;i<count;i++)

{

sb.append("--");

}

return sb.toString();

}

}

 

案例2filter过滤器的使用:包括过滤文件和过滤文件名

FilterDemo.java

import java.io.File;

/*需求:1.在一个文件夹中或者硬盘中寻找到所有以任意文件后缀结尾的文件(只在本目录下,不进行深度遍历)

 *    2.在一个文件夹或者硬盘中寻找到所有的不包括隐藏文件文件(只在本目录下,不进行深度遍历)

 */

 

public class FilterDemo {

public static void main(String[] args) {

File f=new File("d:/file");

file_search(f);

//file_search2(f);

//fileErgodic(f);

}

 

public static void fileErgodic(File f) {

// 遍历当前文件夹中的内容,隐藏文件也能够遍历出来

String[] names=f.list();

for(String name:names)

{

System.out.println(name);

}

}

 

public static void file_search2(File f) {

// 寻找到所有的以任意文件格式结尾的文件

String[] names=f.list(new fileFilter2(".txt"));

for(String name:names)

{

System.out.println(name);

}

}

 

public static void file_search(File f) {

// 寻找到所有的不隐藏文件

File[] names=f.listFiles(new fileFilter());

for(File name:names)

{

System.out.println(name);

}

}

}

②文件名过滤器 fileFilter2.java

import java.io.File;

import java.io.FilenameFilter;

public class fileFilter2 implements FilenameFilter{

private String suffix;

public fileFilter2(String suffix)

{

this.suffix=suffix;

}

public boolean accept(File dir, String name) {

// 

return (name.endsWith(suffix) && !dir.isHidden());

}

 

}

③文件过滤器 fileFilter.java

import java.io.File;

import java.io.FileFilter;

public class fileFilter implements FileFilter{

 

public boolean accept(File pathname) {

// 

return !pathname.isHidden();

}

}

6) Properties类:

案例:获取指定目录下,将指定扩展名的所有文件(深度遍历)的绝对路径写入一个文本文件中。

①程序:FileListDemo.java

import java.io.File;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

import java.util.ArrayList;

 

 

import com.io.filesplit.SufixFilter;

 

//需求:获取指定目录下,将指定扩展名的所有文件(深度遍历)的绝对路径写入一个文本文件中。

public class FileListDemo {

public static void main(String[] args) {

File dir=new File("d:/file");

File file=new File("d:/file/file.txt");

String suffix=".txt";

ArrayList<File> list=new ArrayList<File>();

SufixFilter sf=new SufixFilter(suffix);

getFileList(dir,sf,list);

writeTofile(file,list);

}

 

 

public static void writeTofile(File file, ArrayList<File> list) {

// 将集合中的文件的文件名称写入同一个文本文件中

BufferedWriter bw=null;

try {

bw=new BufferedWriter(new FileWriter(file));

for(int i=0;i<list.size();i++)

{

bw.write(list.get(i).getName()+"="+list.get(i).getAbsolutePath());

bw.newLine();

bw.flush();

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally

{

try {

bw.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

 

 

 

public static void getFileList(File dir, SufixFilter sf,

ArrayList<File> list) {

// 获取指定目录下的所有指定扩展名的文件,将这些文件存储于集合中

File[] files=dir.listFiles();

for(File f:files)

{

if(f.isDirectory())

{

getFileList(f, sf, list);

}

else

{

if(sf.accept(f, f.getName()))

list.add(f);

}

}

}

}

7) 打印流:PrintStreamPrintWriter

8) 序列流(SequenceInputStream):将多个流与集合封装成了对象,可以用这一个流对象来将多个文件合并到一个文件下,方便用户使用。

9) 文件切割机与文件合并:

案例:1.将一个任意文件切割成n份。分别存在一个文件夹中,同时将该文件的配置信息存储下来

  2.将切割了的文件,根据配置信息合并成一个文件。

①SplitDemo.java

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.SequenceInputStream;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Enumeration;

import java.util.Properties;

//需求:1.将一个任意文件切割成n份。分别存在一个文件夹中,同时将该文件的配置信息存储下来

 //2.将切割了的文件,根据配置信息合并成一个文件。

public class SplitDemo {

private static final int size = 1024*1024;

 

public static void main(String[] args) {

//File file=new File("d:/music/aa.mp3");

//System.out.println(file.length()+"..."+size);

//System.out.println(file.getParent()+"\\split");

    //splitFile(file);

File dir=new File("d:/music/split/");

mergeFile(dir);

}

 

public static void mergeFile(File dir) {

// 将一些碎片文件合并成一个完整的文件

SequenceInputStream sis=null;

FileOutputStream fos=null;

Properties prop=new Properties();

File[] Confiles=dir.listFiles(new SufixFilter(".properties"));

//System.out.println(Confiles.length);

if(Confiles.length!=1)

{

throw new RuntimeException("该目录下的配置文件不唯一或者没有配置文件");

}

try {

prop.load(new FileInputStream(Confiles[0]));

String filename=prop.getProperty("filename");

int num=Integer.parseInt(prop.getProperty("splitnum"));

ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();

File[] files=dir.listFiles(new SufixFilter(".part"));

for(int i=0;i<num-1;i++)

{

al.add(new FileInputStream(files[i]));

}

Enumeration<FileInputStream> en=Collections.enumeration(al);

sis=new SequenceInputStream(en);

fos=new FileOutputStream(new File("d:/music/merge",filename));

byte[] buff=new byte[size];

int len=0;

while((len=sis.read(buff))!=-1)

{

fos.write(buff, 0, len);

fos.flush();

}

catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

try {

if(sis!=null)

sis.close();

if(fos!=null)

fos.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

public static void splitFile(File file) {

// 将一个文件切割成n个碎片分别存储在不同的文件中。

FileInputStream fis=null;

FileOutputStream fos;

    try {

fis=new FileInputStream(file);

byte[] buff=new byte[size];

int count=1;

int len=0;

String dir=file.getParent()+"\\split";//创建需要文件被切割后碎片存放的主目录

try {

while((len=fis.read(buff))!=-1)

{

fos=new FileOutputStream(new File(dir,(count++)+".part"));

fos.write(buff, 0, len);

fos.close();

}

Properties prop=new Properties();

prop.setProperty("splitnum", count+"");

prop.setProperty("filename",file.getName());

prop.store(new FileOutputStream(new File(dir,count+".properties")),"filesplit");

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

try {

fis.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

②后缀名过滤器:SufixFilter.java

import java.io.File;

import java.io.FilenameFilter;

public class SufixFilter implements FilenameFilter{

private String suffix;

public SufixFilter(String suffix) {

super();

this.suffix = suffix;

}

public boolean accept(File dir, String name) {

// 

return name.endsWith(suffix);

}

}

10) 操作对象流:ObjectInputStreamObjectOutputStream

11) 随即访问文件(RandomAccessFile):支持对文件的随即读取和写入,自身具备读写的方法。通过skipByteint x),seekint x)来达到随机访问。

特点:

①它不是IO体系中的子类

②既能读,又能写

③该对象内部维护了一个byte数组,并通过指针获取该数字中的元素

④可以通过getFilePointer方法获取该指针的位置,和通过seek方法设置指针的位置。

⑤其实该对象就是将字节输入流和字节输出流进行了封装

⑥该对象的源和目的只能是文件,通过构造函数就可以看出来。

⑦写文件的过程中,如果文件不存在,则自动创建,如果存在,则不创建。这一点与一般的流不一样。

12) 管道流:PipedInputStreamPipedOutputStream 输入输出可以直接进行连接,一般结合线程使用。

13) 操作基本数据类型的的流:DataInputStreamDataOutputStream

14) 操作字节数组的流:ByteArrayInputStreamByteArrayOutputStream 源和目的都是内存,不操作底层数据,没有将数据与硬盘设备等设备关联起来,维护的仅仅是内存中的数据(存储在数组中),所以这类流不需要关闭,关闭也无效。同时该流不会抛出IO异常。

类似的还有操作字符数组的:CharArrayReaderCharArrayWriter以及操作字符串数组的:StringReaderStringWriter

15) IO流之编码表:

编码表的由来:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示的,并一一对应,这就形成了一张表,即编码表。

常见的编码表:

ASCII:美国标准信息交换码,用一个字节的7位来表示。

ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位来表示。

GB2312:中国的中文编码表

GBK:中国的中文编码表的升级,融合了更多的中文文字符号。

Unicode:国际标准编码表。融合多种文字,所有文字都用两个字节来表示,是为了统一所有国家的编码表而出现的。

UTF-8:国际标准编码表的改进,所有文字最多用三个字节来表示,即一个字节能表示的文字用一个字节,两个字节能表示的用两个字节,三个的用三个的。

目前中国最常用的是GBKUTF-8

Java中编解码的表现:

编码:字符串->字节数组

解码:字节数组->字符串

原创粉丝点击