5.2 IO流(File类,Propertis配置文件,其他类(打印流,序列流,操作对象的流(序列化接口),随机访问文件的流,管道流,操作基本数据的流,操作数组的流,操作字符串的流),编码表)

1.File类

IO流的流对象只能操作设备上的数据。

File类:

1.用来将文件或者文件夹(也称目录)封装成对象。
2.方便对文件和文件夹的属性信息进行操作。(操作文件夹,文件的属性(创建时间,修改时间,只读,只写等)等)
3.File对象可以作为参数传递给流的构造函数。(这种用的较多)
    如:  FileReader(File file) 
            在给定从中读取数据的 File 的情况下创建一个新 FileReader。

4.File类的常用方法:
①构造方法:
    1.File(String pathname) 
          通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。

    示例:
    //可以将一个已存在的,或者不存在的文件或者目录封装成file对象。
    File file = new File("a.txt");
    File file = new File("C:\\a.txt");

    2.File(String parent, String child) 
          根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

    示例:
    File file = new File("C:\\", "a.txt");

    3.File(File parent, String child) 
          根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。

    示例:
    File f = new File("C:\\");
    File f1 = new File(f, "a.txt")


②字段:
    1.separatorChar
        public static final char separatorChar
    与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 '/';在 Microsoft Windows 系统上,它为 '\\'。  

    separator
        public static final String separator
    与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar。 

    跨平台:
    File file = new File("C:"+System.getProperty("file.separator") + "a.txt");
    File file = new File("C:"+ File.separator + "a.txt");

    2.pathSeparatorChar
        public static final char pathSeparatorChar
    与系统有关的路径分隔符。此字段被初始为包含系统属性 path.separator 值的第一个字符。此字符用于分隔以路径列表 形式给定的文件序列中的文件名。在 UNIX 系统上,此字段为 ':';在 Microsoft Windows 系统上,它为 ';'。 

        pathSeparator
            public static final String pathSeparator
        与系统有关的路径分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 pathSeparatorChar。 


③常见方法:

/*
1.获取:
    1.1获取文件名称
        String getName() 
                    返回由此抽象路径名表示的文件或目录的名称。 

    1.2获取文件路径
        String getPath()  //给什么路径获得什么路径
                将此抽象路径名转换为一个路径名字符串。 
            String getAbsolutePath() 
                返回此抽象路径名的绝对路径名字符串。 
            File getAbsoluteFile()   //获得绝对路径的File对象
                返回此抽象路径名的绝对路径名形式。 

    1.3获取文件大小
        long length() 
                返回由此抽象路径名表示的文件的长度。 

    1.4获取文件的修改时间
        long lastModified() 
                返回此抽象路径名表示的文件最后一次被修改的时间。 

2.创建与删除:
        2.1创建
             boolean createNewFile() 
                            当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 
            static File createTempFile(String prefix, String suffix) 
                            在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。 
            static File createTempFile(String prefix, String suffix, File directory) 
                            在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 

            文件夹的创建:
                boolean mkdir() 
                            创建此抽象路径名指定的目录。 
                boolean mkdirs()    //delete删除时,删除的是最后一个文件夹
                            创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 

        2.2删除
            boolean delete() 
                    删除此抽象路径名表示的文件或目录。 
                            ①如果文件正在被流操作,文件删除不了。
                            ②如果目录内有内容,目录删不了。
                            ③删除了不经过回收站。
                void deleteOnExit()     //告诉虚拟机在退出时删除
                    在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 

    3.判断:
            存在: boolean exists() 
                                测试此抽象路径名表示的文件或目录是否存在。 
                绝对路径: boolean isAbsolute() 
                                测试此抽象路径名是否为绝对路径名。 
                目录:boolean isDirectory() 
                                测试此抽象路径名表示的文件是否是一个目录。 
                文件:boolean isFile() 
                                测试此抽象路径名表示的文件是否是一个标准文件。                 
                隐藏:boolean isHidden()   //系统级隐藏文件的判断方式不一样,这里不考虑过滤系统级隐藏文件
                                测试此抽象路径名指定的文件是否是一个隐藏文件。                 
                可执行:boolean canExecute() 
                                测试应用程序是否可以执行此抽象路径名表示的文件。                
                可读: boolean canRead() 
                                测试应用程序是否可以读取此抽象路径名表示的文件。 
                可写:boolean canWrite() 
                                测试应用程序是否可以修改此抽象路径名表示的文件。 

        4.重命名:boolean renameTo(File dest)    //如果被重命名文件不存在,重命名失败,返回false
                                    重新命名此抽象路径名表示的文件。                                            
                        File dest的作用:确定重命名的名字和重命名后文件的位置。
                                                    如果位置变化了,原来的文件会被删除,即剪切的意思。

        5.盘空间操作:        
                文件系统根(盘符):static File[]     listRoots()    //因为盘符名是路径,所以getName获取的为空字符串。
                                                                                            //可以通过getPath获得抽象路径。
                                                    列出可用的文件系统根。 
                分区大小:long getTotalSpace() 
                                        返回此抽象路径名指定的分区大小。
                分区可用空间大小(常用): long getFreeSpace() 
                                        返回此抽象路径名指定的分区中未分配的字节数。
                分区可用于虚拟机的空间大小:long getUsableSpace() 
                                        返回此抽象路径名指定的分区上可用于此虚拟机的字节数。 
         6.获取目录内容:
            注意:
            ①如果File对象里的路径不是目录或者是系统级目录(如各盘的隐藏的系统信息卷标目录等),就是会返回null,然后使用时报空指针异常,数组就没有创建成功。
            ②如果是空目录,则数组存在,但是长度为0,遍历时输出的是数组的地址。

                获取目录下的文件和目录(包括隐藏的):String[] list()  
                                                    返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 
                获取目录下的文件和目录 的 File对象:File[] listFiles()  //可以使用getPath获取抽象路径,通过getName获得文件名称
                                                    返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。

                带过滤器:
                String[] list(FilenameFilter filter)   //文件名过滤器。数组中存放的是getName()的文件名。
                                返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。 
                File[] listFiles(FileFilter filter)     //文件过滤器 。数组中存放的是getpath()的路径。
                                返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 

                File[] listFiles(FilenameFilter filter)  //这个方法可以在文件过滤器中通过getName实现文件名的获取。用第二个方法代替。
                                返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。 
*/  
常见方法示例:

public class FileMethodDemo {

    public static void main(String[] args) throws IOException {


//      getDemo();
//      createAndDeleteDmoe();
//      isDemo();
//      renameToDemo();
//      listRootsDemo();
//      panDemo();
        listDemo();

    }



//获取:

        public static void getDemo() throws IOException{
            File file = new File("D:\\Workspaces\\MyEclipse Professional 2014\\IO\\utf8_1.txt");

            String name = file.getName();  //文件名

            String absPath = file.getAbsolutePath(); //获得所给路径的 绝对路径
//          File absPathFile = file.getAbsoluteFile(); //返回绝对路径的File对象


            String Path = file.getPath();//获得指定的路径(相对或者绝对看指定的是什么路径)
            String parentPath = file.getParent();  //父目录,在给出的是绝对路径时返回父目录路径,否者返回null

            long len = file.length();  //文件大小

            long time = file.lastModified();  //修改时间:作用,通过另一个线程监控判断 修改时间是否改变,从而可以对文件进行自动刷新的操作。

            System.out.println("name:\t\t" + name);
            System.out.println("absPath:\t" + absPath);
            System.out.println("Path:\t\t" + Path);
            System.out.println("parPath:\t" + parentPath);
            System.out.println("len:\t\t" + len);
            System.out.println("time:\t\t" + new SimpleDateFormat("yyyy/MM/dd H:m:s").format(new Date(time)));
        }


//文件和目录 创建和删除:      

        public static void createAndDeleteDmoe() throws IOException{
            File file = new File("abc.txt");

//文件的删除和创建:

            /*
                    和输出流不一样,如果文件不存在,则创建,如果文件存在,则不创建。
                    (当然输出流可以在构造函数中定义追加模式,不覆盖)            
             */
            boolean b = file.createNewFile();  //如果文件存在,就不创建,返回false
            System.out.println("b = " + b);

            boolean c = file.delete(); //文件不存在时,返回false
            System.out.println("c = " + c);

//文件夹的删除和创建:

            File file1 = new File("abc");
            boolean d = file1.mkdir();   //文件夹存在时,返回false
            System.out.println("d = " + d);
            file1.delete();

            File file2 = new File("ABC\\abc");  //创建多级目录
            file2.mkdirs();   //文件目录存在时,返回false
            file2.delete();    //删除时,删除的是最后一个文件夹


        }


//判断:
        public static void isDemo(){
            File f = new File("demo12.txt");

            boolean b = f.exists();  //是否存在,如果文件不存在,下面的全都是flase。
                                                            //所在执行其他判断操作时,应先判断文件是否存在。
            System.out.println("b = "+ b);

            boolean c = f.isAbsolute();  //是否是绝对路径
            System.out.println("c = "+ c);

            boolean d = f.isDirectory();  //是否是目录
            System.out.println("d = "+ d);

            boolean e = f.isFile();  //是否是文件
            System.out.println("e = "+ e);

            boolean a = f.isHidden();  //是否隐藏
            System.out.println("a = "+ a);

            boolean  b1= f.canExecute();  //是否能执行
            System.out.println("b1 = "+ b1);

            boolean b2 = f.canRead();  //是否可读
            System.out.println("b2 = "+ b2);

            boolean b3 = f.canWrite();  //是否可写
            System.out.println("b3 = "+ b3);    

        }

//重命名:
        public static void renameToDemo() throws IOException{
            File f1 = new File("D:\\demo1.mp3");
//          File f2 = new File("D:\\demo2.mp3");

            boolean b = f1.renameTo(new File("D:\\demo2.mp3"));
            System.out.println("b = " + b);

        }


//列出系统根
        public static void listRootsDemo(){
            File[] fileArr = File.listRoots();

            for(File f :fileArr){
                System.out.println(f); 
/*  显示:
                C:\
                D:\
                E:\
                H:\
*/
            }
        }


//盘符空间操作:

        private static void panDemo() {
                File f = new File("D:\\");
                //磁盘空间操作:总大小,剩余可用空间。
                System.out.println("getTotalSpace:"+ new DecimalFormat("0.000").format(f.getTotalSpace()/1024.0/1024.0/1024.0) + "G"); //分区大小
                System.out.println("getFreeSpace:" + new DecimalFormat("0.000").format(f.getFreeSpace()/1024.0/1024.0/1024.0) + "G");//分区可用空间
                System.out.println("getUsableSpace:"+new DecimalFormat("0.000").format(f.getUsableSpace()/1024.0/1024.0/1024.0) + "G");//分区可用于虚拟机的空间大小

        //找出剩余空间最大的盘:数组法比较合理

                //①数组法:

                File[] fileArr = File.listRoots();
                Long[] longArr = new Long[fileArr.length];

                int a = 0;
                for(File f1:fileArr){
                        longArr[a] = f1.getFreeSpace();
                        a++;
                }
                Long[] longArr_copy = Arrays.copyOf(longArr,longArr.length); //排序前先做个副本,用于后面根据最值查原来的角标

                Arrays.sort(longArr);
                System.out.println("剩余空间最大的盘是:" + fileArr[Arrays.binarySearch(longArr_copy, longArr[fileArr.length-1])].toString().charAt(0) 
                        + "盘\t剩余:" + new DecimalFormat("0.000").format(longArr[fileArr.length-1]/1024.0/1024.0/1024.0) + "G");


/*              //②Map集合法:空间容量作为键(虽然做出来了,但容量作为键有些不合理,不能体现唯一性)
                File[] fileArr = File.listRoots();
                Map<Long, File> map = new TreeMap<Long, File>();
                for(File file :fileArr){
                    map.put(file.getFreeSpace(), file);
                }

                Iterator<Long> ite = map.keySet().iterator();
                while(true){
                    Long key =  ite.next();
                if(!ite.hasNext()){
                    String s= new DecimalFormat("0.0").format(key/1024.0/1024.0/1024.0);
                    System.out.println("剩余空间最大的盘是:"+ map.get(key).toString().charAt(0) + "盘     "+ " 剩余  " + s + "G");
                    break;
                    }
                }

                //显示所有的盘符和剩余空间:             
//              Iterator<Long> it = map.keySet().iterator();
                for(Iterator<Long> it = map.keySet().iterator();it.hasNext();){
                        Long key = it.next();
                        File value = map.get(key);
                        String s= new DecimalFormat("0.0").format(key/1024.0/1024.0/1024.0);
                        System.out.println(value.toString().charAt(0) + " :  剩余  " + s + "G");
                }*/
            }


//目录内容获取和过滤:过滤器


        public static void listDemo(){
            File file = new File("D:\\");
//          for(String s: file.list()){
//              if(s.endsWith(".java"))   //手动过滤
//              System.out.println(s);
//          }


            //过滤器:
                    //需求1:只要D:\中.java文件。
/*          for(String s:file.list(new FilterByJava())){
                System.out.println(s);
            }*/

                //需求2:只要D:\中不是隐藏的.txt文件。
                //改进:因为常看隐藏属性,是File对象的功能,所以这里用listFiles(),返回目录列表的Files对象形式。
            for(File f:file.listFiles(new FilterByHidden())){
                System.out.println(f.getName());
            }


/*          for(String s:file.list(new SuffixFilter(".txt"))){    //提供构造方法的专属后缀名过滤器
                System.out.println(s);
            }
            */
        }

}

//过滤器
/*class FilterByJava implements FilenameFilter{  //文件名过滤器

    @Override
    public boolean accept(File dir, String name) {
            return name.endsWith(".java");
    }

}*/

class FilterByHidden implements FileFilter{  //文件过滤器

    @Override
    public boolean accept(File pathname) {
        if(!pathname.isHidden()){    //系统级隐藏文件的判断方式不一样,这里不考虑过滤系统级隐藏文件
            if(pathname.getName().endsWith(".txt"))
                return true;
        }           
        return false;
    }

}


//后缀名过滤器,提供构造函数,免得修改过滤器源码

class SuffixFilter implements FilenameFilter{

    private String suffix;
    public SuffixFilter(String suffix) {
//      super();
        this.suffix = suffix;
    }

    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

2.File练习:
1.深度遍历文件夹:递归

/*
        需求:对指定目录进行所有内容的列出(包含子目录中的内容)
        即深度遍历
*/
public class Demo1 {

    public static void main(String[] args) {
        File file = new File("D:\\A");

        demo1(file,0);
    }

    public static void demo1(File file,int level) {
        System.out.println(getSpace(level) + file.getName());   
        level++;
        for(File f:file.listFiles()){     //默认先读文件,后读文件夹
            if(f.isDirectory()){
                    demo1(f,level);
            }
            else
                System.out.println(getSpace(level)+ f.getName());

        }
    }

    public static String getSpace(int level) {
        StringBuilder sb = new StringBuilder();
        sb.append("|--");
        for(int i = 0;i<level;i++)
            sb.insert(0,"|   ");
        return sb.toString();
    }

}

/*

显示:
|--A
|   |--1.txt
|   |--2.txt
|   |--B
|   |   |--3.txt
|   |   |--4.txt
|   |   |--C
|   |   |   |--5.txt

*/

2.递归:

/*
        递归:函数自身直接或者间接的调用了自身。

        注意:
        1.递归一定要明确条件,否者容易栈溢出。
        2.注意递归的次数。



*/
public class DiGuiDemo {

    public static void main(String[] args) {

        toBin(6);  //二进制
        System.out.println(getSum(1500));  //求和

    }


    public static void toBin(int num) {
        if(num>0){
            System.out.println(num%2);  //可以放下面
            toBin(num/2);
        }
    }

    public static int getSum(int num) {
        if(num == 1)
            return 1;

        return num + getSum(num-1);
    }

}

递归图解:
这里写图片描述

这里写图片描述

3.删除一个带内容的目录:

/*
        删除一个带内容的目录。

        原理:必须从最里面往外删。
        需要进行深度遍历。
 */

public class DeleteDemo {

    public static void main(String[] args) {
        File file = new File("D:\\A");

        deleteDemo(file);   //删除目录


    }

    public static void deleteDemo(File file) {
        for(File f: file.listFiles()){

            if(f.isDirectory()){   //是目录
                deleteDemo(f);  //删目录下的文件
            }
            else
                f.delete();   //删目录下的文件                         
        }
                file.delete(); //从后往前,依次删除掉所有空文件夹。              
    }

}

4.综合练习:文件清单列表:

/*
            获取指定目录下,指定扩展名的文件(包含子目录中的)
            这些文件的绝对路径写入到一个文本文件中。

            就是建立一个指定扩展名的文件的列表。

        思路:
        1.必须进行深度遍历
        2.要在遍历的过程中进行过滤。将符合条件的内容,都存储到容器中。
        3.对容器中的内容进行遍历并将绝对路径写到文件中。

 */

public class Demo2 {

    public static void main(String[] args) throws IOException {

            File file = new File("D:\\coding");   //被过滤的文件

            FilterByName filter = new FilterByName(".java");  //过滤器

            LinkedList<File> list = new LinkedList<File>();  //过滤的File对象集合

            File f = new File(file,".java.txt");   //被写入的文件

            getFiles(file,filter,list);         
            writeToFile(list,f);

    }

    public static void writeToFile(LinkedList<File> list, File f)throws IOException{
        BufferedWriter bufw =null;
        try {
            bufw = new BufferedWriter(new FileWriter(f));
            for(File f1:list){
                bufw.write(f1.getPath());
                bufw.newLine(); 
                bufw.flush();
            }

        }/*catch(IOException e){
            throw new RuntimeException("写入失败");
        }*/finally{
            if(bufw!=null)
                try {
                    bufw.close();
                } catch (IOException e) {

                    throw new RuntimeException("关闭失败");
                }
        }


    }

    public static void getFiles(File file, FilterByName filter,LinkedList<File> list) throws IOException {
        for(File f:file.listFiles(filter)){    //使用过滤器代替
            if(f.isDirectory()){
                getFiles(f,filter,list);
            }
/*          else if(f.getName().endsWith(s)){
                bufw.write(f.getPath());
                bufw.newLine();
                bufw.flush();
            }*/
            else{
                list.add(f);
            }
        }

    }

}

class FilterByName implements FilenameFilter{
    private String suffix;

    public FilterByName(String suffix){
        super();
        this.suffix = suffix;
    }

    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

3 .配置文件设置:Properties集合
Hashtable:内部机构是哈希表,是同步的。不允许null作为键和值。
子类 Properties:用来存储键值对形式的配置文件的信息。可以和IO技术相结合。

API:
public class Propertiesextends Hashtable<Object,Object>
    Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。 


Properties集合:
特点:
1.该集合中的键和值都是字符串类型。
2.集合中的数据可以保存到流中,或者从流中获取。

通过该集合用于操作以键值对形式存在的配置文件。 还有一种,xml也是存储配置文件的。

特有方法:
①存: Object setProperty(String key, String value) 
          调用 Hashtable 的方法 put。 
②取:String getProperty(String key) 
          用指定的键在此属性列表中搜索属性。 

③全取: Set<String> stringPropertyNames() 
          返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。 

④与输出流结合,显示Properties列表:  void list(PrintStream out) 
                              将属性列表输出到指定的输出流。 
⑤持久化存储:
    void store(OutputStream out, String comments)   //comments - 属性列表的描述。 
              以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。 
     void store(Writer writer, String comments) 
              以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 
⑥读取:
    void load(InputStream inStream) 
          从输入流中读取属性列表(键和元素对)。 
     void load(Reader reader) 
          按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 
示例:
public class PropertiesDemo {

    public static void main(String[] args) throws IOException {

//      propertiesDemo();
//      propertiesDemo2();
//      propertiesDemo3();
//      propertiesDemo4();
        loadDemo();
    }



//模拟load()原理:

public static void loadDemo() throws IOException {
        Properties prop = new Properties();
        BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
        String line = null;
        while((line = bufr.readLine())!= null){
            if(!line.startsWith("#")){   //排除注释内容
                String[] arr = line.split("=");
                prop.setProperty(arr[0], arr[1]);

//              System.out.println(arr[0]+"::" +arr[1]);
            }
        }
            prop.list(System.out);
    }




//读取文本  中的配置信息

public static void propertiesDemo4() throws IOException {
    Properties prop = new Properties();
        //集合中的数据来自于一个文件。
        //注意:必须保证该文件中的数据是键值对。
            //需要使用读取流。
        FileInputStream fis = new FileInputStream("info.txt");
        prop.load(fis);  //从流中获取    
        prop.list(System.out);      

    }






//  持久化存储元素

public static void propertiesDemo3() throws IOException {
    Properties prop = new Properties();
    //存储元素
    prop.setProperty("zhang", "30");
    prop.setProperty("wang", "50");
    prop.setProperty("kjd", "34");
    prop.setProperty("sss", "56");

    //持久化存储,需要关联到输出流
    FileOutputStream fos = new FileOutputStream("info.txt");
    prop.store(fos, "name + age");   //键值和注释不要写中文信息。因为此方法有自己特定的输出方式。
    fos.close();

    /*  内容:
    #name + age
    #Wed Nov 01 06:44:02 CST 2017
    kjd=34
    zhang=30
    sew=56
    wang=50
*/
    }





//演示Properties集合和流对象相结合的功能。
    public static void propertiesDemo2(){
        Properties prop = new Properties();
        //存储元素
//      prop.setProperty("zhang", "30");
//      prop.setProperty("wang", "50");
//      prop.setProperty("kjd", "34");
//      prop.setProperty("sew", "56");

        prop =  System.getProperties();
        prop.list(System.out);  //一般显示出来,调试用的,并不能操作里面的值。

    }


    // Properties集合的存和取

    public static void propertiesDemo() {
        Properties prop = new Properties();

        prop.setProperty("zhang", "30");
        prop.setProperty("wang", "50");
        prop.setProperty("kjd", "34");
        prop.setProperty("sew", "56");

        //修改元素
        prop.setProperty("wang", "32");

        //取出所有元素
        Set<String> names = prop.stringPropertyNames();

        for (String name : names) {
            String value = prop.getProperty(name);
            System.out.println(name +": " + value);
        }
    }

}

Properties集合练习:

1.对已有的配置文件中的信息进行修改:

/*
        对已有的配置文件中的信息进行修改。

        读取这个文件。
        并将这个文件的键值数据存储到集合中。
        再通过集合对数据进行修改。
        再通过流将修改后的集合存储到文件中。

 */

    public static void test() throws IOException{
        File file = new File("info.txt");
        if(!file.exists()){
            file.createNewFile();
        }
        FileReader fr = new FileReader(file);
        Properties prop = new Properties();
        prop.load(fr);
        prop.setProperty("sss", "16");

        FileWriter fw = new FileWriter(file);  //和文件相关联,新建一个文件。
                    //必须放在读取原文件结束之后,或者加true追加模式。
        prop.store(fw, "...");

        prop.list(System.out);  //测试

        fr.close();
        fw.close();

    }

2.定义功能,获取一个应用程序运行的次数,如果超过5次,给出使用次数已到,请注册的提示。并不要再运行程序。

/*
        定义定义功能,获取一个应用程序运行的次数,如果超过5次,给出使用次数已到,请注册的提示。并不要再运行程序。

        思路:
        1.应该有计数器。
        每次程序启动都需要计数一次,并且是再原有的次数上进行计数。
        2.计数器就是一个变量。程序启动时进行计数,计数器必须存在于内存并进行运算。
            可是程序一结束,计数器消失了。那么再次启动该程序,计数器又重新被初始化。
            而我们需要多次启动同一个应用程序,使用的是同一个计数器。
            这就需要计数器的生命周期变长,从内存存储到硬盘文件中。

        3.如何使用这个计数器。
                首先,程序启动时,应该先读取这个用于记录计数器信息的配置文件。
                获取上一次计数器次数。并进行使用次数的判断。
                其次,对该次数进行自增,并把自增后的次数重新存储到配置文件中。
        4.文件中的信息进行存储并体现。
                直接存储次数值可以,但是不明确该数据的含义。所以起名字很重要。
                这就有了名字和值的对应,所以可以使用键值对。
                可以使用映射关系map集合搞定,又需要读取硬盘上的数据,所以map + io = Properties。
                如果是更复杂的数据,用xml。

 */
public class Demo3 {

