1. IO流
对象的序列化
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
用ObjectOutputStream保存的对象只能用ObjectInputStream读取
void writeObject(Object obj):将指定的对象写入 ObjectOutputStream。
Object readObject():从 ObjectInputStream 读取对象。
请看如下示例:
class Person implements Serializable//标记接口,没有方法
{
//UID是给类添加固定标识,为了序列化方便
//自动生成UID时,class文件的代码变动,无法使用原有对象,所以手动生成唯一UID
publicstaticfinallongserialVersionUID = 42L;
private String name;
transientintage;//transient修饰的变量,不能被序列化
static String country = "cn";//静态是不能被序列化的,;因为他在方法区中(共享区)
Person(String name,int age,String country)
{
this.name = name;
this.age = age;
this.country = country;
}
public String toString()
{
returnname+":"+age+":"+country;
}
}
import java.io.*;
class ObjectStreamDemo
{
publicstaticvoid main(String[] args) throws Exception
{
//writeObj();
readObj();
}
publicstaticvoid readObj()throws Exception
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream
("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
publicstaticvoid writeObj()throws IOException
{
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi0",399,"kr"));
oos.close();
}
}
管道流
1.概念:
PipedInputStream:管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
void connect(PipedOutputStream src):使此管道输入流连接到管道输出流 src。
void connect(PipedInputStream snk) : 将此管道输出流连接到接收者。
2. 特点
管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
涉及多线程的IO流是管道流,涉及IO流的集合是properties
3. 怎么使用?
class Read implements Runnable
{
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in = in;
}
publicvoid run()
{
try
{
byte[] buf = newbyte[1024];
System.out.println("读取前。。没有数据阻塞");
int len = in.read(buf);//read()是阻塞式的方法
System.out.println("读到数据。。阻塞结束");
String s= new String(buf,0,len);
System.out.println(s);
in.close();
}
catch (IOException e)
{
thrownew RuntimeException("管道读取流失败");
}
}
}
class Write implements Runnable
{
private PipedOutputStream out;
Write(PipedOutputStream out)
{
this.out = out;
}
publicvoid run()
{
try
{
System.out.println("开始写入数据,等待6秒后。");
Thread.sleep(6000);
out.write("piped lai la".getBytes());
out.close();
}
catch (Exception e)
{
thrownew RuntimeException("管道输出流失败");
}
}
}
class PipedStreamDemo
{
publicstaticvoid main(String[] args) throws IOException
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
RandomAccessFile
1. 概念
随机访问文件,自身具备读写的方法。
通过skipBytes(int x),seek(int x)来达到随机访问。
int read(byte[] b):将最多 b.length个数据字节从此文件读入 byte 数组。
int readInt():从此文件读取一个有符号的 32位(4字节)整数。
void seek(long pos): 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。调整对象中指针。前后都能跳。
skipBytes(int n):尝试跳过输入的 n个字节以丢弃跳过的字节。不能往回跳。
2. 特点
该类不算是IO体系中子类。
而是直接继承自Object。
但是它是IO包中成员。因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。(操作数据必然是流)
通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读r,,读写rw等。
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。
3. 怎么使用?
class RandomAccessFileDemo
{
publicstaticvoid main(String[] args) throws IOException
{
//writeFile_2();
//readFile();
//System.out.println(Integer.toBinaryString(258));
}
publicstaticvoid readFile()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
//调整对象中指针。
//raf.seek(8*1);
//跳过指定的字节数
raf.skipBytes(8);
byte[] buf = newbyte[4];//一个中文两个字节,刚好读取到两个中文
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();//一次读取四个字节,32位
System.out.println("name="+name);
System.out.println("age="+age);
raf.close();
}
publicstaticvoid writeFile_2()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.seek(8*0);
raf.write("周期".getBytes());
raf.writeInt(103);//writer()方法只写int类的最低八位,其他会丢失。writeInt()写INT类型的4字节32位
raf.close();
}
publicstaticvoid writeFile()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
raf.close();
}
}
4. 什么时候使用?
实现数据的分段写入,每一段自己拥有一个线程,如:下载软件原理,每个软件下载独立执行,分段下载,实现多线程下载
操作基本数据类型的流对象DataStream
1. 概念
可以用于操作基本数据类型的数据的流对象。
gbk:一个中文占2字节
utf-8:一个中文占3字节
utf-8修改版:一个中文占4字节
不同编码写的文件,需要用不同的编码读取,否则读取错误
double readDouble() :读取八个输入字节并返回一个 double值。
int readInt():读取四个输入字节并返回一个 int值。
void writeUTF(String s):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。
EOFException - 如果此输入流在读取所有字节之前到达末尾。既没读完数据就到结尾了
2. 特点
基本数据类型写入和取出的顺序要保持一致
3. 怎么使用?
class test
{
publicstaticvoid main(String[] args) throws IOException
{
//基本数据类型的读写操作
write();
read();
//按照固定字符编码格式写入字符
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c:/t2.txt"),"GBK");
osw.write("你是傻蛋");
osw.close();
BufferedReader osr = new BufferedReader(new InputStreamReader(new FileInputStream("c:/t2.txt")));
String line=null;
while((line=osr.readLine())!=null)
{
sop(line);
}
//writeUTF()方法读写文件
writeUTF();
readUTF();
}
publicstaticvoid readUTF()throws IOException
{
DataInputStream dif= new DataInputStream(new FileInputStream("c:/t3.txt"));
sop(dif.readUTF());//只能读取writeUTF()方法写的文件
dif.close();
}
publicstaticvoid writeUTF()throws IOException
{
DataOutputStream dof = new DataOutputStream(new FileOutputStream("c:/t3.txt"));
dof.writeUTF("我很帅");//英文不涉及编码,用中文
dof.close();
}
publicstaticvoid read()throws IOException
{
DataInputStream dif= new DataInputStream(new FileInputStream("c:/t1.txt"));
sop(dif.readDouble());
sop(dif.readInt());
sop(dif.readBoolean());
sop(dif.readByte());
dif.close();
}
publicstaticvoid write()throws IOException
{
DataOutputStream dof = new DataOutputStream(new FileOutputStream("c:/t1.txt"));
dof.writeDouble(33.333333);
dof.writeInt(35);
dof.writeBoolean(true);
dof.writeByte(88);
dof.close();
}
publicstaticvoid sop(Object obj)
{
System.out.println(obj);
}
}
4.什么时候使用?
当需要操作基本数据类型的时候
操作字节数组-ByteArrayStream
1. 概念
用于操作字节数组的流对象。
ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
ByteArrayInputStream :在构造的时候,需要接收数据源,。而且数据源是一个字节数组。
ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
在流操作规律讲解时:
源设备,
键盘 System.in,硬盘 FileStream,内存 ArrayStream。
目的设备:
控制台 System.out,硬盘FileStream,内存 ArrayStream。
2. 特点
没有调用底层资源,关闭 ByteArrayInputStream无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
因为这两个流对象都操作的数组,并没有使用系统资源。
所以,不用进行close关闭。
3. 怎么使用?
class test
{
publicstaticvoid main(String[] args)
{
//读取数据源,在内存中会难过
ByteArrayInputStream bas = new ByteArrayInputStream("asdfqwer".getBytes());
//数据目的,在内存中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len=0;
while((len=bas.read())!=-1)
{
bos.write(len);
sop("写入的数据是:"+(char)len);
}
//默认数组(缓冲区)的大小
sop(bos.size());
//取出缓冲区数据
sop(bos.toString());
//bos.writeTo(new FileOutputStream("a.txt"));//只有此方法会抛出异常
}
publicstaticvoid sop(Object obj)
{
System.out.println(obj);
}
}
4. 什么时候使用?
当要将硬盘文件读到内存中,放到可变长度的数组里存储起来时(用流的思想操作数组,读和取)
操作字符数组-CharArrayReader与CharArrayWrite
用法与操作字节数组相似
操作字符串数组-StringReader与 StringWriter
用法与操作字节数组相似
转换流的字符编码
1. 概念
编码表
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个
国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
常见的编码表
ASCII:美国标准信息交换码。
• 用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表
• 用一个字节的8位表示。
GB2312:中国的中文编码表。两个字节的8位表示,且两个字节的高位都是1。兼容ASCII码。容纳6000-7000个字。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。容纳20000多个字。
Unicode:国际标准码,融合了多种文字。
• 所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8:最多用三个字节来表示一个字符。(能用一个字节的就用一个字节存储,两个字节的就用两个字节。减少空间)每个字节开头都有标识头,容易区分UTF-8。
当两个编码表都能识别中文,但是同一文字在两张码表中对应的数字不同。所以涉及到了编码转换问题
不同编码进行写出和读取操作原理:
2. 特点
可以将字符以指定编码格式存储。
可以对文本数据指定编码格式来解读。
指定编码表的动作由构造函数完成。
3. 怎么使用?
class EncodeStream
{
publicstaticvoid main(String[] args) throws IOException
{
//writeText();
readText();
}
publicstaticvoid readText()throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"gbk");
char[] buf = newchar[10];
int len = isr.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
isr.close();
}
publicstaticvoid writeText()throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");
osw.write("你好");
osw.close();
}
}
字符编码
1. 概念
编码:字符串变成字节数组。
解码:字节数组变成字符串。
编码正确,解码错误,解决方法原理:
注意:
当用UTF-8解码—》重新编码—》解码,结果错误;因为GBK和UTF-8都识别中文。UTF-8有未知编码区域,当UTF-8解码时,查不到指定的中文时就在未知区域返回相似的字符?,重新编码时,返回相似字符的数字,此时数字已经变化了。
2. 特点
String-->byte[]:str.getBytes(charsetName);
byte[] -->String: new String(byte[],charsetName);
Array.toString(byte[]):此方法可以将字节数组转变为字符串,但是不能指定编码格式。
中文不能用ISO8859-1进行编码,出错
3. 怎么使用?
class EncodeDemo
{
publicstaticvoid main(String[] args)throws Exception
{
String s = "哈哈";
//编码
byte[] b1 = s.getBytes("GBK");
System.out.println(Arrays.toString(b1));
//解码
String s1 = new String(b1,"iso8859-1");//String s1 = new String(b1,"utf-8");
System.out.println("s1="+s1);
//对s1进行iso8859-1编码。
byte[] b2 = s1.getBytes("iso8859-1");//byte[] b2 = s1.getBytes("utf-8");
System.out.println(Arrays.toString(b2));
//对b2进行gbk解码。
String s2 = new String(b2,"gbk");
System.out.println("s2="+s2);
}
}
4. 什么时候使用?
当需要指定格式对数据进行编码,解码时。
字符编码-联通(特殊文字)
UTF-8和GBK编码表的特点
Utf-8根据标识头来判断一次是读一个字节还是两个字节还是三个字节,如下图:
请看如下案例:
class test
{
publicstaticvoid main(String[] args) throws Exception
{
String s = "联通";
byte[] by = s.getBytes("gbk");
for(byte b : by)
{
//将十进制的String类型数据转换成二进制,而且只取后八位
System.out.println(Integer.toBinaryString(b&255));
}
}
}
产生乱码的原因:
"联通"用GBK编码表产生的二进制数,与UTF-8的二进制数一致,当在记事本写入"联通"两字保存后,重新打开,进行解码。此时因为编码产生的二进制数与utf-8的一致,所以默认用utf-8编码表进行解码。导致解码错误;
解决方法:
在"联通"两字前面加上其他的中文;
练习-将学生信息写到文件中
/*
有五个学生,每个学生有3门课的成绩,
从键盘输入以上数据(包括姓名,三门课成绩),
输入的格式:如:zhagnsan,30,40,60计算出总成绩,
并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。
1,描述学生对象。
2,定义一个可操作学生对象的工具类。
思想:
1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。
2,因为学生有很多,那么就需要存储,使用到集合。因为要对学生的总分排序。
所以可以使用TreeSet。
3,将集合的信息写入到一个文件中。
*/
publicclass test
{
publicstaticvoid main(String args[]) throws IOException
{
//默认排序,升序
Set<Student> set = StudentTool.getStudent();
StudentTool.writeStudent(set);
//定义比较器,降序排序,所以要逆转指定比(默认)较器的顺序
/*Comparator<Student> cmp = Collections.reverseOrder();
Set<Student> set1 = StudentTool.getStudent(cmp);
StudentTool.writeStudent(set1);*/
}
}
//定义学生类。因为要排序,所以要实现comparable类,复写hashcode(),equals(),compareTo()方法
class Student implements Comparable<Student>
{
private String name;
privateintch,ma,en;
privateintsum;
Student(String name,int ch,int ma,int en)
{
this.name = name;
this.ch = ch;
this.ma = ma;
this.en = en;
sum = ch+ma+en;
}
public String getname()
{
returnname;
}
publicint getsum()
{
returnsum;
}
publicint HashCode()
{
returnname.hashCode()+sum*39;
}
publicboolean equals(Object obj)
{
if(!(obj instanceof Student))
thrownew ClassCastException("类型不匹配");
Student s = (Student)obj;
returnthis.name.equals(s.name)&&this.sum==s.sum;
}
publicint compareTo(Student s)
{
int num = new Integer(this.sum).compareTo(new Integer(s.sum));//按分数排序,分数相同按照姓名
if(num==0)
returnthis.name.compareTo(s.name);
return num;
}
public String toString()
{
return"Student["+name+", "+ch+", "+ma+", "+en+"]";
}
}
//定义工具类,操作学生对象
class StudentTool
{
//将数据保存到学生对象中,将学生对象保存到TreeSet集合中
//按照自定义比较器排序,降序
publicstatic Set<Student> getStudent(Comparator<Student> cmp) throws IOException
{
BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
String line = null;
//因为要按照分数排序,而且要将学生对象存储,所以定义TreeSet集合
Set<Student> set = null;
if(cmp==null)
set = new TreeSet<Student>();
else
set = new TreeSet<Student>(cmp);
while((line = buf.readLine())!=null)
{
if("over".equals(line))
break;
String s[] = line.split(",");
//将数据保存到学生对象中
Student stu = new Student(s[0],
Integer.parseInt(s[1]),
Integer.parseInt(s[2]),
Integer.parseInt(s[3]));
set.add(stu);
}
buf.close();
return set;
}
//按照默认排序,升序
publicstatic Set<Student> getStudent() throws IOException
{
return getStudent(null);
}
//将集合中的数据写到文件中
publicstaticvoid writeStudent(Set<Student> s) throws IOException
{
BufferedWriter bw = new BufferedWriter(new FileWriter("c:/Student1.txt"));
for(Student stu:s)
{
bw.write(stu.toString()+"\t");
bw.write(stu.getsum()+"");//因为write方法只识别INT类型数据的后八位,所以转换成字符串类型
bw.newLine();
bw.flush();
}
bw.close();
}
}
<span style="font-family:Calibri;font-size:14px;"> </span>