您的位置:首页 > 编程语言 > Java开发

Java笔记(二十八)……IO流下 IO包中其他常用类以及编码表问题

2013-10-27 11:51 501 查看

PrintWriter打印流

Writer的子类,既可以接收字符流,也可以接收字节流,还可以接收文件名或者文件对象,非常方便

同时,还可以设置自动刷新以及保持原有格式写入各种文本类型的print方法

PrintWriter的小例子:打印字符录入的大写

[code] //读取键盘录入,打印大写
private static void printWriterMethod() throws IOException
{
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
 PrintWriter out = new PrintWriter(System.out,true);
 String line = null;
 while( (line = bufr.readLine()) != null)
{
 if("over".equals(line))
 break;
 //只需一条语句,便可打印一行数据,非常方便
 out.println(line.toUpperCase());
}
 
 out.close();
 bufr.close();
}

SequenceInputStream合并流

序列流,可将多个流合并成一个流,按序列进行读取

可手动指定各个流创建对象,也可将多个流存入集合,利用枚举Enumeration来创建对象

合并和分割流的小例子:

[code] import java.io.*;
import java.util.*;
class SequenceInputStreamDemo
{
 public static void main(String[] args) throws IOException
{
 int num = splitFile(new File("pic.jpg"));
 /*
 合并分割后的流
 */
 
 //定义Vector集合存储所有part文件的字节输入流
 Vector<FileInputStream> v = new Vector<FileInputStream>();
 for(int i = 1 ; i <= num ; i ++ )
{
 v.add(new FileInputStream(i+".part"));
}
 Enumeration<FileInputStream> en = v.elements();
 FileOutputStream fos = new FileOutputStream("pic1.jpg");
 
 //定义序列流,通过枚举合并所有的输入流
 SequenceInputStream sis = new SequenceInputStream(en);
 byte[] buf = new byte[1024];
 int len = -1;
 while( (len = sis.read(buf)) != -1)
{
 //将合并后的流写入一个文件
 fos.write(buf,0,len);
}
 fos.close();
 sis.close();
}
 
 //分割流
 private static int splitFile(File f) throws IOException
{
 FileInputStream fis = new FileInputStream(f);
 long size = f.length();
 byte[] buf = null;
 
 //选择缓冲区大小
 if(size > 1024*1024*5)
 buf = new byte[1024*1024];
 else
 buf = new byte[(int)size/5];
 int len = -1;
 int count = 1;
 while( (len = fis.read(buf)) != -1)
{
 //每个缓冲区的内容分别写入不同的part文件
 FileOutputStream fos = new FileOutputStream((count++)+".part");
 fos.write(buf,0,len);
 fos.close();
}
 fis.close();
 return count-1;
}
}

对象的序列化

ObjectInputStream,ObjectOutputStream

将对象存取在硬盘上,叫做对象的持久化存储(存储的是对象的属性值,而不是方法)

想要对对象进行序列化,该对象必须实现Serializable接口,Serializable接口没有方法,称为标记接口,实现过程只是给实现者加入一个序列化的ID:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 其实就是序列号,这个序列号是由变量的声明语句自动生成的,我们也可以自己定义类的序列号

对象序列化的小例子

[code] import java.io.*;
class Person implements Serializable
{
 //序列号,保证类型一致
 static final long serialVersionUID = 42L;
 //静态变量以及transient修饰的变量不会被序列化
 static String country = "cn";
 transient int grade;
 private String name;
 private int age;
 Person(String name,int age,int grade,String country)
{
 this.name = name;
 this.age = age;
 this.grade = grade;
 this.country = country;
}
 public String toString()
{
 return name+"::"+age+"::"+grade+"::"+country;
}
}
class ObjectStreamDemo
{
 public static void main(String[] args) throws Exception
{
 //writeObj();
 readObj();
}
 //将对象写入流中
 private static void writeObj() throws IOException
{
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
 oos.writeObject(new Person("Shawn",30,3,"en"));
 oos.writeObject(new Person("feng",23,6,"usa"));
 oos.close();
}
 
 //将对象从流中读出并打印
 private static void readObj() throws Exception
{
 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
 Person p1 = (Person)ois.readObject();
 Person p2 = (Person)ois.readObject();
 System.out.println("p1 --- "+p1);
 System.out.println("p2 --- "+p2);
 ois.close();
}
}





我们可以看到,静态变量和transient修饰的变量是不会被序列化到硬盘上的

管道流

PipedInputStream,PipedOutputStream

管道Demo,一个线程写,一个线程读

[code] import java.io.*;
//读管道流线程
class Read implements Runnable
{
 private PipedInputStream pis;
 Read(PipedInputStream pis)
{
 this.pis = pis;
}
 public void run()
{
 try
{
 byte[] buf = new byte[1024];
 
 int len = -1;
 
 //阻塞方法,读不到数据会等待
 len = pis.read(buf);
 System.out.println(new String(buf,0,len));
 pis.close();
}
 catch (IOException e)
{
 System.out.println("pipe read error!");
}
 
}
}
//写管道流线程
class Write implements Runnable
{
 private PipedOutputStream pos;
 Write(PipedOutputStream pos)
{
 this.pos = pos;
}
 public void run()
{
 try
{
 pos.write("pipe is coming!".getBytes());
 pos.close();
}
 catch (IOException e)
{
 System.out.println("pipe write error!");
}
}
}
class PipedStreamDemo
{
 public static void main(String[] args) throws IOException
{
 PipedInputStream pis = new PipedInputStream();
 PipedOutputStream pos = new PipedOutputStream();
 
 //链接读写管道
 pis.connect(pos);
 new Thread(new Read(pis)).start();
 new Thread(new Write(pos)).start();
}
}

