JavaIO流04

13.流的操作规律

流的操作规律
IO流中对象很多,解决问题(处理设备上的数据时)到底该用哪个对象呢? 
把IO流进行了规律的总结(四个明确):
明确一:要操作的数据是数据源还是数据目的。
源:InputStream Reader
目的:OutputStream Writer
先根据需求明确要读,还是要写。
明确二:要操作的设备上的数据是字节还是文本呢?
源:
 字节:InputStream
 文本:Reader
目的:
 字节:OutputStream
 文本:Writer
已经明确到了具体的体系上。
明确三:明确数据所在的具体设备。
源设备:
 硬盘:文件 File开头。
 内存:数组,字符串。
 键盘:System.in;
 网络:Socket
目的设备:
 硬盘:文件 File开头。
 内存:数组,字符串。
 屏幕:System.out
 网络:Socket
完全可以明确具体要使用哪个流对象。
明确四:是否需要额外功能呢?
额外功能:
 转换吗?转换流。InputStreamReader OutputStreamWriter
 高效吗?缓冲区对象。BufferedXXX

14.文件切割

文件切割思路
目前我们学习了IO中的常用对象,接下来使用这些对象进行应用。
需求:文件切割,将一个比较大的文件切割成多个碎片文件。文件切割有2中方式。
第一种:指定具体切割成多少文件。
第二种:指定每个碎片的大小,直到把文件切割完成。
切割文件的应用场景:比如有些论坛指定上传的文件有大小限制。一个比较大的文件无法上传,这时就可以将其切割后上传,同时再别人下载后再将文件合并即可应用。
思路:

  1. 读取源文件,将源文件的数据分别复制到多个文件中。
  2. 切割方式有两种:按照碎片个数切,要么按照指定大小切。
  3. 一个输入流对应多个输出流。
  4. 每一个碎片都需要编号,顺序不要错。
  5. 将源文件以及切割的一些信息也保存起来随着碎片文件一起发送。
    切割文件信息:

1.源文件的名称(文件类型)
2.切割的碎片的个数。

将这些信息单独封装到一个文件中。还要一个输出流完成此动作。
文件切割代码体现

public class SplitFileTest {
   private static final int BUFFER_SIZE = 1048576;// 1024*1024
   private static final String LINE_SEPARATOR = System.getProperty("line.separator");
   public static void main(String[] args) throws IOException {
       File srcFile = new File("E:\\1.mp3");
       File partsDir = new File("E:\\PartFiles");
       splitFile(srcFile, partsDir);
   }
   /**
    * 切割文件。
    */
   public static void splitFile(File srcFile, File partsDir)
          throws IOException {
       // 健壮性的判断。
       if (!(srcFile.exists() && srcFile.isFile())) {
          throw new RuntimeException("源文件不是正确的文件或者不存在");
       }
       if (!partsDir.exists()) {
          partsDir.mkdirs();
       }
       // 1,使用字节流读取流和源文件关联。
       FileInputStream fis = new FileInputStream(srcFile);
       // 2,明确目的。目的输出流有多个,只创建引用。
       FileOutputStream fos = null;
       // 3,定义缓冲区。1M.
       byte[] buf = new byte[BUFFER_SIZE];// 1M
       // 4,频繁读写操作。
       int len = 0;
       int count = 1;// 碎片文件的编号。
       while ((len = fis.read(buf)) != -1) {
          // 创建输出流对象。只要满足了缓冲区大小,碎片数据确定,直接往碎片文件中写数据 。
          // 碎片文件存储到partsDir中,名称为编号+part扩展名。
          fos = new FileOutputStream(new File(partsDir, (count++) + ".part"));
          // 将缓冲区中的数据写入到碎片文件中。
          fos.write(buf, 0, len);
          // 直接关闭输出流。
          fos.close();
       }
       /*
        * 将源文件以及切割的一些信息也保存起来随着碎片文件一起发送。
        */
       String filename = srcFile.getName();
       int partCount = count;
       // 创建一个输出流。
       fos = new FileOutputStream(new File(partsDir, count + ".properties"));
       fos.write(("filename=" + filename + LINE_SEPARATOR).getBytes());
       fos.write(("partcount=" + Integer.toString(partCount)).getBytes());
       fos.close();
       fis.close();
   }
}

读取配置文件信息

public class ReaderPartConfigDemo {
   public static void main(String[] args) throws IOException {
       //解析partConfig文件中的信息。
     
       File configFile = new File("E:\\PartFiles\\7.part");
       readPathConfig(configFile);
   }
   public static void readPathConfig(File configFile) throws IOException {
       /*
        * 配置文件规律,只要读取一行文本,按照 = 对文本进行切割即可。
        */
       BufferedReader bufr = new BufferedReader(new FileReader(configFile));
     
       String line = null;
      while((line=bufr.readLine())!=null){
          String[] arr = line.split("=");
       System.out.println(arr[0]+":::::"+arr[1]);
       }
       bufr.close();
   }
}

上述文件切割在保存和读取关于切割信息文件时,十分的繁琐。在Java中当需要保存和读取具备一定关联关系的数据时,可以使用Java提供的一个对象Properties。

15.Properties类介绍

Properties的基本功能
Properties
特点:

  1. Hashtable的子类,map集合中的方法都可以用。
  2. 该集合没有泛型。键值都是字符串。
  3. 它是一个可以持久化的属性集。键值可以存储到集合中,也可以存储到持久化的设备上。键值的来源也可以是持久化的设备。
  4. 有和流技术相结合的方法。load(InputStream) load(Reader)
    store(OutputStream,commonts); stroe(Writer,comments);
public static void methodDemo() {
       // Properties的基本存和取。
       // 1,创建一个Properties
       Properties prop = new Properties();
       prop.setProperty("zhangsan", "20");
       prop.setProperty("lisi", "23");
       prop.setProperty("wangwu", "21");
       prop.list(System.out);// 用于调试。少用!
       Set<String> set = prop.stringPropertyNames();
       for (String name : set) {
          String value = prop.getProperty(name);
          System.out.println(name + "...." + value);
       }
    }

将配置文件中的数据存储到文件中

public static void methodDemo2() throws IOException {
       Properties prop = new Properties();
       prop.setProperty("zhangsan", "20");
       prop.setProperty("lisi", "23");
       prop.setProperty("wangwu", "21");
       // 将集合中的数据持久化存储到设备上。
       // 需要输出流对象。
       FileOutputStream fos = new FileOutputStream("tempfile\\info.properties");
       // 使用prop的store方法。
       prop.store(fos, "my demo ,person info");
       fos.close();
   }

读取配置文件中的数据,同时更新数据,并保存

public static void methodDemo3() throws IOException {
       File configFile = new File("tempfile\\info.properties");
       // 读取流中的数据。
       Properties prop = new Properties();
       // 定义读取流和数据文件关联。
       FileInputStream fis = new FileInputStream(configFile);
       prop.load(fis);
       prop.setProperty("zhangsan", "12");
       // 要将改完的数据重新持久化。
       FileOutputStream fos = new FileOutputStream(configFile);
       prop.store(fos, "");
       fos.close();
       fis.close();
   }

16.文件合并

记录运行次数
需求:定义一个功能,记录程序运行的次数。满足5次后,给出提示,试用次数已到,请注册!
思路:

  1. 需要计数器。
  2. 计数器的值生命周期要比应用程序的周期要长,需要对计数器的值进行持久化。count=1,里面存储的应该是键值方式,map集合,要和设备上的数据关联,需要IO技术。集合+IO =Properties。
public class Test {
   public static void main(String[] args) throws IOException {
       boolean b = checkCount();
       if(b)
          run();
   }
   public static boolean checkCount() throws IOException {
       boolean isRun = true;
       //1,将配置文件封装成File对象。因为要判断文件是否存在。
       File configFile = new File("tempfile\\count.properties");
       if(!configFile.exists()){//如果不存在,就创建。
          configFile.createNewFile();
       }
       int count = 0;//记录住每次存储的次数。
       Properties prop = new Properties();//用于存储配置文件中的数据。
       //2,定义流对象。
       FileInputStream fis = new FileInputStream(configFile);
       //3,将流中的数据加载到集合中。
       prop.load(fis);
       //4,获取键对应的次数。
       String value = prop.getProperty("count");
       if(value!=null){
          count = Integer.parseInt(value);
          if(count>=5){
             System.out.println("试用次数已到,请注册,给钱!");
             isRun = false;
          }
       }
       count++;//对取出的次数进行自增。
       //将键count,和自增后值重新存储到集合中。
       prop.setProperty("count", Integer.toString(count));
       //将集合中的数据存储到配置文件中。
       FileOutputStream fos = new FileOutputStream(configFile);
       prop.store(fos, "");
     
       fos.close();
       fis.close();
       return isRun;
   }
   public static void run(){
       System.out.println("软件运行");
   }
}

SequenceInputStream序列流
SequenceInputStream序列流:
序列流特点:流对象的有序的排列。
序列流解决问题:将多个输入流合并成一个输入流。将多个源合并成一个源。对于多个源的操作会变的简单。
序列流功能:特殊之处在构造函数上。一初始化就合并了多个流进来。
使用场景:对多个文件进行数据的合并。多个源对应一个目的。

