黑马程序员-Java IO输入与输出-day21

时间:2021-04-17 10:55:55

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

1、IO流——对象的序列化

操作对象:ObjectInputStream与ObjectOutputStream,被操作的对象需要实现Serializable(标记接口);

import java.io.*;
class Person implements Serializable
{
//自定义UID值,保证UID值的唯一性
public static final long serialVersionUID = 42L;
String name;
transient int age;//transient可以让age无法序列化,保证其值在堆内存中存在而不再文本文件中存在
static String country="cn";
Person(String name,int age,String country)
{
this.name = name;
this.age = age;
this.country = country;
}
public String toString()
{
return name+":"+age+"::"+country;
}
}

import java.io.*;
class ObjectStreamDemo
{
public static void main(String[] args) throws Exception
{
//writeObj();
readObj();
}

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();
}
public static void writeObj()throws IOException
{
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));

oos.writeObject(newPerson("lisi",30,"cn"));

oos.close();
}
}
注意:在使用对象序列化的时候,必须是两者搭配使用,即用了ObjectInputStream就得用ObjectOutputStream;而其中,被静态所修饰的成员变量是无法被序列化的,另外transient也能让成员变量无法序列化

特别注意一点:静态时无法被序列化的

2、IO流——管道流

管道流分PipedInputStream和PipedOutputStream

输入输出可以直接进行连接,通过结合线程使用。

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。

集合当中涉及到IO流的是Properties,IO当中涉及到多线程的就是管道流

import java.io.*;
class Read implements Runnable
{
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in = in;
}
public void run()
{
//读数据
try
{
byte[]buf = new byte[1024];
System.out.println("读取前。。没有数据,阻塞");
intlen = in.read(buf);
System.out.println("读到数据。。阻塞结束");
Strings = new String(buf,0,len);

System.out.println(s);
in.close();
}
catch (IOException e)
{
throw new RuntimeException("管道读取流失败");
}
}
}
class Write implements Runnable
{
private PipedOutputStream out;
Write(PipedOutputStream out)
{
this.out = out;
}
public void run()
{
try
{
System.out.println("开始写入数据,等待6秒后...");
Thread.sleep(6000);
out.write("pipedlai le".getBytes());//转换成字节流
out.close();
}
catch (Exception e)
{
throw new RuntimeException("管道输出流失败");
}
}
}
class PipedStreamDemo
{
public static void 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();

}
}
小结:

管道流之所以不会造成线程死锁的原因是管道读取流中有一个read方法,只要没有读取到数据,该线程则会出现阻塞情况


3、IO流——RandomAccessFile

RadomAccessFile:

1.随机访问文件,自身具备读写的方法。

2.通过skipBytes(int x),seek(int x)来达到随机访问。

操作基本数据类型:DataInputStream与DataOutputStream

操作字节数组:ByteArrayInputStream与ByteArrayOutputStream

操作字符数组:CharArrayReader与CharArrayWrite

操作字符串:StringReader与StringWriter

RandomAccessFile该类不算是IO体系中的子类,而是直接集成自Object。但是他是IO包中成员,因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:只读r,读写rw等。如果模式为只读r,不会创建文件,然后去读取已存在的文件;如果该文件不存在,则会出现异常。如果模式为rw,操作的文件不存在,会自动创建,如果存在则也不会被覆盖。

public class RandomAccessFile extendsObjece implements DataOutput,DataInput,Closeable   此类的实例支持对随机访问文件的读取和写入

import java.io.*;
class RandomAccessFileDemo
{
public static void main(String[] args) throws IOException
{
//writeFile();
//readFile();
writeFile_2();
}
public static void writeFile_2()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("fan.txt","rw");
raf.seek(8*1);
raf.write("周七".getBytes());
raf.writeInt(107);

raf.close();
}
public static void readFile()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("fan.txt","r");

//调整对象中的指针
//raf.seek(8*1);

//跳过制定的字节数,缺点是不能往回跳
raf.skipBytes(8);

byte[] buf = new byte[4];
raf.read(buf);

String name = new String(buf);
int age = raf.readInt();

System.out.println("name="+name);
System.out.println("age="+age);
raf.close();
}
public static void writeFile()throws IOException
{
RandomAccessFile raf = new RandomAccessFile("fan.txt","rw");
raf.write("李四".getBytes());
raf.writeInt(97);//之所用writeInt是为了防止精度丢失
raf.write("王五".getBytes());
raf.writeInt(99);

raf.close();
}
}
小结:

1.由于write底层封装了只写出数据的最低八位,因此这样容易造成精度丢失,而为了防止出现这样的错误,于是就得将数据的最低八位都写出去,这样才能保证数据的原样性。

2.seek可以随机调整指针位置,而skipBytes只能向后调整指针位置。

 

4、操作基本数据类型的流对象DataStream

操作基本数据类型:DataInputStream与DataOutputStream

操作字节数组:ByteArrayInputStream与ByteArrayOutputStream

操作字符数组:CharArrayReader与CharArrayWriter

操作字符串数组:StringReader与StringWriter

DataInputStream与DataOutputStream可以用于操作基本数据类型的数据的流对象。

import java.io.*;
class DataStreamDemo
{
public static void main(String[] args) throws IOException
{
//writeData();
//readData();
//writeUTFDemo();
//writeGBKDemo();
readUTFDemo();
}
public static void readUTFDemo()throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
String s = dis.readUTF();
System.out.println(s);

dis.close();
}
public static void writeGBKDemo()throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");
osw.write("你好");
osw.close();
}
public static void writeUTFDemo()throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));
dos.writeUTF("你好");
dos.close();
}
public static void readData()throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));

int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();

System.out.println("num="+num);
System.out.println("b="+b);
System.out.println("d="+d);

dis.close();
}
public static void writeData()throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));

dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9879.0);

dos.close();
}
}
小结:

1.切记一点,在读取数据的过程中,读取的顺序不能错,否则就会读取错误

2.凡是操作基本数据类型的就用DataStream

 

5、用于操作字节数组的流对象ByteArrayStream

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组。

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。

public class ByteArrayInputStream extendsInputStream     ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节。关闭ByteArrayInputStreamReader无效。此类中的方法在关闭此流后仍可被调用而不会产生任何IOException。

因为这两个流都操作的是数组,并没有使用系统资源,所以,不用进行close关闭。

在流操作规律讲解时:

源设备:

       键盘:System.in;   硬盘:FileStream;      内存:ArrayStream

目的设备:

       控制台:System.out;    硬盘:FileStream;      内存:ArrayStream

用流的读写思想 来操作数据

import java.io.*;
class ByteArrayStream
{
public static void main(String[] args)
{
//数据源
ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEF".getBytes());

//数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch =0;
while((ch=bis.read())!=-1)
{
bos.write(ch);
}
System.out.println(bos.size());
System.out.println(bos.toString());
}
}

6、转换流的字符编码

字符编码

1.字符流的出现为了方便操作字符。

2.更重要的是加入了编码符集。

3.通过子类转换来完成。InputStreamReader和OutputStreamWriter

4.在两个对象进行构造的时候可以加入字符集。

 

编码表的由来:

1.计算机只能识别二进制数据,早起由来是电信号;

2.为了方便应用计算机,让它可以识别各个国家的文字;

3.就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

常见的编码表

ASCII:美国标准信息交换码,用一个字节的7位可以表示。

ISO8859-1:拉丁码表,欧洲码表,用一个字节的8位表示。

GBK2312:中国的中文编码表。

GBK:中国的中文编码表升级,融合了更多的中文文字符号。

Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode

UTF-8:最多用三个字节来表示一个字符。

import java.io.*;
class EncodeStream
{
public static void main(String[] args) throws IOException
{
//writeText();
readText();
}
public static void readText()throws IOException
{
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"GBK");
char[] buf = new char[10];
int len = isr.read(buf);

String str = new String(buf,0,len);
System.out.println(str);

isr.close();
}
public static void writeText()throws IOException
{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
osw.write("你好");
osw.close();
}
}


7、字符编码

编码:字符串变成字节数组

解码:字节数组变成字符串

String     -->byte[]:str.getBytes(charsetName);

byte[]      -->String :new String(byte[],charsetName);

编码解码的时候要是遇到解错码,且都是中文,一定得注意不能使用UTF-8去解码,因为都识别中文

import java.util.*;
class EncodeDemo
{
public static void 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");//解码成拉丁编码表
System.out.println("s1="+s1);


//解码
byte[] b2 =s1.getBytes("iso8859-1");
System.out.println(Arrays.toString(b2));
String s2 = new String(b2,"GBK");
System.out.println("s2="+s2);
}
}
注意:由于UTF-8和GBK均识别中文,因此在对字符串进行编码解码的过程中,千万要注意这点,否则就会得到乱码;Tomcat服务器使用的就是ISO8859-1的编码表,当数据传递给服务器时,需要通过编码解码才能得到最终数据。


8、字符编码—联通

由于联通的二进制编码表正好也符合UTF-8的编码表,因此在记事本解码联通两个字的时候,就会解析出错

联通二进制最后八位:

11000001

10101010

11001101

10101000

刚好符合UTF-8中的110    10的解码方式,因此就会导致解码出错。

class EncodeDemo2
{
public static void main(String[] args) throws Exception
{
String s = "联通";
byte[] by = s.getBytes();
for(byte b : by)
{
System.out.println(Integer.toBinaryString(b&255));
}
}
}

练习:有五个学生,每个学生有3门课的成绩,从键盘输入以上数据(包括姓名,三门课成绩),输入的格式

如:zhangsan,30,40,60计算出总成绩,并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。

 

1.描述学生对象;

2.定义一个可操作学生对象的工具类。

 

思路:

1.通过获取键盘录入一行数据,并将改行中的信息取出封装成学生对象。

2.因为学生有很多,那么就需要存储,使用到集合,因为要对学生的总分进行排序。所以可以使用TreeSet。

3.将集合的信息写入到一个文件中。

import java.io.*;
import java.util.*;
//定义一个学生类,并实现Comparable接口
class Student implementsComparable<Student>
{
private String name;
private int ma,cn,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并不是传递进来的,因此这里是前者的累加和
sum = ma + cn + en;
}
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;
}
public String getName()
{
return name;
}
public int getSum()
{
return sum;
}
//定义一个hashCode方法,防止获取的值存到其他集合中
public int hashCode()
{
return name.hashCode()+sum*78;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
thrownew ClassCastException("类型不匹配");
//强制类型转换
Student s = (Student)obj;
return this.name.equals(s.name)&& this.sum==s.sum;
}

public String toString()
{
return "Student["+name+","+ma+", "+cn+", "+en+"]";
}

}


//定义一个学生工具类
class StudentInfoTool
{
//定义一个不含比较器的方法
public static Set<Student> getStudents()throws IOException
{
return getStudents(null);
}
//定义一个含Comparator接口的方法
public static Set<Student> getStudents(Comparator<Student> cmp)throws IOException
{
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
Set<Student> stus = null;
if(cmp==null)
stus= new TreeSet<Student>();
else
stus= new TreeSet<Student>(cmp);
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
String[]info = line.split(",");
Studentstu = new Student(info[0],Integer.parseInt(info[1]),
Integer.parseInt(info[2]),
Integer.parseInt(info[3]));
stus.add(stu);
}
bufr.close();
return stus;
}
public static void write2File(Set<Student> stus)throws IOException
{
//将流与文件相关联
BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));
//使用高级for循环遍历集合
for(Student stu:stus)
{
bufw.write(stu.toString()+"\t");
bufw.write(stu.getSum()+"");
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}
class StudentInfoTest
{
public static void main(String[] args)throws IOException
{
Comparator<Student> cmp =Collections.reverseOrder();

Set<Student> stus =StudentInfoTool.getStudents(cmp);

StudentInfoTool.writeTOFile(stus);
}
}
注意:

reverse()只是反转的,而reverseOrder()则是反转比较器的,不能记错!



---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------
详细请查看:http://edu.csdn.net