博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

12、IO流

Posted on 2013-08-14 12:39  mz_zyh  阅读(305)  评论(0编辑  收藏  举报

IO(Input Output)流

概述:

  IO流用来处理设备之间的数据传输

  Java对数据的操作时通过流的方式操作的

  Java用于操作流的对象都在IO包中

  流按操作数据分两种:字节流和字符流

  流按流向分为:输入流和输出流

 

IO流常用基类

  1、字节流的抽象基类:

    InputStream,OutputStream

  2、字符流的抽象基类:

    Reader,Writer

  注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。

    如:InputStream的子类:FileInputStream

    如:Reader的子类:FileReader

 

字符流和字节流:

  字节流两个基类:

    InputStream  OutputStream

 

  字符流两个基类:

    Reader  Writer

  既然IO流是用于操作数据的,那么数据的最常见体系形式是:文件。

  专门用于操作文件的Writer子类对象,FileWriter,后缀名是父类名Writer,前缀名是该流对象的功能

  //创建一个FileWriter对象,该对象一被初始化就必须明确被操作的文件

  //而且该文件会被创建到指定的目录下。如果该目录下已有同名文件,将被覆盖,

  //其实该步就是在明确数据有存放的目的地。

  FileWriter fw = new FileWriter("demo.txt");

  fw.write("zhong");

  //刷新流对象中的缓冲区数据

  //将数据刷到目的地中

  // fw.flush();

 

  //关闭流资源,但是在关闭之前会刷新一次内部的缓冲数据,将数据刷到目的地中

  //和flush的区别:flush刷新后流可以继续使用,close刷新后会将流关闭 

  fw.close();

 

 

 IO异常的处理方式:

  凡是与设备上的数据发生数据关系去处理的,无论读还是写,都会发生IO异常

 

  IOException处理基本方式:

public static void main(String[] args) {

  //将变量定义在外面,是为了finally中关流时能找到变量,定义在外面必须负责

  FileWriter fw = null;

  try {

    fw = new FileWriter("demo.txt");

    fw.write("abcdefg");

  } catch (IOException e) {

    throw new RuntimeException("写入失败");

  } finally {

    //关流是必须完成的动作,故放在finally里面

    try {

      //如果创建目的都不存在,即创建时已经异常,那就没有创建对象,

      //此时不可能调用close方法,需要加判断

      //如果开启了多个流,那么每个流都必须单独判断关闭

      if(fw!=null){

        fw.close();

      }

    }
catch (Exception e) {       System.out.println(e.toString());     }   } }

 

 

文件的续写:

  文件的续写通过构造函数来指定

public static void main(String[] args) {

  FileWriter fw = null;

  try {

    //传递一个true参数,代表不覆盖已有文件,并在已有文件的末尾处续写

    fw = new FileWriter("demo.txt",true);

    //Window环境下,换行是由两个字符组成的"\r\n"
  
    fw.write("nihao\r\nxiexie");

  } catch (IOException e) {

    throw new RuntimeException("写入失败");

  } finally {

    try {

      if (fw!=null){

        fw.close();

      }

    } catch (IOException e){

      System.out.println(e.toString());

    }

  }

}

 

 

文件的读取:

  有两种读取方式:

    1、采用read()方法,每次只读取一个字符

//调用读取流对象read()的方法

//read():一次读一个字符,并会自动往下读

int ch = 0;

while((ch=fr.read())!=-1){

  System.out.print((char)ch);

}

 

    2、采用read(char[] c)方法,每次读取一个字符数组长度的数据

//定义一个字符数组,用于存储读到的字符

//该read(char[])返回的是读到的字符个数

char[] cbuf = new char[1024];

int ch = 0;

//fr.read(cbuf)方法返回的该次读取所读到的字符数

while((ch = fr.read(cbuf))!=-1){

  System.out.println(new String(cbuf,0,ch));

}

 

 

复制文件练习:重点注意,关流要分开来try

private static void copyText() {

  FileReader fr = null;

  FileWriter fw = null;

 

  try {

    fr = new FileReader("c:\\语句记录.txt");

    fw = new FileWriter("d:\\Copy.txt");

 

    int len = 0;

    char[] buf = new char[1024];

 

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

      fw.write(buf, 0, len);

      fw.flush();

    }

  } catch (IOException e) {

    throw new RuntimeException("读写失败");

  } finally {

    if(fr!=null)

      try {

        fr.close();
 
      } catch (IOException e){

        throw new RuntimeException("输入流关闭失败");

      }

 

    if(fw!=null)

      try {

        fw.close();

      } catch (IOException e){

        throw new RuntimeException("输出流关闭失败");

      }

  }

}

 

  

字符流的缓冲区:

  缓冲区的出现提高了对数据的读写效率

  对应类:

    BufferedWriter

    BufferedReader

  缓冲区要结合流才可以使用

  在流的基础上对流的功能进行了增强。

 

  缓冲区的出现是为了提高流的操作效率而出现的

  所以在创建缓冲区之前,必须要先有流对象。

  BufferedWriter提供了一个跨平台的换行符方法:newLine();

  只要是缓冲区,都要记得刷新,

  关缓冲区,其实关的就是该缓冲区缓冲的流对象。

  

  BufferedReader缓冲区,提供了一个一次读取一行的方法readerLine,当读到末尾结束时返回null

 

  readLine方法返回的时候只是返回回车符之前的数据内容,并不返回回车符

  readLine方法的原理:

    无论是读一行,或者读取多个字符,其实最终都是在硬盘上一个一个的读取,最终使用的还是read()方法一次读一个

 

  注意:做工具类时,如果出现异常,就应该抛出去,不要try,让调用者知道并处理

 

装饰设计模式:

  当想要对已有的对象进行功能增强时,可以定义一个类将已有对象传入,基于已有对象的功能,并提供加强功能。

  那么, 自定义的该类就称为装饰类。

 

  装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能提供更强的功能。

  装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系

  装饰类因为是增强已有对象,具备的功能和已有对象是相同的,只不过提供了更强的功能,所以装饰类和被装饰类通常都属于一个体系中。

 

  LineNumberReader类

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

myLineNumberReader例子:

public class MyLineNumberReader extends BufferedReader {

  private int lineNumber;

 

  MyLineNumberReader(Reader r){

    super(r);

  }

 

  /**

   * 设置行号的方法

   * @return

   */

  public void mySetLineNumber(int lineNumber){

    this.lineNumber = lineNumber;

  }

 

  /**

   * 获取行号的方法

   * @return

   */

  public int myGetLineNumber(){

    return lineNumber;

  }

 

  /**

   * 获取一行数据

   * @throws IOException 

   */

  public String myReadLine() throws IOException{

    lineNumber++;

    return super.readLine();

  }

}

 

 

字节流:

  InputStream

  OutputStream

  字节流特点:因为是直接操作最小单位,没有临时的缓冲,即不需要刷新了,直接会存储到目的地,但还是要关流

  而字符流底层也是字节流,但必须先缓存到字节数组中,然后再查编码表获得对应字符,通过刷新到达目的地

 

  字节流中的读取字节数据的方法返回的int类型数据的原因

    读取字节数据时,可能读取到一个字节为11111111的情况,其表示的值为-1,这与结束读取的标记值重复了,会造成调用该方法时出现判断错误。

    这时采用将字节数据提升为int类型数据的来解决,其实,byte数据的-1提升为int型数据也是-1的,要解决这个问题,要将该数据与255即0xff相与,强制使int数据前三个字节数据都置为0。这样该数据就不为-1了,就可以解决判断错误的情况。

    在写数据时,会通过强制换行,去除前三个字节的数据,只获取最后一个字节数据,保证数据的原样性。

 

模拟自定义字节流缓冲区的代码:

/**

 * 模拟自定义字节流缓冲区

 * 思路:

 *   1、定义数组存储从硬盘上读取的数据

 *   2、定义变量存储存入的数组的字节个数

 *   3、定义指针用标明读取的位置

 * @author xiuyuan

 *

 */

