IO中基本概念和开发过程中如何确定该使用到哪些类.
1,基本概念
输入和输出的定义是对于计算机中的内存而言的,从内存到外围设备(比如硬盘)是输出,从外围设备到内存,是输入.
字节:计算机可以认识的数据.
字符:通过是控制台输出的内容,人可以认识的.
字符流的由来:计算机读取到的其实是字节数据,然后在根据提供的各种编码表(每个国家都有自己特有文字的编码表),去获取对应的文字,通俗的来将字符流就是字节流+编码表.
JavaIO操作只要是指Java进行输入和输出的操作,所涉及到的类和接口都存放在java.io包中,使用时需要导包,从上图可以看出,JavaIO中最重要的有5个类和一个接口,
InputStream,Reader,OutputStream,Writer,File,和接口Serializable.一下我将用文字简单的总结各个类的特点和类中特有的方法.
在IO中,主要有字节流和字符流两大类,这两大类都具有输入和输出的功能,在字节流中,输入(InputStream),输出(OutputStream),在字符流中,输入(Reader),输出(Writer)
2,在开发的过程中,需要明确到时是使用以上到哪些类?我们可以通过4个步骤(熟练后只需要确定最后源和目的的设备即可),就能快速的判断出来
2.1,明确源和目的(汇) 源:InputStream Reader 目的:OutputStream Writer
2.2明确数据是否是纯文本数据 源:如果是纯文本,Reader 否:InputStream
目的:是纯文本 Writer 否 :OutputStream
到这里,就可以明确需求中具体要使用到哪个体系
2.3明确具体的设备 源设备: 硬盘:File 键盘:System.in 内存:数组 网络:Socket流
目的设备: 硬盘:File 控制台:System.out 内存:数组 网络:Socket
2.4是否需要其他额外功能 1,是否需要高效(缓冲区) 是,就加上buffered
2.5需求与代码(通过四部曲来判断使用到的类),一下4个需求,我都会加入额外功能,缓冲区(BufferedReader和BufferedWriter)
需求1:复制一个文本文件
分析:源和目的在硬盘设备上的纯文本文件, FileReader和FileWriter
需求2:读取键盘存入信息,并存入一个文件中
分析:源是键盘,目的是硬盘上的纯文本信息, (源需要进行转化,字节->字符)InputStreamReader is = System.in FileWriter
需求3:将文本文件的数据显示到控制台上
分析:源是硬盘,目的是控制台(System.out)的纯文本信息 FileReader (计算机到控制台显示,字符->字节)OutputStreamWriter ops = System.out
需求4:读取键盘录入信息,显示在控制台上
分析:源是键盘(System.in),目的是控制台(System.out) InputStreamReader ips = System.in OutputStreamWriter = System.out
需求5:将一个中文字符串数据按照指定的码表写入到一个文本本件中去
分析:按照指定的码表,这个一定得想到转化流
源(硬盘File):转化流的话,查阅API,那么使用的是OutputStreamWriter中的构造方法传入2个参数OutputStream和Charset
目的(硬盘File):按照源的思路,InputStreamReader中的构造方法传入两个参数InputStream和Charset
上述5个是IO中最基本的操作,其中设计到一个比较关键的问题,我们社么时候需要考虑使用转换流?
1,源和目的对应的设备都是字节流,但操作却是文本数据
2,一旦操作涉及到指定编码的时候,一定得使用转换流
/* * 需求:复制一个文本文件 */ public class demo01 { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new FileReader("a.txt")); BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt")); String line = null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.flush(); } } } //需求:读取键盘录入信息,并存入文件 public class demo02 { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new FileWriter("c.txt")); String line = null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.flush(); } } } //需求:将文本文件数据显示到控制台 public class demo03 { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new FileReader("a.txt")); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.flush(); } } } //需求 读取键盘录入数据,显示到控制台 public class demo04 { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.flush(); } } }
关于BufferedReader和BufferWriter,缓冲区的理解
缓冲区的原理:从源中先拿到一部分数据,方法缓冲区中先使用,当缓冲区的数据使用完毕后,再从源中取出数据到缓冲区,直到源中没有数据,为-1或者的null作为结尾,这样能提高效率.
这两个类都是为了提高访问效率,构造方法中需要要传入各自对应的Reader和Writer的对象,没有无参的构造方法.
源BufferedReader中,有两个读取方法,,一般最后一个比较常用,这里要注意下返回值的不同,int的为-1,String的为null.
对于读取文件中的数据,都是用读取文本行的方法来完成的,这样能大大的提高提取的效率,tips:使用while循环读取的时候,它的返回值是null.
Java中的递归
递归的含义:函数自身直接或者间接的调用自身.
什么时候使用递归:一个功能被重复使用,参与运算的结果与上次函数调用有关,这类问题都可以利用递归来解决
递归的注意事项:一个功能被设计成递归,那么这个功能的运算必须要有入口,也比较要有出口,一定要明确条件,否则会发生栈溢出
注意递归的次数,假如没明确结束条件,会出现异常
public class DiGuiDemo { public static void main(String[] args) { int sum = getSum(5); System.out.println("sum:" + sum); } public static int getSum(int i) { if (i == 1) return 1; return i + getSum(i - 1); } }
以上例子就是一个递归的运用,去实现连续的自然数的相加,这里的if语句中的代码,就把递归的次数明确好了,从5开始,连续的到2,这里是连续的在调用getSum的函数,然后到1的时候,直接返回的是1,这个时候递归结束,注意,从5到4,这个时候只是在调用函数,这个时候并没有进行加法的循环,知道1结束后,就需要弹栈,这个时候从1开始,从后向前进行加法的运算,这个是重点,该开始我想不同的原因,就是没想想明白,递归的时候,函数是什么时候弹栈的.
File类
1 基本概念
它是在IO中,唯一与文件操作有关的类,通过构造方法,传入字符串的参数,将已经存在或者不存在的文件或者目录封装成对象,以后只要操作到具体的文件,我们就应该把它进行封装,然后再进行操作.
在操作文件的时候,一定要使用File.separator来表示分隔符,因为在不同的操作系统上,它会显示对于该操作系统的分割符.
2 下面将介绍几个比较常用的File类中的方法
列出指定目录的全部文件(比如列出D盘下的文件,它只会到D的一层目录,不会到D目录下的子目录或者子子(子子....)目录)
以上2个方法,一个返回的是String字符串形式,一个返回的是File[]数组对象,数组对象的输出需要遍历,你懂的,一般我喜欢第二种方式的输出.
3 那么我们如何可以将D盘目录下的所有的文件的路径都显示出来?这个时候就需要使用到递归了.
深度遍历的思路:1,创建一个显示所有目录的方法,该方法需要传入一个File对象的参数(需要深度遍历的文件)
2,使用方法二对File对象进行遍历
3,在遍历的过程中需要进行判断,使用 判断传入的File对象有子目录,那么就递归在调用自身的方法
4,弹栈的动作,递归到最后了,然后在输出目录.具体代码如下
public class FileTest1 { public static void main(String[] args) { File f = new File("d:"); listAll(f); } public static void listAll(File f) { System.out.println("dir:" + f.getAbsolutePath()); File[] file = f.listFiles(); for (int i = 0; i < file.length; i++) { if (file[i].isDirectory()) listAll(file[i]); else System.out.println("file:" + file[i].getAbsolutePath()); } } } public class FileTest2 { /* * 需求:删除里面带有内容的目录 原理:从最里面往外面删除 需要进行深度遍历 * */ public static void main(String[] args) { File f = new File("d:\\a"); removeDir(f); } public static void removeDir(File f) { File[] file = f.listFiles(); for (File file2 : file) { if (file2.isDirectory()) { removeDir(file2); } else { System.out.println(file2 + ":" + file2.delete()); } } System.out.println(f + ":" + f.delete()); } }
上面的删除动作,加入文件夹中是有数据或者内容的话,该文件夹是不能被该方法所删除的,所以一般进行删除操作,都需要从里面到外面来进行操作.
4,关键文件名过滤的问题
通过上面3张图中的内容可以完成过滤,用法和Java类集中的比较器原理类似.
SequenceInputStream类
该类是将几个输入流连在一起,提供两个构造方法,从API上可以看出,如果需要传入2个以上的输入流的话,我们需要拿到输入流的枚举对象,关于枚举,必须要想到集合框架中的Collections这个工具类,它其中有个方法可以获取集合对象的枚举类型
需求:将a.txt b.txt c.txt三个文本文件中的内容合并到一个文本文件abc.txt中
分析:该需求是将3个输入流合并到一起,应该使用下面那个构造方法,通过Collections中的方法来获取3个小输入流的枚举
1,将a,b,c3个在硬盘(File)上的文本文件进行输入流的封装 FileInputStream
2,创建ArrayList集合,将3个流装入(add)该集合
3,通过Collections中的方法,从该集合中获取枚举
4,SequenceInputStream中的构造方法进行封装
5,创建输出流,确认源目的(abc.txt) FileOutputStream 定义长度,byte数组,进行读写操作
6,关闭各个流.
package cn.wjd.sequence; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.SequenceInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.Vector; public class SequenceDemo { /*需求:将a.txt,b.txt,c.txt中的数据合并到一个文件中去 * */ public static void main(String[] args) throws IOException { ArrayList<FileInputStream> al = new ArrayList<FileInputStream>(); al.add(new FileInputStream("a.txt")); al.add(new FileInputStream("b.txt")); al.add(new FileInputStream("c.txt")); Enumeration<FileInputStream> en = Collections.enumeration(al); SequenceInputStream sis = new SequenceInputStream(en); FileOutputStream fos = new FileOutputStream("abc.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1){ fos.write(buf, 0, len); } sis.close(); fos.close(); } }
该类还有一个具体的应用,是文件的切割和文件的合拼,这个会在我下一个博客的由思路出代码的时候,再详细分析.
java.util中的Properties类
它是用来保存配置文件信息相关的类,表示了一个持久的属性集,该类中数据都是以字符串的形式出现的,并有key->value的关系,集合中的数据可以保存在流中,也可以从流中获取.下面先看API中几个常用的方法.
需求:修改某个配置文件的信息
分析:1,将这个配置文件用File进行封装,创建Properties对象
2,创建输入流 FileInputStream
3,将流中的信息存储到配置文件 load
4,修改配置文件信息 setProperty
5,在修改好的配置文件写出输出流 FileOutputStream store
需求:将一些配置信息写入一个新的配置文件
1,创建Properties对象, 设置配置信息 setProperty
2,创建输出流,确定新配置文件的名称和位置 FileOutputStream
3,将信息集合储存到配置文件,关流 store
需要:输出某个配置文件的信息
1,创建Properties对象, 设置配置信息 setProperty
2,stringPropertyNames()得到一个set集合 得到的是配置文件中的key
3,对set集合进行遍历,然后通过key---->value
package cn.wjd.properties; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import java.util.Set; public class PropertiesDemo { /* * Map -Hashtable -Properties Properties集合的特点: 1,该集合中的key和value都是字符串类型 * 2,集合中的数据可以保存在流中,或者从流中获取 * 3,通常该集合操作以键值对存在的配置文件 */ public static void main(String[] args) throws IOException { // propertiesDemo(); // propertiesMethod_2(); // propertiesMethod_2(); //propertiesMethod_3(); //propertiesMethod_4(); test(); } // 对已有的配置文件中的信息进行修改 /* * 读取这个文件,并将这个文件中的键值存储到集合中 在集合中对数据进行修改 再通过流将修改后的输出 */ private static void test() throws IOException { File file = new File("info.txt"); Properties pro = new Properties(); if(!file.exists()){ file.createNewFile(); } //创建一个输入流 FileInputStream fis = new FileInputStream(file); //将流中的信息存储在配置文件 pro.load(fis); //修改配置文件的数据 pro.setProperty("zhangsan", "1"); FileOutputStream fos = new FileOutputStream(file); pro.store(fos, "name+age"); pro.list(System.out); fis.close(); fos.close(); } public static void propertiesMethod_4() throws IOException { Properties pro = new Properties(); //集合中的数据来源与文件,注意:必须保证文件中的数据是键值对 //需要使用到读取流 FileInputStream fis = new FileInputStream("info.txt"); pro.load(fis); pro.list(System.out); fis.close(); } public static void propertiesMethod_3() throws IOException { // 建立properties集合 Properties pro = new Properties(); // 往集合中添加元素 pro.setProperty("zhangsan", "21"); pro.setProperty("zhangsan2", "22"); pro.setProperty("zhangsan3", "23"); // 将集合中的信息持久化的存储在文件中,需要关联输出流 FileOutputStream fos = new FileOutputStream("info.txt"); //将集合信息存储到配置文件中 pro.store(fos, "name+age"); fos.close(); } /* * 演示Properties与流相结合的方法 */ public static void propertiesMethod_2() { // 建立properties集合 Properties pro = new Properties(); // 往集合中添加元素 pro.setProperty("zhangsan", "21"); pro.setProperty("zhangsan2", "22"); pro.setProperty("zhangsan3", "23"); pro.list(System.out);// 这个方法在程序调试的时候,使用的频率会比较高 } public static void propertiesDemo() { // 建立properties集合 Properties pro = new Properties(); // 往集合中添加元素 pro.setProperty("zhangsan", "21"); pro.setProperty("zhangsan2", "22"); pro.setProperty("zhangsan3", "23"); // 修改集合中的元素 pro.setProperty("zhangsan", "26"); // 取出集合中的元素 Set<String> set = pro.stringPropertyNames(); for (String string : set) { String value = pro.getProperty(string); System.out.println("key:" + string + " value:" + value); } } }