打印流
PrintStream
1.提供了打印方法可以对多种数据类型值进行打印。并保持数据的表现形式。
2.不抛出IOException。
构造函数,接收三种类型的值:
1.字符串路径
2.File对象
3.字节输出流
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
PrintStream out=new PrintStream("print.txt");
// int by=read();//读到一个字节,提升为int
out.write(97);//a 把int中的最低八位写入 只写一个字节
out.write(609);//a
// out.print(97);//97 将97先变成字符串,保持原样,将数据打印到目的地
out.close();
}
}
PrintStream打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。
PrintWriter
向文本输出流打印对象的格式化表示形式。此类实现在PrintStream的所有print方法。此类中的方法不会抛出IO异常。
构造函数参数:
1.字符串路径
2.File对象
3.字节输出流
4.字符输出流
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
// PrintWriter out=new PrintWriter(System.out);
// PrintWriter out=new PrintWriter(System.out,true);//true自主刷新
PrintWriter out=new PrintWriter(new FileWriter("out.txt"),true);
String line=null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());
// out.flush();
}
out.close();
bufr.close();
}
}
序列流 SequenceInputStream
SequenceInputStream 表示其他输入流的逻辑串联,作用是 对多个流进行合并。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从
第二个输入流读取,以此类推,直到到达包含的最后一个输出流的文件末尾为止。
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
/*
* 需求:将1.txt 2.txt 3.txt文件中的数据合并到一个文件中。
* */
/*Vector<FileInputStream> v=new Vector<FileInputStream>();
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
*/
ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
for(int x=1;x<3;x++){
al.add(new FileInputStream(x+".txt"));
}
Enumeration<FileInputStream> en=Collections.enumeration(al);
/*
final Iterator<FileInputStream> it=al.iterator();
Enumeration<FileInputStream> en=new Enumeration<FileInputStream>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public FileInputStream nextElement() {
return it.next();
}
};
*/
// Enumeration<FileInputStream> en=v.elements();
SequenceInputStream sis=new SequenceInputStream(en);
FileOutputStream fos=new FileOutputStream("4.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
多种流对象的综合应用
文件切割器
public class SpltFileDemo {
private static final int SIZE = 1024*1024;
public static void main(String[] args) throws IOException {
File file=new File("c:\\0.bmp");
// splitFile(file);
splitFile_2(file);
}
public static void splitFile(File file) throws IOException {
//用读取流关联源文件
FileInputStream fis=new FileInputStream(file);
//定义一个1M的缓冲区
byte[] buf=new byte[SIZE];
//创建目的
FileOutputStream fos=null;
int len=0;
int count=1;
File dir=new File("c:\\partfiles");
if(!dir.exists())
dir.mkdir();
while((len=fis.read(buf))!=-1){
fos=new FileOutputStream(new File(dir,(count++)+".part"));
fos.write(buf,0,len);
}
fos.close();
fis.close();
}
public static void splitFile_2(File file) throws IOException{
//用读取流关联源文件
FileInputStream fis=new FileInputStream(file);
//定义一个1M的缓冲区
byte[] buf=new byte[SIZE];
//创建目的
FileOutputStream fos=null;
int len=0;
int count=1;
/*
切割文件时,必须记录住被切割文件的名称,以及切割出来的碎片的个数,以方便于合并
这个信息为了进行描述,使用键值对方式,用到了properties对象。
*/
Properties prop=new Properties();
File dir=new File("c:\\partfiles");
if(!dir.exists())
dir.mkdir();
while((len=fis.read(buf))!=-1){
fos=new FileOutputStream(new File(dir,(count++)+".part"));
fos.write(buf,0,len);
fos.close();
}
//将被切割文件的信息保存到prop集合中
prop.setProperty("partcount", count+"");
prop.setProperty("filename", file.getName());
fos=new FileOutputStream(new File(dir,count+".properties"));
//将prop集合中的数据存储到文件中
prop.store(fos,"save file info");
fos.close();
fis.close();
}
碎片文件合并
1.首先要有过滤器将“.properties”文件过滤出来,拿到配置文件信息的碎片个数,和文件原名称。
2。需要序列流将多个碎片文件合并。
public class MergeFile {
public static void main(String[] args) throws IOException {
File dir=new File("c:\\partfiles");
// mergeFile(dir);
mergeFile_2(dir);
}
public static void mergeFile_2(File dir) throws IOException {
/*
获取指定目录下的配置文件对象
*/
File[] files=dir.listFiles(new SuffixFilter(".properties"));
if(files.length!=1)
throw new RuntimeException(dir+",该目录下没有properties文件,或者不唯一");
//记录配置文件对象
File confile=files[0];
// ===========================================
//获取该文件中的信息
Properties prop=new Properties();
FileInputStream fis=new FileInputStream(confile);
prop.load(fis);
String filename=prop.getProperty("filename");
int count=Integer.parseInt(prop.getProperty("partcount"));
// ===============================================
//获取该目录下的所有碎片文件
File[] partFiles=dir.listFiles(new SuffixFilter(".part"));
if(partFiles.length!=(count-1))
throw new RuntimeException("碎片文件不符合要求,个数不对!应该是"+count+"个");
// =============================================
// 将碎片文件和流对象关联并存储到集合中
ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
for(int x=1;x<partFiles.length;x++){
al.add(new FileInputStream(partFiles[x]));
}
// ========================================
//将多个流合并成一个序列流
Enumeration<FileInputStream> en=Collections.enumeration(al);
SequenceInputStream sis=new SequenceInputStream(en);
//读写过程
FileOutputStream fos=new FileOutputStream(new File(dir,filename));
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
public static void mergeFile(File dir) throws IOException{
ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
for(int x=1;x<=3;x++){
al.add(new FileInputStream(new File(dir,x+".part")));
}
Enumeration<FileInputStream> en=Collections.enumeration(al);
SequenceInputStream sis=new SequenceInputStream(en);
FileOutputStream fos=new FileOutputStream(new File(dir,"1.bmp"));
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
过滤器代码:
public class SuffixFilter implements FilenameFilter {
private String suffix;
public SuffixFilter(String suffix) {
super();
this.suffix = suffix;
}
@Override
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}
对象的序列化与反序列化
概念
在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。
但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。
对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
序列化:把Java对象转换为字节序列的过程;
反序列化:把字节序列恢复为Java对象的过程。
对象序列化机制写入的内容:对象的类,类签名,以及非瞬态(瞬态:transient)和非静态(静态:static)字段的值。
当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。 非静态数据不想被序列化可以用transient这个关键字修饰。
Serializable 接口
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出NotSerializableException。
如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable接口。
//Serializable:用于给被序列化的类加ID号,用于判断类和对象是否是同一个版本
public class Person implements Serializable{/**
*
*/
private static final long serialVersionUID = 12358134L;
//实现的Serializable是标记接口
// transient:非静态数据不想被序列化可以用这个关键字修饰
private transient String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
ObjectOutputStream类和ObjectInputStream类
ObjectInputStream 对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化;
ObjectOutputStream 将java对象的基本数据类型和图像写入OutputStream。可以使用ObjectInputStream读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。
反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息,在内存中重新构建对象。
一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeObj();
readObj();
}
public static void readObj() throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.object"));
// 对象的反序列化
Person p=(Person)ois.readObject();
System.out.println(p.getName()+":"+p.getAge());
ois.close();
}
public static void writeObj() throws IOException{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("obj.object"));
//对象序列化。被序列化的对象必须实现Serializable接口
oos.writeObject(new Person("nancy",14));//java.io.NotSerializableException: p2.bean.Person
oos.close();
}
}
serialVersionUID
serialVersionUID: 字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致。
serialVersionUID有两种生成方式:
1.Add default serial version ID这种方式生成的serialVersionUID是1L;
2.Add generated serial version ID这种方式根据类名,接口名,方法和属性等来生成serialVersionUID.
RandomAccessFile
RandomAccessFile不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至不使用InputStream和OutputStream类中已经存在的任何功能。
基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。此外,它的构造函数还要一个表示以只读方式(“r”),还是以读写方式(“rw”)打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。
特点:
1.该对象既能读,又能写。(此类的实例支持对随机访问文件的读取和写入。)
2.该对象内部维护一个byte数组,并通过指针可以操作数组中的元素。
(随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在执行该隐藏数组的光标或者索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。)
3.可以通过getFilePointer方法获取指针的位置,通过seek方法设置指针的位置。
4.其实该对象就是将字节输入流和输出流进行了封装。
5.该对象的源或者目的只能是文件。通过构造函数就可以看出。
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
// writeFile();
readFile();
// randomWrite();
}
public static void randomWrite() throws IOException{
RandomAccessFile raf=new RandomAccessFile("ranacc.txt", "rw");
//往指定位置写入数据
raf.seek(3*8);
raf.write("龙葵".getBytes());
raf.writeInt(1000);
raf.close();
}
public static void readFile() throws IOException{
RandomAccessFile raf=new RandomAccessFile("ranacc.txt", "r");
//通过seek设置指针的位置
raf.seek(1*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);
System.out.println("pos:"+raf.getFilePointer());
raf.close();
}
// 使用RandomAccessFile对象写入一些人员信息,比如姓名和年龄
public static void writeFile() throws IOException{
/*
* 如果文件不存在,则创建,如果文件存在,不创建,会在文件里面接着写入数据
* */
RandomAccessFile raf=new RandomAccessFile("ranacc.txt", "rw");
raf.write("诗诗".getBytes());
// raf.write(609);
raf.writeInt(609);
raf.write("若曦".getBytes());
raf.writeInt(99);
raf.close();
}
}
管道流
管道流是用来在多个线程之间进行信息传递的Java流,输入输出可以直接进行连接,通过结合线程使用。
注意:
第一,管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;
第二,管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或者connect函数进行连接;
第三,一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造函数;
第四,管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
第五,管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
第六,管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值。
public class PipedStream {
public static void main(String[] args) throws IOException {
PipedInputStream input=new PipedInputStream();
PipedOutputStream output=new PipedOutputStream();
input.connect(output);
new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}
}
class Input implements Runnable{
private PipedInputStream in;
Input(PipedInputStream in){
this.in=in;
}
public void run(){
try{
byte[] buf=new byte[1024];
int len=in.read(buf);
String s=new String(buf,0,len);
System.out.println("s="+s);
in.close();
}catch(Exception e){
}
}
}
class Output implements Runnable{
private PipedOutputStream out;
Output(PipedOutputStream out){
this.out=out;
}
public void run(){
try{
Thread.sleep(5000);
out.write("pipipipipipipip".getBytes());
}catch(Exception e){
}
}
}
基本数据类型的流操作
对于基本数据类型的输入输出流操作,java专门提供了一组类:DataInputStream和DataOutputstream。
构造函数:
1. DataInputStream(InputStream in)
2. DataOutputStream(OutputStream out)
特有方法:
readInt()---writeInt(int a)
readDouble()---writeDouble(double a)
readBoolean()---writeBoolean(boolean a)
readUTF()---writeUTF(String str)//以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
writeData();
readData();
}
public static void readData() throws IOException {
@SuppressWarnings("resource")
DataInputStream dis=new DataInputStream(new FileInputStream("data.txt"));
String str=dis.readUTF();
System.out.println(str);
}
public static void writeData() throws IOException {
DataOutputStream dos=new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("你好");
dos.close();
}
}
操作字节数组的流对象
public class ByteArrayStreamDemo {
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.toString());
}
}
编码解码问题
java读取文件的方式总体可以分为两类:按字节读取和按字符读取。
按字节读取就是采用InputStream.read()方法来读取字节,然后保存到一个byte[]数组中,最后经常用new String(byte[]);把字节数组转换成String。在最后一步隐藏了一个编码的细节,new String(byte[]);会使用操作系统默认的字符集来解码字节数组,中文操作系统就是GBK。
getBytes()会采用操作系统默认的字符集来编码字节。
InputStream中的字节编码取决文件本身的编码,而OutputStream生成文件的编码取决于字节的编码。
字符流可以看做是一种包装流,它的底层还是采用字节流。字符输入流使用指定的编码方式将读取字节解码为字符。字符输出流根据指定的编码将字符转换为字节的。
public class EncodeDemo {
public static void main(String[] args) throws IOException {
/*
字符串-->字节数组:编码
字节数组-->字符串:解码
你好对应的编码 GBK: -60 -29 -70 -61
utf-8: -28 -67 -96 -27 -91 -67
如果编码错误,会解码失败
如果编码正确,解码错误,有可能恢复,当解码时用的码表里面有对应的码值时,会解得出来,没有的时候解不出来。
*/
String str="你好";
byte[] buf=str.getBytes("gbk");
// String s1=new String(buf,"iso8859-1");
String s1=new String(buf,"UTF-8");
System.out.println("s1="+s1);
// byte[] buf2=s1.getBytes("iso8859-1");//获取源字节
byte[] buf2=s1.getBytes("UTF-8");
printBytes(buf2);//-17 -65 -67 -17 -65 -67 -17 -65 -67
//哈哈 -17 -65 -67 -17 -65 -67 -17 -65 -67 -17 -65 -67
//谢谢 -48 -69 -48 -69 s1=ππ
String s2=new String(buf2,"GBK");
System.out.println("s2="+s2);
// encodeDemo(str);
}
private static void encodeDemo(String str)
throws UnsupportedEncodingException {
//编码
byte[] buf=str.getBytes("utf-8");
// printBytes(buf);
//解码
String s1=new String(buf,"utf-8");
System.out.println("s1="+s1);
}
public static void printBytes(byte[] buf) {
for(byte b:buf){
System.out.print(b+" ");
}
}
}
public class Liantong {
public static void main(String[] args) throws IOException {
String str="联通";
/*
11000001
10101010
11001101
10101000
联通这两个字的GBK编码正好和UTF-8的编码规范冲突
utf-8的编码规范在用两个字节表示的格式中:
字节1:1 1 0 [位10-6]
字节2:1 0 [位5-0]
*/
byte[] buf=str.getBytes("gbk");
for(byte b:buf){
System.out.println(Integer.toBinaryString(b&255));
}
}
}
public class Liantong {
public static void main(String[] args) throws IOException {
String str="联通";
/*
11000001
10101010
11001101
10101000
联通这两个字的GBK编码正好和UTF-8的编码规范冲突
utf-8的编码规范在用两个字节表示的格式中:
字节1:1 1 0 [位10-6]
字节2:1 0 [位5-0]
*/
byte[] buf=str.getBytes("gbk");
for(byte b:buf){
System.out.println(Integer.toBinaryString(b&255));
}
}
}
public class Test {
public static void main(String[] args) throws IOException {
/*
// String str="ab你好cd谢谢";
String str="ab琲cd琲";
int len=str.getBytes("gbk").length;
for(int x=0;x<len;x++){
System.out.println("截取"+(x+1)+"个字节的结果是:"+cutStringByByte(str,x+1));
}
*/
String str="ab你好cd谢谢";
int len=str.getBytes("utf-8").length;
for(int x=0;x<len;x++){
System.out.println("截取"+(x+1)+"个字节的结果是:"+cutStringByU8Byte(str,x+1));
}
/* //gbk编码时有的中文两个字节都是负的,有的汉字第一个字节是负的,第二个是正的。比如“琲”,读音是“bei”,这种情况还是可以用cutStringByByte方法
String str1="琲";
byte[] buf=str1.getBytes("gbk");
for(byte b:buf)
System.out.println(b);
// -84
// 105
*/
}
/*
在java中,字符串"abcd"与字符串"ab你好"的长度是一样的,都是四个字符。
但是对应的字节数不同,一个汉字占两个字节。
定义一个方法,按照最大的字节数来取子串。
如:对于"ab你好",如果取三个字节,那么子串就是ab与"你"字的半个字节,这半个就要舍弃。
如果取四个字节就是"ab你",取五个字节还是"ab你"
ab你好:97 98 -60 -29 -70 -61
*/
public static String cutStringByByte(String str, int len) throws IOException {
byte[] buf=str.getBytes("utf-8");
int count=0;
for(int x=len-1;x>=0;x--){
if(buf[x]<0)
count++;
else
break;
}
if(count%2==0)
return new String(buf,0,len,"gbk");
else
return new String(buf,0,len-1,"gbk");
}
public static String cutStringByU8Byte(String str,int len) throws IOException{
byte[] buf=str.getBytes("utf-8");
int count=0;
for(int x=len-1;x>=0;x--){
if(buf[x]<0)
count++;
else
break;
}
if(count%3==0)
return new String(buf,0,len,"utf-8");
else if(count%3==1)
return new String(buf,0,len-1,"utf-8");
else
return new String(buf,0,len-2,"utf-8");
}
}