public class MyBufferedInputStream extends InputStream{

 
  private InputStream is;

  private byte[] buf = new byte[1024];

  private int len = 0;

  private int pos = 0;

 

  MyBufferedInputStream(InputStream is){

    this.is = is;

  }

 

  /**

   * 从缓冲区中获取一个字节

   */

  public int myRead() throws IOException {

    if(len==0){

      len = is.read(buf);

      pos=0; 

    }

    if(len > 0){

      len--;

      /*

       * 返回值结果&0xff的原因:对出现读取的字节数据为11111111的情况时,

       * &0xff可以强制使该数据提升为int类型,并且前三个字节数据为0,

       * 避免与结束读取的标识数值重复。 

       */

      return buf[pos++]&0xff;

    }

    return -1;

  }

 

  /**

   * 关闭流资源

   * @throws IOException 

   */

  public void myClose() throws IOException{

    is.close();

  }

 

  @Override

  public int read() throws IOException {

    return 0;

  }

}

 

 

读取键盘录入:

  System.out:对应的是标准的输出设备,控制台

  System.in:对应的标准输入设备,键盘。

 

  Read()方法是阻塞式方法,没有读到数据会一直在等待。

 

  通过键盘录入,发现其实就是读一行数据的原理,也就是readLine方法

 

转换流:

  InputStreamReader:字节流通向字符流的桥梁。(读,将读不懂的转成读的懂的,字节到字符)

  OutputStreamWriter:字符流通向字节流的桥梁(写入,将看的懂的转成看不懂的,字符到字节)

 

键盘录入的最常见写法:

  BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

键盘录入代码:

public static void inputStreamReader(){

  BufferedReader bufr = null;

  BufferedWriter bufw = null;

  try {

    bufr = new BufferedReader(new InputStreamReader(System.in));

    bufw = new BufferedWriter(new OutputStreamWriter(System.out));

    String line = null;

    while((line=bufr.readLine())!=null){

      if("over".equals(line)){

        break;

      }

      bufw.write(line);

      bufw.newLine();

      bufw.flush();

    }

  } catch (IOException e) {

    throw new RuntimeException("读取失败");

  } finally {

    try{
  
      if(bufr!=null){

        bufr.close();

      }

    }catch (IOException e2){

      throw new RuntimeException("关闭读取流失败");

    }

    try {

      if(bufw!=null){

        bufw.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException("关闭写出流失败");

    }

  }

}

 

 

流操作的基本规律:

  通过四个明确来完成体系的选择

    1、明确源和目的;

      源:输入流。 InputStream   Reader

      目的:输出流 OutputStream  Writer

    2、操作的数据是否纯文本。

      是:字符流

      不是:字节流

    3、当体系明确后,在明确要使用哪个具体的对象

      通过设备来区分:

        源设备:内存,硬盘,键盘

        目的设备:内存,硬盘,控制台

    4、是否需要额外功能

      需要提高效率:Buffered

      需要转换流InputStreamReader OutputStreamWriter

  

  转换流什么时候使用?

    字符和字节直接的桥梁,通常,涉及到字符编码转换时,需要用到转换流。

    FileReader和FileWriter都是使用默认编码表,转换流可以指定编码表

 

  在System类中提供了改变标准的输入或输出设备的方法:

    setIn(InputStream in) 重新分配”标准“输入流

    setOut(PrintStream out) 重新分配”标准“输出流

 

PrintStream类:为其他输出流添加功能,使它们能够方便的打印各种数据值表示形式。

  非抽象类,可以创建对象

 

异常的日志信息:

  将异常信息打印在控制台并没有多大意义,将异常信息存储在文件日志方便检查

  Throwable类中:

    printStackTrace()方法:将此throwable及其追踪输出至标准错误流。

    其重载函数printStackTrace(PrintStream s):将此throwable及其追踪输出到指定的输出流。

    故可以PrintStream构造函数指定日志存储路径

    通过System.out中的方法setOut方法指定输出设备

    通过Date类可记录异常出现时间。

  

 

    有专门工具可以建立java日志信息的,该工具为log4j里面提供了很多方法可以方便建立日志信息。

  

File类:是文件和目录路径名的抽象表示形式。

    用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作

      File对象可以作为参数传递给流的构造函数

 

  字段:static String separator :与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。

 这样较利于跨平台,直接用"\\"不利于跨平台,在其他平台上的分隔符可能不同。

 

File类的常见方法:

  1、创建

    Boolean createNewFile()方法,创建File对象,返回一个布尔型变量,(该方法仅在不存在此抽象路径名指定名称的文件时,才创建):在指定文字创建文件,如果该文件已经存在,则不创建,返回false,和输出流不一样,输出流对象已建立就创建文件,即使文件已经存在,也会将其覆盖。

    Static File createTempFile(String prefix,String suffix)在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀字符串生成其名称,用来创建临时文件

 

    Boolean mkdier();创建此抽象路径名指定的目录,创建文件夹

    Boolean mkdirs()创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。创建多级文件夹

 

  2、删除

    Boolean delete();删除文件对象,删除失败返回false。

    Void deletOnExit();在程序退出是删除指定文件

 

  3、判断

    Boolean exists():文件是否存在。

    Boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录,即判断是否为文件夹

    Boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件,即判断是否为文件。

 

    注意:在判断文件对象是否为目录或文件时,必须先判断该文件对象封装的内容是否存在,通过exists判断。若不然判断无意义。

 

    Boolean isHidden()判断是否为隐藏文件

    Boolean isAbsolute()判断是否为绝对路径。

 

  4、获取信息

    getName();获取名称

    getPath();获取封装的路径

    getParent();获取绝对路径中的父目录名(该路径指的是定义File对象时的路径),如果获取的是相对路径,返回的是null。如果相对路径中有上一层目录,那么该目录就是返回结果

    getAbsolutePath();获取绝对路径

    lastModified();获取该文件对象最后一次被修改的时间

    Length();获取长度

 

  如果文件在被操作时,不能够被删除

  文件覆盖的原理是:先删除再创建。

 

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

    Static File[] listRoots();列出可以的文件系统根。会获取有效的盘符

  String[] list();返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录,即获取该路径下的所有文件和文件夹,包括隐藏文件的。

  调用list方法的file对象必须是封装了一个目录,该目录还必须存在。

 

String[] list(FilenameFilter filter);返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。

FilenameFilter是一个接口,实现该接口的类实例可以用于过滤器文件名。

  该接口下只有一个方法:

    boolean accept(File dir,String name);其中,dir为调用该过滤器的文件对象的路径,name为该路径下的所以的目录的名称,方法体内可以指定判断标准,以此过滤获取所需文件。

代码示例:获取指定目录下的.java文件

public static void getJava(){

  File f = null;

  try {

    f = new File("F:\\");

    String[] names = f.list(new FilenameFilter(){

      public boolean accept(File dir,String name){

        return name.endsWith(".java");

      }

    });

    for(String name : names){

      System.out.println(name);

    }

  } catch (Exception e) {

    throw new RuntimeException("获取失败");

  }
  
}

 

 

File[] listFiles()该方法获取的文件对象路径下的所有文件对象,该方法要比list()实用,因list()方法只获取文件名称,而该方法获取文件对象

File[] listFiles() 

  返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 

File[] listFiles(FileFilter filter) filter为文件过滤器

  返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 

File[] listFiles(FilenameFilter filter) filter为文件名过滤器

  返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 

  

递归:

  方法自身调用自身的表现形式或编程手法称为递归。

递归要注意:

  1、限定条件:

  2、要注意递归的次数;尽量避免内存溢出。

 

删除原理:

  在window中,删除目录是从里面往外删除的。

 

  在系统中,有一些隐藏的目录是java不能访问的,访问会造成空指针异常,如在遍历目录时,如果遍历的是盘符,而且该盘符下还存有系统隐藏不让访问的目录时,就会报空指针异常。此时,可以加判断条件,对隐藏目录不进行遍历

  该处要在遍历时注意,但删除时不要加该判断,因为删除只能从里往外删

代码示例如下:

public static void main(String[] args) {

  File f = null;

  try {

    f = new File("g:\\");

    getListFile(f,0);

  } catch (Exception e) {

    throw new RuntimeException(e.toString());

  }

}

 

/**

 * 该文件用于遍历获取指定路径下的文件或文件夹,包含子目录内容

 */

public static void getListFile(File file,int level){

  System.out.println(getLevel(level++)+file.getName());

  for(File f : file.listFiles()){

    //系统中存在一些隐藏的目录java是不能访问的,访问会报空指针异常,故加判断将隐藏目录滤去,仅遍历时判断,对删除动作时不要加判断

    if(!f.isHidden() && f.isDirectory()){

      getListFile(f,level);

    }else{

      System.out.println(getLevel(level)+f.getName());

    }

  }

}

 

/**

 * 该方法用于打印文件对象名

 */

public static String getLevel(int value){

  StringBuilder sb = new StringBuilder();

  for(int x = 0; x<value;x++){

    sb.append("  ");

  }

  sb.append("|--");

  return sb.toString();

}

 

 

 

练习:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。

代码:

public static void main(String[] args) {

  try {

    //定义要遍历的路径

    File file = new File("f:\\");

    //定义存储java文件绝对路径的文本文件

    File path = new File("f:\\FileListing.txt");

 

    //定义集合存储遍历时获取的java文件对象

    Set<File> set = new TreeSet<File>();

 

    //调用方法将.java文件对象存储到集合中

    fileToSet(file,set);

 

    //调用方法将集合中存储的java文件的绝对路径写入到文本文件中

    writerToFile(set,path);

  } catch (Exception e) {

    throw new RuntimeException(e.toString());

  } 

}

 

/**

 * 该方法用于将集合中的文件写入到指定文本文件中,形成列表清单

 * @param set

 */

private static void writerToFile(Set<File> set,File file) {

  BufferedWriter bufw = null;

  try {

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

    for(File s : set){

      //将集合中存储的java文件对象的绝对路径写入文本文件

      bufw.write(s.getAbsolutePath());

      bufw.newLine();

      bufw.flush();

    }
  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    if(bufw!=null){

      try {

        bufw.close();

      } catch (IOException e2) {

        throw new RuntimeException("输出关闭失败"+e2.toString());

      }

    }

  }

}

 

/**

 * 该方法用于将一个 指定目录下的java文件对象存储到集合中

 */

public static void fileToSet(File file,Set<File> set){

  for(File f : file.listFiles()){

    if(!f.isHidden()&&f.isDirectory()){

      fileToSet(f,set);

    }else{

      if(f.getAbsolutePath().endsWith(".java")){

        set.add(f);

      }

    }

  }

}

 

 

Properties集合是hashtable的子类

  也就是说它具备map集合的特点,而且它里面存储的键值对都是字符串,不需要泛型

  是集合中和IO技术相结合的集合容器

  该对象的特点:可以用于键值对形式的配置文件

  那么在加载数据时,需要数据有固定格式:键=值。

 

  常用方法:

    String getProperty(String key);用指定的键在此属性列表中个搜索属性

    Void list(PrintStream out)将属性列表输出到指定的输出流

    Void load(InputStream inStream)从输入流中读取属性列表(键和元素对)

    Void load(Reader reader)按简单的面向行的格式从输入字符流中读取属性列表(键值对)

    Set<String> stringPropertyNames()返回此属性列表中的键集,其中该键及其对应值是字符串

    Void store(OutputStream out,String comments)以适合使用load(InputStream)方法加载到Properties表中的格式,将此Properties表中的属性类表写入输出流。

 

小练习:

/**

 * 记录应用程序运行的次数

 * 如果使用次数已到,给出注册信息

 * 思路:

 *   1、在配置文件中定义一个计数器,

 *   2、程序每次运行都会先读取该配置文件,将计数器值自增1,在写入配置文件中

 * @author xiuyuan

 *

 */

public class PropertiesTest {

  public static void main(String[] args) {

    int count = softwareCount();

    if(count==-1){
  
      System.out.println("亲,您已经体验5次咯,是不是考虑注册啦");

    }else{

      System.out.println("该软件运行了"+count+"次。");

    }

}

 

/**

 * 该方法用于统计软件运行的次数

 */

public static int softwareCount(){

  FileReader fr = null;

  FileWriter fw = null;

  try {

    //封装配置文件

    File file = new File("E:\\count.properties");

 
    //判断配置文件是否存在

    if(!file.exists()){

      file.createNewFile();

    }

 
    //关联配置文件

    fr = new FileReader(file);
 

    //创建集合存储处理配置信息

    Properties prop = new Properties();

 
    //读取配置文件数据到集合中,将计数器值加1,

    int count = 0;

    prop.load(fr);

    String value = prop.getProperty("count");

 

    //需要进行判断,因为如果是第一次使用,那么配置文件是不存在的,此时获取的value即为null

    if(value!=null){

      count = Integer.parseInt(value);

    }

    count++;

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

 

    //进行运行次数判断,如果次数已到,结束程序,并不将信息写入配置文件

    if(count>5){

      return -1;

    }

 

    //将修改后的数据写入配置文件

    fw = new FileWriter(file);

    prop.store(fw, null);

 

    //将计算得到的软件运行次数返回

    return count;

  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      if(fr!=null){

        fr.close();
  
      }

    } catch (IOException e2) {
  
      throw new RuntimeException(e2.toString());

    }

    try {

      if(fw!=null){

        fw.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

  }

}

}

 

 

打印流:

  PrintWriter与PrintStream

  PrintStream为其他输出流添加了功能,使它们能够打印

  该流提供了打印方法,可以将各种数据类型的数据都原样打印。

 

字节打印流:PrintStream

  可以直接操作文件。

  凡是能和文件相关的流对象都是比较重要的流对象

  构造函数可以接收的参数类型:

    1、file对象 File

    2、字符串路径:String

    3、字节输出流:OutputStream

 

字符打印流:PrintWriter

  构造函数可以接收的参数类型:

    1、file对象 File

    2、字符串路径:String

    3、字节输出流:OutputStream

    4、字符输出流:Writer

 

打印流练习:

/**

 * 该方法用于将键盘录入的信息写入到文本文件中

 */

public static void toFile(){

  BufferedReader bufr = null;

  PrintWriter pw = null;

  try {

    bufr = new BufferedReader(new InputStreamReader(System.in));

    //此处不直接用文件路径的原因是:只有操作流时才能给自动刷新

    pw = new PrintWriter(new FileWriter("E:\\a.txt"),true);

 
    String line = null;

    while((line = bufr.readLine())!=null){

      if("over".equals(line)){

        break;

      }

      pw.println(line);

    }

  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      //判断输入流有没有开启

      if(bufr!=null){

        bufr.close();

      }

      if(pw != null){

        pw.close();

      }

    } catch (IOException e2) {
      throw new RuntimeException(e2.toString());

    }

  }

}

 

 

合并流:

  SequenceInputStream类

  SequenceInputStream表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

 

练习:合并文本文件

合并文件的原理,采用了序列流,多个输入流对应着一个输出流

/**

 * 该方法用于将合并文本文件

 */

public static void sequenceFile(){

  SequenceInputStream sis = null;

  PrintWriter pw = null;

  try {

    //创建Vector集合,用于存储流对象

    Vector<FileInputStream> v = new Vector<FileInputStream>();

 

    //将输入流对象添加到集合中

    v.add(new FileInputStream("e:\\1.txt"));

    v.add(new FileInputStream("e:\\2.txt"));

    v.add(new FileInputStream("e:\\3.txt"));

 

    Enumeration<FileInputStream> en = v.elements();

    sis = new SequenceInputStream(en);

 

    pw = new PrintWriter(new FileWriter("e:\\4.txt"),true);

 

    byte[] buf = new byte[1024];

    int len = 0;

    while((len=sis.read(buf))!=-1){

      pw.println(new String(buf,0,len));
  
    }

  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      if(pw!=null){

        pw.close();

      }

      if(sis!=null){

        sis.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

  }

}

 

 

练习:文件的切割程序

注意点,可以指定依次切割的大小,但不要太大,太大容易造成内存溢出。切割的原理是一个输入流对应着多个输出流

代码:

/**

 * 该方法用于切割文件

 */

public static void split(){

  BufferedInputStream bufis = null;

  BufferedOutputStream bufos = null;

  try {

    bufis = new BufferedInputStream(new FileInputStream("F:\\1.mp3"));

 
    byte[] buf = new byte[1048576];

    int len = 0;

    int count=1;

    while((len = bufis.read(buf))!=-1){

      bufos = new BufferedOutputStream(new FileOutputStream("F:\\split\\"+(count++)+".part"));

      bufos.write(buf, 0, len);

      bufos.close();

    }

  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      if(bufis!=null){

        bufis.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

    try {

      if(bufos!=null){

        bufos.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

  }

}

 

 

操作对象的流:

  ObjectInputStream与ObjectOutputStream

  被操作的对象需要实现Serializable(标记接口)

  可以直接操作对象的流,是字节流

  通过流的方式将对象存储到硬盘上,就是对象的持久化存储

 

  ObjectOutputStream类

    ObjectOutputStream将Java对象的基本数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。通过在流中使用文件可以实现对象的持久化存储。

    该类可以直接操作基本数据类型

 

 

    Write(int val)是继承自父类的方法,只将int类型的最低八位写入目的地

    WriterInt(int val)可以将int类型四个字节的数据都写入目的地。

    writerObject(Object obj)将指定的对象写入ObjectOutputStream

 

  只有通过实现Serializable接口的类才可以启动其序列化功能。未实现此接口的类无法使其任何状态序列化或反序列化。

 

  可序列化类的所有子类型本身都是可序列化的。

  没有方法的接口通常称为标记接口

  序列化的ID是通过成员变量计算出来的,不要使用java自动生成的序列号。因为读取时可能会修改了该对象,这时序列号不对应会出现读取错误,所以要自定义序列号

  静态成员是不可以被序列化的。

 

  如果不想让成员被序列化,而也不希望成员静态,此时可以用transient关键字修饰该成员。

 

  ObjectInputStream与ObjectOutputStream是成对存在的,用ObjectOutputStream生成的对象文件,必须用ObjectInputStream才能读取。

 

/**

 * 该方法用于读取存储在硬盘上的对象

 */

public static void readerObj(){

  ObjectInputStream ois = null;

  try {

    ois = new ObjectInputStream(new FileInputStream("F:\\Person.object"));

    System.out.println(ois.readObject());

  } catch (Exception e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      if(ois!=null){

        ois.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

  }

}

 

 

/**

 * 该方法用于将对象存储到硬盘上

 */

public static void writeObj(){

  ObjectOutputStream oos = null;

  try {

    oos = new ObjectOutputStream(new FileOutputStream("F:\\Person.object"));

    oos.writeObject(new Person("小强",23));

  } catch (IOException e) {

    throw new RuntimeException(e.toString());

  } finally {

    try {

      if(oos!=null){

        oos.close();

      }

    } catch (IOException e2) {

      throw new RuntimeException(e2.toString());

    }

  }

}

 

 

 

  

字符编码

  编码:字符串变成字节数组

    String-->byte[]: str.getBytes(charsetName)

    将字符串转成字节数组,可以指定编码表进行编码;

    charsetName为指定的编码表

 

  解码:字节数组变成字符串

    Byte[] -->String:new String(byte[],charsetName)

    将字节数组转成字符串,指定编码表

 

  服务器中默认的编码表通常都是iso8859-1,该码表不识别中文,此时,用jbk编码的文件访问时,会解码出乱码。

  该种情况的解决方案:用iso8859-1在此对解码出来的结果编码,然后再用指定码表进行解码

  有一种特殊情况:如果服务器中用的utf-8码表解码的,此时上述方案会出现错误,在再次编码时会编码错误,会造成错误的原因是:jbk和utf-8都能够识别中文

 

联通的问题:解码会出现问题