java IO之 序列流 集合对象Properties 打印流 流对象
序列流
也称为合并流。
SequenceInputStream
序列流,对多个流进行合并。
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从
第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达
包含的最后一个输入流的文件末尾为止。
注意:
构造函数
SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(InputStream s1, InputStream s2)
合并两个流
使用构造函数SequenceInputStream(InputStream s1, InputStream s2)
private static void 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 = new byte[1024]; FileOutputStream fos = new FileOutputStream("c:\\z.txt"); while ((len = s1.read(byt)) != -1) { fos.write(byt, 0, len); } s1.close(); }
合并多个流:
public static void 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 public boolean 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歌曲文件进行切割拷贝,并合并.
public class Demo2 { public static void 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("合并完毕"); } private static void 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 public boolean hasMoreElements() { return it.hasNext(); } @Override public InputStream nextElement() { return it.next(); } }); byte[] byt = new byte[1024 * 1024]; int len = 0; while ((len = seq.read(byt)) != -1) { fos.write(byt, 0, len); } seq.close(); fos.close(); } // 1. 切割文件 /* * 切割文件,切割份数, 切割后保存路径 */ private static void split(File src, int count, File dir) throws IOException { FileInputStream fis = new FileInputStream(src); FileOutputStream fos = null; byte[] byt = new byte[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(); } }
对象的序列化
当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了。但是如果希望
对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程
序上次运行时拥有的信息相同。可以使用对象的序列化。
对象的序列化:将内存中的对象直接写入到文件设备中。
对象的反序列化:将文件设备中持久化的数据转换为内存对象
基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法
用于读取流并反序列化对象。
ObjectOutput
writeObject(Object obj) 将对象写入底层存储或流。
ObjectInput
readObject() 读取并返回对象。
ObjectOutputStream
ObjectInputStream
由于上述ObjectOutput和ObjectInput是接口,所以需要使用具体实现类。
ObjectOutput
ObjectOutputStream 被写入的对象必须实现一个接口:Serializable 否则会抛出:NotSerializableException
ObjectInput
ObjectInputStream 该方法抛出异常:ClassNotFountException
ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。
案例:
序列化和反序列化Cat对象。
public class demo3 { public static void 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("1"+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("2"+cat2); fis.close(); } } class Cat implements Serializable { public String name; public int age; public Cat() { } public Cat(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; } }
例子关键点:
- 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。
- 新建Cat对象。
- 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。
- 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。
- writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。
- 反序列化就是调用ObjectInputStream的readObject()方法。
- 异常处理和流的关闭动作要执行。
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; public class Dog implements Serializable { private Collar collar; private String name; public Dog(Collar collar, String name) { this.collar = collar; this.name = name; } public Collar getCollar() { return collar; } } class Collar { private int size; public int getSize() { return size; } public Collar(int size) { this.size = size; } }
序列化
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Demo4 { public static void 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。
public class Demo4 { public static void 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
注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。
静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。
基本数据类型可以被序列化
public class Demo5 { public static void 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;
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这样的键值对,与文件名无关,注释使用#
练习:记录一个程序运行的次数,当满足指定次数时,该程序就不可以再继续运行了。
通常可用于软件使用次数的限定。
public static void sysPropList() throws IOException { Properties prop = System.getProperties(); // prop.list(System.out);// 目的是控制台。 // 需求是:将jvm的属性信息存储到一个文件中。 prop.list(new PrintStream("java.txt")); } public static void 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中的数据特有方法
案例:
/* * 将配置文件中的数据通过流加载到集合中。 */ public static void 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(); }
获取记录程序运行次数:
public class Demo6 { public static void 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(); } }
打印流
PrintStream可以接受文件和其他字节输出流,所以打印流是对普通字节输出流的增强,
其中定义了很多的重载的print()和println(),方便输出各种类型的数据。
PrintStream
PrintWriter
1,打印流。
PrintStream:
是一个字节打印流,System.out对应的类型就是PrintStream。
它的构造函数可以接收三种数据类型的值。
1,字符串路径。
2,File对象。
3,OutputStream。
public class pp { public static void 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 打价格战", "京东", "苏宁"); } }
//结果: 100a100.5世界java.lang.Object@40e455bf-------------- 100 a 100.5 世界 java.lang.Object@4eb98fe1
注意: 打印流的三种方法
void print(数据类型 变量)
println(数据类型 变量)
printf(String format, Object... args)
可以自定数据格式
print 和println方法的区别在于,一个换行一个不换行
print 方法和write方法的却别在于,print提供自动刷新.
普通的write方法需要调用flush或者close方法才可以看到数据.
JDK1.5之后Java对PrintStream进行了扩展,增加了格式化输出方式,可以使用printf()重载方法直接格式化输出。但是在格式化输出的时候需要指定输出的数据类型格式。
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);
public static void 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
public static void 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(); }
操作数组的流对象
操作字节数组
ByteArrayInputStream 以及ByteArrayOutputStream
toByteArray();
toString();
writeTo(OutputStream);
public static void testByteArrayInputStream() throws Exception { InputStream in = new ByteArrayInputStream(new byte[] { 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); }
操作字符数组
CharArrayReader
CharArrayWriter
对于这些流,源是内存。目的也是内存。
而且这些流并未调用系统资源。使用的就是内存中的数组。
所以这些在使用的时候不需要close。
操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。
对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组作为缓冲区。
public static void testCharArrayReader() throws Exception { CharArrayReader reader = new CharArrayReader(new char[] { '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。
public static void 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()); }
操作基本数据类型的流对象
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写数据文件。
public static void 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(); }