黑马程序员——io流2(其它流)
2014-09-03 20:59
225 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
注意:
合并两个流
使用构造函数SequenceInputStream(InputStream s1, InputStream s2)
合并多个流:
案例:将map3歌曲文件进行切割拷贝,并合并.
对象的序列化: 将内存中的对象直接写入到文件设备中
对象的反序列化: 将文件设备中持久化的数据转换为内存对象
基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。
ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。
案例:
序列化和反序列化Cat对象。
例子关键点:
1. 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。
2. 新建Cat对象。
3. 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。
4. 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。
5. writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。
6. 反序列化就是调用ObjectInputStream的readObject()方法。
7. 异常处理和流的关闭动作要执行。
所以需要被序列化的类必须是实现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类
序列化
执行程序,出现了运行时异常。
所以我们也必须将Dog中使用的Collar序列化。但是如果我们无法访问Collar的源代码,或者无法使Collar可序列化,如何处理?
两种解决方法:
一:继承Collar类,使子类可序列化
但是:如果Collar是final类,就无法继承了。并且,如果Collar引用了其他非序列化对象,也无法解决该问题。
transient
此时就可以使用transient修饰符,可以将Dog类中的成员变量标识为transient
那么在序列化Dog对象时,序列化就会跳过Collar。
这样我们具有一个序列化的Dog和非序列化的Collar。
此时反序列化Dog后,访问Collar,就会出现运行时异常
注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。
基本数据类型可以被序列化
serialVersionUID
用于给类指定一个UID。该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。
只要是这些成员没有变化,那么该值每次运算都一样。
该值用于判断被序列化的对象和类文件是否兼容。
如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。
定义方式:static final long serialVersionUID = 42L;
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这样的键值对,与文件名无关,注释使用#
练习:记录一个程序运行的次数,当满足指定次数时,该程序就不可以再继续运行了。
通常可用于软件使用次数的限定。
Properties类与配置文件
Map
|--Hashtable
|--Properties
注意:是一个Map集合,该集合中的键值对都是字符串。该集合通常用于对键值对形式的配置文件进行操作.
配置文件:将软件中可变的部分数据可以定义到一个文件中,方便以后更改,该文件称之为配置文件。
优势: 提高代码的维护性。
Properties: 该类是一个Map的子类,提供了可以快速操作配置文件的方法
load() : 将文件设备数据装载为Map集合数据
get(key): 获取Map中的数据
getProperty()获取Map中的数据特有方法
案例:
获取记录程序运行次数:
1.4.1. PrintStream
PrintWriter
1,打印流。
PrintStream:
是一个字节打印流,System.out对应的类型就是PrintStream。
它的构造函数可以接收三种数据类型的值。
1,字符串路径。
2,File对象。
3,OutputStream。
注意: 打印流的三种方法
void print(数据类型变量)
println(数据类型 变量)
printf(String format, Object... args)
可以自定数据格式
print 和println方法的区别在于,一个换行一个不换行
print 方法和write方法的却别在于,print提供自动刷新.
普通的write方法需要调用flush或者close方法才可以看到数据.
JDK1.5之后Java对PrintStream进行了扩展,增加了格式化输出方式,可以使用printf()重载方法直接格式化输出。但是在格式化输出的时候需要指定输出的数据类型格式。
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);
Scanner
以及ByteArrayOutputStream
toByteArray();
toString();
writeTo(OutputStream);
CharArrayWriter
对于这些流,源是内存。目的也是内存。
而且这些流并未调用系统资源。使用的就是内存中的数组。
所以这些在使用的时候不需要close。
操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。
对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组作为缓冲区。
这几个流的出现其实就是通过流的读写思想在操作数组。
类似的对象同理:
StringReader
StringWriter。
查看API文档DataInputStream的信息。发现从底层输入流中读取基本 Java 数据类型。查看方法,有读一个字节,读一个char读一个double 的方法,
DataInputStream 从数据流读取字节,并将它们转换为正确的基本数据类型值或字符串。
该流有操作基本数据类型的方法.
有读的,那么必定有对应的写的就是DataOutputStream 将基本类型的值或字符串转换为字节,并且将字节输出到数据流。
DataInputStream类继承FilterInputStream类,并实现了DataInput接口。DataOutputStream
类继承FilterOutputStream 并实现了DataOutput 接口。
例如:
测试: DataOutputStream
使用DataOutputStream写数据文件。
计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,a 1 等字符
计算机中是没有存储字符的,但是我们却看到了。计算机在存储这些信息的时候,根据一个有规则的编号,当用户输入a 有a对映的编号,就将这个编号存进计算机中这就是编码。
计算机只能识别二进制数据。
为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。
例如:汉字 中
有一种编码:
中字在utf 8中对映的编码
utf-8 -->100
在gbk中呢?有可能就不是100了
gbk --> 150
很显然同一个信息在不同的编码中对映的数字也不同,
不同的国家和地区使用的码表是不同的,
gbk 是中国大陆
bjg5 是台湾同胞中的繁体字。所以如果给big5一个简体字是不认识的。
还有ASCII 美国标准信息交换码
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个字节,是为了节省空间。
String类的getBytes() 方法进行编码,将字符串,转为对映的二进制,并且这个方法可以指定编码表。如果没有指定码表,该方法会使用操作系统默认码表。
注意:中国大陆的Windows系统上默认的编码一般为GBK。在Java程序中可以使用System.getProperty("file.encoding")方式得到当前的默认编码。
String类的构造函数完成。
String(byte[] bytes) 使用系统默认码表
String(byte[],charset)指定码表
注意:我们使用什么字符集(码表)进行编码,就应该使用什么字符集进行解码,否则很有可能出现乱码(兼容字符集不会)。
存文件时可以使用各种编码,但是解码的时候要对映的采用相同的解码方式。
我们的字符流自动的做了编码和解码的工作,写一个中文,字符流进行了编码,存到了计算机中读到了一个字符,字符流进行了解码,我们可以看到字符。因为文件存的都是二进制。
但是拷贝图片时,是纯二进制,不是有意义的字符,所以码表无法转换。
字符流的弊端:
一:无法拷贝图片和视频。
二:拷贝文件使用字节流而不使用字符流,因为字符流读文件涉及到解码,会先解码,写文件的时候又涉及到编码,这些操作多余,而且读和写的码表不对应还容易引发问题。
例如FileReader 读文件,我们没有指定编码时,默认是按照系统编码gbk进行操作,如果读到utf-8的文件也是按照gbk编码进行解码,那就会出现问题。
这个方法读取文本文件,中文是无法正确显示的。
很显然这些字节需要解码,可以将字节输入流读取的信息保存在字节数组中,指定对应的码表进行解码即可。
注意:如果指定的编码表和解码表不对应就会出现问题
使用String的getBytes方法,无参数的会使用系统默认的码表进行编码,也可以指定码表
系统默认编码
使用utf-8进行编码
在明白了字节流也可以正确的处理中文字符之后,就应该明白字符流其实就是字节流在加上系统默认的码表。自动进行了编码和解码的操作。底层还是使用字节流读取文件。通过转换流的学习就可以明白这些道理。
查看API文档,发现是字节流通向字符流的桥梁。查看构造,可以传递字节流,可以指定编码,该流可以实现什么功能?很显然可以包装我们的字节流,自动的完成节流编码和解码的工作。该流是一个Reader的子类,是字符流的体系。所以将转换流称之为字节流和字符流之间的桥梁。
InputStreamReader 是字节流通向字符流的桥梁
测试InputStreamReader:
第一步: 需要专门新建以GBK编码的文本文件。为了便于标识,我们命名为gbk.txt
和以UFT-8编码的文本文件,命名为utf.txt
第二步: 分别写入汉字”中国”
第三步:编写测试方法,用InputStreamReader 分别使用系统默认编码,GBK,UTF-8编码读取文件.
注意:码表不对应
分别测试:
使用系统默认编码读取utf-8编码文件
使用utf-8编码读取gbk编码文件
使用"gbk”编码读取utf-8文件.
发现都会出现乱码的问题.
类 OutputStreamWriter
OutputStreamWriter
有了InputStreamReader 可以转换InputStream
那么其实还有OutputStreamWriter 可以转换OutputStream
OutputStreamWriter 是字符流通向字节流的桥梁
测试OutputStreamWriter
一: 分别使用OutputStreamWriter使用系统默认编码,GBK,UTF-8相对应的默认编码文件,GBK编码文件,UTF-8编码文件中写出汉字”中国”.
二: 在使用上述案例中的readFile方法传入相对应码表读取.
注意: 码表不对应的问题
分别测试:
向GBK文件中写入utf-8编码的信息
向utf文件中写入gbk编码的信息
发现文件都有问题,无法正常的读取了.
InputStreamReader:字节到字符的桥梁。
OutputStreamWriter:字符到字节的桥梁。
它们有转换作用,而本身又是字符流。所以在构造的时候,需要传入字节流对象进来。
构造函数:
InputStreamReader(InputStream)
通过该构造函数初始化,使用的是本系统默认的编码表GBK。
InputStreamReader(InputStream,String charSet)
通过该构造函数初始化,可以指定编码表。
OutputStreamWriter(OutputStream)
通过该构造函数初始化,使用的是本系统默认的编码表GBK。
OutputStreamWriter(OutputStream,String charSet)
通过该构造函数初始化,可以指定编码表。
注意:
操作文件的字符流对象是转换流的子类。
注意:
在使用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");
(自己调用自己,有结束条件)
注意:递归时一定要明确结束条件。
数学中递归运算.
对于任何正整数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程序计算阶乘?
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
答案:
案例一:
案例二
案例三:
2,移动一个非空的目录到另一个地方(剪切)。
3,把File类中的重要方法设计代码测试一遍。
0o
练习2:
使用File类的renameTo 方法和递归实现非空目录的剪切.
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(); } |
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 |
案例:
序列化和反序列化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(); } class Cat implements Serializable { public String name; 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. 反序列化就是调用ObjectInputStream的readObject()方法。
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; publicclass Dog implements Serializable { private Collar collar; private String name; 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 |
两种解决方法:
一:继承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,就会出现运行时异常
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(); } } |
用于给类指定一个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)); } } |
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
PrintWriter1,打印流。
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(); } |
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. 操作字符数组
CharArrayReaderCharArrayWriter
对于这些流,源是内存。目的也是内存。
而且这些流并未调用系统资源。使用的就是内存中的数组。
所以这些在使用的时候不需要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写数据文件。
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)); } } } |
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
有了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-8向utf-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-8向utf-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"); } |
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,列出指定目录中所有的子孙文件与子孙目录名,只需要列出名称即可。 privatestaticvoid listFile(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,列出指定目录中所有的子孙文件与子孙目录名,要求名称前面要有相应数量的空格: privatestaticvoid listFile2(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(); } |
练习2:
使用File类的renameTo 方法和递归实现非空目录的剪切.
publicstaticvoid main(String[] args) throws IOException { // 重命名文件(成功) // File src = new File("c:\\aaa.txt"); // File dest = new File("c:\\bbb.txt"); // src.renameTo(dest); // //移动文件(成功) // File src = new File("c:\\aaa.txt"); // File dest = new File("d:\\aaa.txt"); // src.renameTo(dest); // 移动一个空目录(失败) // File src = new File("c:\\aaa"); // File dest = 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(); } |
相关文章推荐
- 黑马程序员_Java基础_其它对象和IO流(字符流)
- 黑马程序员—【Java基础篇】之IO流(二)———File类、递归、IO其它流和编码表
- 黑马程序员——java基础(IO流其它类)
- 黑马程序员_IO流
- 黑马程序员——IO流(一)
- 黑马程序员__IO流
- 黑马程序员_IO流
- 黑马程序员_JAVA 之IO流
- AdaultBird--我的黑马程序员之路!Chapter7---File类,IO流里的其他工具类,GUI
- 黑马程序员_java基础笔记(07)...IO流
- 黑马程序员_IO流
- 黑马程序员—IO流
- 黑马程序员-Java语言基础– IO流 第18天
- 黑马程序员--IO流(20天)
- 黑马程序员-IO流练习
- 黑马程序员-IO流 02
- 黑马程序员--IO流III--
- 黑马程序员--Java基础学习笔记【IO流-字节流、转换流】
- 黑马程序员--IO流(1)
- 黑马程序员—字节流+字符流缓冲区(装饰设计)+转换流+IO流操作规律+异常日志+系统信息列表输出