黑马程序员_javaIO编程和字符集

来源:互联网 发布:傻瓜进销存软件破解版 编辑:程序博客网 时间:2024/06/14 08:50

File类是IO包中唯一代表磁盘文件本身信息的类,而不是代表文件中的内容。
java中的目录被当作一种特殊的文件使用,用list()方法返回一个字符串数组,表示目录中的所有子目录和文件名。

RandomAccessFile类支持随机访问文件,可随机读取和写入。(主要优势在:断点续传,多线程下载一个文件,读写等长记录格式的文件等)
RandomAccessFile类同时实现了DataInput和DataOutput接口,利用这个类,可以在文件的任何位置写入各种类型的数据。
它提供了一个文件指针,用来标识要进行读写操作的下一个数据的位置。但是要注意的是,指针会随着读或写的操作而移动。

RandomAccessFile类中的方法writeChars(String s)是按字符序列将一个字符串写入该文件。
比如writeChars("abcd")将会写入“a b c d ”,也就是说只占一个字节的字符将会添加一个空字符。
比如writeChars("哈哈")将会写入“哈哈”,占两个字节的字符就直接写这个字符。

java中用“\”表示一个空字符。

所有的IO操作完后记得要调用close()关闭对象,以释放系统所占用的与流相关的资源。

流的两大分类:节点流和过滤流(处理流)。
用于直接操作目标设备的所对应的类叫节点流类,节点流类所对应的IO源或者目标称为流节点。
通过间接的流类去调用节点流类以更加方便灵活的去读写各种类型的数据,那么这个间接的流类就称为过滤流或者叫流的包装类。

InputStream中有一个方法read(),返回值是int型的值,它每次只读取一个字节,为什么不返回byte类型的值呢,我们来看看,假如读取的数据为
二进制形式的“11111111”,那么int类型的值将会把最后一个字节的8位填充为“11111111”,其他24个高位全部用“0”补充,那么它的值是255,
如果返回值是byte类型的话,那么“11111111”的值表示十进制的-1,这时就跟read()读到流的末尾时返回的-1是有冲突的。这个返回值跟强制转换是不一样的。
byte b = -1;  //这时他的二进制形式是“11111111”;强制转换到int类型的值的话,其他24个高位将会全部用“1”填充,也就是32位的“1”来表示-1。

InputStream中的方法skip(long n)表示跳过和丢弃此输入流中数据的n个字节,此方法只能用于过滤流类。

InputStream中的方法available()返回输入流中可读的字节数。

InputStream中的方法mark(int readlimit)用于在输入流中建立标记,参数readlimit表示从建立标记的位置开始,最多能读取多少个字节,此方法只能用于过滤流类。
InputStream中的方法reset()是和mark(int readlimit)方法配对使用的,将此流重新定位到最后一次对此输入流调用 mark 方法时的位置,此方法只能用于过滤流类。
InputStream中的方法markSupported()返回此输入流是否支持mark和reset方法的操作。
InputStream中的方法close()用于关闭此输入流并释放与该流关联的所有系统资源。

有了垃圾回收器,为什么还要调用close()方法?
因为产生了输入流对象以后,系统会产生与该流相关的系统资源,而垃圾回收器只能管理程序中的类实例对象,不能管理系统所产生的资源,
所以程序需要调用close()方法去通知系统释放与该流相关的系统资源。
当我们调用了close()方法以后,通知系统释放与该流相关的系统资源,同时关闭此流,此流的对象也成了垃圾对象,由Java垃圾回收器管理。


OutputStream中的方法write(int b)将整数的最低字节写到输出流中,其他24个高位被舍弃。
OutputStream中的方法flush()用于将内存缓冲区中的内容彻底的清空,并且强制输出所有缓冲的输出字节。
但是OutputStream类中的flush()方法只是空实现,不执行任何操作,没有实际的作用。
当我们用close()方法关闭此输出流并释放与此流有关的所有系统资源的时候,缓冲区中的内容也会被强制输出。

FileInputStream,FileOutputStream,磁盘文件的读写操作。

InputStream和OutputStream都是用字节的形式来处理输入与输出。而Reader和Writer都是用字符的形式来处理输入与输出。

InputStream和OutputStream主要用于二进制格式的输入与输出。而Reader和Writer主要用于文本文件的输入与输出。

InputStream和OutputStream是所有字节流类的抽象基类。Reader和Writer是所有字符流类的抽象基类,用于简化对字符串的输入输出编程,即用于读写文本数据。

PipedInputStream和PipedOutputStream用于在应用程序中创建管道通信。
一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进行连接来产生一个通信管道。主要用来完成线程之间的通信。
PipedReader和PipedWriter用来创建字符文本的管道通信。

使用管道流类,可以实现各个程序模块之间的松耦合通信。

ByteArrayInputStream和ByteArrayOutputStream用于以IO流的方式来完成对字节数组内容的读写,来实现类似内存虚拟文件或者内存映像文件的功能。

ByteArrayInputStream(byte[] b)构造函数的参数b就是一个数据源,相当于FileInputStream实例对象的数据源是一个文件里面的内容。

ByteArrayOutputStream使用write(byte[] b, int off, int len)方法写出的数据,其实还是保存在ByteArrayOutputStream实例对象本身里面,
此时我们可以调用toByteArray(),toString()等方法获取里面的内容,还可以调用方法writeTo(OutputStream out)把里面的数据写到一个输出流中。

一个字符串其实就相当于一个字符数据的存储缓冲区,所以JDK还提供了StringReader类和StringWriter类来以字符IO流的方式来处理字符串。
这两个类的功能分别相当于ByteArrayInputStream和ByteArrayOutputStream。

 

不管各种底层物理设备用什么方式实现数据的终止点,InputStream的read()方法总是返回-1来表示输入流的结束。

 


字符编码:

中国大陆将每一个中文字符都用两个字节的数字来表示,中文字符的每个字节的最高位bit都为1,中国大陆为每个中文字符制定的编码规则称为GB2312(国标码)。
后来在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码称为GBK。在中国大陆使用的计算机系统上,GBK和GB2312就被称为该系统的本地字符集。

同一个字符采用不同的字符编码集的时候,会对应不同的表示数字。

一个文本文件中存储的内容实际上就是一个个的数字,我们使用某些软件打开文本文件的时候,里面显示的是一个个的字符,这其实是由于这个软件按照某种编码规则
将这个文本文件中实际上存储的数字用对应的字符显示出来的结果。

随着国际化的需求,ISO(国际标准化组织)将全世界所有的符号进行了统一编码,称之为Unicode编码。

Unicode编码的字符都占用两个字节的大小,对于ASCII码所表示的字符,只是简单地在ASCII码原来占用的一个字节前面,增加一个所有bit都为0的字节。

Unicode只占用两个字节来表示一个字符,在全世界范围内所表示的字符个数不会超过65535(2的16次方),
实际上,Unicode编码中还保留了两千多个数值没有用于字符编码。

由于各种原因,很难立刻让全世界完全采用Unicode编码,在相当长的一段时间内,本地化字符编码将与Unicode编码共存。

Java中的字符使用的都是Unicode字符编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台的字符集。

 


在UTF-8编码中,ASCII码字符保持原样,仍然只占用一个字节,对于其他国家的字符,UTF-8使用两个或三个字节来表示。
使用UTF-8编码的文件,通常都要用 EF BB BF 作为文件开头的三个字节数据。

字符的UTF-8编码与Unicode编码之间的转换关系具体可以看张老师的视频IO部分第7个视频或者网上查找。

UTF-8编码有如下特征:
占用一个字节的字符:最高位为0,形式为  0xxxxxxx;
占用两个字节的字符:形式为  110xxxxx 10xxxxxx;
占用三个字节的字符:形式为  1110xxxx 10xxxxxx 10xxxxxx;

从以上特征中可以看出,应用程序很容易从固定不变的bit值来确定一个字符所占用的字节数。

 

 

在UTF-16编码在Unicode的基础上进行了一些细节上的扩充,增加了对Unicode编码没有包括的那些字符的表示方式。

UTF-16对Unicode的扩充并没有影响Unicode编码所包括的那些字符,只是增加了对Unicode编码没有包括的那些字符的表示方式,
一个使用Unicode编码的字符就是UTF-16格式的。

一般的应用中都不会使用到Unicode编码中没有包括的那些字符,所以基本上可以忽略UTF-16和Unicode这种细节上的区别,甚至可以认为两者就是等同的。

在不同体系结构的计算机系统中,UTF-16编码的Unicode字符在内存中的字节存储顺序是不同的。
高字节在前,低字节在后的称为:Big Endian
低字节在前,高字节在后的称为:little Endian

如果文件以 FE FF 这两个字节开头,则表明文件的其余部分是Big Endian的UTF-16编码。
如果文件以 FF FE 这两个字节开头,则表明文件的其余部分是little Endian的UTF-16编码。

如果我们想查看一个文件用什么字符编码保存,可以用记事本打开这个文件,然后点击另存为,此时可以看见最下面有一个“编码”的选框,
默认时显示的就是此文本保存时的字符编码。

我们看一个有趣的例子:
创建一个记事本内容为“联通”,关闭后再打开显示异常。
创建一个记事本内容为“联”,关闭后再打开显示异常。
创建一个记事本内容为“联想”,关闭后再打开显示是正常的。
我们看看他们对应的GB2312码:
联:C1AA------11000001 10101010
通:CDA8------11001101 10101000
想:CFEB------11001111 11101011
可见,“联”和“通”刚好符合UTF-8编码的特征(占用两个字节的字符:形式为  110xxxxx 10xxxxxx),所以记事本检查编码格式的时候认为它是UTF-8编码格式的,
所以用UTF-8编码来显示。
而“想”不符合UTF-8编码,所以“联想”是以本地字符集来显示。
当记事本打开文件时,会检查文件中所有字符的编码规则,看他是否遵循某种编码规则,如果所有字符都遵循某一种编码规则,
那就用这种字符编码集来显示这个文件。否则就用本地字符集显示文件。


java中使用的是Unicode码。

PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
(System.getProperties().list(System.out)可以查看java虚拟机的环境属性,其中就包括了缺省的字符编码集file.encoding=GBK)。
比如:System.out.println("中国"); (System.out返回的就是PrintStream类的一个实例对象),
java会先将"中国"两个字符使用平台的默认字符编码(我们的默认字符编码是gb2312)转换为字节("中国".getBytes("gb2312")),
然后调用write()方法将转换成的字节数组写入到显示器上。然而,系统怎么知道用什么字符编码集来解码字节数组呢?我们来开PrintStream类的其中一个构造函数
PrintStream(OutputStream out, boolean autoFlush, String encoding),我们可以指定输出流的字符编码集用来解码字节,
我们默认的就是使用本地系统字符集gb2312。

还有String类有一个getBytes()方法,默认也是采用java缺省的字符集来进行编码。我们可以指定一个字符集编码来转换字节数组,就是方法getBytes(Charset charset);
在java缺省的字符集是gb2312的情况下,使用getBytes()和getBytes("gb2312")是一样的效果,都是采用gb2312字符集来编码,产生字节数组。

PrintStream类中的println()方法在写入byte数组之后还会自动调用flush()方法。
OutputStream调用write(int b)写一个字节的时候,不会自动调用flush()方法,
而调用write(byte[] b, int off, int len)写一个字节数组的时候会自动调用flush()方法刷新缓冲区。

new String(str.getBytes("iso8859-1"), "gb2312");  //用iso8859-1字符集来编码str字符串返回一个字节数组,然后用gb2312字符集来解码这个字节数组。

 

要将浮点小数写入文件中只能将浮点小数用Float.floatToIntBits(float val)转换成小数在内存中存数的字节码,然后再写入,
我们又怎么将一个整数写入到文件中呢?显然是比较麻烦的事情。
读取的时候先取出字节码,然后用Float.intBitsToFloat(int bits)将字节码转换成float浮点数。
此时,我们可以用DataInputStream和DataOutputStream来写入各种类型的数据。
DataOutputStream类提供了往各种输出流对象中写入各种类型的数据(包括浮点小数)的方法。DataInputStream与之对应使用。
DataInputStream和DataOutputStream属于包装类,也可以叫过滤流类和处理流类。

程序---包装流类---节点流类---具体的目标设备。


缓冲流为IO流增加了内存缓冲区,增加内存缓冲区有两个目的:
1,允许java程序一次不只操作一个字节,这样提高了程序的性能;
2,由于有了缓冲区,使得在流上执行skip(),mark()和reset()方法都成为可能。

BferedInputStream和BferedOutputStream是java提供的两个缓冲区包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中都会创建缓冲区,
底层系统提供的缓冲区直接与目标设备交换数据,而在包装类中创建的缓冲区会调用所包装类的缓冲区。
底层系统提供的缓冲区是一次性从硬盘读取大量的数据(或者写入),而包装类的缓冲区是从底层系统提供的缓冲区中一个一个的读取之后缓存起来(或者写入)。

BferedReader和BferedWriter也是缓冲区包装类,用于包装字符流类。


程序---DataOutputStream---BferedOutputStream---FileOutputStream---文件   (这样组成了一个流栈)

程序---DataInputStream---BferedInputStream---FileInputStream---文件   (这样组成了一个流栈)

关闭流栈中的最上层的流对象,流栈中的所有底层流对象也将会自动关闭并释放系统资源。
比如上面的流栈,我们只需调用close()方法关闭DataOutputStream或者DataInputStream对象就可以关闭整个流栈中的所有流对象。


PrintStream类提供了一系列的print()和println()方法,可以将基本数据类型的数据格式化成字符串输出。
例如:97被格式化输出的实际字节数据为0x39(对应9)和0x37(对应7)。

与PrintStream对应的PrintWriter类,即使遇到了文本换行标识符“\n”,PrintWriter类也不会自动清空缓冲区。
PrintWriter类中的方法println()方法能根据操作系统的不同而生成相应的文本换行标识符。
在windows下的文本换行标识符是“\r\n”,而在Linux下的文本换行标识符是“\n”。

 

ObjectInputStream和ObjectOutputStream这两个包装类,用于从底层输入流中读取对象类型的数据和将对象类型的数据写入到底层输出流。
ObjectInputStream和ObjectOutputStream类所读写的对象必须实现了java.io.Serializable接口,对象中的transient和static类型的成员变量不会被写入和读取。
(用transient修饰的变量表示此变量是临时的或者是特殊的,不能把他当作对象的特征使用。)

Serializable接口里面没有定义任何属性和方法,仅仅是一种标识接口。

当我们反序列化一个对象的时候,如果这个对象所对应的类字节码尚未装载,那么Java虚拟机就会加载这个类,在内存中产生一份这个类的字节码,
但并不会调用这个类的任何构造方法,它仅仅根据我们的对象所保存的状态信息在内存当中重新构建一个对象。

当一个对象被序列化后,存储的只有成员变量(transient或static修饰的变量除外)的状态信息。如果成员变量的值是一个引用类型,
那么所引用的对象的数据成员也会被保存。

如果一个可序列化的对象,包含了对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且抛出NotSerializableException。