    public static void main(String[] args) throws IOException {

        int i = 5;   
        String s = "notepad.exe";

        Demo(s,i);
    }

    public static void Demo(String s, int i) throws IOException {
            Process p = Runtime.getRuntime().exec(s);   //启动程序

            File file = new File("notepad.properties");
            if(!file.exists()){
                file.createNewFile();
            }
            FileReader fr= new FileReader(file);

            Properties prop =new Properties();

            prop.load(fr);      //加载配置文件

            String value = prop.getProperty("times");   //获取times值

            int count = 0;   //计数器
            if(value!=null) {
                count = Integer.parseInt(value);  //获取上一次的值                     
            }
            count++;

            prop.setProperty("times", String.valueOf(count)); 

            if(count > i){        //判断配置文件的times值
                System.out.println("5次免费使用次数已到,请注册!");
                prop.setProperty("times", String.valueOf(i));
                p.destroy();    //停止程序
            }


            FileWriter fw = new FileWriter(file);    //更新配置文件

            prop.store(fw, "TimeCon");

            fr.close();
            fw.close();

    }

}

xml:使用的是标签

<Persons>
    <Person id = "001">
        <name>zhansan</name>
        <age>18</age>
    </Person>

    <Person id = "005">
        <name>lisan</name>
        <age>17</age>
    </Person>

</Persons>

4 .IO包中的其他类:

1.打印流:操作目的
①PrintStream

/*
        打印流PrintStream:字节流的扩展形式。print()将字节流封装成了字符流。
        1.提供了打印方法可以对多种数值类型进行打印。并保持数据的表现形式。
        2.它不抛IOException

        API:
        public class PrintStream
            extends FilterOutputStream
            implements Appendable, Closeable
            1.PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
            2.另外,为了自动刷新,可以创建一个 PrintStream,这时参数为true。这意味着可
            在写入 byte 数组之后自动调用 flush 方法:可调用其中一个 println 方法;
            或写入一个换行符或字节 ('\n'),然后flush。 
            3.PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
            在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

        构造函数:
        1.(字符串)路径
                PrintStream(File file) 
                        创建具有指定文件且不带自动行刷新的新打印流。 
                PrintStream(File file, String csn) 
                        创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 

        2.File对象
                PrintStream(String fileName) 
                        创建具有指定文件名称且不带自动行刷新的新打印流。 
                PrintStream(String fileName, String csn) 
                        创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 

        3.字节输出流
                PrintStream(OutputStream out) 
                        创建新的打印流。 
                PrintStream(OutputStream out, boolean autoFlush) 
                        创建新的打印流。 
                PrintStream(OutputStream out, boolean autoFlush, String encoding) 
                        创建新的打印流。 

          方法:
          1.void print(boolean b) 
                        打印 boolean 值。 
          2.void write(int b) 
                        将指定的字节写入此流。 
                void write(byte[] buf, int off, int len) 
                        将 len 字节从指定的初始偏移量为 off 的 byte 数组写入此流。 

 */


public class Demo4 {

    public static void main(String[] args) throws IOException {

        PrintStream out = new PrintStream("print.txt");

//      out.write(97);  //只写入最后的八位,只写一个字节。a

        out.print(97);  //打印并保持数据的表现形式。将97先变成字符保持原样将数据打印到目的地

        out.close();
    }

}

②PrintWriter: web开发最常用

/*
        打印流PrintWriter:字符流的扩展形式。此类实现在 PrintStream 中的所有 print 方法。
        1.提供了打印方法可以对多种数值类型进行打印。并保持数据的表现形式。
        2.它不抛IOException

        API:
            public class PrintWriter
        extends Writer
            1.向文本输出流打印对象的格式化表示形式。
            2.与 PrintStream 类不同,如果启用了自动刷新,则只有在
            调用 println、printf 或 format 的其中一个方法时才可能完成此操作,
            而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。


        构造函数:比字节打印流多了一个接收字符流对象
        1.(字符串)路径
                PrintWriter(File file) 
                        创建具有指定文件且不带自动行刷新的新打印流。 
                PrintWriter(File file, String csn) 
                        创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 

        2.File对象
                PrintWriter(String fileName) 
                        创建具有指定文件名称且不带自动行刷新的新打印流。 
                PrintWriter(String fileName, String csn) 
                        创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 

        3.字节输出流
                PrintWriter(OutputStream out) 
                        创建新的打印流。 
                PrintWriter(OutputStream out, boolean autoFlush)  
                 //autoFlush - boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区
                        创建新的打印流。 
        4.字符输出流:
                PrintWriter(Writer out) 
                        创建不带自动行刷新的新 PrintWriter。 
                PrintWriter(Writer out, boolean autoFlush) 
                        创建新 PrintWriter。 


          方法:
          1.void print(boolean b) 
                        打印 boolean 值。 
          2.void write(int b) 
                        将指定的字节写入此流。 
                void write(byte[] buf, int off, int len) 
                        将 len 字节从指定的初始偏移量为 off 的 byte 数组写入此流。 

 */


public class Demo4 {

    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);   //自动刷新开启

        String line = null;
        while((line = bufr.readLine())!= null){
            if("over".equals(line))
                break;
            out.println(line);
//          out.flush();
        }

        out.close();
        bufr.close();
    }
}

2.序列流 SequenceInputStream:操作源,

API:
public class SequenceInputStream
extends InputStream

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


1.构造方法:
    SequenceInputStream(Enumeration<? extends InputStream> e) 
         通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。 

    SequenceInputStream(InputStream s1, InputStream s2) 
         通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。 

2.示例:文件合并

//文件合并:
public class Demo5 {

    public static void main(String[] args) throws IOException {

//      Vector<FileInputStream> vec = new Vector<FileInputStream>();    //效率低

        ArrayList<FileInputStream> vec = new ArrayList<FileInputStream>();
        vec.add(new FileInputStream("demo2.txt"));
        vec.add(new FileInputStream("demo5.txt"));
        vec.add(new FileInputStream("out.txt"));

        final Iterator<FileInputStream> it = vec.iterator();  //内部类访问局部变量时,加final
/*      
        //迭代器转换枚举方法:工具类Collections的enumeration(Collection<T> c)方法:  
                    static <T> Enumeration<T> enumeration(Collection<T> c) 
                                            返回一个指定 collection 上的枚举。 
        */

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

/*  原理:
            Enumeration<FileInputStream> en = new Enumeration(){   //ArrayList不能返回枚举,自己造

            @Override
            public boolean hasMoreElements() {

                return it.hasNext();
            }

            @Override
            public Object nextElement() {

                return it.next();
            }

        };*/

//      SequenceInputStream sis = new SequenceInputStream(vec.elements());
        SequenceInputStream sis = new SequenceInputStream(en);


        FileOutputStream fos= new FileOutputStream("new.txt");

        byte ch[] = new byte[1024];

        int len = 0;
        while((len = sis.read(ch))!= -1){
            fos.write(ch,0,len);
        }
        fos.close();
        sis.close();

    }

}

文件切割器 ,合并器:

/*
        文件切割与合并:

 */
public class Demo6 {

    private static final int BUFFER_SIZE = 1048576;

    public static void main(String[] args) throws IOException {

        File file = new File("E:\\电影\\经典\\《声之形》.mp4");  //切割文件目录
        File file1 = new File("E:\\电影\\经典\\《声之形》切割碎片"); //切割碎片目录
        File file2 = new File("E:\\电影\\经典\\《声之形》合并后目录"); //碎片合并目录
        long date = 10485760;       //碎片大小:10M

//      qieGe(file,file1,date);   //切割

        heBin(file1,file2);  //合并

    }


    public static void qieGe(File file,File file1, long date) throws IOException {
        //计划显示:
        int num = (int)(file.length()/date)+1;
        System.out.print("计划切割碎片数量:" +num+"\n是否继续切割?(Y/N):");
        //判断:
        BufferedReader bufr =new BufferedReader(new InputStreamReader(System.in));
        String str = null;
        while((str = bufr.readLine())!=null){
            if("y".equals(str)||"Y".equals(str))
                break;
            else if("n".equals(str)||"N".equals(str))
                System.exit(0);
        }
        //开始时间:
        long startTime = System.currentTimeMillis();

        System.out.println("正在切割...");


        //用读取流关联文件
        FileInputStream fis = new FileInputStream(file);

        String[] name = file.getName().split("\\.");
        int i = 1 ;

        //定义一个1M缓冲区
        byte []ch = new byte[BUFFER_SIZE];

        //创建目的
        File f = createFile(name,i++,file1);

        //用写入流关联目的
        FileOutputStream fos = new FileOutputStream(f);

        //开始读写操作,这里的操作,是默认碎片大小大于缓冲区。
                        //否者可以简化,不需要if,else判断,直接每次都新建目录。


        int len = 0;
        while((len = fis.read(ch))!= -1){
            if(f.length()<date)    //判断,碎片大小没有达到指定大小,就继续写入·
                fos.write(ch,0,len);   
            else{                    //达到指定大小,新建碎片文件,本次字节写入新的碎片文件。循环继续。

                f = createFile(name,i++,file1);     //新建碎片文件
                fos = new FileOutputStream(f);              
                fos.write(ch);     //这里也得写一次,否者合并的视频有的地方会卡,保证数据的完整性
            }           
        }

        fos.close();
        fis.close();

        //结束时间:
        long endTime = System.currentTimeMillis();
        String timecost = new DecimalFormat("0.00").format((endTime - startTime)/1000.0);

        //切割文件时,必须记录住被切割文件的名称,以及切割出来的碎片文件的个数。便于合并。

        //写入配置文件:       
        FileOutputStream fos1 = new FileOutputStream(new File(file1,"info.properties"));
        Properties prop =new Properties();
        prop.setProperty("timecost", timecost+"s");  //分割耗时

        prop.setProperty("filenum", String.valueOf(i-1)); //碎片数量
        prop.setProperty("filename", file.getName());  //文件名称
        prop.setProperty("prefix", name[0] + "_cut"); //碎片前缀
        prop.setProperty("suffix", "."+name[1]); //碎片后缀

        prop.store(fos1, "");

//      prop.list(System.out);

        System.out.println("切割完毕! 用时" + prop.getProperty("timecost"));

    }

//创建目的  
    public static File createFile(String[] name,int i,File file1) throws IOException{
        String newName = name[0] + "_cut"+i+"."+name[1];

        if(!file1.exists()){
            file1.mkdirs();
        }
        File f = new File(file1,newName);       //File对象在被使用需要确认目录是否存在。             
        return f;
    }



//合并    
    public static void heBin(File file1,File file2) throws IOException {
        //加载碎片目录的配置文件:===================================================
        File[] files = file1.listFiles(new SuffFilter(".properties"));
        if(files.length == 0){
            throw new RuntimeException("碎片目录缺少配置文件,无法合并!");
        }

        //记录配置文件对象
        File config = files[0];
        FileInputStream fos1 = new FileInputStream(config);
        Properties prop1 =new Properties();
        prop1.load(fos1);

        int num = Integer.parseInt(prop1.getProperty("filenum"));  //碎片数量
        String suffix = prop1.getProperty("suffix");  //碎片后缀名
        String prefix = prop1.getProperty("prefix");  //碎片前缀名
        String filename = prop1.getProperty("filename");  // 源文件名称

        //判断碎片数量是否完整:
            File[] file = file1.listFiles(new SuffFilter(suffix));
            if(file.length<num){
                throw new RuntimeException("碎片文件数量小于配置文件默认数量"+num+",无法合并!");
            }


        //计划显示:碎片数量:

            System.out.print("计划合并碎片数量:" + num+"\n是否继续合并?(Y/N):");

            //判断:
                BufferedReader bufr =new BufferedReader(new InputStreamReader(System.in));
                String str = null;
                while((str = bufr.readLine())!=null){
                    if("y".equals(str)||"Y".equals(str))
                        break;
                    else if("n".equals(str)||"N".equals(str))
                        System.exit(0);
                }
        //开始时间:
            long startTime = System.currentTimeMillis();

            System.out.println("正在合并...");


        //将碎片文件和流对象关联,存储到集合中。===========================
        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();  //用来存放合并需要的碎片目录的读取流的集合

        for(int x= 1; x<= num;x++){
            al.add(new FileInputStream(new File(file1, prefix+x+suffix)));
        }

        //将多个流合并成一个序列流
        SequenceInputStream seq = new SequenceInputStream(Collections.enumeration(al));

        //定义合并文件的名称
        String newName = filename+ "_merge" +suffix ;

        //用写入流关联合并目的
        if(!file2.exists()){
            file2.mkdirs();
        }       
        FileOutputStream fos = new FileOutputStream(new File(file2,newName));  //参数如果是字符串会自己建,如果是File对象则要确定File目录存在。

        //用缓冲区进行写入流的操作,效率更高
        byte[] b = new byte[BUFFER_SIZE];

        //开始读写操作
        int len = 0;
        while((len = seq.read(b))!= -1){
            fos.write(b,0,len);
        }

        fos.close();
        seq.close();

        //结束时间:
        long endTime = System.currentTimeMillis();
        String timecost = new DecimalFormat("0.00").format((endTime - startTime)/1000.0);

        //写入配置文件:
        FileOutputStream fos2 = new FileOutputStream(new File(file2,"info.properties"));
        Properties prop2 =new Properties();
        prop2.setProperty("timecost", timecost+"s");
        prop2.store(fos2, "");
//      prop.list(System.out);

        System.out.println("合并完毕! 用时" + prop2.getProperty("timecost"));
    }

}

//过滤器
class SuffFilter implements  FilenameFilter{
    private String suffix;
    public SuffFilter(String suffix){
        super();
        this.suffix = suffix;
    }
    @Override
    public boolean accept(File dir, String name) {

        return name.endsWith(suffix);
    }

}

3.操作对象的字节流。ObjectInputStream, ObjectOutputStream。

ObjectOutputStream:是对象的序列化。(序列流是流的序列化)

ObjectOutputStream:
将 Java 对象的基本数据类型和图形写入 OutputStream。
通过在流中使用文件可以实现对象的持久存储。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。

构造方法:
    ObjectOutputStream(OutputStream out) 
       创建写入指定 OutputStream 的 ObjectOutputStream。

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

示例:
public class Demo7 {

    public static void main(String[] args) throws IOException {

        writeObj();
    }

    public static void writeObj() throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
        //对象的序列化
        oos.writeObject(new Person1("小强",30));  //被操作的对象需要实现Serializable(标记接口)

        oos.close();
    }

}

public class Person1 implements Serializable/*标记接口*/{

    private String name;
    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    private int 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;
    }
}

ObjectInputStream:是对象的反序列化。

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。 

示例:
public class Demo7 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        writeObj();
        readObj();
    }

    public static void readObj() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));

        //对象的反序列化
        Person1 p = (Person1)ois.readObject();
        System.out.println(p.getName() + ":" + p.getAge());

        ois.close();
    }

    public static void writeObj() throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));

        //对象的序列化
        oos.writeObject(new Person1("小强",30));  //被操作的对象需要实现Serializable(标记接口)

        oos.close();


    }

}

public class Person1 implements Serializable/*标记接口*/{

    private String name;
    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    private int 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;
    }
}

4.序列化接口 Serializable :

关键字 瞬态关键字transient : 非静态数据不想被序列化可以使用这个关键字修饰。

作用:给类加id
①序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。
②如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。
例:
 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

①如果序列化的类改变了,id号会变。反序列化会发生异常。
②所以安全起见,自己显示声明id号。这样,如果序列化的类改变了,id号不会变。


public class Person1 implements Serializable/*标记接口*/{

    /**
     * 
     */
    private static final long serialVersionUID = 9527L;

    private transient String name;  //如果想要非静态的值不被序列化对象存储,加瞬态关键字transient
    private static int age;  //静态的值序列化不会存储
    public Person1(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;
    }
}

5.RandomAccessFile : 随机访问文件对象(可用于多线程写入)

/*
        RandomAccessFile (多线程写入)
        不是io体系中的子类。

        构造方法:
        RandomAccessFile(File file, String mode) 
                创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。 
        RandomAccessFile(String name, String mode) 
                创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。 

        特点:
        1.该对象既能读,也能写。
        2.该对象内部维护了一个byte数组,并通过指针可以操作数组中的元。
        3.可以通过getFilePointer方法获取指针的位置,和通过seek方法设置指针的位置。
        4.其实该对象就是将字节输入流和输出流进行了封装。
        5.该对象的源和目的只能是文件。

        使用环境:
        1.读取有规律的文本
        2.多个线程同时写一个文本的不同位置。(其他流都是从头开始写,所以做不到)
 */
public class Demo8 {

    public static void main(String[] args) throws IOException {
        writeFile();  
        readFile();
    }
    public static void readFile() throws IOException{
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");
        byte[] buf =new byte[4];
        raf.read(buf);

        String name = new String(buf);
        System.out.println("name = " + name);

        int age = raf.readInt();
        System.out.println("age = " + age);

        System.out.println(raf.getFilePointer());
        raf.seek(0);  //设置指针位置。向从哪读就哪读,     随机读。

        raf.close();

    }

    //使用RandomAccessFile对象写入一些人员信息,比如姓名和年龄。
    public static void writeFile() throws IOException{
        /*
                如果文件不存在,则创建,如果文件存在,不创建,但是写数据时会从0开始写,修改原有数据。
         */
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");

        raf.write("张三".getBytes());
//      raf.write(97);  //只保留低8位
        raf.writeInt(97);  //4个字节全存

        raf.seek(2);    //设置指针位置。     随机写。
        raf.write("王五".getBytes());

        raf.close();
    }
}

6.管道流:PipedInputStream,PipedOutputStream

输入输出可以直接进行连接,通过结合线程使用。

PipedInputStream:
    管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。



public class PipedStream {

    public static void main(String[] args) throws IOException {

        PipedInputStream input = new PipedInputStream();
        PipedOutputStream output = new PipedOutputStream();

        input.connect(output);

        new Thread(new Input(input)).start();
        new Thread(new Output(output)).start();
    }

}


class Input implements Runnable{

    private PipedInputStream in;
    Input(PipedInputStream in){
        this.in = in;
    }

    public void run(){
        try {
            byte[] buf = new byte[1024];
            int len = in.read(buf);

            String s = new String(buf,0,len);
            System.out.println("s="+ s);
            in.close();
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
}

class Output implements Runnable{
    private PipedOutputStream out;
    Output(PipedOutputStream out){
        this.out = out;
    }

    public void run(){
        try {
            Thread.sleep(5000);
            out.write("hi, 管道来了!".getBytes());
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

}

7.操作基本类型数据的流对象 DataInputStream,DataOutputStream

/*
        DataOutputStream:
        数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。
        然后,应用程序可以使用数据输入流DataInputStream将数据读入。 


 */
public class DataStreamDemo {

    public static void main(String[] args) throws IOException {
//      writeData();
        readData();
    }

    public static void readData() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        String s = dis.readUTF();
        System.out.println(s);
    }

    public static void writeData() throws IOException {
         DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
         dos.writeUTF("你好");        //UTF修改版:带编码的标头。转换流指定不了它的编码。
                                                        //只能用对应的数据输入流DataInputStream读了。
         dos.close();
    }

}

8.操作数组的流:

1.操作字节数组:ByteArrayInputStream,ByteArratOutputStream,只是操作内存。

ByteArratOutputStream:
    此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。 
    关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。 

ByteArrayInputStream :
    ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。 
    关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。 

构造函数:源和目的都是数组
    ByteArrayOutputStream() 
          创建一个新的 byte 数组输出流。 
    ByteArrayOutputStream(int size) 
          创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。 


    ByteArrayInputStream(byte[] buf) 
          创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。 
    ByteArrayInputStream(byte[] buf, int offset, int length) 
          创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。 

示例:

public class ByteArrarStreamDemo {

    public static void main(String[] args) {
//如果源或目的设备的内存的话可以用这两个对象。一般操作的数据都不大
        ByteArrayInputStream bis = new ByteArrayInputStream("abceds".getBytes());
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int ch = 0;

        while((ch = bis.read())!=-1){
            bos.write(ch);
        }

        System.out.println(bos.toString());
    }

}

2.操作字符数组:CharArrayReader,CharArrayWriter

3.操作字符串:StingReader,StringWriter

9.编码表:

ASCII:美国标准信息交换码。
    用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表。
    用一个字节的8位表示。(兼容ASCII码表)
GB2312:中国的中文编码表。(六七千)
GBK:中国的中文编码表升级,融合了更多的中文文字符号。(两万多)
Unicode:国际标准码,融合了多种文字。
    所有的文字都是用2个字节来表示,Java语言用的就是unicode
UTF-8:最多用三个字节来表示一个字符。

简单编码解码:

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

你好:GBK: -60 -29 -70 -61
你好:utf-8:-28 -67 -96 -27 -91 -67


String str ="你好";
//编码:
byte[] buf = str.getBytes("GBK");
//解码:
String str =new String(buf,"GBK");  //你好

编码解码问题:

①编码:如果你编错了,解不出来。(编错了,用的是未知字符代替的,如?)
     如果编对了,解错了,有可能有救。(如果解错了,就用错误的码表再返回去编码(如过这个错误的码不是未知字符的话,ISO8859-1码表就没问题,因为它不支持中文但是是单字节编码,会有对应的字节。而UTF-8 1到3个字节的都识别,所以可能出现未知字符?), 得到原字节,在换正确的码表重新解码)

这里写图片描述

②但是如果是用UTF-8解码解错的,那么返回去编码的时候,得到的可能就不是原来的原字节了。
    因为UTF-8里可能没有对应的字符,用的用未知字符?(-17 -65 -67)代替。

这里写图片描述

UTF-8编码解码规范:编码头用来识别如何开始查表,0表示,一个字节开始查表,110 ,10两个字节开始查表,1110,10,10三个字节开始查表。其实的未知字符? 表示 。

UTF-8编码解码

③ ” 联通 ” 问题

String str = "联通";

byte[] buf = str.getByte("gbk");

for(byte b : buf){
    System.out.println(Integer.toBinaryString(b&255));
}
/*
    11000001
    10101010
    11001101
    10101000
*/

联通的gbk编码正好和UTF-8的编码格式一致。

编码解码练习:按字节截取字符串

/*
 * 在java中,字符串“abcd”与字符串“ab你好”的长度是一样的,都是4个字符。
 * 但对应的字节数不同,一个字节占两个字节。
 * 
 * 定义一个方法,按照指定的字节数来取子串,保证取得的子串是完整的。
 * 
 * 如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”的半个,那么半个就要舍弃。
 * 如果去取4个字节就是“ab你”,取5个字节还是“ab你”
 */

public class Demo9 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(cutStringByte("ab你好",4));
    }

    public static String cutStringByte(String str,int len) throws UnsupportedEncodingException{
            byte[] buf =  str.getBytes("gbk");
            int count = 0;     //记录到截取未知的汉字字节数

            for(int x= len - 1;x>=0;x--){   //判断截取的最后一个字节是不是负数(汉字一个字节)
                if(buf[x] <0)
                    count++;       //记录负数(汉字一个字节)
                else
                    break;
            }

            if(count%2==0)   //如果到截取未知的汉字字节数是偶数,则不舍弃
                return new String(buf,0,len,"gbk");
            else//如果到截取未知的汉字字节数是奇数,则舍弃
                return new String(buf,0,len-1,"gbk");
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值