黑马程序员——io流2(其它流)

来源:互联网 发布:python内置函数手册 编辑:程序博客网 时间:2024/06/05 23:42

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1.     其他流

1.1. 序列流

也称为合并流。

1.1.1.    SequenceInputStream

序列流,对多个流进行合并。

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

注意:

构造函数

SequenceInputStream(InputStream s1, InputStream s2)

SequenceInputStream(InputStream s1, InputStream s2)

 

合并两个流

使用构造函数SequenceInputStream(InputStream s1, InputStream s2)

privatestaticvoid testSequenceInputStream()throws IOException {

       FileInputStream fis1 = new FileInputStream("c:\\a.txt");

       FileInputStream fis2 = new FileInputStream("c:\\b.txt");

 

       SequenceInputStream s1 = new SequenceInputStream(fis1, fis2);

       int len = 0;

       byte[] byt =newbyte[1024];

 

       FileOutputStream fos = new FileOutputStream("c:\\z.txt");

 

       while ((len = s1.read(byt)) != -1) {

           fos.write(byt, 0, len);

       }

       s1.close();

    }

 

合并多个流:

 

publicstaticvoid testSequenceInputStream()throws Exception {

       InputStream in1 = new FileInputStream("c:/a.txt");

       InputStream in2 = new FileInputStream("c:/b.txt");

       InputStream in3 = new FileInputStream("c:/c.txt");

 

       LinkedHashSet<InputStream> set =new LinkedHashSet<InputStream>();

       set.add(in1);

       set.add(in2);

       set.add(in3);

       final Iterator<InputStream> iter = set.iterator();

 

       SequenceInputStream sin =new SequenceInputStream(

              new Enumeration<InputStream>() {

                  @Override

                  publicboolean hasMoreElements() {

                     return iter.hasNext();

                  }

 

                  @Override

                  public InputStream nextElement() {

                     return iter.next();

                  }

              });

 

       FileOutputStream out = new FileOutputStream("c:/z.txt");

 

       for (int b = -1; (b = sin.read()) != -1;) {

           out.write(b);

       }

        sin.close();

       out.close();

    }

案例:将map3歌曲文件进行切割拷贝,并合并.

publicclass Demo2 {

    publicstaticvoid main(String[] args)throws IOException {

      

       split(new File("c:\\a.mp3"), 10,new File("c:\\"));

       System.out.println("切割完毕");

 

       LinkedHashSet<InputStream> hs =new LinkedHashSet<InputStream>();  

       hs.add(new FileInputStream(new File("c:\\part.1.mp3")));

       hs.add(new FileInputStream(new File("c:\\part.2.mp3")));

       hs.add(new FileInputStream(new File("c:\\part.3.mp3")));

       hs.add(new FileInputStream(new File("c:\\part.4.mp3")));

       merage(hs, new File("c:\\merage.mp3"));

       System.out.println("合并完毕");

    }

 

    privatestaticvoid merage(LinkedHashSet<InputStream> hs, File dest)

           throws IOException {

 

       final Iterator<InputStream> it = hs.iterator();

       FileOutputStream fos = new FileOutputStream(dest);

       SequenceInputStream seq =new SequenceInputStream(

              new Enumeration<InputStream>() {

 

                  @Override

                  publicboolean hasMoreElements() {

 

                     return it.hasNext();

                  }

 

                  @Override

                  public InputStream nextElement() {

                      return it.next();

                  }

              });

       byte[] byt =newbyte[1024 * 1024];

       int len = 0;

       while ((len = seq.read(byt)) != -1) {

           fos.write(byt, 0, len);

       }

       seq.close();

       fos.close();

    }

 

    // 1.切割文件

    /*

     * 切割文件,切割份数,切割后保存路径

     */

    privatestaticvoid split(File src,int count, File dir)throws IOException {

       FileInputStream fis = new FileInputStream(src);

       FileOutputStream fos = null;

       byte[] byt =newbyte[1024 * 1024];

       int len = 0;

       for (int i = 1; i <= count; i++) {

           len = fis.read(byt);

           if (len != -1) {

              fos = new FileOutputStream(dir +"part." + i +".mp3");

              fos.write(byt, 0, len);

           }

 

           // fos.close();

 

       }

       fis.close();

 

    }

}

 

 

 

1.2. 对象的序列化

当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同。可以使用对象的序列化。

 对象的序列化:   将内存中的对象直接写入到文件设备中

 对象的反序列化: 将文件设备中持久化的数据转换为内存对象

基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。

ObjectOutput

writeObject(Object obj)

          将对象写入底层存储或流。

ObjectInput

readObject()

          读取并返回对象。

 

1.2.1.    ObjectOutputStream

1.2.2.    ObjectInputStream

由于上述ObjectOutput和ObjectInput是接口,所以需要使用具体实现类。

ObjectOutput

 ObjectOutputStream被写入的对象必须实现一个接口:Serializable

否则会抛出:NotSerializableException

ObjectInput

     ObjectInputStream    该方法抛出异常:ClassNotFountException 

ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。

 

案例:

序列化和反序列化Cat对象。

