黑马程序员-JAVA基础-IO流其他类
一.Properties 类
Properties 类是HashTable 的子类,它具备Map 集合的特点,而且它里面存储的键值对都是字符串。同时,Properties 类是集合中和IO技术相结合的集合容器。
该对类的特点:可以用于键值对形式的配置文件。通常键与值用“=”连接。
1.设置和获取Properties 对象元素。
步骤:
1、创建Properties 对象。
2、调用setProperty 方法设置元素。
3、通过调用 stringPropertyNames() 方法返回 Set<String> 集合进行遍历。
1 public class PropertiesDemo { 2 public static void main(String[] args)throws IOException { 3 setAndGet(); 4 } 5 // 设置和获取元素 6 public static void setAndGet() { 7 Properties prop = new Properties() ; 8 9 // 设置元素: 10 prop.setProperty("张三", "10000") ; 11 prop.setProperty("李四", "12") ; 12 // 获取元素: 13 Set<String> names = prop.stringPropertyNames(); 14 for (String name : names) { 15 System.out.println(name +"="+prop.getProperty(name)); 16 } 17 } 18 }
> Set<String> stringPropertyNames() : 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。
> Object setProperty(String key, String value) : 调用 Hashtable 的方法 put。
2.将流中的数据存储到集合中。
练习:将info.txt 中键值对数据存到集合中进程操作。
步骤:
1、用一个流和info.txt文件关联。
2、读取一行数据,将该行数据用“=”进行切割。
3、等号左边为键,右边为值,存储Properties 集合中即可。
1 public class PropertiesDemo { 2 public static void main(String[] args)throws IOException { 3 Method_1(); 4 } 5 public static void Method_1() { 6 BufferedReader br = null ; 7 Properties prop = new Properties() ; 8 try { 9 br = new BufferedReader(new FileReader("D:\\info.txt")); 10 String str = null ; 11 while ((str = br.readLine()) != null) { 12 String[] name = str.split("=") ; 13 prop.setProperty(name[0] , name[1]) ; 14 } 15 // 获取元素: 16 Set<String> names = prop.stringPropertyNames(); 17 for (String name : names) { 18 System.out.println(name +"="+prop.getProperty(name)); 19 } 20 // System.out.println(prop); 21 } 22 catch (IOException e) { 24 } 25 finally { 26 if (br != null) 27 try { 28 br.close() ; 29 } 30 catch(Exception e) { 31 } 32 } 33 }
34 }
以上代码其实相当于模拟Properties 类中的 load 方法。
> void list(PrintStream out) 将属性列表输出到指定的输出流。
> void list(PrintWriter out) 将属性列表输出到指定的输出流。
> void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
> void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)(JDK 1.6才出现的)。
1 public class PropertiesDemo { 2 public static void main(String[] args)throws IOException { 3 LoadDemo(); 4 } 5 6 public static void LoadDemo() throws IOException { 7 Properties prop = new Properties() ; 8 FileReader fis = new FileReader("D:\\info.txt") ; 9 // 将流中的数据加载进集合。那么在加载数据是,需要数据有固定格式: 键=值。 10 prop.load(fis) ; 11 prop.list(System.out) ; 12 13 prop.setProperty("萧鼎", "诛仙") ; 14 // 将修改的集合存储到文件中、 15 FileWriter fw = new FileWriter("D:\\info.txt") ; 16 prop.store(fw, "info") ; 17 prop.list(System.out) ; 18 19 fw.close() ; 20 fis.close() ; 21 22 } 23 }
>void store(OutputStream out, String comments) 以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
>void store(Writer writer, String comments) 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符(JDK 1.6才出现的)。
通过load 方法和store 方法可以很方便的操作配置文件数据的存储。
3.练习:用于记录应用程序运行的次数。如果使用次数已到,那么给出注册提示。
步骤:
1、创建File 对象用于封装文件路径。
2、设置读取流 。
3、读取配置文件,如果time 的值等于大于规定次数,则给出提示并结束程序。
4、如果没到规定次数,则设置配置文件。
1 public class PropertiesTest { 2 public static void main(String... args) throws IOException { 3 // 该程序运行一次,运行次数增加一次,当次数达到限定的使用次数, 4 // 那么给出注册提示。 5 6 // 封装文件 7 File file = new File("D:\\count.ini") ; 8 if (! file.exists()) 9 file.createNewFile() ; 10 11 // 设置读取流 12 FileInputStream fis = new FileInputStream(file) ; 13 Properties prop = new Properties() ; 14 15 // 读取配置文件。 16 prop.load(fis) ; 17 String values = prop.getProperty("time") ; 18 int count = 0 ; 19 if (values != null) { 20 count = new Integer(values).intValue() ; 21 if (count >= 5) { 22 System.out.println("免费使用次数已经到了!"); 23 return ; 24 } 25 } 26 count ++ ; 27 // 设置配件信息 28 prop.setProperty("time", count+"") ; 29 FileOutputStream fos = new FileOutputStream(file) ; 30 prop.store(fos, "" ) ; 31 fos.close() ; 32 fis.close() ; 33 } 34 }
二.PrintStream 与PrintWriter 类:
该流提供了打印方法,可以将各种数据类型的数据都原样打印,如:print、printf、println等等。
构造方法可以接收的参数类型:
字节打印流:PrintStream
1、File 对象 :
> PrintStream(File file) 创建具有指定文件且不带自动行刷新的新打印流.
> PrintStream(File file, String csn) 创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
2、字符串路径:String
3、字节输出流:
>PrintStream(OutputStream out) 创建新的打印流。
>PrintStream(OutputStream out, boolean autoFlush) 创建新的打印流。如果为 true,则每当写入 byte 数组、调用其中一个 println
方法或写入换行符或字节 ('\n'
) 时都会刷新输出缓冲区。
字符打印流:PrintWriter
1、File 对象 :
> PrintWriter(File file) 创建具有指定文件且不带自动行刷新的新打印流.
> PrintWriter(File file, String csn) 创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
2、字符串路径:String
3、字节输出流:
>PrintWriter(OutputStream out) 根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
>PrintWriter(OutputStream out, boolean autoFlush) 创建新的打印流。autoFlush
- boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区。
4、字符输出流:
>PrintWriter(Writer out) 创建不带自动行刷新的新 PrintWriter。
>PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter。 autoFlush - boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区。
1 import java.io.*; 2 public class PrintStreamDemo { 3 public static void main(String... args) throws IOException { 4 // 键盘读入流 5 BufferedReader br = 6 new BufferedReader (new InputStreamReader(System.in)) ; 7 // 打印流: 8 PrintWriter out = new PrintWriter(System.out , true) ; 9 10 String line = null ; 11 while((line = br.readLine()) != null) { 12 if ("over".equals(line)) 13 break ; 14 out.println(line.toUpperCase()) ; 15 } 16 } 17 }
三.切割文件和合并流:
1、切割文件
顾名思义,切割文件则是将一个文件切割成多个碎片。没有专门用于切割文件的类,其步骤:
1、关联被切割的文件。
2、创建一个容器,容器大的小定为没块碎片的大小。
3、每次读取容器大小文件,并创建输出流,将其存储到硬盘上。
1 import java.io.*; 2 import java.util.*; 3 public class SpiltFile { 4 public static void main(String[] args ) throws IOException { 5 // 关联被切割的文件。 6 FileInputStream fis = new FileInputStream("F:\\KuGou\\Celine Dion - I Surrender.mp3") ; 7 8 FileOutputStream fos = null ; 9 10 // 创建每块文件碎片的大小。 11 byte[] bt = new byte[1024 * 1024] ; 12 int count = 1 ; 13 int len = 0 ; 14 while((len = fis.read(bt)) != -1 ) { 15 // 每次创建一个 bt 数组大小的文件碎片。 16 fos = new FileOutputStream("D:\\Celine Dion - I Surrender"+(count++)+".part") ; 17 fos.write(bt,0,len) ; 18 fos.close() ; 19 } 20 fis.close() ; 21 } 22 }
2、合并流:
合并流则是将多个源文件组装成一个源文件,然后再通过输出流将其保存到目标文件。实现该操作则要用到SequenceInputStream 类。
SequenceInputStream
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
构造函数:
>SequenceInputStream(Enumeration<? extends InputStream> e) 通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。
>SequenceInputStream(InputStream s1, InputStream s2) 通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。
操作步骤:
1、创建Vector 集合。将要合并的文件添加至Vector 集合
2、通过Vector 集合,创建Enumeration 对象。
3、创建 SequenceInputStream 对象进行合并流。
4、输出文件。
将切割文件时切割出的文件碎片进行合并:
1 import java.io.*; 2 import java.util.*; 3 public class SpiltFile { 4 public static void main(String[] args ) throws IOException { 5 merger() ; 6 } 7 public static void merger() throws IOException { 8 // 创建Vector 集合。 9 Vector<FileInputStream> v = new Vector<FileInputStream>() ; 10 File file = new File("D:\\") ; 11 // 筛选出后缀名是.part的文件进行合并。 12 File[] files = file.listFiles(new FileFilter() { 13 public boolean accept(File pathname) { 14 if (pathname.getName().endsWith(".part")) 15 return true; 16 return false ; 17 } 18 }) ; 19 // Vector 集合添加元素。 20 for (File name : files) { 21 v.add(new FileInputStream(name)) ; 22 } 23 // 创建Enumeration 对象 24 Enumeration<FileInputStream> en = v.elements() ; 25 // 创建SequenceInputStream 对象 26 SequenceInputStream sis = new SequenceInputStream(en) ; 27 // 输出文件。 28 FileOutputStream fos = new FileOutputStream("D:\\I.mp3") ; 29 byte[] by = new byte[1024] ; 30 int len = 0 ; 31 while((len = sis.read(by)) != -1) { 32 fos.write(by,0,len) ; 33 } 34 sis.close() ; 35 fos.close() ; 36 } 37 }
四.对象的序列化:
ObjectInputStream 和 ObjectOutputStream 被操作的对象需要实现Serializable (标记接口:没有方法的接口)。把对象存储到硬盘上,实现对象的持久化(序列化) 。
注意:
关于 Serializable 接口,类通过实现 java.io.Serializable 接口以启用其序列化功能。序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程
中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致
InvalidClassException
。可序列化类可以通过声明名为 "serialVersionUID"
的字段(该字段必须是静态 (static)、最终 (final) 的 long
型字段)显式声明其自己的 serialVersionUID:如果可序列化
类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。
static final long serialVersionUID = 42L
注意:静态属性无法被序列化。
1 import java.io.*; 2 public class ObjectInputStreamDemo { 3 public static void main(String[] args )throws Exception { 4 // writerObj(); 5 readObj(); 6 } 7 public static void readObj() throws Exception { 8 // 创建 ObjectInputStream 对象。 9 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\person.object")) ; 10 11 Person p = (Person ) ois.readObject() ; 12 System.out.println(p) ; 13 ois.close() ; 14 } 15 public static void writerObj() throws IOException { 16 ObjectOutputStream oos = 17 new ObjectOutputStream(new FileOutputStream("D:\\person.object")) ; 18 19 Person p = new Person("李四" , 22 , "beijing") ; 20 oos.writeObject(p ) ; 21 System.out.println(p); 22 oos.close() ; 23 24 } 25 } 26 class Person implements Serializable { 27 private String name ; 28 private int age ; 29 static String city = "hunan" ; 30 public Person(String name , int age , String city) { 31 this.name = name ; 32 this.age = age ; 33 this.city = city ; 34 } 35 public String toString() { 36 return name + ":" + age + ":"+city; 37 } 38 }
先运行writerObj() 方法时,第21行打印出来的是”李四:22:beijing“ , 然后再运行readObj() 方法时,第12行打印出来的是”李四:22:hunan" 。所以静态属性是不能被序列化的,但是如果想让不是静态的属性也不被序列化,则可以用 transient 关键字进行修饰。
注意:如果是 ObjectOutputStream 做的文件,只能用ObjectInputStream 来进行读取。
五.管道流:PipedInputStream 与PipedOutputStream 类输入和输出可以直接进行连接,通过结合线程使用。
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream
对象读取,并由其他线程将其写入到相应的
PipedOutputStream
。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
PipedInputStream 与 PipedOutputStream 类 输入和输出可以直接进行连接,通过结合线程使用。
如何将PipedInputStream 与 PipedOutputStream类进行连接呢?
1、通过构造函数:
> PipedInputStream(PipedOutputStream src) 创建 PipedInputStream,使其连接到管道输出流 src。
> PipedOutputStream(PipedInputStream snk) 创建连接到指定管道输入流的管道输出流。
2、通过connect 方法:
> void connect(PipedOutputStream/PipedOutputStream src) 使此管道输入/输出流连接到管道输入/输出流 src。
1 import java.io.*; 2 class ReadThread implements Runnable { 3 private PipedInputStream pis ; 4 public ReadThread(PipedInputStream pis){ 5 this.pis = pis ; 6 } 7 public void run() { 8 try { 9 byte[] by = new byte[1024] ; 10 // 如果没有数据,则会线程阻塞在这,当有数据时,继续运行。 11 int len = pis.read(by) ; 12 String s = new String(by,0,len) ; 13 System.out.println(s); 14 pis.close() ; 15 } 16 catch(Exception e) { 17 } 18 } 19 } 20 class WriterThread implements Runnable { 21 private PipedOutputStream pos ; 22 public WriterThread(PipedOutputStream pos) { 23 this.pos = pos ; 24 } 25 public void run() { 26 try { 27 // Thread.sleep(9000) ; 28 // 开始写入数据, 29 String str = "hi!" ; 30 pos.write(str.getBytes()) ; 31 pos.close() ; 32 } 33 catch(Exception e) { 34 } 35 } 36 } 37 public class PipedDemo { 38 public static void main(String[] args) throws IOException { 39 PipedInputStream pis = new PipedInputStream() ; 40 PipedOutputStream pos = new PipedOutputStream() ; 41 // 连接输入输出流 42 pis.connect(pos) ; 43 44 // 创建线程 45 ReadThread rt = new ReadThread(pis) ; 46 WriterThread wt = new WriterThread(pos) ; 47 48 new Thread(rt).start() ; 49 new Thread(wt).start() ; 50 } 51 }
六.RandomAccessFile 类:
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读
取字节,并随着对字节的读取而前移此文件指针。该文件指针可以通过 getFilePointer
方法读取,并通过 seek
方法设置。
简单来说就是:内部封装了一个数组,而且通过指针对数组的元素进行操作。
其构造方法:
> RandomAccessFile(File file, String mode) : 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
> RandomAccessFile(String name, String mode) 创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
其中 mode 参数指定用以打开文件的访问模式,允许的值及其含义为:
"r" | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException 。 |
"rw" | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
"rws" | 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" | 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
所以:通过构造函数知道,该类的局限性
1、只能操作文件。
2、操作文件还有模式规定。
1 import java.io.*; 2 public class RandomAccessFileDemo { 3 public static void main(String[] args)throws IOException { 4 readFile() ; 5 } 6 // 读取文件 7 public static void readFile() throws IOException { 8 RandomAccessFile raf = new RandomAccessFile("D:\\Random.txt", "r") ; 9 // 调整对象中指针 10 raf.seek(8*1) ; 11 byte[] buf = new byte[4] ; 12 raf.read(buf) ; 13 String name = new String(buf) ; 14 System.out.println(name); 15 // RandomAccessFile 类中提供读取基本类型的方法 16 int age = raf.readInt() ; 17 System.out.println(age); 18 } 19 // 写入文件 20 public static void writeFile() throws IOException { 21 RandomAccessFile raf = new RandomAccessFile("D:\\Random.txt" , "rw"); 22 raf.write("李四".getBytes()) ; 23 // RandomAccessFile 类中提供写基本类型的方法 24 raf.writeInt(65) ; 25 raf.write("张三".getBytes()) ; 26 // RandomAccessFile 类中提供写基本类型的方法 27 raf.writeInt(69) ; 28 } 29 }
seek 方法不仅可以在读取文件时调整文件指针,再写入文件时,也可以调整文件指针,进行覆盖前面的数据,或者跳一个位置写入数据。
口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口
个
指针(0)
当运行 raf.seek(8) 时。
口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口 口
个
指针(8)
七.DataInputStream 和DataOutputStram 类
可以用操作基本数据类型的数据,只要是操作基本数据类型的数据则用该流。
注意:虽然ObjectInputStream 流 也有操作基本数据类型数据的方法,但它主要是用来操作对象的。
1 import java.io.*; 2 public class DataStreamDemo { 3 public static void main(String[] args) throws IOException{ 4 readData() ; 5 } 6 public static void writeData() throws IOException { 7 DataOutputStream dos = 8 new DataOutputStream (new FileOutputStream("D:\\data.txt")) ; 9 10 dos.writeInt(2388) ; 11 12 dos.writeBoolean(false) ; 13 14 dos.writeDouble(8989.423) ; 15 16 dos.close() ; 17 } 18 public static void readData() throws IOException { 19 DataInputStream dis = 20 new DataInputStream (new FileInputStream("D:\\data.txt")) ; 21 // 读取的顺序要和写入的顺序一致,否则会出现错误。 22 int num = dis.readInt() ; 23 System.out.println(num); 24 25 boolean b = dis.readBoolean() ; 26 System.out.println(b) ; 27 28 double doub = dis.readDouble() ; 29 System.out.println(doub); 30 31 dis.close(); 32 } 33 }
> void writeUTF(String str) : 以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
注意:使用DataOutputStram 类中的writeUTF 方法写入时,必须只能用DataInputStream 类中的 readUTF 方法读取出来。
八.ByteArrayInputStream 与ByteArrayOutputStream 类:
这两个流是用来操作字节数组的流。
ByteArrayInputStream : 在构造的时候,需要接收数据源,而且数据源是一个字节组。包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
> ByteArrayInputStream(byte[] buf) 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
> ByteArrayInputStream(byte[] buf, int offset, int length) 创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayOutputStream : 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。此类实现了一个输出流,其中的数据被写入一个 byte 数
组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
注意:因为该流没有调用底层资源,关闭 ByteArrayInputStream 和ByteArrayOutputStream无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
1 import java.io.*; 2 public class ByteArrayStreamDemo { 3 public static void main(String[] args) { 4 ByteArrayInputStream bais = new ByteArrayInputStream("afewfa".getBytes()) ; 5 6 ByteArrayOutputStream baos = new ByteArrayOutputStream() ; 7 8 int len = 0 ; 9 while ((len = bais.read()) != -1) { 10 baos.write(len) ; 11 } 12 System.out.println(baos.toString()); 13 } 14 }
除了ByteArrayInputStream/ByteArrayOutputStream 操作字节数组流外,还有操作字符数组的流 CharArrayReader/CharArrayWrite和操作字符串流 StringReader/StringWrite。