您的位置:首页 > 职场人生

黑马程序员-Java学习笔记之IO流(一)

2015-03-07 09:36 260 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一 IO流

1,IO就是用于处理设备上的数据(设备之间的数据传输)。Java中对数据的操作是通过流 的方式,用于操作流的对象都在IO包中。

流:可以理解为数据的流动,就是一个数据流。

数据流是一串连续不断的数据的集合,就像水管里的水流,在水管的一端一点一点地供水,而 在水管的另一端看到的是一股连续不断的水流.数据写入程序可以使一段一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流.

 

在程序中所有的数据都是以流的方法进行传输和保存的。

Java 的IO是实现输入和输出的基础。

Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入输出功能。

输入和输出是一个相对的概念,我们一般站在程序的角度来分析和处理问题的。

程序需要数据 --> 读进来    --> 输入

程序保存数据 --> 写出去    --> 输出,

 

2,流的分类(面试常考)

 

从不同角度分类:

按流动方向的不同可以分为输入流和输出流;

按处理数据的单位不同分为字节流和字符流;

按功能的不同可分为节点流和处理流;

 

字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

 

那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。

注意:流的操作只有两种:读和写。

 

3,流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。

字节流:InputStream  OutputStream

字符流:Reader  Writer

在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。

读、写都会发生IO异常

1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。

2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。

3:如果指定位置,出现了同名文件,文件会被覆盖。

Example:

import java.io.*;
class IODemo
{
public static void main(String[] args)throws IOException//读写都会放生IO异常
{
FileWriter fw=new FileWriter("demo.txt");
fw.write("abcde");//调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。
fw.flush();//刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
fw.close();//关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。

}
}

4,close和flush的区别:

flush:将缓冲区的数据刷到目的地中后,流可以使用。

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

 

5,字节流和字符流的区别

 

字节流和字符流在使用上的代码结构都是非常类似的,但是其内部本身也是有区别的,因为在进行字符流操作的时候会使用到缓冲区(内存中),而字节流操作的时候是不会使用到缓冲区的。

在输出的时候,OutputStream类即使最后没有关闭内容也可以输出。但是如果是Writer的话,则如果不关闭,最后一条内容是无法输出的,因为所有的内容都是保存在了缓冲区之中,每当调用了close()方法就意味着清空缓冲区了。那么可以证明字符流确实使用了缓冲区:

字节流:程序 → 文件

字符流:程序 → 缓冲区(内存中) → 文件

 

如果现在字符流即使不关闭也可以完成输出的话,则必须强制性清空缓冲区:

方法:public void flush() throws IOException

二 字符流

Java中的字符是Unicode编码,是双字节的,1个字符 等于 2个字节;

使用字节来处理字符文本就不太方便了,此时可以考虑使用字符流;

字符流主要是操作char的类型数据:

字符输出流:Writer

字符输入流:Reader

io异常的处理方式:io一定要写finally;

 

FileWriter写入数据的细节:

1:window中的换行符:\r\n两个符号组成。 linux:\n。

2:续写数据,只要在构造函数中传入新的参数true。

3:目录分割符:window \\  。

Example:写入数据。

import java.io.*;
class IODemo1
{
public static void main(String[]args)
{
FileWriter fw=null;
try
{
fw=new FileWriter("demo.txt",true);//创建目标文件。
fw.write("abcde");//写入数据。
fw.flush();//刷新流

}
catch(IOException e)
{
throw new RuntimeException("写入数据失败");
}
finally
{
if(fw!=null)
{
try
{
fw.close();//关闭流。
}
catch(IOException e)
{
throw new RuntimeException("流关闭错误");
}

}

}
}
}

FileReader:使用Reader体系,读取一个文本文件中的数据。

方法read(),如果返回-1,标志读到结尾。

Example:第一种方式。

import java.io.*;
class IODemo3
{
public static void main(String[]args)
{
//创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相联。
FileReader fr=null;
try
{
fr=new FileReader("demo.txt");
int ch=0;
while((ch=fr.read())!=-1)
{
System.out.println((char)ch);//调用读取流的read方法,读取一个字符。

}

}
catch (IOException e)
{
throw new RuntimeException("读取数据失败");
}
finally
{
if(fr!=null)
try
{
fr.close();//关闭流。

}
catch (IOException e)
{
throw new RuntimeException("流关闭错误");
}

}

}
}

Example:
读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。

import java.io.*;
class IODemo4
{
public static void main(String[] args)
{
FileReader fr=null;
try
{
fr=new FileReader("demo.txt");//创建读取流对象和指定的文件关联。
char[] buf=new char[1024];//定义一个char数组
int ch=0;
while((ch=fr.read(buf))!=-1)//将数据读取到数组中。
{
System.out.println(new String(buf,0,ch));
}

}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
finally
{
if(fr!=null)
{
try
{
fr.close();//关闭流

}
catch (IOException e)
{
throw new RuntimeException("流关闭失败");
}
}
}
}

}

三 装饰设计

IO中的使用到了一个设计模式:装饰设计模式:比继承体要灵活,避免了继承体系的臃肿。而且降低了类与类之间的关系。

