上面我们学习到了基本的字符流操作。但是,如果想要操纵图片数据,我们还是需要字节流才能解决。下面,我们来看字节流。字节流和字符流的不同,从类定义来看,最容易发现的就是字符流使用的是char类型数据作为传输对象,而字节流则是以byte类型数据来作为传输对象的。字节流是对字节的最小操作单位,因此不需要刷新。字符流刷新,是因为它底层调用的还是字节流传输数据的方法,只不过建立的是字符数组。
------- android培训、java培训、期待与您交流! ----------
字节流OutputStream
字节流OutputStream主要作用同字符写入流,只不过它能够传输的数据类型,不再局限于字符。OutputStream的操作流程如下:
图4.InputStream字节流写操作顺序图
字节流InputStream
InputStream的读取功能read方法,同Reader类似,也可以实现d读单个字节、一次性或分段读,返回值为所读取的字节数。使用流程如下:
图5.OutputStream字节流读操作顺序图
InputStream字节写入流对应的类中有一个特有的方法available方法,该方法不用传参,它的返回值为对应文件中的字节数。其中,对于Windows来说,如果出现换行,则换行符"\r\n"占用两个字节。此方法,主要读取数据进对应byte数组时,测数组大小。但是,使用这种方法建立存储数据的字节数组的前提,是必须保证数据大小小于内存大小的75%,否则,会导致内存溢出。
我用下面这个例子,来说明一下字节流对非文字数据的操作:
/**@author:LZ
*/
/*目标:将D盘下图片test_pic.png在D盘做个备份(即,copy操作)
思路:1 创建读取流,读取源文件
2 创建写入流,将读取入缓冲的数据写入
3 关闭读写流
*/
import java.io.*;
class InOutStreamTest
{
public static void main(String[] args)
{
FileInputStream i = null; //外部声明
FileOutputStream o = null;
try
{
i = newFileInputStream("D:\\test_pic.png"); //字节流的初始化
o = newFileOutputStream("D:\\test_pic_back.png");
byte [] b =new byte[1024];//暂存一般都定义为1024的整数倍
int num = 0;
//复制操作(即,本例中的复制图片)
while ((num =i.read(b))!=-1)
{
o.write(b,0,num);
}
}
catch (IOException e)
{
throw newRuntimeException("copy fail!");
}
finally
{
myClose(i);
myClose(o);
}
System.out.println("HelloWorld!");
}
public static <T extends java.io>void myClose(T t) //关闭流
{
if(t != null)
{
try
{
t.close();
}
catch (IOException e)
{
throw newRuntimeException("Error: close stream fail!");
}
}
}
}
字节流也有自己对应的缓冲区BufferedInputStream、BufferedOutputStream,具体使用方式和前文中字符类缓冲区的使用方式原理相同。此处就省略了。
根据上面的总结,我们就可以分析System.io中直接通过控制台,向程序中输入数据是怎么完成的了。我们可以观察到System.io在静态字段定义中,System.in成员其实是一个静态InputStream类型成员。那么,我们就可以明白了。控制台中向程序录入数据,实质上是通过System.in调用read指令来实现的。就如下面的例子就是一例典型的控制台输入操作:
/**@author:LZ
*/
/*目标:1简单的控制台输入,要求按原样在控制台输出
2 当输入"end"时,结束程序
思路:1 定义字节流,关联系统输入流
2 判断程序是否应该终结,根据情况决定任务
3 关闭流
*/
import java.io.*;
class SystemInTest
{
public static void main(String[] args)
{
FileInputStream i =System.in; //外部声明
StringBuilder s = newStringBuilder();
int c = 0; //暂存定义
while (1)
{
c = i.read(); //除第一次,以后均在此位置发生输入请求
s.append((char)c); //保证每一次输入和结果,与下一次有一行间隙
if(c == '\r') //Windows特性,保证不可识别的回车换行符不被输出
continue;
if(c == '\n')
{
String ts =s.toString();
if(ts.equalsIgnoreCase("end"))
break;
sop(ts);
s.delete(0,s.length());
}
}
i.close();
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
转换类 Reader字符流类子类InputStreamReader
该类可以通过用字节流初始化本类对象,再用本类对象初始化字符流对象,来实现字节流向字符流的转化操作。这种用法,常用来简化控制台命令和后台控制操作的程序代码。以增强程序可读性,并提高程序文字处理效率。比如上面例子中的控制台读取方式,我们就可以换成这样:
import java.io.*;
class SystemInwithBReaderTest
{
public static void main(String[] args)
{
//外部声明,以及流转换与缓冲区
FileInputStream i =System.in;
InputStreamReader i2r = newInputStreamReader(i);
BufferedReader br = newBufferedReader(i2r);
StringBuilder builder = newStringBuilder();
String s = null;
while(!(s.equals("end"))) //判断是否继续执行程序
{
s = br.readLine(); //请求外部输入
sop(s);
}
br.close();
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
这种纯字符式的输入,我们用这种处理方式,是最合适不过的了。但使用的时候还是得便宜行事,根据需求而来。
转换类Writer字符流类子类OutputStreamWriter
既然有字节流向字符流的转换,那就一定存在字符流向字节流的转换。那么,这个类便是Writer类子类OutputStreamWriter。OutputStreamWriter可将字节流转为字符流,让他可以调用字符流缓冲BufferedWriter,从而实现字符对原数据的操控。
我们可以这么理解字节流字符流的相互转换。当你想要读取文档或从键盘输入命令的时候,实际上是以byte(除bit外,可以操作的最数据)的形式存放在,或输入进电脑的。这样的情况下,如果想要通过字符流及其缓冲区的一些特有方法来操纵原有字 节流的数据,我们就需要现将文件或输入的字节流信息提取出来,再将其转为字符流信息才行。这是一个存在先后顺序的过程。
同样,当我们做写入操作时,我们是将我们想要输入的数据存入到电脑之中。这样的情况之下,如果我们是以字符流形式录入的数据,那么在存储的时候,为了启用一些底层字符流方法,我们就必须得将字符流信息转换为字节流对象才可以使用字节流特有的方法,绕过繁琐的步骤,底层调用实现设备写。这也是一个有先后的过程。这不仅局限于单个读或者写的过程,其读写也有着顺序,即先读后写,而正是这种顺序性,才使得有了这种转换特征。
我们发现,这两种转换操作,实质上都是想通过字符流操作来实现字节流,字符流便于实现一些计算机的一些文字类运算,字节流更偏重于计算机内设备间的传输:
图6.字节字符流转换示意图
不难看出,这两种转换往往都是配合使用的,我们这里就列出了一种典型的使用方法。注意其中的字符流的创建方式,这种转换式的创建方式经常在后台用到:
import java.io.*;
class IOStreamTansBufferedTest
{
public static void main(String[] args)
{
//定义字符流控制输入缓冲区
BufferedReader br =
new BufferedReader(newInputStreamReader(System.in));
//定义字符控制输出流缓冲区
BufferedWriter bw =
new BufferedWriter(newOutputStreamWriter(System.out));
String s = null;
while ((s =br.readLine())!="over")
{
bw.write(s); //因为此时的输出流对应System.out,会直接在控制台输出
bw.newLine(); //保证兼容性
bw.flush(); //因为是字符流,所以需要刷新,然后在用底层字节流输出
}
}
}
我们用一张表来表明字节流字符流的最佳使用条件:
表2 字节流字符流使用条件简表
目的 数据 |
写入(输出) |
读取(输入) |
纯文本 |
Writer及强转 |
Reader及强转 |
非纯文本 |
OutputStream |
InputStream |
表中,常见的作为输出设备的计算机部件有:内存、硬盘、控制台
常见的作为输入设备的计算机部件有:内存、硬盘、键盘
这都是经常能够调用转换方法的地方。从表中,不难看出如果要使用转换,那么必须要两个条件,就是:第一,被操作数据必须为字节类型数据;第二,被操作数据必须是纯文本数据。只有满足这两个条件,在同为字节纯文本的情况下,我们才能调用对应的转换方法,用更简单的形式来实现所需。否则,不满足条件下的强行调用,就是舍近求远。但是,字符类的方法在调去数据方面具有高效的优势,我们为了让非纯文本字节流数据能够使用转换方法类实现字符流特点,就必须有对应的字节流码表,来将字节对应到相应字符上。
//指定编码格式来读取非文字数据
BufferedReader br =
new BufferedReader(newInputStreamReader(new FileInputStream("test.png"), "GBK"));
//指定编码格式来写入非文字数据
BufferedWriter bw =
new BufferedWriter(newOutputStreamWriter(new FileOutputStream("test.png"), "GBK"));
我们可以看出,字节字符转换流是加入了编码表的对象。常用的编码表有:
ASCII:美国标准信息交换码;
ISO8859-1:拉丁码表,即欧洲码表;
GB2912:中国的中文码表;
GBK:中国的中文码表升级版,融合了更多的中文文字,每年都有扩充换代;
Unicode:国际标准码,融合了多种文字,统一用2字节代表一个文字,造成空间浪费;
UTF-8:UnicodeTransform Format最多用三个字节来表示一个字符,Unicode改进版;
读取文件时没有采用指定的对应编码表,将会造成读取数据的紊乱。这是因为编码与解码采用了不同的编码格式造成的。编码过程就是字符变字节的过程,解码过程就是字节变字符的过程。下面就是一个典型的编码转换实例:
class decodingTest
{
public static void main(String[] args)
{
//C-S模式,get数据传输过程编码转换模拟
//客户端发送GBK
String s1 = "联通";
byte[] b1 =s1.getBytes("GBK");
sop("get:"+b1);
//服务器处理
String s2 =Arrays.toString(b1,"ISO8859-1");
byte[] b2 =s2.getBytes("ISO8859-1");
sop("process:"+b2);
//处理后发送回客户端
String s3 =Arrays.toString(b2,"GBK");
sop("ans:"+s3);
}
public static <T> void sop(T t)
{
System.out.println(t);
}
}
关于编码的一件趣事是,如果当存储的中文字符符合全都符合UTF-8格式,则用Windows自带记事本编码的时候会优先选取UTF-8编码格式,这样就会造成编码错误,比如说“联通”这个词,单独保存就会这样。
------- android培训、java培训、期待与您交流! ----------