PrintWriter打印流
Writer的子类,既可以接收字符流,也可以接收字节流,还可以接收文件名或者文件对象,非常方便
同时,还可以设置自动刷新以及保持原有格式写入各种文本类型的print方法
PrintWriter的小例子:打印字符录入的大写
1: //读取键盘录入,打印大写
2: private static void printWriterMethod() throws IOException
3: {
4: BufferedReader bufr =
5: new BufferedReader(new InputStreamReader(System.in));
6:
7: PrintWriter out = new PrintWriter(System.out,true);
8:
9: String line = null;
10:
11: while( (line = bufr.readLine()) != null)
12: {
13: if("over".equals(line))
14: break;
15: //只需一条语句,便可打印一行数据,非常方便
16: out.println(line.toUpperCase());
17: }
18:
19: out.close();
20: bufr.close();
21: }
SequenceInputStream合并流
序列流,可将多个流合并成一个流,按序列进行读取
可手动指定各个流创建对象,也可将多个流存入集合,利用枚举Enumeration来创建对象
合并和分割流的小例子:
1: import java.io.*;
2: import java.util.*;
3:
4: class SequenceInputStreamDemo
5: {
6: public static void main(String[] args) throws IOException
7: {
8:
9: int num = splitFile(new File("pic.jpg"));
10:
11: /*
12: 合并分割后的流
13: */
14:
15: //定义Vector集合存储所有part文件的字节输入流
16: Vector<FileInputStream> v = new Vector<FileInputStream>();
17:
18: for(int i = 1 ; i <= num ; i ++ )
19: {
20: v.add(new FileInputStream(i+".part"));
21: }
22:
23: Enumeration<FileInputStream> en = v.elements();
24:
25: FileOutputStream fos = new FileOutputStream("pic1.jpg");
26:
27: //定义序列流,通过枚举合并所有的输入流
28: SequenceInputStream sis = new SequenceInputStream(en);
29:
30: byte[] buf = new byte[1024];
31:
32: int len = -1;
33:
34: while( (len = sis.read(buf)) != -1)
35: {
36: //将合并后的流写入一个文件
37: fos.write(buf,0,len);
38: }
39:
40: fos.close();
41: sis.close();
42: }
43:
44: //分割流
45: private static int splitFile(File f) throws IOException
46: {
47: FileInputStream fis = new FileInputStream(f);
48:
49: long size = f.length();
50:
51: byte[] buf = null;
52:
53: //选择缓冲区大小
54: if(size > 1024*1024*5)
55: buf = new byte[1024*1024];
56: else
57: buf = new byte[(int)size/5];
58:
59: int len = -1;
60: int count = 1;
61:
62: while( (len = fis.read(buf)) != -1)
63: {
64: //每个缓冲区的内容分别写入不同的part文件
65: FileOutputStream fos = new FileOutputStream((count++)+".part");
66: fos.write(buf,0,len);
67: fos.close();
68: }
69:
70: fis.close();
71:
72: return count-1;
73: }
74: }
对象的序列化
ObjectInputStream,ObjectOutputStream
将对象存取在硬盘上,叫做对象的持久化存储(存储的是对象的属性值,而不是方法)
想要对对象进行序列化,该对象必须实现Serializable接口,Serializable接口没有方法,称为标记接口,实现过程只是给实现者加入一个序列化的ID:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 其实就是序列号,这个序列号是由变量的声明语句自动生成的,我们也可以自己定义类的序列号
对象序列化的小例子
1: import java.io.*;
2:
3: class Person implements Serializable
4: {
5: //序列号,保证类型一致
6: static final long serialVersionUID = 42L;
7:
8: //静态变量以及transient修饰的变量不会被序列化
9: static String country = "cn";
10: transient int grade;
11: private String name;
12: private int age;
13:
14: Person(String name,int age,int grade,String country)
15: {
16: this.name = name;
17: this.age = age;
18: this.grade = grade;
19: this.country = country;
20: }
21:
22: public String toString()
23: {
24: return name+"::"+age+"::"+grade+"::"+country;
25: }
26: }
27:
28: class ObjectStreamDemo
29: {
30: public static void main(String[] args) throws Exception
31: {
32:
33: //writeObj();
34: readObj();
35: }
36:
37: //将对象写入流中
38: private static void writeObj() throws IOException
39: {
40: ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
41:
42: oos.writeObject(new Person("Shawn",30,3,"en"));
43: oos.writeObject(new Person("feng",23,6,"usa"));
44:
45: oos.close();
46: }
47:
48: //将对象从流中读出并打印
49: private static void readObj() throws Exception
50: {
51: ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
52:
53: Person p1 = (Person)ois.readObject();
54: Person p2 = (Person)ois.readObject();
55:
56: System.out.println("p1 --- "+p1);
57: System.out.println("p2 --- "+p2);
58:
59: ois.close();
60: }
61: }
我们可以看到,静态变量和transient修饰的变量是不会被序列化到硬盘上的
管道流
PipedInputStream,PipedOutputStream
管道Demo,一个线程写,一个线程读
1: import java.io.*;
2:
3: //读管道流线程
4: class Read implements Runnable
5: {
6: private PipedInputStream pis;
7:
8: Read(PipedInputStream pis)
9: {
10: this.pis = pis;
11: }
12:
13: public void run()
14: {
15: try
16: {
17: byte[] buf = new byte[1024];
18:
19: int len = -1;
20:
21: //阻塞方法,读不到数据会等待
22: len = pis.read(buf);
23:
24: System.out.println(new String(buf,0,len));
25:
26: pis.close();
27: }
28: catch (IOException e)
29: {
30: System.out.println("pipe read error!");
31: }
32:
33: }
34: }
35:
36: //写管道流线程
37: class Write implements Runnable
38: {
39: private PipedOutputStream pos;
40:
41: Write(PipedOutputStream pos)
42: {
43: this.pos = pos;
44: }
45:
46: public void run()
47: {
48: try
49: {
50: pos.write("pipe is coming!".getBytes());
51:
52: pos.close();
53: }
54: catch (IOException e)
55: {
56: System.out.println("pipe write error!");
57: }
58:
59: }
60: }
61:
62: class PipedStreamDemo
63: {
64: public static void main(String[] args) throws IOException
65: {
66: PipedInputStream pis = new PipedInputStream();
67: PipedOutputStream pos = new PipedOutputStream();
68:
69: //链接读写管道
70: pis.connect(pos);
71:
72: new Thread(new Read(pis)).start();
73:
74: new Thread(new Write(pos)).start();
75: }
76: }
随机访问文件流
RandomAccessFile
直接继承Object类,内部封装了字节输入输出流,同时封装了文件的指针,可对基本数据类型进行直接读写,最大的好处是可以实现数据的分段写入,通过seek方法。
用随机访问实现的多线程复制文件(后期会改进代码,完成多线程下载)
1: import java.io.*;
2:
3: //下载线程
4: class DownLoadThread implements Runnable
5: {
6: private RandomAccessFile in;
7: private RandomAccessFile out;
8: private int offset;//偏移量
9: private int buf_size;//分配数据量
10: private int block_size;//缓冲区大小
11:
12: //初始化
13: DownLoadThread(RandomAccessFile in,RandomAccessFile out,int offset,int buf_size)
14: {
15: this.in = in;
16: this.out = out;
17: this.offset = offset;
18: this.buf_size = buf_size;
19:
20: block_size = 1024*512;
21: if(buf_size < block_size)
22: block_size = buf_size;
23:
24: }
25:
26: public void run()
27: {
28: try
29: {
30: System.out.println(Thread.currentThread().getName()+"开始下载...");
31:
32: //读写流都偏移到指定位置
33: in.seek(offset);
34: out.seek(offset);
35:
36: byte[] buf = new byte[block_size];
37:
38: int len = -1;
39:
40: int lastSize = buf_size;
41:
42: //读取信息并写入到目的地
43: while( (len = in.read(buf)) != -1)
44: {
45: out.write(buf,0,len);
46:
47: lastSize -= len;
48:
49: //分配数据量完成,结束线程
50: if(lastSize == 0)
51: break;
52: if(lastSize < block_size)
53: {
54: block_size = lastSize;
55: buf = new byte[block_size];
56: }
57: }
58:
59: System.out.println(Thread.currentThread().getName()+"下载完成!");
60:
61: in.close();
62: out.close();
63:
64: }
65: catch (IOException e)
66: {
67: throw new RuntimeException(e);
68: }
69:
70: }
71: }
72:
73: class MutiDownLoadDemo
74: {
75: public static void main(String[] args) throws Exception
76: {
77: //确定源文件和目的文件
78: File fin = new File("1.avi");
79: File fout = new File("5.avi");
80:
81:
82: multiDownload(fin,fout,12);
83:
84:
85: }
86:
87: //多线程下载 thread_num为线程数
88: private static void multiDownload(File fin,File fout,int thread_num) throws Exception
89: {
90: RandomAccessFile in = new RandomAccessFile(fin,"r");
91:
92: RandomAccessFile out = new RandomAccessFile(fout,"rwd");
93:
94: int len = (int)fin.length();
95:
96: //确定目的文件大小
97: out.setLength(len);
98:
99: in.close();
100: out.close();
101:
102: System.out.println("-----------File size : "+(len>>20)+" MB--------------");
103: System.out.println("-----------Thread num: "+thread_num+"---------");
104:
105: //确定每个线程分配的数据量
106: int buf_size = len/thread_num;
107:
108: System.out.println("-----------buffer size: "+(buf_size>>20)+" MB-----------");
109:
110: //开启每个线程
111: for(int i = 0 ; i < thread_num ; i ++)
112: {
113: //"rwd"模式代表可读可写并且线程安全
114: new Thread(
115: new DownLoadThread(new RandomAccessFile(fin,"r"),new RandomAccessFile(fout,"rwd"),i*buf_size,buf_size)
116: ).start();
117: }
118: }
119: }
基本数据类型流对象
DataInputStream,DataOutputStream
1: public static void main(String[] args) throws IOException
2: {
3: DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
4:
5: dos.writeInt(123);
6:
7: dos.writeDouble(123.45);
8:
9: dos.writeBoolean(true);
10:
11: DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
12:
13: System.out.println(dis.readInt());
14: System.out.println(dis.readDouble());
15: System.out.println(dis.readBoolean());
16: }
内存作为源和目的的流对象
操作字节数组
ByteArrayInputStream与ByteArrayOutputStream
操作字符数组
CharArrayReader与CharArrayWrite
操作字符串
StringReader 与 StringWriter
字符编码
字符流出现是为了更方便的操作字符,通过InputStreamReader和OutputStreamWriter可以任意指定编码表进行解码转换
编码表
计算机开始只能识别二进制数据,为了更方便的表示各个国家的文字,就将各个国家的文字与二进制数据进行一一对应,形成了一张表,即为编码表
常见的编码表
ASCII:美国标准信息交换码。
用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。
所有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8:最多用三个字节来表示一个字符
......
编码规则
只有GBK和UTF-8识别中文,GBK向下兼容GB2312
GBK两个字节表示一个字符,UTF-8是1-3个字节表示一个字符,每个字节前面1-3位作为标识头
GBK和UTF-8都兼容ASCII码表
模拟编解码过程代码
1: public static void main(String[] args) throws Exception
2: {
3: //字符串
4: String s = "你好";
5:
6: //用UTF-8编码表编码s字符串
7: byte[] b = s.getBytes("UTF-8");
8:
9:
10: System.out.println(Arrays.toString(b));
11:
12: //用GBK编码表解码
13: String s1 = new String(b,"GBK");
14:
15: //获取之后发现不是原来的字符串
16: System.out.println(s1);
17:
18: //用GBK重新编码回去
19: byte[] b1 = s1.getBytes("GBK");
20:
21: //再用UTF-8解码
22: String s2 = new String(b1,"UTF-8");
23:
24: System.out.println(s2);
25:
26:
27: }
这样做存在一个问题,由于GBK与UTF-8都支持中文,所以UTF-8编解码时有可能会去内部的相似码表去查找,这样编码出来的字符就会与原字符不符,所以一般使用ISO8859-1与中文码表互相编解码转换
一个有趣的小例子
新建一个文本文档,写入“联通”两个字,保存,关闭,再打开,发现变成了一个奇怪的字符,这是为什么呢?
首先windows默认的是ANSI编码,而UTF-8编码的标识头规则如下图
由于记事本是由编码本身的规律判断选取哪个编码表的
所以答案是,“联通”这两个字由ANSI编码之后的码流符合UTF-8的规则,则记事本自动识别是UTF-8的字符,而去查了UTF-8的码表
解决方法,我们只要在联通前面加上任意字符,记事本就不会误判为UTF-8解码了