public class SequenceInputStreamDemo {
   public static void main(String[] args) throws IOException {
       /*
        * 演示序列流。SequenceInputStream。
        */
       //如何获取一个Enumeration呢?Vector有,但是效率低,使用ArrayList。
       ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
       //添加三个输入流对象,和指定的具体文件关联。
       for(int x=1; x<=3; x++){
          al.add(new FileInputStream("tempfile\\"+x+".txt"));
       }
       //怎么通过ArrayList获取枚举接口。可以使用Collections工具类中的方法。
       Enumeration<FileInputStream> en = Collections.enumeration(al);
       //创建序列流对象。需要传递Enumeration。
       SequenceInputStream sis = new SequenceInputStream(en);
       //创建目录。文件。
       FileOutputStream fos = new FileOutputStream("tempfile\\4.txt");
       //频繁的读写操作。
       //1,创建缓冲区。
       byte[] buf = new byte[1024];
       int len = 0;
       while((len=sis.read(buf))!=-1){
          fos.write(buf,0,len);
       }
       //关闭流
       fos.close();
       sis.close();
   }
}

文件合并使用SequenceInputStream
对一个文件进行切割(一个源对应多个目的),切成碎片,在将碎片进行合并成原来的文件。

public class MergerFileTest3 {
   public static void main(String[] args) throws IOException {
     
       File partsDir = new File("E:\\PartFiles");
       mergerFile(partsDir);
   }
   public static void mergerFile(File partsDir) throws IOException {
     
       /*
        * 合并问题如下:
        * 1,如何明确碎片的个数,来确定循环的次数,以明确要有多少个输入流对象。
        * 2,如何知道合并的文件的类型。
        * 解决方案:应该先读取配置文件。
        */
       //1,获取配置文件。
       File configFile = getConfigFile(partsDir);
       //2,获取配置文件信息容器。获取配置信息的属性集。
       Properties prop = getProperties(configFile);
       //3,将属性集对象传递合并方法中。
       merge(partsDir,prop);
   }
   //根据配置文件获取配置信息属性集。
   private static Properties getProperties(File configFile) throws IOException {
       FileInputStream fis = null;
       Properties prop = new Properties();
       try{
          //读取流和配置文件相关联。
          fis = new FileInputStream(configFile);
          //将流中的数据加载的集合中。
          prop.load(fis);
       }finally{
          if(fis!=null){
             try{
             fis.close();
             }catch(IOException e){
                //写日志,记录异常信息。便于维护。
             }
          }
       }
       return prop;
   }
   //根据碎片目录获取配置文件对象。
   private static File getConfigFile(File partsDir) {
       if(!(partsDir.exists() &&partsDir.isDirectory())){
          throw new RuntimeException(partsDir.toString()+",不是有效目录");
       }
       //1,判断碎片文件目录中是否存在properties文件。使用过滤器完成。
       File[] files = partsDir.listFiles(new FileFilter() {
          public boolean accept(File pathname) {
             return pathname.getName().endsWith(".properties");
          }
       });
       if(files.length!=1){
          throw new RuntimeException("properties扩展名的文件不存在,或不唯一");
       }
       File configFile = files[0];
       return configFile;
   }
   private static void merge(File partsDir,Properties prop) throws   IOException {
       //获取属性集中的信息。
        String filename = prop.getProperty("filename");
        int partCount = Integer.parseInt(prop.getProperty("partcount"));
       //使用io包中的SequenceInputStream,对碎片文件进行合并,将多个读取流合并成一个读取
流。
       List<FileInputStream> list = new ArrayList<FileInputStream>();
     
       for (int i = 1; i < partCount; i++) {
          list.add(new FileInputStream(new File(partsDir, i + ".part")));
       }
       //怎么获取枚举对象呢?List自身是无法获取枚举Enumeration对象的,考虑到Collections中去
找。
       Enumeration<FileInputStream> en = Collections.enumeration(list);
       //源。
       SequenceInputStream sis = new SequenceInputStream(en);
       //目的。
       FileOutputStream fos = new FileOutputStream(new File(partsDir,filename));
       //不断的读写。
       byte[] buf = new byte[4096];
       int len = 0;
       while((len=sis.read(buf))!=-1){
          fos.write(buf,0,len);
       }
       fos.close();
       sis.close();
   }
}

17.其他功能流介绍