publicclass Demo3 {

    publicstaticvoid main(String[] args)throws IOException,

           ClassNotFoundException {

       Cat cat = new Cat("tom", 3);

       FileOutputStream fos = new FileOutputStream(new File("c:\\Cat.txt"));

       ObjectOutputStream oos = new ObjectOutputStream(fos);

       oos.writeObject(cat);

       System.out.println(cat);

       oos.close();

       //反序列化

       FileInputStream fis = new FileInputStream(new File("c:\\Cat.txt"));

       ObjectInputStream ois = new ObjectInputStream(fis);

       Object readObject = ois.readObject();

       Cat cat2 = (Cat) readObject;

       System.out.println(cat2);

       fis.close();

}

 

classCat implements Serializable {

    public Stringname;

    publicintage;

 

    public Cat() {

 

    }

 

    public Cat(String name,int age) {

 

       this.name = name;

       this.age = age;

    }

 

    @Override

    public String toString() {

       return"Cat [name=" +name + ", age=" + age +"]";

    }

 

}

 

例子关键点:

1.  声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。

2.  新建Cat对象。

3.  新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。

4.  新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。

5.  writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。

6.  反序列化就是调用ObjectInputStreamreadObject()方法。

7.  异常处理和流的关闭动作要执行。

1.2.3.    Serializable:

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

所以需要被序列化的类必须是实现Serializable接口,该接口中没有描述任何的属性和方法,称之为标记接口。

如果对象没有实现接口Serializable,在进行序列化时会抛出:NotSerializableException 异常。

注意:

保存一个对象的真正含义是什么?如果对象的实例变量都是基本数据类型,那么就非常简单。但是如果实例变量是包含对象的引用,会怎么样?保存的会是什么?很显然在Java中保存引用变量的实际值没有任何意义,因为Java引用的值是通过JVM的单一实例的上下文中才有意义。通过序列化后,尝试在JVM的另一个实例中恢复对象,是没有用处的。

如下:

首先建立一个Dog对象,也建立了一个Collar对象。Dog中包含了一个Collar(项圈)

现在想要保存Dog对象,但是Dog中有一个Collar,意味着保存Dog时也应该保存Collar。假如Collar也包含了其他对象的引用,那么会发生什么?意味着保存一个Dog对象需要清楚的知道Dog对象的内部结构。会是一件很麻烦的事情。

    Java的序列化机制可以解决该类问题,当序列化一个对象时,Java的序列化机制会负责保存对象的所有关联的对象(就是对象图),反序列化时,也会恢复所有的相关内容。本例中:如果序列化Dog会自动序列化Collar。但是,只有实现了Serializable接口的类才可以序列化。如果只是Dog实现了该接口,而Collar没有实现该接口。会发生什么?

Dog类和Collar类

import java.io.Serializable;

 

publicclassDog implements Serializable {

    private Collarcollar;

    private Stringname;

 

    public Dog(Collar collar, String name) {

 

       this.collar = collar;

       this.name = name;

    }

 

    public Collar getCollar() {

       returncollar;

    }

 

}

 

class Collar {

    privateintsize;

 

    publicint getSize() {

       returnsize;

    }

 

    public Collar(int size) {

       this.size = size;

    }

 

}

 

序列化

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectOutputStream;

 

publicclass Demo4 {

    publicstaticvoid main(String[] args)throws IOException {

       Collar coll = new Collar(10);

       Dog dog = new Dog(coll,"旺财");

 

       FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));

       ObjectOutputStream os = new ObjectOutputStream(fis);

       os.writeObject(dog);

    }

}

 

执行程序,出现了运行时异常。

Exception in thread "main" java.io.NotSerializableException: Collar

 

所以我们也必须将Dog中使用的Collar序列化。但是如果我们无法访问Collar的源代码,或者无法使Collar可序列化,如何处理?

两种解决方法:

一:继承Collar类,使子类可序列化

但是:如果Collar是final类,就无法继承了。并且,如果Collar引用了其他非序列化对象,也无法解决该问题。

transient

此时就可以使用transient修饰符,可以将Dog类中的成员变量标识为transient

那么在序列化Dog对象时,序列化就会跳过Collar。

 

publicclass Demo4 {

    publicstaticvoid main(String[] args)throws IOException,

           ClassNotFoundException {

       Collar coll = new Collar(10);

       Dog dog = new Dog(coll,"旺财");

       System.out.println(dog.getCollar().getSize());

 

       FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));

       ObjectOutputStream os = new ObjectOutputStream(fis);

       os.writeObject(dog);

 

       //反序列化

       FileInputStream fos = new FileInputStream(new File("c:\\dog.txt"));

       ObjectInputStream ois = new ObjectInputStream(fos);

       Object readObject = ois.readObject();

       Dog dog2 = (Dog) readObject;

       // Collar未序列化。

       dog2.getCollar().getSize();

    }

}

 

这样我们具有一个序列化的Dog和非序列化的Collar。

此时反序列化Dog后,访问Collar,就会出现运行时异常

10

Exception in thread "main" java.lang.NullPointerException

注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。

 

基本数据类型可以被序列化

publicclass Demo5 {

    publicstaticvoid main(String[] args)throws IOException {

       //创建序列化流对象

       FileOutputStream fis = new FileOutputStream(new File("c:\\basic.txt"));

       ObjectOutputStream os = new ObjectOutputStream(fis);

       //序列化基本数据类型

       os.writeDouble(3.14);

       os.writeBoolean(true);

       os.writeInt(100);

       os.writeInt(200);

 

       //关闭流

       os.close();

 

       //反序列化

       FileInputStream fos = new FileInputStream(new File("c:\\basic.txt"));

       ObjectInputStream ois = new ObjectInputStream(fos);

 

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

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

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

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

 

       fos.close();

    }

}

 

 

serialVersionUID

用于给类指定一个UID。该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。

只要是这些成员没有变化,那么该值每次运算都一样。

该值用于判断被序列化的对象和类文件是否兼容。

如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。

定义方式:static final long serialVersionUID = 42L;

 

1.3. Properties.

 

可以和流相关联的集合对象Properties.

Map

|--Hashtable

|--Properties

Properties:该集合不需要泛型,因为该集合中的键值对都是String类型。

1,存入键值对:setProperty(key,value);

2,获取指定键对应的值:value getProperty(key);

3,获取集合中所有键元素:

Enumeration  propertyNames();

在jdk1.6版本给该类提供一个新的方法。

Set<String> stringPropertyNames();

4,列出该集合中的所有键值对,可以通过参数打印流指定列出到的目的地。

list(PrintStream);

list(PrintWriter);

例:list(System.out):将集合中的键值对打印到控制台。

list(new PrintStream("prop.txt")):将集合中的键值对存储到prop.txt文件中。

5,可以将流中的规则数据加载进行集合,并称为键值对。

load(InputStream):

jdk1.6版本。提供了新的方法。

load(Reader):

注意:流中的数据要是"键=值" 的规则数据。

6,可以将集合中的数据进行指定目的的存储。

store(OutputStram,String comment)方法。

jdk1.6版本。提供了新的方法。

store(Writer ,String comment):

使用该方法存储时,会带着当时存储的时间。

注意:

Properties只加载key=value这样的键值对,与文件名无关,注释使用#

练习:记录一个程序运行的次数,当满足指定次数时,该程序就不可以再继续运行了。

通常可用于软件使用次数的限定。

publicstaticvoid sysPropList()throws IOException {

       Properties prop = System.getProperties();

 

       // prop.list(System.out);//目的是控制台。

       //需求是:将jvm的属性信息存储到一个文件中。

       prop.list(new PrintStream("java.txt"));

    }

 

    publicstaticvoid sysProp() {

       Properties prop = System.getProperties();

 

       Set<String> keys = prop.stringPropertyNames();

 

       for (String key : keys) {

           System.out.println(key +":" + prop.getProperty(key));

       }

    }

 

Properties类与配置文件

Map

    |--Hashtable

       |--Properties

注意:是一个Map集合,该集合中的键值对都是字符串。该集合通常用于对键值对形式的配置文件进行操作.

配置文件:将软件中可变的部分数据可以定义到一个文件中,方便以后更改,该文件称之为配置文件。

优势: 提高代码的维护性。

Properties:  该类是一个Map的子类,提供了可以快速操作配置文件的方法

load()  :    将文件设备数据装载为Map集合数据

get(key):    获取Map中的数据

getProperty()获取Map中的数据特有方法

案例:

/*

     * 将配置文件中的数据通过流加载到集合中。

     */

    publicstaticvoid loadFile()throws IOException {

        // 1,创建Properties(Map)对象

       Properties prop = new Properties();

 

       // 2.使用流加载配置文件。

       FileInputStream fis = new FileInputStream("c:\\qq.txt");

 

       // 3。使用Properties对象的load方法将流中数据加载到集合中。

       prop.load(fis);

 

       //遍历该集合

       Set<Entry<Object, Object>> entrySet = prop.entrySet();

       Iterator<Entry<Object, Object>> it = entrySet.iterator();

       while (it.hasNext()) {

           Entry<Object, Object> next = it.next();

           Object key = next.getKey();

           Object value = next.getValue();

       }

       //通过键获取指定的值

       Object object = prop.get("jack");

       System.out.println(object);

 

       //通过键修改值

       prop.setProperty("jack","888888");

 

       //将集合中的数据写入到配置文件中。

       FileOutputStream fos = new FileOutputStream("c:\\qq.txt");

 

       //注释:

       prop.store(fos, "yes,qq");

 

       fos.close();

       fis.close();

 

    }

 

   

获取记录程序运行次数:

publicclass Demo6 {

    publicstaticvoid main(String[] args)throws IOException {

       int count = 0;

       Properties pro = new Properties();

 

       File file = new File("c:\\count.ini");

       FileInputStream fis = null;

       if (!file.exists()) {

           file.createNewFile();

       }

       fis = new FileInputStream(file);

       pro.load(fis);

       String str = pro.getProperty("count");

       if (str !=null) {

           count = Integer.parseInt(str);

       }

       if (count == 3) {

           System.out.println("使用次数已到,请付费");

           System.exit(0);

       }

 

       count++;

       System.out.println("欢迎使用本软件" + "你已经使用了:" + count +" ");

 

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

       FileOutputStream fos = new FileOutputStream(new File("c:\\count.ini"));

       pro.store(fos, "请保护知识产权");

 

       fis.close();

       fos.close();

 

    }

}

 

 

1.4. 打印流

PrintStream可以接受文件和其他字节输出流,所以打印流是对普通字节输出流的增强,其中定义了很多的重载的print()println(),方便输出各种类型的数据。

 

1.4.1.    PrintStream

PrintWriter

1,打印流。

PrintStream:

是一个字节打印流,System.out对应的类型就是PrintStream。

它的构造函数可以接收三种数据类型的值。

1,字符串路径。

2,File对象。

3,OutputStream。

    publicstaticvoid main(String[] args)throws IOException {

       PrintStream ps = System.out;

 

       //普通write方法需要调用flush或者close方法才会在控制台显示

       // ps.write(100);

       // ps.close();

 

       //不换行打印

       ps.print(100);

       ps.print('a');

       ps.print(100.5);

       ps.print("世界");

       ps.print(new Object());

       System.out.println("--------------");

       //换行

       ps.println(100);

       ps.println('a');

       ps.println(100.5);

       ps.println("世界");

       ps.println(new Object());

 

       //重定向打印流

       PrintStream ps2 = new PrintStream(new File("c:\\a.txt"));

       System.setOut(ps2);

       //换行

       ps2.println(100);

       ps2.println('a');

       ps2.println(100.5);

       ps2.println("世界");

       ps2.println(new Object());

 

       //printf(); 格式化

       ps2.printf("%d,%f,%c,%s", 100, 3.14,'',"世界你好!!!");

       ps2.printf("%4s%8s打价格战","京东","苏宁");

 

      

    }   }

注意: 打印流的三种方法

       void print(数据类型变量)

   println(数据类型 变量)

   printf(String format, Object... args)

      可以自定数据格式

    print 和println方法的区别在于,一个换行一个不换行

    print 方法和write方法的却别在于,print提供自动刷新.

    普通的write方法需要调用flush或者close方法才可以看到数据.

JDK1.5之后Java对PrintStream进行了扩展,增加了格式化输出方式,可以使用printf()重载方法直接格式化输出。但是在格式化输出的时候需要指定输出的数据类型格式。

 

1.4.2.  PrintWriter

是一个字符打印流。构造函数可以接收四种类型的值。

1,字符串路径。

2,File对象。

对于1,2类型的数据,还可以指定编码表。也就是字符集。

3,OutputStream

4,Writer

对于3,4类型的数据,可以指定自动刷新。

注意:该自动刷新值为true时,只有三个方法可以用:println,printf,format.

如果想要既有自动刷新,又可执行编码。如何完成流对象的包装?

PrintWrter pw =

new PrintWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);

如果想要提高效率。还要使用打印方法。

PrintWrter pw =

newPrintWriter(new  BufferdWriter(new OutputSteamWriter(

newFileOutputStream("a.txt"),"utf-8")),true);

publicstaticvoid testPrintWriter()throws Exception {

       PrintWriter pw = new PrintWriter("c:/b.txt","gbk");

 

       // pw.append("xxx");

       // pw.println(55);

       // pw.println('c');

       // pw.printf("%.1s%4s打价格战, %c", "京东","苏宁", 'a');

 

       pw.close();

 

    }

 

Scanner

publicstaticvoid testScanner()throws Exception {

       // Scanner scanner = new Scanner(new File("c:/test.txt"));

       Scanner scanner = new Scanner(System.in);

 

       System.out.println(scanner.nextInt());

       System.out.println(scanner.nextBoolean());

 

       scanner.close();

    }

1.5. 操作数组的流对象

1.5.1.    操作字节数组

ByteArrayInputStream

    以及ByteArrayOutputStream

toByteArray();

toString();

writeTo(OutputStream);

publicstaticvoid testByteArrayInputStream()throws Exception {

       InputStream in = new ByteArrayInputStream(newbyte[] { 65, 66, 67 });

       ByteArrayOutputStream out =new ByteArrayOutputStream();

 

       for (int b = -1; (b = in.read()) != -1;) {

           out.write(b);

       }

 

       in.close();

       out.close();

 

       System.out.println(Arrays.toString(out.toByteArray()));

       System.out.println(out);

    }

 

1.5.2.    操作字符数组

CharArrayReader

CharArrayWriter

对于这些流,源是内存。目的也是内存。

而且这些流并未调用系统资源。使用的就是内存中的数组。

所以这些在使用的时候不需要close

操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。

对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组作为缓冲区。

publicstaticvoid testCharArrayReader()throws Exception {

       CharArrayReader reader = new CharArrayReader(newchar[] {'A', 'b','c' });

       CharArrayWriter writer = new CharArrayWriter();

 

       for (int b = -1; (b = reader.read()) != -1;) {

           writer.write(b);

       }

 

       reader.close();

       writer.close();

 

       System.out.println(writer.toCharArray());

    }

 

这几个流的出现其实就是通过流的读写思想在操作数组。

类似的对象同理:

StringReader

StringWriter。

publicstaticvoid testStringReader()throws Exception {

       StringReader reader = new StringReader("test中国");

       StringWriter writer = new StringWriter();

 

       for (int b = -1; (b = reader.read()) != -1;) {

           writer.write(b);

       }

 

       reader.close();

       writer.close();

 

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

    }

1.6. 操作基本数据类型的流对象

1.6.1.    DataInputStream

以及DataOutputStream

 

查看API文档DataInputStream的信息。发现从底层输入流中读取基本 Java 数据类型。查看方法,有读一个字节,读一个char读一个double 的方法,

 

DataInputStream 从数据流读取字节,并将它们转换为正确的基本数据类型值或字符串。

该流有操作基本数据类型的方法.

有读的,那么必定有对应的写的就是DataOutputStream 将基本类型的值或字符串转换为字节,并且将字节输出到数据流。

DataInputStream类继承FilterInputStream类,并实现了DataInput接口。DataOutputStream

类继承FilterOutputStream 并实现了DataOutput 接口。

例如:

DataInputStream

操作基本数据类型的方法:

int readInt():一次读取四个字节,并将其转成int值。

boolean readBoolean():一次读取一个字节。

short readShort();

long readLong();

剩下的数据类型一样。

String readUTF():按照utf-8修改版读取字符。注意,它只能读writeUTF()写入的字符数据。

DataOutputStream

DataOutputStream(OutputStream):

操作基本数据类型的方法:

writeInt(int):一次写入四个字节。

注意和write(int)不同。write(int)只将该整数的最低一个8位写入。剩余三个8位丢弃。

writeBoolean(boolean);

writeShort(short);

writeLong(long);

剩下是数据类型也也一样。

writeUTF(String):按照utf-8修改版将字符数据进行存储。只能通过readUTF读取。

 

 

测试:   DataOutputStream

       使用DataOutputStream写数据文件。

publicstaticvoid testDataInputStream()throws Exception {

       DataOutputStream out = new DataOutputStream(new FileOutputStream(

              "c:/a.txt"));

 

       out.writeBoolean(true);

       out.writeByte(15); // 0x05 1 个字节

       out.writeBytes("abc");// 0x 0041 2个字节

       out.writeChar('X');// ??

       out.writeChars("xyz");

       out.writeLong(111);

       out.writeUTF("中国");

 

       out.close();

 

       DataInputStream in = new DataInputStream(

              new FileInputStream("c:/a.txt"));

       System.out.println(in.readBoolean());

       System.out.println(in.readByte());

      

       System.out.println(in.readByte());

       System.out.println(in.readByte());

       System.out.println(in.readByte());

      

       System.out.println(in.readChar());

      

       System.out.println(in.readChar());

       System.out.println(in.readChar());

       System.out.println(in.readChar());

      

       System.out.println(in.readLong());

      

       System.out.println(in.readUTF());

       in.close();

    }

 

 

2.     编码

什么是编码?

    计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,a  1 等字符

计算机中是没有存储字符的,但是我们却看到了。计算机在存储这些信息的时候,根据一个有规则的编号,当用户输入a 有a对映的编号,就将这个编号存进计算机中这就是编码。

 

计算机只能识别二进制数据。

为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

例如:汉字 中 

有一种编码:

中字在utf 8中对映的编码

utf-8  -->100 

在gbk中呢?有可能就不是100了

gbk    -->  150

很显然同一个信息在不同的编码中对映的数字也不同,

不同的国家和地区使用的码表是不同的,

gbk 是中国大陆

bjg5 是台湾同胞中的繁体字。所以如果给big5一个简体字是不认识的。

还有ASCII 美国标准信息交换码

2.1. 码表

常见的码表如下:

ASCII:    美国标准信息交换码。用一个字节的7位可以表示。

ISO8859-1:   拉丁码表。欧洲码表,用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,

藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。

GB2312:   中国的中文编码表。

GBK:      中国的中文编码表升级,融合了更多的中文文字符号。

Unicode:  国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。

UTF-8:    最多用三个字节来表示一个字符。

(我们以后接触最多的是iso8859-1、gbk、utf-8)

   

查看上述码表后,很显然中文的‘中’在iso8859-1中是没有对映的编码的。或者一个字符在2中码表中对应的编码不同,例如有一些字在不同的编码中是有交集的,例如bjg5 和gbk 中的汉字简体和繁体可能是一样的,就是有交集,但是在各自码表中的数字不一样。

例如

使用gbk 将中文保存在计算机中,

    中  国

对映  100  200   如果使用big5 打开

可能   ?  ...  

不同的编码对映的是不一样的。

很显然,我们使用什么样的编码写数据,就需要使用什么样的编码来对数据。

ISO8859-1:一个字节

GBK: 两个字节包含了英文字符和扩展的中文   ISO8859-1+中文字符

UTF-8 万国码,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间。

 

2.2. 编码:

字符串---》字节数组

String类的getBytes() 方法进行编码,将字符串,转为对映的二进制,并且这个方法可以指定编码表。如果没有指定码表,该方法会使用操作系统默认码表。

注意:中国大陆的Windows系统上默认的编码一般为GBK。在Java程序中可以使用System.getProperty("file.encoding")方式得到当前的默认编码。

2.3. 解码:

字节数组---》字符串

String类的构造函数完成。

String(byte[] bytes)  使用系统默认码表

String(byte[],charset)指定码表

注意:我们使用什么字符集(码表)进行编码,就应该使用什么字符集进行解码,否则很有可能出现乱码(兼容字符集不会)。

 

    //编码操作与解码操作。

    publicstaticvoid main(String[] args)throws Exception {

       String value = System.getProperty("file.encoding");

       System.out.println("系统默认的编码为 " + value);

 

       String str = "";

 

       //编码操作

       byte[] bytes = str.getBytes();

       byte[] bytes2 = str.getBytes("gbk");// d6d0

       byte[] bytes3 = str.getBytes("utf-8");// e4b8ad

 

       System.out.println(Arrays.toString(bytes));// [-42, -48]

       System.out.println(Arrays.toString(bytes2));// [-42, -48]

       System.out.println(Arrays.toString(bytes3));// [-28, -72, -83]

 

       //解码操作

       //编码gbk,解码utf-8乱码。

       String str2 = new String(bytes2,"utf-8");

       System.out.println(str2);

 

       //编码utf-8解码gbk,乱码

       str2 = new String(bytes3,"gbk");

       System.out.println(str2);

       //gbk兼容gb2312所以,没有问题。

       str = new String("中国".getBytes("gb2312"),"gbk");

       System.out.println(str);

    }

 

存文件时可以使用各种编码,但是解码的时候要对映的采用相同的解码方式。

我们的字符流自动的做了编码和解码的工作,写一个中文,字符流进行了编码,存到了计算机中读到了一个字符,字符流进行了解码,我们可以看到字符。因为文件存的都是二进制。

但是拷贝图片时,是纯二进制,不是有意义的字符,所以码表无法转换。

字符流的弊端:

    一:无法拷贝图片和视频。

    二:拷贝文件使用字节流而不使用字符流,因为字符流读文件涉及到解码,会先解码,写文件的时候又涉及到编码,这些操作多余,而且读和写的码表不对应还容易引发问题。

例如FileReader 读文件,我们没有指定编码时,默认是按照系统编码gbk进行操作,如果读到utf-8的文件也是按照gbk编码进行解码,那就会出现问题。

 

2.4. 字节流读取中文

   

publicclass TestIo {

    publicstaticvoid main(String[] args)throws IOException {

       readFileByInputStream2("c:\\a.txt");

    }

 

    privatestaticvoid readFileByInputStream2(String path)throws IOException {

       FileInputStream fis = new FileInputStream(path);

       int len = 0;

 

       while ((len = fis.read()) != -1) {

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

       }

 

    }

}

这个方法读取文本文件,中文是无法正确显示的。

很显然这些字节需要解码,可以将字节输入流读取的信息保存在字节数组中,指定对应的码表进行解码即可。

publicclass TestIo {

    publicstaticvoid main(String[] args)throws IOException {

       readFileByInputStream("c:\\a.txt");

    }

 

    privatestaticvoid readFileByInputStream(String path)throws IOException {

       FileInputStream fis = new FileInputStream(path);

       int len = 0;

       byte[] buffer =newbyte[1024];

       while ((len = fis.read(buffer)) != -1) {

           System.out.println(new String(buffer, 0, len,"gbk"));

       }

 

    }

}

 

 

注意:如果指定的编码表和解码表不对应就会出现问题

publicclass TestIo {

    publicstaticvoid main(String[] args)throws IOException {

       //该文件默认是gbk编码

       readFileByInputStream("c:\\a.txt");

    }

 

    privatestaticvoid readFileByInputStream(String path)throws IOException {

       FileInputStream fis = new FileInputStream(path);

       int len = 0;

       byte[] buffer =newbyte[1024];

       while ((len = fis.read(buffer)) != -1) {

           //使用utf-8解码,解错。

           System.out.println(new String(buffer, 0, len,"utf-8"));

       }

 

    }

}

 

 

2.5. 字节流写出中文

需要编码,可以指定码表。就需要自己把字符串进行编码操作后,把得到的二进制内容通过字节流写入到文件中

使用String的getBytes方法,无参数的会使用系统默认的码表进行编码,也可以指定码表

系统默认编码

publicclass TestIo {

    publicstaticvoid main(String[] args)throws IOException {

 

       String path = "c:\\test.txt";

       writeFileByOutputStream(path,"世界你好");

       readFileByInputStream(path);

    }

 

    privatestaticvoid writeFileByOutputStream(String path, String content)

           throws IOException {

       FileOutputStream fos = new FileOutputStream(path);

 

       //把字符串进行编码操作,系统默认编码

       byte[] bytes = content.getBytes();

       //内容通过字节流写入到文件中。

       fos.write(bytes);

       fos.close();

    }

 

    privatestaticvoid readFileByInputStream(String path)throws IOException {

       FileInputStream fis = new FileInputStream(path);

       int len = 0;

       byte[] buffer =newbyte[1024];

 

       while ((len = fis.read(buffer)) != -1) {

           //二进制解码,使用系统默认编码

           System.out.println(new String(buffer, 0, len));

       }

 

    }

}

 

 

使用utf-8进行编码

publicclass TestIo {

    publicstaticvoid main(String[] args)throws IOException {

 

       String path = "c:\\test.txt";

       writeFileByOutputStream(path,"世界你好");

       readFileByInputStream(path);

    }

 

    privatestaticvoid writeFileByOutputStream(String path, String content)

           throws IOException {

       FileOutputStream fos = new FileOutputStream(path);

 

       //把字符串进行编码操作

       byte[] bytes = content.getBytes("utf-8");

       //内容通过字节流写入到文件中。

       fos.write(bytes);

       fos.close();

    }

 

    privatestaticvoid readFileByInputStream(String path)throws IOException {

       FileInputStream fis = new FileInputStream(path);

       int len = 0;

       byte[] buffer =newbyte[1024];

 

       while ((len = fis.read(buffer)) != -1) {

           //二进制解码,使用系统默认编码

           System.out.println(new String(buffer, 0, len,"utf-8"));

       }

 

    }

}

 

 

在明白了字节流也可以正确的处理中文字符之后,就应该明白字符流其实就是字节流在加上系统默认的码表。自动进行了编码和解码的操作。底层还是使用字节流读取文件。通过转换流的学习就可以明白这些道理。

 

2.6. 转换流

InputStreamReader

查看API文档,发现是字节流通向字符流的桥梁。查看构造,可以传递字节流,可以指定编码,该流可以实现什么功能?很显然可以包装我们的字节流,自动的完成节流编码和解码的工作。该流是一个Reader的子类,是字符流的体系。所以将转换流称之为字节流和字符流之间的桥梁。

InputStreamReader 是字节流通向字符流的桥梁

测试InputStreamReader:

第一步: 需要专门新建以GBK编码的文本文件。为了便于标识,我们命名为gbk.txt

       和以UFT-8编码的文本文件,命名为utf.txt
    第二步: 分别写入汉字”中国”

第三步:编写测试方法,用InputStreamReader 分别使用系统默认编码,GBK,UTF-8编码读取文件.

 

publicclass Demo4 {

    publicstaticvoid main(String[] args)throws IOException {

       File file = new File("c:\\a.txt");

       File fileGBK = new File("c:\\gbk.txt");

       File fileUTF = new File("c:\\utf.txt");

       //默认编码

       testReadFile(file);

       //传入gbk编码文件,使用gbk解码

       testReadFile(fileGBK,"gbk");

       //传入utf-8文件,使用utf-8解码

       testReadFile(fileUTF,"utf-8");

 

    }

 

    //该方法中nputStreamReader使用系统默认编码读取文件.

    privatestaticvoid testReadFile(File file)throws

           IOException {

       FileInputStream fis = new FileInputStream(file);

       InputStreamReader ins = new InputStreamReader(fis);

       int len = 0;

       while ((len = ins.read()) != -1) {

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

       }

       ins.close();

       fis.close();

    }

 

    //该方法使用指定编码读取文件

    privatestaticvoid testReadFile(File file, String encod)

           throws IOException {

       FileInputStream fis = new FileInputStream(file);

       InputStreamReader ins = new InputStreamReader(fis, encod);

       int len = 0;

       while ((len = ins.read()) != -1) {

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

       }

       ins.close();

       }

}

 

 

注意:码表不对应
分别测试:

使用系统默认编码读取utf-8编码文件

使用utf-8编码读取gbk编码文件

使用"gbk”编码读取utf-8文件.

发现都会出现乱码的问题.

// 使用系统默认编码读取utf-8

       testReadFile(fileUTF);

       //传入gbk编码文件,使用utf-8解码

       testReadFile(fileGBK,"utf-8");

       //传入utf-8文件,使用"gbk解码

       testReadFile(fileUTF, "gbk");

类 OutputStreamWriter

OutputStreamWriter

有了InputStreamReader 可以转换InputStream 

那么其实还有OutputStreamWriter 可以转换OutputStream

OutputStreamWriter 是字符流通向字节流的桥梁

测试OutputStreamWriter

    一: 分别使用OutputStreamWriter使用系统默认编码,GBK,UTF-8相对应的默认编码文件,GBK编码文件,UTF-8编码文件中写出汉字”中国”.

    二: 在使用上述案例中的readFile方法传入相对应码表读取.

publicclass TestIo {

    publicclass Demo4 {

    publicstaticvoid main(String[] args)throws IOException {

       File file = new File("c:\\a.txt");

       File fileGBK = new File("c:\\gbk.txt");

       File fileUTF = new File("c:\\utf.txt");

 

       //写入

       //使用系统默认码表写入

       testWriteFile(file);

       //使用gbk编码向gbk文件写入信息

       testWriteFile(fileGBK,"gbk");

       //使用utf-8utf-8文件中写入信息

       testWriteFile(fileUTF,"utf-8");

   

       //读取

       //默认编码

       testReadFile(file);

       //传入gbk编码文件,使用gbk解码

       testReadFile(fileGBK,"gbk");

       //传入utf-8文件,使用utf-8解码

       testReadFile(fileUTF,"utf-8");

 

    }

 

    //使用系统码表将信息写入到文件中

    privatestaticvoid testWriteFile(File file)throws IOException {

       FileOutputStream fos = new FileOutputStream(file);

       OutputStreamWriter ops = new OutputStreamWriter(fos);

       ops.write("中国");

       ops.close();

    }

 

    //使用指定码表,将信息写入到文件中

    privatestaticvoid testWriteFile(File file, String encod)

           throws IOException {

       FileOutputStream fos = new FileOutputStream(file);

        OutputStreamWriter ops =new OutputStreamWriter(fos, encod);

       ops.write("中国");

       ops.close();

    }

 

    //该方法中nputStreamReader使用系统默认编码读取文件.

    privatestaticvoid testReadFile(File file)throws IOException {

       FileInputStream fis = new FileInputStream(file);

        InputStreamReader ins = new InputStreamReader(fis);

       int len = 0;

       while ((len = ins.read()) != -1) {

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

       }

       ins.close();

   

    }

 

    //该方法适合用指定编码读取文件

    privatestaticvoid testReadFile(File file, String encod)

           throws IOException {

       FileInputStream fis = new FileInputStream(file);

       InputStreamReader ins = new InputStreamReader(fis, encod);

       int len = 0;

       while ((len = ins.read()) != -1) {

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

       }

   

       ins.close();

    }

 

}

 

注意: 码表不对应的问题

分别测试:

    向GBK文件中写入utf-8编码的信息

    向utf文件中写入gbk编码的信息

发现文件都有问题,无法正常的读取了.

   

publicstaticvoid main(String[] args)throws IOException {

       File file = new File("c:\\a.txt");

       File fileGBK = new File("c:\\gbk.txt");

       File fileUTF = new File("c:\\utf.txt");

 

       //写入

       // //使用系统默认码表写入

       // testWriteFile(file);

       // //使用gbk编码向gbk文件写入信息

       // testWriteFile(fileGBK, "gbk");

       // //使用utf-8utf-8文件中写入信息

       // testWriteFile(fileUTF, "utf-8");

 

       testWriteFile(fileGBK);

       //GBK文件中写入utf-8编码的信息

       testWriteFile(fileGBK,"utf-8");

        // utf文件中写入gbk编码的信息

       testWriteFile(fileUTF,"gbk");

 

       //读取

       //默认编码

       testReadFile(file);

       //传入gbk编码文件,使用gbk解码

       testReadFile(fileGBK,"gbk");

       //传入utf-8文件,使用utf-8解码

       testReadFile(fileUTF,"utf-8");

 

    }

 

InputStreamReader:字节到字符的桥梁。

OutputStreamWriter:字符到字节的桥梁。

 

它们有转换作用,而本身又是字符流。所以在构造的时候,需要传入字节流对象进来。

构造函数:

InputStreamReader(InputStream)

通过该构造函数初始化,使用的是本系统默认的编码表GBK。

InputStreamReader(InputStream,String charSet)

通过该构造函数初始化,可以指定编码表。

OutputStreamWriter(OutputStream)

通过该构造函数初始化,使用的是本系统默认的编码表GBK。

OutputStreamWriter(OutputStream,String charSet)

通过该构造函数初始化,可以指定编码表。

注意:

操作文件的字符流对象是转换流的子类。

Reader

|--InputStreamReader

|--FileReader

Writer

|--OutputStreamWriter

|--FileWriter

 

注意:

在使用FileReader操作文本数据时,该对象使用的是默认的编码表。

如果要使用指定编码表时,必须使用转换流。

 

如果系统默认编码是GBK的:

FileReader fr = new FileReader("a.txt");//操作a.txt的中的数据使用的本系统默认的GBK

操作a.txt中的数据使用的也是本系统默认的GBK

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));

这两句的代码的意义相同。

但是:如果a.txt中的文件中的字符数据是通过utf-8的形式编码。使用FileReader就无能为力,那么在读取时,就必须指定编码表。那么转换流必须使用。

InputStreamReader isr =

new InputStreamReader(new FileInputStream("a.txt"),"utf-8");

 

 

3.     递归

    递归做为一种算法在程序设计语言中广泛应用。是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。

    (自己调用自己,有结束条件)

    注意:递归时一定要明确结束条件。

数学中递归运算.

    对于任何正整数N ,N! (读作N的阶乘)的值定义为1-N(包括N)的所有的整数的成绩.因此3! 就是 3!=3*2*1 =6;

5! 定义为5!=5*4*3*2*1=120

那么整数N 的阶乘 N! 可以表示为

1!=1

N!=N*(N-1)! for N>1

若果N 等于1 那么1的继承就是1,其他所有N! =N*(N-1)!,例如:50!=50*49!

49!=49*48! 48!=48*47! 一直持续到1出现.

如何使用Java程序计算阶乘?

    publicstaticlong recursion(int n) {

       if (n == 1) {

           return 1;

       } else {

           return n *recursion(n - 1);

       }

    }

 

 

3.1. 案例:

    1,列出指定目录中所有的子孙文件与子孙目录名,只需要列出名称即可。

    2,列出指定目录中所有的子孙文件与子孙目录名,要求名称前面要有相应数量的空格:

       第一级前面有0个,第二级前面有1个,第三级前面有2个...,以此类推。

    3,列出指定目录中所有的子孙文件与子孙目录名,要求要是树状结构,效果如下所示:

       |--src

       |   |--cn

       |   |   |--itcast

       |   |   |   |--a_helloworld

       |   |   |   |   |--HelloWorld.java

       |   |   |   |--b_for

       |   |   |   |   |--ForTest.java

       |   |   |   |--c_api

       |   |   |   |   |--Student.java

       |--bin

       |   |--cn

       |   |   |--itcast

       |   |   |   |--i_exception

       |   |   |   |   |--ExceptionTest.class

       |   |   |   |--h_linecount

       |   |   |   |   |--LineCounter3.class

       |   |   |   |   |--LineCounter2.class

       |   |   |   |   |--LineCounter.class

       |--lib

       |   |--commons-io.jar

答案:

案例一:

// 1,列出指定目录中所有的子孙文件与子孙目录名,只需要列出名称即可。

    privatestaticvoidlistFile(File file) {

 

       File[] listFiles = file.listFiles();

 

       for (File f : listFiles) {

           if (f.isFile()) {

              System.out.println(f.getName());

           } elseif (f.isDirectory()) {

              System.out.println(f.getName());

              listFile(f);

           }

 

       }

    }

publicstaticvoid main(String[] args) {

       File file = new File("c:\\abc");

        listFile(file);

    }

 

案例二

// 2,列出指定目录中所有的子孙文件与子孙目录名,要求名称前面要有相应数量的空格:

    privatestaticvoidlistFile2(File file, String str) {

 

       File[] listFiles = file.listFiles();

 

       for (int i = 0; i < listFiles.length; i++) {

           File f = listFiles[i];

           System.out.println(str + f.getName());

 

           if (f.isDirectory()) {

              listFile2(f, str +"-");

           }

       }

    }

publicstaticvoid main(String[] args) {

       File file = new File("c:\\abc");

       String str = "-";

       listFile2(file,str);

      

 

 

 

案例三:

// 列出指定目录中所有的子孙文件与子孙目录名,要求要是树状结构

    privatestaticvoid listFile3(File file, String str) {

 

       File[] listFiles = file.listFiles();

 

       for (File f : listFiles) {

           System.out.println(str + f.getName());

           if (f.isDirectory()) {

              listFile3(f,"|  " + str);

           }

 

       }

    }

publicstaticvoid main(String[] args) {

       File file = new File("c:\\abc");

       file = new File("c:\\day18ide");

       file = new File("c:\\MyIo");

       str = "|-";

        listFile3(file, str);

 

 

 

3.2. 练习:

    1,删除一个非空的目录。

    2,移动一个非空的目录到另一个地方(剪切)。    

    3,把File类中的重要方法设计代码测试一遍。

 

// 1,删除一个非空的目录。并加强健壮性

    privatestaticvoid deleteFile(File file) {

       if (!file.exists()) {

           System.out.println("路径不存在");

           return;

       }

       if (!file.isDirectory()) {

           System.out.println("不是目录");

           return;

       }

       //如果当前目录中有子目录和文件,先删除子目录和文件

       File[] listFiles = file.listFiles();

       for (File f : listFiles) {

           if (f.isFile()) {

              f.delete();

           } elseif (f.isDirectory()) {

              deleteFile(f);

           }

       }

       //删除当前目录

       file.delete();

 

    }

0o

练习2:

使用File类的renameTo 方法和递归实现非空目录的剪切.

publicstaticvoid main(String[] args)throws IOException {

       //重命名文件(成功)

       // Filesrc = new File("c:\\aaa.txt");

       // Filedest = new File("c:\\bbb.txt");

       // src.renameTo(dest);

 

       // //移动文件(成功)

       // Filesrc = new File("c:\\aaa.txt");

       // Filedest = new File("d:\\aaa.txt");

       // src.renameTo(dest);

 

       //移动一个空目录(失败)

       // Filesrc = new File("c:\\aaa");

       // Filedest = new File("d:\\aaa");

       // System.out.println(src.renameTo(dest));

 

       //使用File类和递归实现文件的剪切.

       File src = new File("c:\\abc");

       File dest = new File("d:\\");

       cutFile(src, dest);

 

    }

 

    //移动一个非空的目录到另一个地方(剪切)。

    privatestaticvoid cutFile(File srcDir, File dest)throws IOException {

       if (!srcDir.exists() || !dest.exists()) {

           System.out.println("指定的源目录或者目标目录不存在");

           return;

       }

       if (!srcDir.isDirectory() || !dest.isDirectory()) {

           System.out.println("指定的源目录或者目标目录不是目录");

           return;

       }

 

       //得到源目录名

       String srcDirName = srcDir.getName();// abc

       //根据源目录名创建新目录名

       File destDir = new File(dest + srcDirName);// d:\\abc dest 为父路径

                                             // srcDirName 为子路径

       //创建目标目录

       destDir.mkdir();

 

       //遍历源目录

       File[] listFiles = srcDir.listFiles();

 

       for (File f : listFiles) {

           //如果是子源文件,使用renameTo方法,移动至目标目录中(该方法同时会删除源目录中的文件)

           if (f.isFile()) {

              f.renameTo(new File(destDir, f.getName()));// 指定目标文件的父目录,文件名(根据源文件名生成).

           } elseif (f.isDirectory()) {

              //如果是子目录,执行重复动作.将源子目录 , 目标目录(父目录+//)

              cutFile(f,new File(destDir, File.separator));// 指定源目录,指定目的路径d:\\abc\\

           }

       }

       //删除源目录

       srcDir.delete();

 

    }

 

0 0