随机访问文件流

RandomAccessFile

直接继承Object类,内部封装了字节输入输出流,同时封装了文件的指针,可对基本数据类型进行直接读写,最大的好处是可以实现数据的分段写入,通过seek方法。

用随机访问实现的多线程复制文件(后期会改进代码,完成多线程下载)

[code] import java.io.*;
//下载线程
class DownLoadThread implements Runnable
{
 private RandomAccessFile in;
 private RandomAccessFile out;
 private int offset;//偏移量
 private int buf_size;//分配数据量
 private int block_size;//缓冲区大小
 //初始化
 DownLoadThread(RandomAccessFile in,RandomAccessFile out,int offset,int buf_size)
{
 this.in = in;
 this.out = out;
 this.offset = offset;
 this.buf_size = buf_size;
 
 block_size = 1024*512;
 if(buf_size < block_size)
 block_size = buf_size;
}
 public void run()
{
 try
{
 System.out.println(Thread.currentThread().getName()+"开始下载...");
 
 //读写流都偏移到指定位置
 in.seek(offset);
 out.seek(offset);
 byte[] buf = new byte[block_size];
 
 int len = -1;
 int lastSize = buf_size;
 
 //读取信息并写入到目的地
 while( (len = in.read(buf)) != -1)
{
 out.write(buf,0,len);
 lastSize -= len;
 
 //分配数据量完成,结束线程
 if(lastSize == 0)
 break;
 if(lastSize < block_size)
{
 block_size = lastSize;
 buf = new byte[block_size];
}
}
 
 System.out.println(Thread.currentThread().getName()+"下载完成!");
 in.close();
 out.close();
 
}
 catch (IOException e)
{
 throw new RuntimeException(e);
}
 
}
}
class MutiDownLoadDemo
{
 public static void main(String[] args) throws Exception
{
 //确定源文件和目的文件
 File fin = new File("1.avi");
 File fout = new File("5.avi");
 
 multiDownload(fin,fout,12);
}
 
 //多线程下载 thread_num为线程数
 private static void multiDownload(File fin,File fout,int thread_num) throws Exception
{
 RandomAccessFile in = new RandomAccessFile(fin,"r");
 RandomAccessFile out = new RandomAccessFile(fout,"rwd");
 int len = (int)fin.length();
 
 //确定目的文件大小
 out.setLength(len);
 
 in.close();
 out.close();
 System.out.println("-----------File size : "+(len>>20)+" MB--------------");
 System.out.println("-----------Thread num: "+thread_num+"---------");
 
 //确定每个线程分配的数据量
 int buf_size = len/thread_num;
 System.out.println("-----------buffer size: "+(buf_size>>20)+" MB-----------");
 //开启每个线程
 for(int i = 0 ; i < thread_num ; i ++)
{
 //"rwd"模式代表可读可写并且线程安全
 new Thread(
 new DownLoadThread(new RandomAccessFile(fin,"r"),new RandomAccessFile(fout,"rwd"),i*buf_size,buf_size)
 ).start();
}
}
}

基本数据类型流对象

DataInputStream,DataOutputStream

[code] public static void main(String[] args) throws IOException
{
 DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
 dos.writeInt(123);
 dos.writeDouble(123.45);
 dos.writeBoolean(true);
 DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
 System.out.println(dis.readInt());
 System.out.println(dis.readDouble());
 System.out.println(dis.readBoolean());
}


内存作为源和目的的流对象

操作字节数组

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码表

模拟编解码过程代码

[code] public static void main(String[] args) throws Exception
{
 //字符串
 String s = "你好";
 
 //用UTF-8编码表编码s字符串
 byte[] b = s.getBytes("UTF-8");
 
 System.out.println(Arrays.toString(b));
 
 //用GBK编码表解码
 String s1 = new String(b,"GBK");
 
 //获取之后发现不是原来的字符串
 System.out.println(s1);
 
 //用GBK重新编码回去
 byte[] b1 = s1.getBytes("GBK");
 
 //再用UTF-8解码
 String s2 = new String(b1,"UTF-8");
 System.out.println(s2);
 
 
}

这样做存在一个问题,由于GBK与UTF-8都支持中文,所以UTF-8编解码时有可能会去内部的相似码表去查找,这样编码出来的字符就会与原字符不符,所以一般使用ISO8859-1与中文码表互相编解码转换

一个有趣的小例子

新建一个文本文档,写入“联通”两个字,保存,关闭,再打开,发现变成了一个奇怪的字符,这是为什么呢?

首先windows默认的是ANSI编码,而UTF-8编码的标识头规则如下图





由于记事本是由编码本身的规律判断选取哪个编码表的

所以答案是,“联通”这两个字由ANSI编码之后的码流符合UTF-8的规则,则记事本自动识别是UTF-8的字符,而去查了UTF-8的码表

解决方法,我们只要在联通前面加上任意字符,记事本就不会误判为UTF-8解码了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: