序列化对象
将堆内存中的东西(对象)写到硬盘中
ObjectOutputStream(OutputStream)中的writeObject方法可以将对象写入到硬盘中来
ObjectInputStream(InputStream)中的readObject方法可以将硬盘中的对象读取出来。
先上手写程序:
先定义一个Person类,作为要操作的对象
class Person
{
String name;
int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String toString()
{
return name+"....."+age;
}
}
写入对象的代码如下:
//将对象写入到硬盘中来
import java.io.*;
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception
{
writeObj();
}
public static void writeObj() throws Exception
{
//将目标锁定为obj.txt文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
//开始写入文件
oos.writeObject(new Person("zhangsan",23));
}
}
编译后运行出现以下结果:
通过查找API了解该异常:
Person类需要具有序列化接口。
也就是说Person类要实现Serializable接口。
将Person类实现了Serializable接口后,再次执行程序,得到obj.txt存储对象的文件。
那么既然能将对象写进文件中,也就能将文件中得对象读出来,
//将对象写入到硬盘中来
import java.io.*;
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception
{
//writeObj();
readObj();
}
public static void writeObj() throws Exception
{
//将目标锁定为obj.txt文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
//开始写入文件
oos.writeObject(new Person("zhangsan",23));
oos.close();
}
public static void readObj() throws Exception
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
}
调用读取文件的方法获取对象,得到如下结果:
这时当我将Person类中的name属性变成私有的,再次编译,运行程序:
这时候文件中的Person对象是之前的,Person类代码是重新修改后的,
结果显示,从文件获取到的对象和调用Person获取到的对象的UID不一样,
当把private去掉之后,运行程序,就又可以获取到对象了,
这说明UID序列号是根据类的成员算出来的。
如果想要加上私有之后,还可以从之前的文件对象中获取到修改后的对象。
就是不让java给我算UID。我用自己定义UID,就可以了
import java.io.*;
class Person implements Serializable
{
//写上自己的UID
public static final long serialVersionUID = 42L;
private String name;
int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String toString()
{
return name+"....."+age;
}
}
这样不管参数中有没有加私有修饰符都不会影响从文件中读取对象。
注意,静态是不能被序列化的。
因为静态成员是在方法区中存在的,是不能被序列化的
只有堆中的对象才可以被序列化。
如果内容不是静态的,但是还不想被序列化,可以在元素前添加transient标签。
保证其值在堆内存中存在,而不在文本中存在。
这是存入一个对象,如果存入多个对象的话也不用着急,因为readObject方法第一次读数据返回的是第一个对象,第二次读数据返回的就是第二个对象。
管道流
输入输出可以直接进行连接,通过结合线程使用。
PipedInputStream();
PipedOutputStream();
由图上信息可知,此处应该有多线程,
是什么原理呢?
就像是一根管子,里边同时放着输入(读取)流和输出(写入)流
当读取流没有读取到数据时,会等待,等到写入流写上数据了,读取流才会启动,将数据读出来,以上过程是由两个线程完成的。
//管道流的实例
import java.io.*;
//定义两个线程,一个为读,一个写,
//定义两个流对象,接在一块,形成管道流
//用两个线程一起操作数据
class Read implements Runnable
{
private InputStream in;
Read(InputStream in)
{
this.in = in;
}
public void run()
{
try
{
//线程一开始就开始读取数据
byte[] buf = new byte[1024];
System.out.println("没有读到数据,等待");
int len = in.read(buf);
System.out.println("读到数据,开始打印");
String s = new String(buf,0,len);
//读到数据后打印,关流
System.out.println(s);
in.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class Write implements Runnable
{
private OutputStream out;
Write(OutputStream out)
{
this.out = out;
}
public void run()
{
try
{
//线程一开启就写入数据
System.out.println("6秒后写入数据....");
Thread.sleep(6000);
out.write("你好,世界".getBytes());
System.out.println("数据写入完毕");
out.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public class PipedStreamDemo {
public static void main(String[] args)
{
//创建出来两个管道流
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
try
{
//将两个管道流连接起来的方法
pis.connect(pos);
}
catch (IOException e)
{
e.printStackTrace();
}
//开启两个线程
Read r = new Read(pis);
Write w = new Write(pos);
new Thread(r).start();
new Thread(w).start();
}
}
运行结果:
随机访问文件RandomAccessFile
该类不算是IO体系中的子类,
而是直接继承自Object
但是他是IO包中的成员,因为他具备读取和写入的功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作
可以通过getFilePointer获取指针位置。
同时可以通过seek方法改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流
通过构造函数可以看出,该类只能操作文件
而且操作文件还有模式:只读r,读写rw等
如果模式为只读r,不会创建文件,会去读取一个已经存在的文件,如果该文件不存在,则会出现异常
如果模式为rw
而且该对象的构造函数要操作的文件不存在,会自动创建,如果存在不会覆盖。
既然随机访问文件对象可以对文件进行读取和写入的,我们就先演示一下写文件的方法。
import java.io.*;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException
{
writeDemo();
}
public static void writeDemo() throws IOException
{
//先创建一个对象,直接关联文件,并且指定文件的访问类型为可读可写
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
//因为他里边封装的是一个字节数组,所以这里应该使用字节数据写入
raf.write("张三".getBytes());
raf.write(97);
//里边封装的是字节流对象的访问模式,所以这里也是要关闭资源的
raf.close();
}
}
运行结果:
结果显示:可以存进去,但是写进去的97成了字符a。这是因为我写入的是字节数据,在写进记事本的时候,会按照传进来的字节数据按照查表的方式获取到相应的字符,然后写入到记事本中。
这里需要注意的是,自由访问文件流中的write方法和流中的write方法一样,也是只写八位,也就是一个字节(即使传进来的数据是4个字节或者更多),
那么就出现了局限性,要是我写入的数据长度大于一个字节的最大表示数(255)要写入的数据不仅仅是8位的数据
而write方法只能操作8位。
这样会造成数据的损失。
所以我们可以在写数据的时候用的是writeInt方法,可以一次写入4个字节的数据。
那么接下来演示读取文件中数据的方法。
重新用writInt方法写了一次人的年龄,所以人的年龄是4个字节了。
//使用自由访问文件对象获取文件中的信息
import java.io.*;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException
{
//writeDemo();
readDemo();
}
public static void readDemo() throws IOException
{
//先创建一个自由访问文件流对象,和文件关联,并且设置权限为只读
RandomAccessFile raf = new RandomAccessFile("random.txt","r");
//因为读到的都是字节,而且read方法一次只能读取一个字节的数据,
//所以先将读到的数据放进自定义好的数组中
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
//而readInt方法可以直接读取到四个字节的内容
int age = raf.readInt();
System.out.println(name+"...."+age);
}
public static void writeDemo() throws IOException
{
//先创建一个对象,直接关联文件,并且指定文件的访问类型为可读可写
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
//因为他里边封装的是一个字节数组,所以这里应该使用字节数据写入
raf.write("张三".getBytes());
//使用write方法之后这里的97就占用了4个字节
raf.writeInt(97);
raf.write("李四".getBytes());
raf.writeInt(99);
//里边封装的是字节流对象的访问模式,所以这里也是要关闭资源的
raf.close();
}
}
运行结果为:
如果要同时获取到张三李四的信息,可以直接在以上代码中将readDemo方法中的read方法和readInt方法再多写一次。
这里我想要直接获取到李四的信息,应该怎么做呢?
使用到了随机访问对象的设置指针的方法,seek方法
演示:
import java.io.*;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException
{
//writeDemo();
readDemo_2();
}
//直接跳转指针的方式跳过前边的信息获取排在后边的信息
public static void readDemo_2() throws IOException
{
//直接获取李四的信息
RandomAccessFile raf = new RandomAccessFile("random.txt","r");
//指针向后跳到8的位置上。从这里开始读取
raf.seek(8);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+"...."+age);
}
public static void readDemo() throws IOException
{
//先创建一个自由访问文件流对象,和文件关联,并且设置权限为只读
RandomAccessFile raf = new RandomAccessFile("random.txt","r");
//因为读到的都是字节,而且read方法一次只能读取一个字节的数据,
//所以先将读到的数据放进自定义好的数组中
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
//而readInt方法可以直接读取到四个字节的内容
int age = raf.readInt();
System.out.println(name+"...."+age);
}
public static void writeDemo() throws IOException
{
//先创建一个对象,直接关联文件,并且指定文件的访问类型为可读可写
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
//因为他里边封装的是一个字节数组,所以这里应该使用字节数据写入
raf.write("张三".getBytes());
//使用write方法之后这里的97就占用了4个字节
raf.writeInt(97);
raf.write("李四".getBytes());
raf.writeInt(99);
//里边封装的是字节流对象的访问模式,所以这里也是要关闭资源的
raf.close();
}
}
运行结果:
还有一种方法获取指定位置上的元素就是:
跳过指定的角标开始获取:skipBytes();
import java.io.*;
public class RandomAccessFileDemo2 {
public static void main(String[] args) throws IOException
{
skipBytesDemo();
}
//直接跳转指针的方式跳过前边的信息获取排在后边的信息
public static void skipBytesDemo() throws IOException
{
//直接获取李四的信息
RandomAccessFile raf = new RandomAccessFile("random.txt","r");
//指针向后跳8个角标
raf.skipBytes(8);
//开始读取
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+"...."+age);
}
}
运行结果:
seek方法和skipBytes方法都可以指定指针的位置来获取指定位置上的元素。
两者有不同点,就是seek方法可以指定任意位置(可以向前也可以向后),而skipBytes方法只能向后跳,不可以再返回来。
使用seek方法除了可以获取指定位置的元素,还可以在指定位置添加元素。包括跳过中间位置。
演示:
import java.io.*;
public class RandomAccessFileDemo2 {
public static void main(String[] args) throws IOException
{
addElement();
}
public static void addElement() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
raf.seek(8*3);//在角标位24的位置上。开始写数据
raf.write("王五".getBytes());
raf.writeInt(103);
raf.close();
}
}
运行结果:
import java.io.*;
public class RandomAccessFileDemo2 {
public static void main(String[] args) throws IOException
{
addElement();
}
//seek方法还可以对已有元素的位置进行数据的修改
public static void addElement() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
raf.seek(8*0);//在角标位0的位置上。开始写数据
//这样写一下就相当于是修改了数据了
raf.write("周七".getBytes());
raf.writeInt(106);
raf.close();
}
}
运行结果:
发现,在第一个位置存储的元素由张三变成了周七,seek可以实现对文件中数据的修改。
这样的原理可以用到下载视频上来,一个线程下载020M,同时另一个线程下载2040M,,,,这样下载下来的视频是有连接的,可以提高效率。
DataInputStream与DataOutputStream
可以用来操作基本数据类型的数据的流对象
直接演示方法:
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
writeDemo();
}
public static void writeDemo() throws IOException
{
//先创建对象
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//写数据有各自的方法
dos.writeInt(7845);
dos.writeBoolean(false);
dos.writeDouble(452.452);
dos.close();
}
}
运行结果:
出现乱码,正常,因为是在记事本中,会有一个查编码表的过程。
接下来读取刚刚写入的数据
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
//writeDemo();
readDemo();
}
public static void writeDemo() throws IOException
{
//先创建对象
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//写数据有各自的方法
dos.writeInt(7845);
dos.writeBoolean(false);
dos.writeDouble(452.452);
dos.close();
}
public static void readDemo() throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
//这里读取的顺序必须是按照之前写入的顺序来,
dis.close();
System.out.println("num= "+num);
System.out.println("b= "+b);
System.out.println("d= "+d);
}
}
运行结果为
可以获取到数据。
writeUTF方法的演示:
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
UTFWrite();
}
public static void UTFWrite() throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utf.txt"));
dos.writeUTF("你好");
dos.close();
}
}
运行结果:
文件大小为8字节
使用DataOutputStream的writeUTF方法写入的数据,只有同类的readUTF方法获取到数据。
读取数据
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
UTFRead();
}
public static void UTFWrite() throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utf.txt"));
dos.writeUTF("你好");
dos.close();
}
public static void UTFRead() throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
String s = dis.readUTF();
dis.close();
System.out.println(s);
}
}
运行结果:
获取成功
当需要使用GBK或者UTF-8编码写入数据的时候用到了转换流:
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
GBKWrite();
}
public static void GBKWrite() throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
osw.write("你好");
osw.close();
}
}
写入成功:文件大小为4字节:
使用utf-8呢?
//演示操作基本数据类型的流对象的方法
import java.io.*;
public class DataStreamDemo {
public static void main(String[] args) throws IOException
{
GBKWrite();
}
public static void GBKWrite() throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf-8.txt"),"UTF-8");
osw.write("你好");
osw.close();
}
}
运行结果:写入成功,文件大小为6字节
当用readUTF(使用修改版的UTF_8编码表)方法读取用utf_8编码的文件时,会出现这样的效果:
该异常的具体信息是:
所以说用writeUTF写入的数据,只有用readUTF才能读取的出来。
那么utf-8编码表和readUTF码表中用到的utf_8修改版有什么不一样的呢?
操作字节数组
ByteArrayInputStream与ByteArrayOutputStream
用于操作字节数组的流对象
ByteArrayInputStream :在构造的时候,需要接受数据来源,而且数据源是一个字节数组。
ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的。
因为这两个流对象都操作的数组,并没有调用到系统资源
所以,不用进行close关闭。
演示一下字节数组的操作方法。
//演示字节数组流对象的操作方法
import java.io.*;
public class ByteArrayStream {
public static void main(String[] args)
{
//源设备,给定一个字节数组
ByteArrayInputStream bais = new ByteArrayInputStream("abcdef".getBytes());
//目的设备:里边自己封装了一个可变长度的数组,这就是目的了
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//开始读写操作
int by = 0;
while((by = bais.read())!=-1)
{
baos.write(by);
}
//将目的中的东西打印出来
System.out.println(baos.toString());
}
}
运行结果为:
在流操作规律讲解时:
源设备:键盘 System.in 硬盘 FileStream 内存:ArrayStream
目的设备:控制台:System.out 硬盘:FileStream 内存:ArrayStream
有时候要将一个文件中的内容放到一个数组里先存起来,这个时候,源就是硬盘,目的就是内存。就用到了字节数组对象。
其实字节数组流对象就是用流的思想来操作数组的,
设定自己的源和目的,判断读取的值是否为-1.也就是相当于判断有没有到了数组的末尾。
一次一次的读取操作也就是数组中的遍历元素。
写入操作也就是数组中的设置某一个位置上的元素的值。
只不过是把读取的方法封装起来了。
writeTo 方法
writeTo(OutputStream os):将数组中的数据写入到硬盘中取。
那么字节数组流对象是操作字节数组的,那要是操作字符数组怎么办呢?
有CharArrayReader和CharArrayWriter
里边也存在源,就是要接收一个字符数组,
目的就是新建一个空的缓冲区。
里边的方法有toCharArray,append,write,toString
同时想操作数组的话还有StringReader和StringWriter
都是同样的道理源接收一个字符串作为源,目的是一个空的字符串。
字符编码
- 字符流的出现是为了方便操作字符
- 更重要的是加入了编码转换
- 通过子类转换流来完成
- InputStreamReader
- OutputStreamWriter
- 在两个对象进行构造的时候可以加入字符集
编码表的由来
- 计算机只能识别二进制数据,早期由来是电信号
- 为了方便应用计算机,让它可以识别各个国家的文字
- 就将各个国家的文字用数字来表示,并一一对应。形成一张表。
- 这就是编码表
常见的编码表
- ASCII美国标准信息交换码
- 用一个字节的7为就可以表示
- ISO8859-1:拉丁码表,欧洲码表
- 用一个字节 的8为表示
- GB2312:中国的中文编码表
- GBK:中国的中文编码表升级,融合了更多的中文文字符号
- Unicode:国际标准码,融合了多种文字
- 所有文字都用两个字节来表示,Java语言使用的就是Unicode
- UTF-8:最多用三个字节来表示一个字符。
那么现在,问题来了,GBK可以表示汉字的编码,UTF-8也可以表示汉字编码,但是相同的字在这两个编码表中不一样,这样会出什么问题呢?
用一段代码来演示:
//字符编码——使用转换流
//使用GBK编码给文件中写入一段数据
//再用UTF-8编码给文件写入同样的数据
import java.io.*;
public class EncodeStream {
public static void main(String[] args) throws IOException
{
writeGBK();//使用GBK编码表给文件中写入你好
writeUTF();//使用UTF-8编码表给文件中写入你好
}
public static void writeGBK() throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
osw.write("你好");
osw.close();
}
public static void writeUTF() throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");
osw.write("你好");
osw.close();
}
}
运行结果发现,gbk.txt文件中有“你好”,文件大小4KB
utf.txt文件中有“你好”文件大小6KB
接下来使用各自的读取方式去读文件
//字符编码——使用转换流
//使用GBK编码读取用GBK码表写上去的文件
//再用UTF-8编码读取用UTF-8码表写上去的文件
import java.io.*;
public class EncodeStream {
public static void main(String[] args) throws IOException
{
readGBK();
readUTF();
}
public static void readGBK() throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"gbk");
char[] chs = new char[10];//已经知道了文件的大小,就可以直接定义数组的大小,不用再去循环了
int len = isr.read(chs);
String str = new String(chs,0,len);
System.out.println("GBK编码读到的结果为:"+str);
}
public static void readUTF() throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"UTF-8");
char[] chs = new char[10];
int len = isr.read(chs);
String str = new String(chs,0,len);
System.out.println("UTF-8编码读到的结果为:"+str);
}
}
运行结果为:
那如果对文件进行读取的时候指定的编码表出现了错误的时候呢?
例如,使用GBK码表去读取UTF-8编码的文件,用UTF-8码表去读取GBK编码的文件
//字符编码——使用转换流
//使用GBK编码读取用utf-8码表写上去的文件
//再用UTF-8编码读取用GBK码表写上去的文件
import java.io.*;
public class EncodeStream {
public static void main(String[] args) throws IOException
{
readGBK();
readUTF();
}
public static void readGBK() throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"UTF-8");
char[] chs = new char[10];//已经知道了文件的大小,就可以直接定义数组的大小,不用再去循环了
int len = isr.read(chs);
String str = new String(chs,0,len);
System.out.println("GBK编码读到的结果为:"+str);
}
public static void readUTF() throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"GBK");
char[] chs = new char[10];
int len = isr.read(chs);
String str = new String(chs,0,len);
System.out.println("UTF-8编码读到的结果为:"+str);
}
}
运行结果:
为什么会出来这种情况呢?
我们知道,GBK编码表中一个汉字占用的两个字节
而UTF-8码表中,一个汉字占用了三个字节
在具体读取中,过程是这样的。
字符编码
编码:字符串变成字节数组
解码:字节数组变成字符串
通常我们将字符串变成字节数组的方法是:str.getBytes();
通常我们将字节数组变成字符串的方法是:String s = new String(byte[]);
以上方法都是按照默认的编码表(GBK)来进行转换的。
想要使用自己的指定的编码表进行转换的话,
有重载方法:
编码:str.getBytes(String charsetName);charsetName表示字符集
解码:String s = new String(byte[] buf,String charsetName);给定字节数组,并指定编码表进行解码。
先看看两种编码形式返回来的字节数组是什么样的:
//字符编码
//看看默认编码形式获取到的字节数组是什么样的
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
String s = "你好";
byte[] b1 = s.getBytes();//这里按照默认的方式进行编码
System.out.println(Arrays.toString(b1));//将编码后返回的字节数据打印出来
}
}
运行结果:
//字符编码
//看看GBK编码形式获取到的字节数组是什么样的
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("GBK");//这里按照GBK的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println(Arrays.toString(b1));//将编码后返回的字节数据打印出来
}
}
运行结果:
发现默认编码和指定的GBK编码出来的字节数组是一样的,也就证实了,默认的编码方式是GBK的。
接下来用utf-8的方式进行编码,看看出来的字节数组是什么样的?
//字符编码
//看看UTF-8编码形式获取到的字节数组是什么样的
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("utf-8");//这里按照UTF-8的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println(Arrays.toString(b1));//将编码后返回的字节数据打印出来
}
}
运行结果:
发现,使用utf-8编码之后返回一共有6个字节,也就是说一个汉字由三个字节组成。
以上是对汉字进行编码的过程,接下来演示解码GBK的过程。
//字符编码
//GBK码表的解码过程
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("GBK");//这里按照GBK的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println(Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"GBK");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("s2="+s2);//将解码后的数据打印出来
}
}
运行结果:
接下来演示解码UTF-8文件
//字符编码
//UTF-8码表的解码过程
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("UTF-8");//这里按照UTF-8的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println(Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"UTF-8");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("s2="+s2);//将解码后的数据打印出来
}
}
运行结果:
那么如果使用不同的编码表读取数据呢?
//字符编码
//GBK读取Utf-8编写的数据
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
GBKreadU8();
}
public static void GBKreadU8()
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("UTF-8");//这里按照UTF-8的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用Utf-8编码之后的字节数组为:"+Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"GBK");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用GBK码表去读取Utf-8码表编写的数据结果为:"+s2);//将解码后的数据打印出来
}
}
运行结果:
如果在写数据的时候使用了错误的编码形式,比如说ISO8859-1(欧洲语言码表),就没有办法了,因为发送了错误的编码形式,对方不知道这到底是什么样的编码形式。
如果编写的时候用的是GBK(编对了),但是解析的时候写错了(用了ISO8859-1编码表)
会出现这样的情况:
//字符编码
//GBK读取Utf-8编写的数据
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
ISOreadGBK();
}
public static void ISOreadGBK()
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("GBK");//这里按照GBK的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用Utf-8编码之后的字节数组为:"+Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"ISO8859-1");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用ISO8859-1码表去读取GBK码表编写的数据结果为:"+s2);//将解码后的数据打印出来
}
}
运行结果:
这是怎么样的一个过程呢?
当“你好”被解码的时候,解码成为-60,-29,-70,-61这是没有错的,
但是当解析的时候,用这些字节数组去查表的时候查的是ISO8859-1,
查一个,找不到,返回来一个?,后边的都一样找不到,所以会返回来4个?,
想要能把数据解析出来,可以这样做:
//字符编码
//GBK读取Utf-8编写的数据
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
ISOreadGBK();
}
public static void ISOreadGBK()
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("GBK");//这里按照GBK的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用Utf-8编码之后的字节数组为:"+Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"ISO8859-1");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用ISO8859-1码表去读取GBK码表编写的数据结果为:"+s2);//将解码后的数据打印出来
//******************再编码*******************************************************
byte[] b2 = null;
try
{
b2 = s2.getBytes("ISO8859-1");//对获取到的错误的乱码再进行基于原码表的解码获取到字节数组
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("对乱码再进行编码后得到的字节数组为:"+Arrays.toString(b2));
//******************再编码*******************************************************
//针对字节数组,再指定GBK编码表进行解码数据
String s3 = null;
try
{
s3 = new String(b2,"GBK");
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("进行再编码解码之后得到的数据为:"+s3);
}
}
运行结果为:
过程是这样的:
这种方法适用在tomcat服务器的使用中,因为服务器中的默认编码形式为ISO8859-1,所以这就涉及到一个编码转换的问题了。
同样的道理,我们可不可以用GBK编码,用UTF-8解码,得到的乱码再进行编码,解码得到原来的数据呢?
//字符编码
//GBK读取Utf-8编写的数据
import java.util.*;
public class EncodeDemo {
public static void main(String[] args)
{
ISOreadGBK();
}
public static void ISOreadGBK()
{
//******************编码过程****************************************************
String s = "你好";
byte[] b1 = null;
try
{
b1 = s.getBytes("GBK");//这里按照GBK的方式进行编码,会发生异常,因为可能会传的参数没有这样的码表
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用Utf-8编码之后的字节数组为:"+Arrays.toString(b1));//将编码后返回的字节数据打印出来
//******************解码过程****************************************************
String s2 = null;
try
{
s2 = new String(b1,"Utf-8");//这里传进来一个字节数组,然后指定编码表,会解码成一个字符串
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("使用Utf-8码表去读取GBK码表编写的数据结果为:"+s2);//将解码后的数据打印出来
//******************再编码*******************************************************
byte[] b2 = null;
try
{
b2 = s2.getBytes("Utf-8");//对获取到的错误的乱码再进行基于原码表的解码获取到字节数组
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("对乱码再进行编码后得到的字节数组为:"+Arrays.toString(b2));
//******************再编码*******************************************************
//针对字节数组,再指定GBK编码表进行解码数据
String s3 = null;
try
{
s3 = new String(b2,"GBK");
}
catch (Exception e)
{
throw new RuntimeException("指定的编码表不存在");
}
System.out.println("进行再编码解码之后得到的数据为:"+s3);
}
}
运行结果为:
结果发现,并没有出现我们想要的结果,
用ISO8859-1就可以实现乱码的还原,为什么用Utf-8反而不行了呢?
在上边运行结果中我们发现,第一次编码没有问题,得到了GBK对应的字节数组,
之后使用Utf-8码表去解析的时候出错了,得到了乱码字符,也没有问题
到对乱码在进行编码的时候,出现了和源码不一样的字节数组,
问题就出在了这里,
从GBK的编码获得的字节数组查Utf-8码表的时候找不到,返回了“?”
这个“?”在Utf-8中是存在了未知字符区域,
当再进行编码的时候就会给?重新编一个值,编成了一个三个字节的?
为什么Utf-8会重新编值呢?而ISO8859-1就不会重新编值呢?
因为Utf-8和GBK都可以识别中文,而ISO8859-1不识别中文,不会对中文进行编码,读到什么,解出来的还是什么。
联通的示例
我在记事本上写下一个联通,然后保存再打开,结果成了这个样子:
出现了乱码,这是为什么呢?不是默认是GBK编码的吗?怎么会出现乱码呢?
一看记事本这个时候的编码方式,变成了Utf-8:
默认的GBK编码为什么会变成Utf-8编码的呢?
这个时候就要了解一下Utf-8中的编码格式了:
我们知道,Utf-8有用一个字节存储的,也有两个字节存储的,还有用三个字节存储的。
那么他是按照什么方式来判断到底要读取几个字节的呢?
Utf-8中的编码形式如下图:
解读一下就是:
当读到的字节的首位是0的时候,就只读一个字节,回去查表
当读到的字节的首位是110,第二个字节的首位是10的时候,就读取两个字节,回去查表
当读到的字节的首位是1110,第二个字节的首位是10,第三个字节的首位是10的时候,就读取三个字节,回去查表
而联通这两个字,在内存中的二进制码是这样的:
//获取联通的GBK编码的二进制表现数
public class EncodeDemo2 {
public static void main(String[] args) throws Exception
{
String s = "联通";
byte[] buf = s.getBytes("GBK");
for(int i:buf)
{
System.out.println(Integer.toBinaryString(i&255));
}
}
}
四个字节的二进制码如下:
他是怎么样从GBK变啊变成Utf-8编码的呢?
当记事本将联通写入到文件中的时候,用GBK码表进行编码,编成如上图所示的二进制表现形式,
然后再打开记事本软件的时候,这就是一个解析编码的过程,
记事本一看,第一个字节首位是110,符合Utf-8编码的形式,继续往下读,发现是10,完全符合Utf-8编码形式,这就转变成Utf-8编码形式了。
这么多字符里边,只联通字符是这样的,其他字符并不这样,
解决方式:就是写联通的时候,在联通之前加上一个汉字,
练习:
有五个学生,每个学生有三门课程的成绩
从键盘输入以上数据,(包括姓名,三门课成绩)
输入的格式如:zhagnsanm30,40,32计算出总成绩
并把学生的信息和计算出的总分数高低顺序存放在磁盘文件“stud.txt”中
/*
有五个学生,每个学生有三门课程的成绩
从键盘输入以上数据,(包括姓名,三门课成绩)
输入的格式如:zhagnsanm30,40,32计算出总成绩
并把学生的信息和计算出的总分数高低顺序存放在磁盘文件“stud.txt”中
*/
/*
考虑到有很多学生对象,所以可以用集合来进行存学生对象
还涉及到了写入流的操作:
*/
import java.io.*;
import java.util.*;
//先来定义一个Student类:实现可以自己排序的,还要为存储在集合中做准备
class Student implements Comparable<Student>
{
private String name;
private int ma;
private int cn;
private int en;
private int sum;
Student(String name,int ma,int cn,int en)
{
this.name = name;
this.ma = ma;
this.cn = cn;
this.en = en;
sum = ma+cn+en;
}
public int getSum()
{
return sum;
}
public String getName()
{
return name;
}
//这是为将来写数据的时候做准备
public String toString()
{
return "Student["+name+", "+ma+", "+cn+", "+en+" ]";
}
//说不准对象会存到HashSet中呢,所以先复写了hashCode方法和equals方法,
//姓名和总分一样视为同一个人
public int hashCode()
{
return name.hashCode()+sum*7;
}
public boolean equals(Object obj)
{
if (!(obj instanceof Student))
{
throw new RuntimeException("传入的类型出错");
}
Student s = (Student)obj;
return this.name.equals(s.name) && this.sum==s.sum;
}
//实现了可比较的接口,就要复写这个方法
public int compareTo(Student s)
{
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0)
return this.name.compareTo(s.name);
return num;
}
}
//接下来定义操作学生的工具类
class StudentInfoTool
{
//如果按照自己默认排序的话这里的方法传的比较器就为空
public Set<Student> getStudent()
{
return getStudent(null);
}
//如果想要逆转排序的话,这里该方法可以讲原始的排序方式逆转
//该方法可以将从键盘获取到的对象存储进集合
//返回一个Set集合。
public Set<Student> getStudent(Comparator<Student> com)
{
//判断参数是否为空,空的话就创建一个不带比较器的集合,不为空的话,就创建一哥带有比较器的集合
TreeSet<Student> ts = null;
if(com==null)
ts = new TreeSet<Student>();
else
ts= new TreeSet<Student>(com);
BufferedReader bufr = null;
try
{
bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
//从键盘中获取到创建对象的各个元素
String[] strs = line.split(",");
Student stu = new Student(strs[0],Integer.parseInt(strs[1]),
Integer.parseInt(strs[2]),
Integer.parseInt(strs[3]));
ts.add(stu);//将创建好的对象添加进集合中
}
}
catch (IOException e)
{
throw new RuntimeException("流对象创建失败");
}
finally
{
if(bufr!=null)
{
try
{
bufr.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return ts;
}
//该方法可以讲集合中的对象,写入到文件中
//接收一个Set集合,
public void Set2File(Set<Student> ts)
{
BufferedWriter bufw = null;
try
{
bufw = new BufferedWriter(new FileWriter("stud.txt"));
for(Student s:ts)
{
bufw.write(s.toString()+"\t");
bufw.write(s.getSum()+"");
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("写入流开启失败");
}
finally
{
if(bufw!=null)
{
try
{
bufw.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
public class StudentInfoTest {
public static void main(String[] args)
{
Comparator<Student> com = Collections.reverseOrder();
StudentInfoTool sit = new StudentInfoTool();
Set<Student> set = sit.getStudent(com);
sit.Set2File(set);
}
}
运行结果为: