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解码了