对象序列化流
用于操作对象的流对象。对象的序列化。ObjectOutputStream
特点:用于操作对象。
解决问题:可以将对象进行序列化和反序列化。
注意:对象序列化一定要实现Serializable接口。为了给类定义一个serialVersionUID。
功能:ObjectInputStream readObject() ObjectOutputStream writeObject()
关键字:瞬态transient

public class ObjectStreamDemo {
   public static void main(String[] args) throws IOException, ClassNotFoundException {
       /*
        * 将一个对象存储到持久化(硬盘)的设备上。
        */
       writeObj();//对象的序列化。
   }
   public static void writeObj() throws IOException {
       //1,明确存储对象的文件。
       FileOutputStream fos = new FileOutputStream("tempfile\\obj.object");
       //2,给操作文件对象加入写入对象功能。
       ObjectOutputStream oos = new ObjectOutputStream(fos);
       //3,调用了写入对象的方法。
       oos.writeObject(new Person("wangcai",20));
       //关闭资源。
       oos.close();
   }
}

对象反序列化流
当把一个对象持久化存储起来之后,需要使用反序列化技术获取存储起来的对象使用此ObjectInputStream对象就可以完成反序列化动作

public class ObjectStreamDemo {
   public static void main(String[] args) throws IOException, ClassNotFoundException {
       readObj();//对象的反序列化。
   }
   public static void readObj() throws IOException, ClassNotFoundException {
     
       //1,定义流对象关联存储了对象文件。
       FileInputStream fis = new FileInputStream("tempfile\\obj.object");
     
       //2,建立用于读取对象的功能对象。
       ObjectInputStream ois = new ObjectInputStream(fis);
     
       Person obj = (Person)ois.readObject();
     
       System.out.println(obj.toString());
     
   }
}

序列化接口
当一个对象要能被序列化,这个对象所属的类必须实现Serializable接口。否则会发生异常NotSerializableException异常。
同时当反序列化对象时,如果对象所属的class文件在序列化之后进行的修改,那么进行反序列化也会发生异常InvalidClassException。发生这个异常的原因如下:
该类的序列版本号与从流中读取的类描述符的版本号不匹配
该类包含未知数据类型
该类没有可访问的无参数构造方法

Serializable标记接口。该接口给需要序列化的类,提供了一个序列版本号。serialVersionUID.该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Person implements Serializable {
   //给类显示声明一个序列版本号。
   private static final long serialVersionUID = 1L;
   private String name;
   private int age;
   public Person() {
       super();
     
   }
   public Person(String name, int age) {
       super();
       this.name = name;
       this.age = age;
   }
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public int getAge() {
       return age;
   }
   public void setAge(int age) {
       this.age = age;
   }
   @Override
   public String toString() {
       return "Person [name=" + name + ", age=" + age + "]";
   }
}

瞬态关键字
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰。只要被transient修饰了,序列化时这个属性就不会琲序列化了。
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化。

public class Person implements Serializable {
   /*
    * 给类显示声明一个序列版本号。
    */
   private static final long serialVersionUID = 1L;
   private static String name;
   private transient/*瞬态*/ int age;
 
   public Person() {
      super();
    
   }
 
   public Person(String name, int age) {
      super();
      this.name = name;
      this.age = age;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public int getAge() {
      return age;
   }
   public void setAge(int age) {
      this.age = age;
   }
   @Override
   public String toString() {
      return "Person [name=" + name + ", age=" + age + "]";
   }
}

17.其他功能流

打印流
PrintStream (字节流) PrintWriter(字符流)
打印流特点:打印。不抛异常。
PrintStream 打印的目的:File对象,字符串路径,字节输出流。
打印流解决问题:方便地打印各种数据值表示形式。它的打印方法可以保证数值的表现形式不变。写的是什么样子,目的就是什么样子。

public class PrintStreamDemo {
   public static void main(String[] args) throws IOException {
       File dir = new File("tempfile");
       if(!dir.exists()){
          dir.mkdir();
       }
       //演示PrintStream的特有方法。
       //1,创建PrintStream对象。目的就定为文件。
       PrintStream out = new PrintStream("tempfile\\print2.txt");
     
       //将数据打印到文件中。
       //out.write(353);//字节流的write方法一次只写出一个字节也就是将一个整数的最低8位写
出。
      //out.write("353".getBytes());//麻烦。
       out.print(97);//保证数值的表现形式。其实原理就是将数值转成字符串。
       out.close();
   }
}

PrintWriter:一样具备打印功能。
PrintWriter打印的目的:File对象,字符串路径,字节输出流,字符输出流。

public class PrintWriterDemo {
   public static void main(String[] args) throws IOException {
       //演示一个小例子。 读取键盘录入。将数据转成大写显示在屏幕上。
       // 1,键盘录入。
       BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
       //2,定义目的。
       //BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)));
       PrintWriter pw = new PrintWriter(System.out,true);//对println方法可以实现自动刷新。
       //改变目的为文件。还想自动刷新。
       pw = new PrintWriter(new BufferedWriter(new FileWriter("tempfile\\1.txt")),true);
       //3,读一行写一行。键盘录入一定要定义结束标记。
       String line = null;
      while((line=bufr.readLine())!=null){
          if("over".equals(line)){
             break;
          }
          pw.println(line.toUpperCase());
//        pw.flush();
       }
       pw.close();
//     bufr.close();//不需要关闭键盘录入这种标准输入流。一旦关闭后面获取不到。
   }
}

管道流
特点:读取管道和写入管道可以连接。需要使用多线程技术。单线程容易死锁。
功能:connect()

public class PipedStreamDemo {
   public static void main(String[] args) throws IOException {
       //创建管道对象。
       PipedInputStream pis = new PipedInputStream();
       PipedOutputStream pos = new PipedOutputStream();
       //将两个流连接上。
       pis.connect(pos);
       new Thread(new Input(pis)).start();
       new Thread(new Output(pos)).start();
   }
}
//定义输入任务。
class Input implements Runnable{
   private PipedInputStream pis;
   public Input(PipedInputStream pis) {
       super();
       this.pis = pis;
   }
   @Override
   public void run() {
     
       byte[] buf = new byte[1024];
       int len;
       try {
          len = pis.read(buf);
          String str = new String(buf,0,len);
          System.out.println(str);
        
          pis.close();
       } catch (IOException e) {
          e.printStackTrace();
       }
   }
}
//定义输出任务。
class Output implements Runnable{
   private PipedOutputStream pos;
   public Output(PipedOutputStream pos) {
       super();
       this.pos = pos;
   }
   @Override
   public void run() {
       //通过write写方法完成。
       try {
          pos.write("hi,管道来了!".getBytes());
          pos.close();
       } catch (IOException e) {
          e.printStackTrace();
       }
   }
}

随机访问流
RandomAccessFile对象是用于随机访问文件。
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入
隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

特点:只能操作文件。既能读,又能写。维护了一个byte数组。内部定义了字节流的读取和写入。通过对指针的操作可以实现对文件的任意位置的读取和写入。

功能:getFilePointer seek用于操作文件指针的方法。

public class RandomAccessFileDemo {
   public static void main(String[] args) throws IOException {
       /*
        * RandomAccessFile:
        * 特点:
        * 1,只能操作文件。
        * 2,既能读,又能写。
        * 3,维护了一个byte数组。内部定义了字节流的读取和写入。
        * 4,通过对指针的操作可以实现对文件的任意位置的读取和写入。
        */
     
       writeFile();
       readFile();
   }
   public static void readFile() throws IOException {
       RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "r");
       //随机读取,只要通过设置指针的位置即可。
       raf.seek(8*1);
       byte[] buf = new byte[4];
       raf.read(buf);
       String name = new String(buf);
       int age = raf.readInt();
       System.out.println(name+":"+age);
       raf.close();
   }
   public static void writeFile() throws IOException {
       //1,创建一个随机访问文件的对象。文件不存在,则创建,存在,则不创建不覆盖。
       RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "rw");
       //2,写入姓名和年龄。
       raf.write("张三".getBytes());
       raf.writeInt(97);//保证整数的字节原样性。
       raf.write("李四".getBytes());
       raf.writeInt(99);//保证整数的字节原样性。
       //3,随机写入。
       raf.seek(8);//设置指针的位置。
       raf.write("王五".getBytes());
       raf.writeInt(100);
   System.out.println(raf.getFilePointer());
       raf.close();
   }
}

基本类型数据流
操作基本数据值的对象。
DataInputStream DataOutputStream
用于操作基本数据类型值。write基本类型 read基本类型。

public class DataStreamDemo {
   public static void main(String[] args) throws IOException {
       writeData();
       readData();
   }
   public static void readData() throws IOException {
       FileInputStream fis = new FileInputStream("tempfile\\data.txt");
       DataInputStream dis = new DataInputStream(fis);
       boolean  b = dis.readBoolean();
       System.out.println(b);
       dis.close();
   }
   public static void writeData() throws IOException {
       //写入一些基本数据值。存储到文件。
       FileOutputStream  fos = new FileOutputStream("tempfile\\data.txt");
       DataOutputStream dos = new DataOutputStream(fos);
       dos.writeBoolean(true);
       dos.close();
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值