(
我觉得:
从反射技术可以看出:其实一个类中声明的所有成员都属于类的信息,只是对应的状态的信息才属于某一个实例的信息,而状态的描述其实就是成员变量的值。
在内存里面一个类就共享了一份字节码,他所持有的信息是所有成员的原始信息,比如变量的名字就属于一种类的原始信息,方法也属于原始信息,
包括静态成员和非静态成员,对于静态成员,字节码信息里面还保存了信息所对应的状态描述,
而每个实例所保存的信息只是所对应类里面的非静态成员变量所对应的状态的描述信息。
)

在需要被序列化的类中,我们可以通过实现下面两个方法:
private void writeObject(ObjectOutputStream oos) throws IOException {...}
private void readObject(ObjectInputStream ois) throws IOException {...}
来控制对象的序列化和反序列化,当序列化和反序列化对象的时候就会分别调用上面的两个方法。
需要特别注意的是:上面两个方法都是声明为private的。

下面是一个对象序列化的例子:
package com.heima.exam;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

p lic class St?nt implements Serializable {

 static {
  System.out.println("静态初始化块运行。");
 }
 int id;
 String name;
 int age;
 String department;
 
 p lic St?nt(int id, String name, int age, String department) {
  System.out.println("st?nt构造函数运行了");
  this.id = id;
  this.name = name;
  this.age = age;
  this.department = department;
 }
 
 private void writeObject(ObjectOutputStream oos) throws IOException {
  oos.writeInt(id);
  oos.writeUTF(name);
  oos.writeInt(age);
  oos.writeUTF(department);
 }
 
 private void readObject(ObjectInputStream ois) throws IOException {
  id = ois.readInt();
  name = ois.readUTF();
  age = ois.readInt();
  department = ois.readUTF();
 }
}


package com.heima.exam;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


p lic class SerializationTest {
 
 p lic static void main(String[] args) throws Exception {

  St?nt st = new St?nt(20, "张三", 25, "计算机");
  St?nt st = new St?nt(21, "李四", 23, "商务英语");
  
  FileOutputStream fos = new FileOutputStream("st?nt.txt");
  ObjectOutputStream oos = new ObjectOutputStream(fos);
  
  oos.writeObject(st);
  oos.writeObject(st);
  oos.close();
  
  FileInputStream fis = new FileInputStream("st?nt.txt");
  ObjectInputStream ois = new ObjectInputStream(fis);
  
  St?nt st1 = (St?nt)ois.readObject();
  St?nt st2 = (St?nt)ois.readObject();
  ois.close();
  
  System.out.println(st2.id + ":" + st2.name + ":" + st2.age + ":" + st2.department);
  System.out.println(st1.id + ":" + st1.name + ":" + st1.age + ":" + st1.department);
 }
}

 


IO包中的流类可以分为字节流和字符流。
字符流与字节流的转换:

InputStreamReader字节流通向字符流的桥梁
OutputStreamWriter 是字符流通向字节流的桥梁

我们在构造InputStreamReader对象的时候可以指定字符集来对需要转换的字节进行解码成字符。不指定的话就使用默认字符集。
同样,我们在构造OutputStreamWriter对象的时候可以指定字符集来对写出的字符进行编码成字节数组后写出。不指定的话就使用默认字符集。

有关字符集的包是java.io.charset.*;     其中Charset.availableCharsets()可以获得当前Java虚拟机所支持的字符集。返回一个Map对象。

为了达到最高效率,可要考虑在 BferedReader 内包装 InputStreamReader。
例如: BferedReader in = new BferedReader(new InputStreamReader(System.in));

为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BferedWriter 中,以避免频繁调用转换器。
例如: Writer out = new BferedWriter(new OutputStreamWriter(System.out));

InputStreamReader和OutputStreamWriter两个类是用于将字节流转换字符流来读写的两个类,
InputStreamReader可以将一个字节流中的字节解码成字符后读取,OutputStreamWriter可以将字符编码成字节后写入到一个字节流中。

为了避免频繁的在字符与字节之间进行转换,最好不要直接使用InputStreamReader和OutputStreamWriter类来读写数据,
应该尽量使用BferedWriter类来包装OutputStreamWriter类,用BferedReader来包装InputStreamReader类。

实际上所有的字符流都是包装类,即都是InputStreamReader或者OutputStreamWriter的子类。例如:FileReader就是InputStreamReader的子类。

Java I/O库的设计原则:流的链接。组合基本的流类,将他们的功能组合在一起,从而获得一个功能更加强大的流的对象。

 


java程序与其他进程的数据通信。
在Java程序中可以用Process类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器,
而是以管道流的形式连接到父进程的一个输出流和输入流对象上。

调用Process类的getOutputStream()和getInputStream()可以获得连接到子进程的输出流和输入流对象。

子进程从标准输入读取到的内容就是父进程通过输出流对象写入到它们两者之间的通信进程管道中的数据。
子进程写入到标准输出的数据,通过它们之间的进程管道传送到了父进程的输入流对象中,
父进程从输入流对象中读取到的内容就是子进程写入到标准输出的数据。

在管道缓冲区写满以后,与PipedInputStream相连的PipedOutputStream无法再写入新的数据,PipedOutputStream.write()方法处于阻塞状态。

当我们调用Runtime.getRuntime().exec(String command)方法来启动子进程的时候,我们一定要记得调用Process类的destroy()方法来结束子进程的运行。

如下是java程序与其他进程的数据通信的例子:

import java.io.BferedReader;
import java.io.IOException;
import java.io.InputStreamReader;


p lic class MyTest {

 p lic static void main(String[] args) {

  String strLine = null;
  BferedReader bfr = new BferedReader(new InputStreamReader(System.in));
  while(tr) {
   
   try {
    strLine = bfr.readLine();
    if(strLine != null) {
     System.out.println("hi: " + strLine);
    } else {
     return;
    }
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }
}


import java.io.*;


p lic class TestInOut implements Runnable {
 
 Process pro;
 
 p lic TestInOut() {
  
  try {
   pro = Runtime.getRuntime().exec("java MyTest");
  } catch (IOException e) {
   e.printStackTrace();
  }
  new Thread(this).start();
 }
 
 p lic void send(String info) {
  
  OutputStream ops = pro.getOutputStream();
  
  while(tr) {
   try {
    ops.write(info.getBytes());
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

 @Override
 p lic void run() {
  
  InputStream ips = pro.getInputStream();
  BferedReader in = new BferedReader(new InputStreamReader(ips));
  String strLine = null;
  while(tr) {
   
   try {
    strLine = in.readLine();
    if(strLine != null) {
     System.out.println(strLine);
    } else {
     return;
    }
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }
 
 p lic static void main(String[] args) {

  new TestInOut().send("help\r\n");
 }
}

 


Decorator设计模式:在程序中用一个对象(the Decorators)包装另外的一个对象,这是一种被称为Decorator的设计模式(包装模式)。

如果要设计自己的IO包装类,这个类需要继承以FilterXXX命名的类.
例如:设计一对输入输出包装类:RecordInputStream和RecordOutputStream,来完成从数据库文件中读取记录和往数据库文件中写入记录。

合理的利用流类,解决具体的问题。
比如,Exception的方法e.printStackTrace()方法,只能将详细的异常信息打印到屏幕上,我们如何用字符串的形式得到这些信息?
看代码:
import java.io.*;
p lic class PrintWriterTest {

 p lic static void main(String[] args) {

  try{
   throw new Exception("test");
  } catch(Exception e) {
   StringWriter sw = new StringWriter();
   PrintWriter pw = new PrintWriter(sw);
   e.printStackTrace(pw);
   System.out.println(sw.toString());
  }
 }
}

0 0
原创粉丝点击