一、PrintStream和PrintWriter
PrintStream
为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
PrintStream
打印的所有字符都使用平台的默认字符编码转换为字节。
在需要写入字符而不是写入字节的情况下,应该使用
类。PrintWriter
1 /**
2 * PrintStream 3 * 1.提供了打印方法,可以对多种类型值进行打印,并保持数据的原有格式 4 * 2.它不抛IOException 5 * 6 * 构造函数 接受三种类型的值 7 * 1.字符串路径 8 * 2.File对象 9 * 2.字符输出流 10 */
11 public class PrintStreamDemo { 12
13 public static void main(String[] args) throws IOException { 14 PrintStream out=new PrintStream("print.txt"); 15 out.write(97);//a 特点:只保留数最低的八位
16 out.write(610);//a
17 out.print(97);//97 特点:将数据先转成String类型,再打印出来
18 } 19 }
1 /**
2 * PrintWriter:字符打印流 3 * 构造方法参数: 4 * 1.字符串路径 5 * 2.File对象 6 * 3.字节输出流 7 * 4.字符输出流 8 */
9 public class PrintWriterDemo { 10
11 public static void main(String[] args) throws IOException { 12 BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));//读取流 13 //字符写入流
14 PrintWriter pw=new PrintWriter(new FileWriter("out.txt"),true);//自动刷新
15 String line=null; 16 while((line=bufr.readLine())!=null){ 17 if(line.equals("over")){ 18 break; 19 } 20 pw.println(line.toUpperCase()); 21 } 22 pw.close(); 23 bufr.close(); 24 } 25 }
二、ObjectOutputStream和ObjectInputStream以及Serializable接口
ObjectOutputStream为了延长对象的生命周期,把对象序列化存储到硬盘的文件中,写入的对象必须实现Serializable接口;
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。
可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
readObject
方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。
在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型
1 public class ObjectStreamDemo { 2
3 public static void main(String[] args) throws IOException, ClassNotFoundException { 4 writeObjectDemo(); 5 readObjectDemo(); 6 } 7
8 public static void readObjectDemo() throws IOException, ClassNotFoundException { 9 //ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
10 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("out.object")); 11 Person p=(Person)ois.readObject(); 12 Person p2=(Person)ois.readObject(); 13 System.out.println(p.getName()+"::"+p.getAge());//花蝴蝶::12
14 System.out.println(p2.getName()+"::"+p2.getAge());//大脸猫::2
15 } 16
17 public static void writeObjectDemo() throws IOException, FileNotFoundException { 18 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("out.object")); 19 oos.writeObject(new Person("花蝴蝶",12)); 20 oos.writeObject(new Person("大脸猫",2)); 21 oos.close(); 22 } 23 }
* Serializable接口可以为被序列化的类提供类ID号
* 来判断存储的对象和类是否是同一个版本
* 如果ID号不同的话,会发生InvaildClassException异常
*
* transient:如果一个属性不是静态化的,但又不想被序列化存储,就可以用这个关键字修饰
下面是实现了Serializable接口的Person类的代码:
1 package www.brighten.io; 2
3 import java.io.Serializable; 4
5 public class Person implements Serializable { 6 /**
7 * Serializable接口可以为被序列化的类提供类ID号 8 * 来判断存储的对象和类是否是同一个版本 9 * 如果ID号不同的话,会发生InvaildClassException异常 10 * 11 * transient:如果一个属性不是静态化的,但又不想被序列化存储,就可以用这个关键字修饰 12 */
13 private static final long serialVersiOnUID= 3221L; 14 private transient String name; 15 private static int age; 16 public Person(String name, int age) { 17 super(); 18 this.name = name; 19 this.age = age; 20 } 21 public String getName() { 22 return name; 23 } 24 public void setName(String name) { 25 this.name = name; 26 } 27 public int getAge() { 28 return age; 29 } 30 public void setAge(int age) { 31 this.age = age; 32 } 33
34 }
三、RandomAccessFile
此类的实例支持对随机访问文件的读取和写入。
随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。
存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。
* RandomAccessFile这个类从名字就可以看出,
* 不是IO体系总的子类,它的父类是Object
* 特点:
* 1.既能读,又能写。
* 2.该对象内部维护了一个大型byte数组,可以通过指针对数组元素进行操作
* 3.可以通过getFilePointer获取指针;通过seek方法设置指针位置
* 4.其实该对象就是对字节输入流和输出流进行了封装
* 5.该对象的源或者目的只能是文件,通过构造方法可以看出。
1 package www.brighten.io; 2
3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7
8 public class RandomAccessFileDemo { 9
10 public static void main(String[] args) throws IOException { 11 writeFiles(); 12 randomWrite(); 13 readFiles(); 14 } 15 public static void randomWrite() throws IOException { 16 RandomAccessFile raf=new RandomAccessFile("randomFile.txt", "rw"); 17 raf.seek(3*8); 18 /**
19 * 可以让不同的线程对不同的部分进行写入 20 * 应用如断点续传 21 */
22 raf.write("爱新觉罗溥仪".getBytes()); 23 raf.writeInt(22); 24 raf.close(); 25
26 } 27 public static void readFiles() throws IOException { 28 RandomAccessFile raf=new RandomAccessFile("randomFile.txt", "rw"); 29 //用seek方法设置指针的位置,直接读取第二个人的信息
30 raf.seek(8); 31 byte[] buf=new byte[4]; 32 raf.read(buf); 33 String name=new String(buf); 34
35 int age=raf.readInt(); 36 System.out.println("name="+name);//name=杜甫
37 System.out.println("age="+age);//age=99
38
39 raf.close(); 40 } 41 //使用RandomAccessFile写入一些人员信息,比如,姓名和年龄
42 public static void writeFiles() throws IOException { 43 /**
44 * IO流不同,存不存在,都会创建 45 * RandomAccessFile,如果文件不存在,则创建;如果文件存在,就不创建 46 */
47 RandomAccessFile raf=new RandomAccessFile(new File("randomFile.txt"), "rw"); 48 raf.write("李白".getBytes() ); 49 raf.writeInt(97); 50 raf.write("杜甫".getBytes() ); 51 raf.writeInt(99); 52 raf.close(); 53 } 54 }
四、PipedInputStream和PipedOutputStream
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。
通常,数据由某个线程从 PipedInputStream
对象读取,并由其他线程将其写入到相应的 PipedOutputStream
。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
1 public class PipedStream { 2
3 public static void main(String[] args) throws IOException { 4 PipedInputStream in=new PipedInputStream(); 5 PipedOutputStream out=new PipedOutputStream(); 6 in.connect(out);//管道读取流和写入流相连接
7 new Thread(new Input(in)).start(); 8 new Thread(new Output(out)).start(); 9 } 10 } 11 class Input implements Runnable{ 12 private PipedInputStream in; 13 public Input(PipedInputStream in){ 14 this.in=in; 15 } 16 @Override 17 public void run() { 18 byte[] buf=new byte[1024]; 19 int len; 20 try { 21 len = in.read(buf); 22 String str=new String(buf,0,len); 23 System.out.println(str); 24 } catch (IOException e) { 25 // TODO Auto-generated catch block
26 e.printStackTrace(); 27 } 28 } 29 } 30
31 class Output implements Runnable{ 32 private PipedOutputStream out; 33 public Output(PipedOutputStream out){ 34 this.out=out; 35 } 36 @Override 37 public void run() { 38 try { 39 Thread.sleep(3000); 40 out.write("Hello,管道流来了!".getBytes()); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 } 45 }
五、DataInputStream和DataOutputStream
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
1 public class DataStreamDemo { 2
3 public static void main(String[] args) throws IOException { 4 writeDemo(); 5 readDemo(); 6 } 7
8 public static void readDemo() throws IOException { 9 DataInputStream dis=new DataInputStream(new FileInputStream("data.txt")); 10 String str=dis.readUTF(); 11 dis.close(); 12 System.out.println(str); 13 } 14
15 public static void writeDemo() throws IOException { 16 DataOutputStream dos=new DataOutputStream(new FileOutputStream("data.txt")); 17 dos.writeUTF("世界,你好!"); 18 dos.close(); 19 } 20 }
六、以ByteArrayInputStream为代表的类
ByteArrayInputStream
包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read
方法要提供的下一个字节。
关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
特点:源和目的都是内存
类似的类有CharArrayReader 、CharArrayWriter、StringReader、 StringWriter。
1 public class ByteArrayStreamDemo { 2 public static void main(String[] args) { 3 ByteArrayInputStream bais=new ByteArrayInputStream("Hello World!".getBytes()); 4 ByteArrayOutputStream baos=new ByteArrayOutputStream(); 5 int ch=0; 6 while((ch=bais.read())!=-1){ 7 baos.write(ch); 8 } 9 System.out.println(baos.toString());//Hello World!
10 } 11 }
七、编码问题(解码错误的补救方法和联通问题)
开发常用的编码有GBK、utf-8
* 字符串-->字节数组:编码
* 字节数组-->字符串:解码
* 你好的GBK编码:-60 -29 -70 -61
* 你好的UTF-8编码:-28 -67 -96 -27 -91 -67
1 public class EncodeDemo { 2
3 public static void main(String[] args) throws UnsupportedEncodingException { 4 //demo1();
5 demo2(); 6 } 7
8 /**
9 * 如果你编错了,肯定解不出来 10 如果解错了,可能还有办法补救 11 */
12 public static void demo1() throws UnsupportedEncodingException { 13 String str=new String("自律给我自由"); 14 //编码
15 byte[] buf=str.getBytes("GBK"); 16 //printBytes(buf); 17 //解码
18 String s1=new String(buf,"iso8859-1"); 19 System.out.println("s1="+s1);//s1=×???????×??? 20
21 //解码错误,补救方法
22 byte[] buf2=s1.getBytes("iso8859-1");//获取源字节
23 String s2=new String(buf2,"GBK"); 24 System.out.println("s2="+s2);//s2=自律给我自由
25 } 26 /**
27 * 补救失败,因为按照GBK编码得到的字节在utf-8中无法查到对应数据,就用了别的字符替代 28 * 这样再次解码成字节后,已经不是原来的字节数组了 29 * 特例:“谢谢”可以。 30 * 所以说“解码错了,可能还有方法补救” 31 * @throws UnsupportedEncodingException 32 */
33 public static void demo2() throws UnsupportedEncodingException { 34 String str=new String("举杯邀明月"); 35 byte[] buf=str.getBytes("gbk"); 36 printBytes(buf);//-66 -39 -79 -83 -47 -5 -61 -9 -44 -62
37
38 String s1=new String(buf,"utf-8"); 39 System.out.println("s1="+s1); 40
41 //用demo1中的方法补救
42 byte[] buf2=s1.getBytes(); 43 printBytes(buf2);//63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
44 String s2=new String(buf2,"gbk"); 45 System.out.println("s2="+s2); 46 } 47
48 public static void printBytes(byte[] buf) { 49 for(byte b:buf){ 50 System.out.print(b+" "); 51 } 52 System.out.println(); 53 } 54 }
* 有一个让人疑惑的现象。
* 用Windows记事本新建一个文件,在里面写上“联通”两字
* 保存以后再打开,就会看到"ͨ",这种很奇怪的字符,
* 下面的程序就来分析一下原因。
* 运行程序后可以得到“联通”的GBK编码的字节是
11000001
10101010
11001101
10101000
开头依次是110、10、110、110;正好也符合utf-8两个字节一个字符的编码特点,
所以记事本就以utf-8的规则进行解码了,而没用GBK解码,所以出现了乱码
1 public class LianTongBug { 2
3 public static void main(String[] args) throws IOException { 4 String str=new String("联通"); 5 byte[] buf=str.getBytes("gbk"); 6 for(byte b:buf){ 7 System.out.println(Integer.toBinaryString(b&255));//只取后八位
8 } 9 } 10 }