黑马程序员——IO流

来源:互联网 发布:淘宝商城家具床 编辑:程序博客网 时间:2024/06/01 08:07

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

一,IO流概述
用于处理设备上数据。Java对数据的操作是通过流的方式,可以理解数据的流动,就是一个数据流。java 用于操作流的对象都在IO包中。流的操作只有两种:读和写。
流的分类:
1,按操作数据分为:字节流和字符流。
2,按流向分为:输入流和输出流。

字节流和字符流的由来:
字节流是处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时,指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。

流的四个基类:
字节流的两个抽象基类:InputStream 和OutputStream
字符流的两个抽象基类:Reader 和Writer
它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。如:InputStream的子类FileInputStream。

字符流体系
(1)读
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
|——BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
|——|——LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
|——InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
|——|——FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
(2)写
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
|——BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|——OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
|——|——FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。

字节流体系
(1)读
InputStream:是表示字节输入流的所有类的超类。
|—— FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|—— FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|——|—— BufferedInputStream:该类实现缓冲的输入流。

(2)写
OutputStream:此抽象类是表示输出字节流的所有类的超类。
|—— FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|—— FilterOutputStream:此类是过滤输出流的所有类的超类。
|——|—— BufferedOutputStream:该类实现缓冲的输出流。

二,IO流的使用
1,FileWriter

public static void main(String[] args) throws IOException//读、写都会发生IO异常 {     //创建一个字符输出流对象FileWriter,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。    //对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。    //如果指定位置,出现了同名文件,文件会被覆盖。    FileWriter fw = new FileWriter("demo.txt");    //调用FileWriter类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中?    fw.write("abcde");    // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。    fw.flush();// 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。    fw.close();}}

close()和flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可以继续使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。

IO异常的处理方式:

public static void main(String[] args)     {        FileWriter fw = null;        try        {            fw = new FileWriter("demo.txt");            fw.write("abcdefg");        }        catch (IOException e)        {            System.out.println("catch:"+e.toString());        }        finally//一定要写,用于关闭流        {            try            {        //因为close 方法也会抛IOException,所以也要进行try。如果有多个流,要分开进行try,不要写到一起。                if(fw!=null)                    fw.close();                     }            catch (IOException e)            {                System.out.println(e.toString());            }        }           }

FileWriter对已有数据的续写

public static void main(String[] args) throws IOException    {        //传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。        FileWriter fw = new FileWriter("demo.txt",true);    // window中的换行符:\r\n两个符号组成。 linux:\n。        fw.write("nihao\r\nxiexie");        fw.close();    }

2,FileReader
(1)每次读取一个字符:int read()

public static void main(String[] args) throws IOException    {        //创建一个文件读取流对象,和指定名称的文件相关联。        //要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException        FileReader fr = new FileReader("demo.txt");        //调用读取流对象的read()方法,是一个阻塞方法,如果没有读到字符会一直等待。一次读一个字符。而且会自动往下读。当返回-1时,表示读到结尾,返回读取到的字符的整数表现形式。        int ch = 0;        while((ch=fr.read())!=-1)        {            System.out.println((char)ch);        }        fr.close();    }}

(2)通过字符数组读取,较为高效:int read(char[] buf) 将字符读入数组。

public static void main(String[] args) throws IOException {        FileReader fr = new FileReader("demo.txt");         //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。返回的是读到字符个数。        char[] buf = new char[1024];        int len = 0;        while(( len=fr.read(buf)) != -1) {            System.out.println(new String(buf,0,len));        }        fr.close();    }}

三,缓冲区
字符流缓冲区:BufferedReader和BufferedWriter
字节流缓冲区:BufferedInputStream和BufferedOutputStream
字节流缓冲区和字符流缓冲区的用法类似,下面以字节流缓冲区为例。
1,字符流缓冲区
缓冲区的出现提高了对数据的读写效率,对应的类是BufferedWriter和BufferedReader。缓冲区要结合流才可以使用,所以在创建缓冲区之前,必须要先有流对象。缓冲区实际上是在流的基础上对流的功能进行了增强。
(1)字符写缓冲区——BufferedWriter
BufferedWriter是给字符输出流提高效率用的,该缓冲区中提供了一个跨平台的换行符——newLine();

public static void main(String[] args) throws IOException    {        //创建一个字符写入流对象。        FileWriter fw = new FileWriter("buf.txt");        //为了提高字符写入流效率。加入了缓冲技术。        //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。        BufferedWriter bufw = new BufferedWriter(fw);        for(int x=1; x<5; x++)        {            bufw.write("abcd"+x);            bufw.newLine();//写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。            bufw.flush();//记住,只要用到缓冲区,就要记得刷新。        }           //其实关闭缓冲区,就是在关闭缓冲区中的流对象。        bufw.close();    }

(2)字符读取缓冲区——BufferedReader
该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。
当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符

public static void main(String[] args) throws IOException    {        FileReader fr = new FileReader("buf.txt");        //为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。        BufferedReader bufr = new BufferedReader(fr);        String line = null;        while((line=bufr.readLine())!=null)//readLine方法返回的时候是不带换行符的。        {            System.out.print(line);        }        bufr.close();    }

readLine 方法的原理:
无论是读一行还是多个字符,其实最终都是在硬盘上一个一个的读取。所有最终使用的还是read方法一次读一个的方法。

2,装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类将已有的对象传人,基于已有的功能,并提供加强功能。那么自定义的类称为装饰类(包装类)。
注意:
(1)包装类和被包装对象要实现同样的接口;因为包装类只是增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
(2)包装类要持有一个被包装对象;
(3)包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;

MyReader//专门用于读取数据的类。
|–MyTextReader
|–MyMediaReader
|–MyDataReader
|–MyBufferReader

找到其参数的共同类型。通过多态的形式。可以提高扩展性。

class MyBufferReader extends MyReader
{
private MyReader r;
MyBufferReader(MyReader r){ }
}

以前是通过继承将每一个子类都具备缓冲功能。那么继承体系会复杂,并不利于扩展。
现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单,优化了体系结构。

装饰模式的优点:
装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类于类之间的关系。
(1)自定义字符流缓冲区——MyBufferedReader

import java.io.*;class MyBufferedReader extends Reader{    private Reader r;    MyBufferedReader(Reader r)    {        this.r = r;    }    //可以一次读一行数据的方法。    public String myReadLine()throws IOException    {        //定义一个临时容器。原BufferReader封装的是字符数组。        //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。        StringBuilder sb = new StringBuilder();        int ch = 0;        while((ch=r.read())!=-1)        {            if(ch=='\r')                continue;            if(ch=='\n')                return sb.toString();            else                sb.append((char)ch);        }        if(sb.length()!=0)//如果文件最后一行没有回车符时,sb存储了这一行,但是没有返回,因此要判断一下。            return sb.toString();        return null;            }    //覆盖Reader类中的抽象方法。    public int read(char[] cbuf, int off, int len) throws IOException    {        return r.read(cbuf,off,len) ;    }    public void close()throws IOException    {        r.close();    }    public void myClose()throws IOException    {        r.close();    }}

(2)自定义字节流缓冲区——MyBufferedInputStream

import java.io.*;class MyBufferedInputStream{    private InputStream in;    private byte[] buf = new byte[1024*4];    private int pos = 0,count = 0;    MyBufferedInputStream(InputStream in)    {        this.in = in;    }    //一次读一个字节,从缓冲区(字节数组)获取。    public int myRead()throws IOException    {        //通过in对象读取硬盘上数据,并存储buf中。        if(count==0)        {            count = in.read(buf);            if(count<0)                return -1;            pos = 0;            byte b = buf[pos];            count--;            pos++;            return b&255;        }        else if(count>0)        {            byte b = buf[pos];            count--;            pos++;            return b&0xff;        }        return -1;    }    public void myClose()throws IOException    {        in.close();    }}

字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.
那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
所以,为了避免这种情况将读到的字节进行int类型的提升。
并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在写入数据时,只写该int类型数据的最低8位。
即int read()提升成int
void write(int b)强转成byte

3.转换流
读取键盘录入。
System.out:对应的是标准输出设备,控制台。
System.in:对应的标准输入设备:键盘。
InputStreamReader是字节流通向字符流的桥梁,是Reader的子类,构造方法是:InputStreamReader(InputStream in)
OutputStreamWriter是字符流通向字节流的桥梁,是Writer的子类,构造方法是:OutputStreamWriter(OutputStream out)
字节流对象通过转换流转换之后,可以使用字符流缓冲区对其进行操作。

获取键盘录入对象。
InputStream in = System.in;

将字节流对象转成字符流对象,使用转换流。
InputStreamReader InputStreamReader isr = new InputStreamReader(in);

为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReader bufr = new BufferedReader(isr);

键盘的最常见写法。
//从键盘读取
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//输出到控制台
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

如何选择流对象?
流对象有很多,可是不知道该用哪一个?。通过三个明确来完成。
流的操作规律:
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer。
2,操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
目的对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4,需要在基本操作上附加其他功能吗?
比如缓冲。如果需要就进行装饰。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。

转换流有一个子类就是操作文件的字符流对象:
InputStreamReader
|–FileReader
OutputStreamWriter
|–FileWrier

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader(“a.txt”);
InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),”gbk”);
以上两句代码功能一致,
如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader(“a.txt”); //因为简化。

如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

四,File类
用来将文件系统中的文件和文件夹封装成对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。File对象可以作为参数传递给流的构造函数。
字段
public static final String separator与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar。

File类常见方法:
1,构造函数
File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例
File(String parent,String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

//将a.txt封装成file对象。可以将已有的和为出现的文件或者文件夹封装成对象。
File f1 = new File(“a.txt”);
//f2创建方式和f3的创建方式,创建结果是一样的
// File f2 = new File(“c:\abc”,”b.txt”);
File d = new File(“c:\abc”);
File f3 = new File(b,”c.txt”);
System.out.println(“f1:”+f1);//new File(” “)里写的是什么就打印什么

2,创建
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建,返回false。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建文件夹。
boolean mkdirs():创建多级文件夹。
3,删除
boolean delete():删除此抽象路径名表示的文件或目录删除失败返回false。如果文件正在被使用,则删除不了返回falsel。
void deleteOnExit():在程序退出时删除指定文件。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。
4,获取
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():该方法返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。如果相对路径中有上一层目录那么该目录就是返回结果。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。

5,判断
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。

注意:在判断文件对象是否是文件或者目的时,必须要先判断该文件对象封装的内容是否存在。通过exists判断。

6,重命名
boolean renameTo(File dest):重新命名此抽象路径名表示的文件。

递归:就是函数自身调用自身。
什么时候用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。

递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。

例:删除一个带内容的目录。
在window中,删除目录从里面往外删除的。既然是从里往外删除。就需要用到递归。

public static void removeDir(File dir)    {        File[] files = dir.listFiles();        for(int x=0; x<files.length; x++)        {            if(files[x].isDirectory())                removeDir(files[x]);            else                System.out.println(files[x].toString()+":-file-:"+files[x].delete());        }        System.out.println(dir+"::dir::"+dir.delete());    }

Properties是hashtable的子类。
也就是说它具备map集合的特点。用于属性配置文件,而且它里面存储的键值对都是字符串。是集合中和IO技术相结合的集合容器。
该对象的特点:1:可以持久化存储数据。2:键值都是字符串。3:一般用于配置文件。那么在加载数据时,需要数据有固定格式:键=值。
方法:
void load(InputStream inStream)throws IOException:从输入流中读取属性列表(键和元素对)。
void load(Reader reader)throws IOException:按从输入字符流中读取属性列表(键和元素对)。
原理:其实就是将读取流和指定文件相关联,并读取一行数据,因为数据是规则的key=value,所以获取一行后,通过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。
void store(OutputStream out,String comments)throws IOException:写入各个项后,刷新输出流。此方法返回后,输出流仍保持打开状态
void store(Writer writer,String comments)throws IOException:写入各个项后,刷新输出流。此方法返回后,输出流仍保持打开状态。
void list(PrintStream out):将属性列表输出到指定的输出流。
void list(PrintWriter out):将属性列表输出到指定的输出流。
Object setProperty(String key,String value):调用 Hashtable 的方法 put。
String getProperty(String key):属性列表中具有指定键值的值。

如何将流中的数据存储到集合中。
想要将info.txt中键值数据存到集合中进行操作。
1,用一个流和info.txt文件关联。
2,读取一行数据,将该行数据用”=”进行切割。
3,等号左边作为键,右边作为值。存入到Properties集合中即可。

public static void method_1()throws IOException    {        BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));        String line = null;        Properties prop = new Properties();        while((line=bufr.readLine())!=null)        {            String[] arr = line.split("=");            prop.setProperty(arr[0],arr[1]);        }        bufr.close();        System.out.println(prop);    }

五,IO包中的其他类。
1,字节打印流——PrintStream
(1)PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
(2)PrintStream 永远不会抛出 IOException
(3)它有一个自动刷新机制
(4)它使用的本机默认的字符编码.

构造函数:
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。

PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建新的打印流。
autoFlush - boolean 变量;如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 (‘\n’) 时都会刷新输出缓冲区
PrintStream(String fileName) :创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn) 创建具有指定文件名称和字符集且不带自动行刷新的新打印流。

2,字符打印流——PrintWriter
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
4,字符输出流,Writer。

PrintWriter:具备了PrintStream的特点同时,还有自身特点:
开发时尽量使用PrintWriter。

public static void main(String[] args) throws IOException{//读取键盘录入将数据转成大写显示在控制台.BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:键盘输入//目的:把数据写到文件中,还想自动刷新。PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新String line = null;while((line=bufr.readLine())!=null){    if("over".equals(line))        break;    out.println(line.toUpperCase());//转大写输出}out.close();bufr.close();   }

注意:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。随时可以使用。当jvm结束了,这两个流就结束了。但是,当使用了显示的close方法关闭时,这两个流在提前结束了。

3,序列流——SequenceInputStream
作用就是将多个读取流合并成一个读取流。实现数据合并。表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
构造函数:
SequenceInputStream(Enumeration< ? extends InputStream> e):通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。将按顺序读取由该枚举生成的输入流,以提供从此 SequenceInputStream 读取的字节。在用尽枚举中的每个输入流之后,将通过调用该流的 close 方法将其关闭。
SequenceInputStream(InputStream s1,InputStream s2):通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。

方法:
int available()throws IOException:返回不受阻塞地从当前底层输入流读取(或跳过)的字节数的估计值。
int read()throws IOException:从此输入流中读取下一个数据字节。
int read(byte[] b,int off,int len)throws IOException:将最多 len 个数据字节从此输入流读入 byte 数组。
void close()throws IOException:关闭此输入流并释放与此流关联的所有系统资源。

class SequenceDemo {    public static void main(String[] args) throws IOException    {        Vector<FileInputStream> v = new Vector<FileInputStream>();        v.add(new FileInputStream("c:\\1.txt"));        v.add(new FileInputStream("c:\\2.txt"));        v.add(new FileInputStream("c:\\3.txt"));        Enumeration<FileInputStream> en = v.elements();        SequenceInputStream sis = new SequenceInputStream(en);        FileOutputStream fos = new FileOutputStream("c:\\4.txt");        byte[] buf = new byte[1024];        int len =0;        while((len=sis.read(buf))!=-1)        {            fos.write(buf,0,len);        }        fos.close();        sis.close();    }}

4,操作对象 ——ObjectInputStream和ObjectOutputStream
对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。
被操作的对象需要实现Serializable接口(标记接口)
Serializable 接口用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。UID是根据类中的成员(都有一个数字标识)算出的用来标识类。

注意:类如果可以序列化都有一个ID标识 ,是给编译器使用的。
静态不能被序列化,静态在方法区,可以序列化的是在堆里面。
被transient修饰的成员也不能被序列化,保证其成员在堆内存中存在,而不在文件中存在。

ObjectInputStream(InputStream in)throws IOException:创建从指定 InputStream 读取的 ObjectInputStream。
ObjectOutputStream(OutputStream out)throws IOException:创建写入指定 OutputStream 的 ObjectOutputStream

public static void readObj()throws Exception    {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));        Person p = (Person)ois.readObject();        System.out.println(p);        ois.close();    }    public static void writeObj()throws IOException    {        ObjectOutputStream oos =             new ObjectOutputStream(new FileOutputStream("obj.txt"));        oos.writeObject(new Person("lisi0",399,"kr"));        oos.close();    }

注意:ObjectOutputStream和ObjectInputStream只能用对方读/写

5,RandomAccessFile
此类的实例支持对随机访问文件的读取和写入。
该类不是算是IO体系中子类。而是直接继承自Object,但是它是IO包中成员。

特点:
1,具备读和写功能。
2,内部封装了一个数组,而且通过指针对数组的元素进行操作。
3,可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4,完成读写的原理就是内部封装了字节输入流和输出流。
5,通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读r,读写rw等。
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。

//随机写入数据,可以实现已有数据的修改。public static void write()throws IOException{       //rw读写模式:当这个文件不存在,会创建该文件。当文件已存在,不会创建。所以不会像输出流一样覆盖。        RandomAccessFile raf = new RandomAccessFile("random.txt","rw");        raf.seek(8*4); //指定指针的位置。        raf.write("周期".getBytes())        raf.writeInt(102);        raf.close();    }    //实现随机读取文件中的数据。注意:数据最好public static void readFile()throws IOException    {        RandomAccessFile raf = new RandomAccessFile("ran.txt","r");        //调整对象中指针。        //raf.seek(8*1);        //跳过指定的字节数        raf.skipBytes(8);        byte[] buf = new byte[4];        raf.read(buf);        String name = new String(buf);        int age = raf.readInt();        System.out.println("name="+name);        System.out.println("age="+age);        raf.close();    }   }

6,管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。

注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。

import java.io.*;class Read implements Runnable{    private PipedInputStream in;    Read(PipedInputStream in)    {        this.in = in;    }    public void run()    {        try        {            byte[] buf = new byte[1024];            System.out.println("读取前。。没有数据阻塞");            int len = in.read(buf);            System.out.println("读到数据。。阻塞结束");            String s= new String(buf,0,len);            System.out.println(s);            in.close();        }        catch (IOException e)        {            throw new RuntimeException("管道读取流失败");        }    }}class Write implements Runnable{    private PipedOutputStream out;    Write(PipedOutputStream out)    {        this.out = out;    }    public void run()    {        try        {            System.out.println("开始写入数据,等待6秒后。");            Thread.sleep(6000);            out.write("piped lai la".getBytes());            out.close();        }        catch (Exception e)        {            throw new RuntimeException("管道输出流失败");        }    }}class  PipedStreamDemo{    public static void main(String[] args) throws IOException    {        PipedInputStream in = new PipedInputStream();        PipedOutputStream out = new PipedOutputStream();        in.connect(out);        Read r = new Read(in);        Write w = new Write(out);        new Thread(r).start();        new Thread(w).start();    }}

7,其他的类
(1)DataOutputStream和DataInputStream:专门用于操作基本数据类型数据的对象。
(2)ByteArrayInputStream和 ByteArrayOutputStream:用于操作字节数组的流对象。
这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。
直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。
对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。
(3)CharArrayReader和CharArrayWrite:用于操作字符数组
(4)StringReader和 StringWriter:用于操作字符串

8,字符编码
(1)编码表的由来
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。
(2)常见编码表
ASCII:美国标准信息交换码,用一个字节的7位表示。
ISO8859-1:拉丁码表,欧洲码表,用一个字节的8位表示。
GB2312:中国的中文码表
GBK:中国的中文码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字,所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8:最多用三个字节来表示一个字节。

编码:字符串变成字节数组。
解码:字节数组变成字符串。
String–>byte[]; str.getBytes(charsetName);//用指定的编码表
byte[] –>String: new String(byte[],charsetName);

字符流的出现是为了方便操作字符,更重要的是加入了编码转换。通过子类转换流来完成,InputStreamReader和OutputStreamWriter,在这两个对象进行构造的时候可以加入字符集。

public static void readText()throws IOException     {        InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"gbk");        char[] buf = new char[10];        int len = isr.read(buf);        String str = new String(buf,0,len);        System.out.println(str);        isr.close();    }    public static void writeText()throws IOException     {        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");        osw.write("你好");        osw.close();    }

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

0 0
原创粉丝点击