装饰设计模式解决:对一组类进行功能的增强。

1,包装:写一个类(包装类)对被包装对象进行包装;

 * 1、包装类和被包装对象要实现同样的接口;

 * 2、包装类要持有一个被包装对象;

 * 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;

2,字符流:

Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

方法摘要 

abstract  void close
bf57
() 

          关闭该流并释放与之关联的所有资源。 

 void mark(int readAheadLimit) 

          标记流中的当前位置。 

 boolean markSupported() 

          判断此流是否支持 mark() 操作。 

 int read() 

          读取单个字符。 

 int read(char[] cbuf) 

          将字符读入数组。 

abstract  int read(char[] cbuf, int off, int len) 

          将字符读入数组的某一部分。 

 int read(CharBuffer target) 

          试图将字符读入指定的字符缓冲区。 

 boolean ready() 

          判断是否准备读取此流。 

 void reset() 

          重置该流。 

 long skip(long n) 

          跳过字符。 

 

     |---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

方法摘要 

 void close() 

          关闭该流并释放与之关联的所有资源。 

 void mark(int readAheadLimit) 

          标记流中的当前位置。 

 boolean markSupported() 

          判断此流是否支持 mark() 操作(它一定支持)。 

 int read() 

          读取单个字符。 

 int read(char[] cbuf, int off, int len) 

          将字符读入数组的某一部分。 

 String readLine() 

          读取一个文本行。 

 boolean ready() 

          判断此流是否已准备好被读取。 

 void reset() 

          将流重置到最新的标记。 

 long skip(long n) 

          跳过字符。 

 

        |---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

方法摘要 

 int getLineNumber() 

          获得当前行号。 

 void mark(int readAheadLimit) 

          标记该流中的当前位置。 

 int read() 

          读取单个字符。 

 int read(char[] cbuf, int off, int len) 

          将字符读入数组中的某一部分。 

 String readLine() 

          读取文本行。 

 void reset() 

          将该流重新设置为最新的标记。 

 void setLineNumber(int lineNumber) 

          设置当前行号。 

 long skip(long n) 

          跳过字符。 

 

     |---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

        |---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

     |---CharArrayReader:

     |---StringReader:

-------------------------------------------------

Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。

     |---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

方法摘要 

 void close() 

          关闭此流,但要先刷新它。 

 void flush() 

          刷新该流的缓冲。 

 void newLine() 

          写入一个行分隔符。 

 void write(char[] cbuf, int off, int len) 

          写入字符数组的某一部分。 

 void write(int c) 

          写入单个字符。 

 void write(String s, int off, int len) 

          写入字符串的某一部分。 

 

     |---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

        |---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。

     |---PrintWriter:

     |---CharArrayWriter:

     |---StringWriter:

3,缓冲区是提高效率用的,给谁提高呢?

BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。

Example:

import java.io.*;
class BufferedReaderDemo
{
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)
{
System.out.println(line);
}

bufr.close();
}

}

四 字节流:

1,InputStream:是表示字节输入流的所有类的超类。

方法摘要 

Int available()

 void close() 

          关闭此输入流并释放与该流关联的所有系统资源。 

 abstract  int read() 

          从输入流中读取数据的下一个字节。 

 int read(byte[] b) 

          从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 

 int read(byte[] b, int off, int len) 

          将输入流中最多 len 个数据字节读入 byte 数组。 

 

     |--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。

     |--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。

        |--- BufferedInputStream:该类实现缓冲的输入流。

        |--- Stream:

     |--- ObjectInputStream:

     |--- PipedInputStream:

 

2,OutputStream:此抽象类是表示输出字节流的所有类的超类。

     |--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

     |--- FilterOutputStream:此类是过滤输出流的所有类的超类。

        |--- BufferedOutputStream:该类实现缓冲的输出流。

        |--- PrintStream:

        |--- DataOutputStream:

     |--- ObjectOutputStream:

     |--- PipedOutputStream:

Example:

/*
复制图片
*/
import java.io.*;
class PictureDemo
{
public static void main(String[] args)throws IOException
{
FileInputStream fis=new FileInputStream("C:\\Users\\liupeng\\Pictures\\1.jpg");
FileOutputStream fos=new FileOutputStream("C:\\Users\\liupeng\\Desktop\\2.jpg");

byte[] buf=new byte[1024];//厂家数组。
int len=0;
while((len=fis.read(buf))!=-1)//将数据读到数组中。
{
fos.write(buf,0,len);

}
fis.close();
fos.close();
System.out.println("Hello world!");
}

}

 

五 流的操作规律:

流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

 

1,明确源和目的。

数据源:就是需要读取,可以使用两个体系:InputStream、Reader;

数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;

2,操作的数据是否是纯文本数据?

如果是:数据源:Reader

    数据汇:Writer 

如果不是:数据源:InputStream

      数据汇:OutputStream

3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

明确操作的数据设备。

数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)

数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。

4,需要在基本操作上附加其他功能吗?比如缓冲。

如果需要就进行装饰。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: