跟着老毕学Java之IO之File、Properties及其他流对象与编码问题

------- android培训java培训、期待与您交流! ----------
————————————————————————————————————————————

File

文件是一类事物,它有自己的名字、属性、大小、位置、后缀名等属性信息,那么根据面向对象的思想,就可以把它封装描述为一个类,这个类就java.io包中的File类。File类是文件和目录路径名的抽象表示形式,它可以方便的对文件与文件夹的属性信息进行操作,也可以作为参数传递给流对象。它弥补了了流的不足,比如流只能操作数据,而不能操作文件的属性与文件夹。

 

File类的常见操作

1.创建。

         boolean    creatNewFile():重点掌握

         在指定位置创建文件,如果该文件已经存在,在不创建,放回false。和输出流不一样,输出流对象一建立就创建文件,乳沟文件已经存在,则会覆盖。

         createTempFile(Stringprefix, String suffix) :创建临时文件

 

         boolean   mkdir():创建文件夹。

         bollean   mkdirs():创建多级文件夹。

 

2.删除。

         boolean  delete():删除失败,返回false

         void   deleteOnExit():

在程序退出时,删除指定文件。比如使用的临时文件,如果出现异常,这句如果放在finally中,可能出现删除不了的情况(文件正在被其他程序使用),这时就可以用这个方法。    

 

3.判断。

         boolean   exists() :重点掌握

         文件是否存在。流在读取钱,可以先判断然后在

        

         boolean   isFile():

         boolean  isDirectory();

         boolean   isHidden();

         boolean    isAbsolute();

 

4.获取信息。

         getName();

         getPath();

         getParent();

 

         String  getAbsolutePath() 

         long    lastModified() 

         long    length() 

代码示例:

import java.io.*;
class  FileDemo
{
	public static void main(String[] args) throws IOException
	{	
		
		method_5();
		//method_2();
		//method_1();
		//consMethod();
		//System.out.println("Hello World!");
	}
	public static void method_5()
	{
		File f1	= new File("c:\\error.log");
		File f2 = new File("d:\\1.txt");
		sop("renameTo:"+f2.renameTo(f1));
		//renameTo()方法:如果f1和f2在同一个目录下,则相当于重命名;如果在不同目录下,则相当于剪切移动
	}
	public static void method_4() throws IOException
	{
		File f = new File("file.txt");
		sop("path--"+f.getPath());
		sop("absPath--"+f.getAbsolutePath());
		
		sop("parent:"+f.getParent());
		//该方法返回的是绝对路经先的父目录,如果是相对路径,返回null
		//如果相对路径中有上一层目录,那么该目录就是返回结果。
	}


	public static void method_3() throws IOException
	{
		File f = new File("c:\\file.txt");
		//f.createNewFile();
		//f.mkdir();
		//记住,在判断文件对象是否是文件或者目录时,必须先判断该文件对象封装的内容是否存在。
		//通过exists判断。
		sop("dir:"+f.isDirectory());
		sop("file:"+f.isFile());
		sop(f.isAbsolute());//

	}
	public static void method_2()
	{
		File f = new File("FileDemo.java");
		sop("exists::"+f.exists());
		//sop("Execute::"+f.canExecute());
		
		//创建文件夹:一级目录
		//File  dir = new File("abc");
		File  dir = new File("abc");//必须abc先存在,然后才能创建kk,否则会返回false;若文件已存在则返回false。
		
		//mkdir只能创建一级目录,而不能创建多级目录
		sop("mkdir:"+dir.mkdir());

		//创建文件夹:多级目录
		File  dirs = new File("abc\\eee\\aaa\\o\\p\\sss");
		sop("mkdirs:"+dirs.mkdirs());

	}
	public static void method_1() throws IOException
	{
		File f = new File("FileDemo.java");
		f.deleteOnExit();
		//code("")
		//sop("create:"+f.createNewFile());
		sop("delete:"+f.delete());
	}

	




	//创建File对象
	public static void consMethod()
	{
		//将a.txt封装成File对象,可用将已有的和未出现的文件或者文件夹封装成对象。
		File f1= new File("a.txt");
		//
		File f2 = new File("c:\\abc","b.txt");

		File d  = new File("c:\\abc");
		File f3 = new File(d,"c.txt");
		sop("f1:"+f1);
		sop("f2:"+f2);
		sop("f3:"+f3);

		//File f4 = new File("c:\\aba\\zzz\\a.txt");
		File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");
		sop("f4:"+f4);
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}

}


 

File类的list方法

import java.io.*;
class FileDemo2 
{
	public static void main(String[] args) 
	{
//		listFilesDemo();

//		listDemo();
		listRootsDemo();
	}
	//listFiles(filter)方法返回的是File数组,返回的是文件对象,使用它操作更方便。
	public static void listFilesDemo() {
		File dir = new File("c:\\");
		File[] files = dir.listFiles(new FilenameFilter()
		{
			public boolean accept(File dir,String name)
			{							
				return name.endsWith(".sys");
			}
		});
		
		for(File f:files)
		{
			System.out.println(f.getName()+"::"+f.length());
		}
	}
	//过滤出符合某种条件的文件或文件夹。list方法只返回文件名,是String[]数组
	public static void listDemo_2()
	{
		File dir = new File("c:\\");
		String [] arr = dir.list(new FilenameFilter()
		{
			public boolean accept(File dir,String name)
			{
				//System.out.println("dir:"+dir+".....name:"+name);				
				return name.endsWith(".sys");
			}
		});
		System.out.println("len:"+arr.length);
		for(String name :arr)
		{
			System.out.println(name);
		}

	}
	//打印指定目录下的文件及文件夹的名称,包括隐藏文件及文件夹。
	public static void listDemo()
	{
		File f = new File("c:\\windows");
		if(f.isDirectory()){
			String [] names = f.list();//调用list方法的file对象必须是封装了一个目录,该目录还必须存在
			for(String name :names)
			{
				System.out.println(name);
			}
		}
	}
	//列出机器上的有效盘符
	public static void listRootsDemo()
	{
		File[] files = File.listRoots();
		for(File f: files)
		{
			System.out.println(f+"--------------------------");			
		}
	}
}


 

 

递归获取文件目录

递归

因为目录中还有目录,只要使用同一个列出目录功能更的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。这种表现形式,或者变成手法,称为递归。

递归要注意

1.限定条件。

2.要注意递归的次数,尽量避免内存溢出。

 

/*
需求:列出指定目录下文件或者文件夹,包含子目录的内容。也就是列出指定目录的所有内容。

因为目录中还有目录,只要使用同一个列出目录功能更的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。
也就是函数自身调用自身。这种表现形式,或者变成手法,称为递归。
递归要注意:
1.限定条件。
2.要注意递归的次数,尽量避免内存溢出。

*/
import java.io.*;
class FileDemo3 
{
	public static void main(String[] args) 
	{
		File dir = new File("W:\\javawork\\bxd");
		showDir(dir,0);
		toBin(6);
		//int n = getSum(10);
		//System.out.println(n);
	}
	//获取层级标示符
	public static String getLevel(int level)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("|--");
		for(int x =0; x<level; x++)
		{					
			sb.insert(0,"|  ");
		}
		return sb.toString();
	}
	//递归获取所有的文件列表
	public static void showDir(File dir,int level)
	{
		System.out.println(getLevel(level)+dir);
		level++;
		File[] files =dir.listFiles();
		for(int x = 0; x<files.length;x++)
		{
		 	if(files[x].isDirectory())
			 	showDir(files[x],level);//递归调用
			else
				System.out.println(getLevel(level)+files[x]);
		}

	}
	//利用递归求和
	public static int getSum(int n)
	{		
		if(n==1)
			return 1;
		return n+getSum(n-1);			
	}
	//二进制转换
	public static void toBin(int num)
	{
		//递归调用 
		if(num>0)
		{			
			toBin(num/2);
			System.out.print(num%2);//结果为110,如果和上句换位置,则结果为011
			
		}
		//非递归
		/*
		while(num>0)
		{
			System.out.println(num%2);
			num = num/2;
		}*/
	}
	
}


 

删除一个带内容的目录

/*
删除一个带内容的目录。
删除原理:
在window中,删除目录从里边往外面删除的。

既然是从里边往外删除,就需要用到递归。

*/
import java.io.*;
class RemoveDir 
{
	public static void main(String[] args) 
	{
		File dir =new File("b:\\bxd");
		removeDir(dir);
	}
	//java删除是不走回收站的
	public static void removeDir(File dir)
	{
		File[] files =dir.listFiles();
		//删除目录中的内容
		for(int x =0; x<files.length; x++)
		{
			//java不能访问隐藏文件,所以遍历的时候最好判断下(!files[x].isHidden())&&files[x].isDirectory()
			if(files[x].isDirectory())
				removeDir(files[x]);
			else
				sop(files[x]+":-file-:"+files[x].delete());

		} 
		//删除目录
		sop(dir+"::del::"+dir.delete());
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}


 

获取目录下所有java文件

/*
练习:
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。
建立一个java文件列表清单。

思路:
1.对指定的目录进行递归。
2.在递归过程中所有的java文件的路径。
3.将这些路径存储到集合中。
4.将集合中的数据写入到一个文件中。
*/
import java.io.*;
import java.util.*;
class  JavaFileList
{
	public static void main(String[] args) throws IOException
	{
		/*我先写的程序
		TreeMap<String,String> tm = new TreeMap<String,String>();
		File dir =new File("b:\\bxd");
		FileWriter fw = new FileWriter("b:\\javalist.txt");
		showDir(dir,tm);
		createList(fw,tm);
		*/
		File dir = new File("j:\\");
		
		System.out.println(dir.isDirectory());
		List<File> list = new ArrayList<File>();
		fileToList(dir,list)  ;
		File file = new File(dir,"aviListFile.doc");
		writeToFile(list,file.toString());

	}
	public static void fileToList(File dir ,List<File> list)  
	{
		File [] files  = dir.listFiles();
		for(File file :files)
		{
			if(!(file.isHidden())&&file.isDirectory())
				fileToList(file,list);
			else
			{
				if(file.getName().endsWith(".avi")
						||file.getName().endsWith(".rmb")
						||file.getName().endsWith(".mp4")
						||file.getName().endsWith(".wmv"))
					list.add(file);
			}
		}
	}
	public static void writeToFile(List<File> list,String javaListFile) throws IOException
	{
		BufferedWriter bufw = null;
			try
			{
				bufw = new BufferedWriter(new FileWriter(javaListFile));
				for(File f:list)
				{
					String path = f.getAbsolutePath();
					bufw.write(path);
					bufw.newLine();
					bufw.flush();

				}
			}
			catch (IOException e)
			{
				throw e;
			}
			finally
			{
				try
				{
					if(bufw!=null)
						bufw.close();
				}
				catch (IOException e)
				{
					throw e;
				}
			}
	}

	/*我先写的程序
	public static void showDir(File dir,TreeMap<String,String> tm)
	{
		File [] files = dir.listFiles();
		for(int x =0; x<files.length;x++)
		{
			if(files[x].isDirectory())
				showDir(files[x],tm);
			else
			{
				if(files[x].toString().endsWith(".java"))
					tm.put(files[x].getAbsolutePath(),files[x].getName());
			}
		}
	}
	public static void  createList(FileWriter fw ,TreeMap<String,String> tm) throws IOException
	{
		BufferedWriter bufw = new BufferedWriter(fw);
		Iterator<Map.Entry<String ,String>> it = tm.entrySet().iterator();
		while(it.hasNext())
		{
			Map.Entry<String ,String> me = it.next();
			String path = me.getKey();
			String name = me.getValue();
			bufw.write(path+","+name);
			bufw.newLine();

		}
		bufw.close();
	}*/
}


 

 

 

Properties

PropertiesHashTable的子类,也就是说它具备Map集合的特点,而且它里边存储的键值对都是字符串,它是集合中和IO技术像相结合的集合容器。该对象的特点:可以用于键值对形式的配置文件。在加载数据时,需要数据有固定格式:键=值。

Properties存储配置信息的局限:比如存入复制的内容,比如一个Person的属性,这时用XML比较方便且容易区分,java中提供了Document类与之对应,用于取出XML中的信息,但是因为操作比较麻烦,于是有人开发了新的工具DOM4Jdom for java)。

使用示例:

/*
Properties是hashtalbe的子类。
也就说他具备map集合的特点,而且它里边存储的键值对都是字符串。

是集合和IO技术相结合的集合容器。

该对象的特点:可以用于键值对形式的配置文件。

在加载数据时,需要数据有固定格式:键=值;


*/
import java.util.*;
import java.io.*;
class PropertiesDemo 
{
	public static void main(String[] args) throws IOException
	{
		//setAndGet();
		//method_1();
		loadDemo();
	}
	//利用Properties的load(instream)方法加载属性配置文件并存储修改结果。
	public static void loadDemo() throws IOException
	{
		Properties prop = new Properties();
		FileInputStream fis = new FileInputStream("info.txt");
		//将流中数据加载进集合。
		prop.load(fis);
		//sop(prop);

		prop.setProperty("wangwu","32");	
		FileOutputStream fos = new FileOutputStream("info.txt");
		prop.store(fos,"hahs");//将修改后的结果存入配置文件,“hahs”是注释信息,注释信息不要写中文,解析不了。

		prop.list(System.out);

		fis.close();
		fos.close();


	}
//演示如何将流中的数据存储到集合中。
//想要将info.txt中的键值数据存到集合中进行操作。
/*
	思路:
	1.用一个流和info.txt文件关联。
	2.读取一行数据,将该行数据用“=”进行切割。
	3.等号左边作为键,右边作为值存入到properties集合中即可。
*/
	public static void method_1() throws IOException
	{
		BufferedReader bufr = new BufferedReader (new FileReader("info.txt"));
		Properties prop = new Properties();

		String line = null;
		while((line = bufr.readLine())!=null)
		{
			String [] arr = line.split("=");
			//sop(arr[0]+"...."+arr[1]);
			prop.setProperty(arr[0],arr[1]);
		}
		bufr.close();
		sop(prop);

	}
// 设置和获取元素。
	public static void setAndGet()
	{
		Properties prop = new Properties();
		prop.setProperty("zhangsan","30");
		prop.setProperty("lisi","50");
		//System.out.println(prop);
		prop.setProperty("lisi",80+"");
		String value = prop.getProperty("lisi");
		sop(value);

		Set<String> names = prop.stringPropertyNames();
		for(String s : names)
		{
			sop(s+":"+prop.getProperty(s));
		}		

	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}


 

练习:

/*
用于记录应用程序运行次数。如果使用次数已到,那么给出注册提示。很容易想到:计数器。可以改计数器定义在程序中,随着程序的运行而在内存中存在,并进行了自增。
可以随着该应用程序的退出,该计数器也在内存中消失了。下一次再启动该程序,又重新开始从零计数。这样不是我们想要的。程序即使结束,该计数器的值也存在。
下一次程序启动,会先加载该计数器的值,并加1后重新存储起来。该配置文件使用键值对的形式。这样便于阅读数据并操作数据。
键值对数据是Map集合,数据是以文件形式存储,使用IO技术。那么Map+IO————>Properties.
配置文件可以实现应用程序数据的共享。
*/
import java.util.*;
import java.io.*;

class  RunCount
{
	public static void main(String[] args)  throws IOException
	{
		Properties prop = new Properties();
		File file = new File("count.ini");
		if(!file.exists())
			file.createNewFile();
		FileInputStream fis = new FileInputStream(file);
		prop.load(fis);

		int count = 0; 
		String value = prop.getProperty("time");
		if(value!=null)
		{
			count = Integer.parseInt(value);
			if(count>=5)
			{
				sop("您好,使用次数已到,拿钱!");
				return ;
			}
		}
		count++;

		prop.setProperty("time",count+"");
		FileOutputStream fos = new FileOutputStream(file);
		prop.store(fos,"");
		fos.close();
		fis.close();

	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

//dom4j dom for java


 

IO中的其他流对象。

打印流

分为字节打印流PrintStream和字符打印流PrintWriter

其特点:是可以对基本数据类型进行原样打印操作,可以直接操作文件,可以设置自动涮新,它只能打印也就是输出。

import java.io.*;

字节打印流: PrintStream 构造函数可以接收 的参数类型: 1.file对象,File 2.字符串路径:String 3.字节输出流:outputStream

字符打印流 PrintWriter 1.file对象,File 2.字符串路径:String 3.字节输出流:outputStream 4.字符输出流:Writer

class PrintStreamDemo { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(new FileWriter("a.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是能对多个流进行合并成一个读取流,它在构造时需要传入Enumeration,而这个只用Vector中有,所以这个多个读取流要加入Vector集合中。注意:它只是对读取流进行合并。

它使用步骤:

1.      创建Vector<InputStream>

2.      将要合并的InputStream加入Vector

3.      通过Vector获取Enumeration

4.      创建SequenceInputStream,将Enumeration作为参数传入。

import java.io.*;
import java.util.*;
class SequenceDemo 
{
	public static void main(String[] args) throws IOException
	{
		//Enumeration只在Vector中有,所以要把流加入到Vector集合中
		 Vector<FileInputStream> v = new Vector<FileInputStream>();
		 v.add(new FileInputStream("sequ\\1.txt"));
		 v.add(new FileInputStream("sequ\\2.txt"));
		 v.add(new FileInputStream("sequ\\3.txt"));
		 //获取Enumeration
		 Enumeration<FileInputStream> en =v.elements();
		 //将Enumeration加入序列流
		 SequenceInputStream sis = new SequenceInputStream(en);
		 //创建要写入的文件
		 FileOutputStream fos = new FileOutputStream("sequ\\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();
		
	}
}


 

 

练习:文件切割与合并

import java.io.*;
import java.util.*;

class SplitFile 
{
	public static void main(String[] args) throws IOException
	{
		//splitFile();
		merge();
		
	}
	//合并文件。
	public static void merge() throws IOException
	{
		//Vector效率低,可以改用ArrayList
		ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
		for(int x =1;x<=3;x++)
		{
			al.add(new FileInputStream("splitFiles\\"+x+".patr"));
		}
		
		//因为it要被Enumeration的匿名内部类对象使用,所以要加final
		final Iterator<FileInputStream> it = al.iterator();
		//定义Enumeration子类对象进行,使用ArrayList的迭代器复写其方法,其实与ArrayList关联
		Enumeration<FileInputStream> en =new Enumeration<FileInputStream>()
		{
			public boolean hasMoreElements()
			{
				return it.hasNext();
			}
			public FileInputStream nextElement()
			{
				return it.next();
			}
		};
		//定义序列流,合并分割后的文件关联的流对象
		SequenceInputStream sis = new SequenceInputStream(en);

		FileOutputStream fos = new FileOutputStream("splitFiles\\merge.JPG");
		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 splitFile() throws IOException
	{
		FileInputStream fis = new FileInputStream("splitFile.JPG");
		FileOutputStream fos = null;
		//指定切割后每个文件的大小或者缓冲区的大小。
		byte[] buf = new byte[1024*1024];

		 int len = 0;
		 int count = 1;
		while((len = fis.read(buf))!=-1)
		{
			//创建每个分割文件。
			fos = new FileOutputStream("splitFiles\\"+(count++)+".patr");
			fos.write(buf,0,len);
			fos.close();
		}
		fis.close();
	}  
	
}


 

对象序列化

数据可以封装成对象,对象运行时是在堆内存中的,如果对象的数据需要存储在硬盘上,那么就要用到对象的序列化流。对象序列化(也叫对象的可串行性)其实就是对象持久化,把内存中的对象,变成硬盘上的文件内容。IO中供对象序列化的流对象为ObjectInputStreamObjectOutputStream

注意:

1.      ObjectOutputStream写入的的文件,只能用ObjectInputStream来重构读取

2.      被序列化的对象必须实现Serializable接口。

3.      对象的静态成员和被transient关键字修饰的成员不能被序列化。(当对象在堆内存的私有对象不希望被序列化时,可以使用transient关键字)。

此外,序列化的文件一般以.ojbect作为类型后缀名,一个文件中可以存放多个不同类型的序列化对象。

Serializable接口

在对对象进行序列化时,必须实行Serializable接口,否则使用ObjectOutputStream写入时,会出现NotSerializableException异常。

Serializable接口并没必须要实现的方法,类定义时仅标示一下实现即可。实现Serializable的类,都有serialVersionUID,如果你没有在类中显式定义一个serialVersionUID,那么编译器会根据该类中的成员生成一个具有唯一性的serialVersionUID

显式定义serialVersionUID的好处:如果你在对类对象进行了序列化之后,又修改了这个类,那么再次读取修改前序列化的对象时,编译器可以识别;如果没有显式定义,你修改后的类经过编译器编译后会生成一个新的serialVersionUID,这个serialVersionUID跟修改前类的serialVersionUID不同,当你再次读取时,编译器会报出InvalidClassException异常。所以,如果类对象需要序列化,建议显式定义serialVersionUID

代码示例

import java.io.*;
//没有方法的接口,称为标记接口,
class Person implements Serializable
{
	public  static final long serialVersionUID = 42L;//UID是为了给类定义标记。
	private String name;
	transient int age;//age如果不想序列化,可以在前边加 transient 关键字,保证其值在堆内存中存在而不在文本文件中存在。
	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;
	}
	public  void println(){
		
	}
}

import java.io.*;

class  ObjectStreamDemo
{
	public static void main(String[] args) throws Exception
	{
		readObj();
		 //writeObj();
	}
	//通过ObjectInputStream读取序列化后的对象
	public static void readObj() throws Exception
	{
		ObjectInputStream ois = 
			new ObjectInputStream(new FileInputStream("obj.txt"));
		Person p =(Person)ois.readObject();
		System.out.println(p);
	}
	//通过ObjectOutputStream将对象序列化
	public static void writeObj() throws IOException
	{
		ObjectOutputStream oos =
			new ObjectOutputStream(new FileOutputStream("obj.txt"));
		oos.writeObject(new Person("lisi",366,"kr"));//country为静态,不能序列化,所以,写入文件中的不是“kr”,而是CN

		oos.close();
	}
}


 

管道流

管道流分为字节管道流(PipedInputStreamPipedOutputStream)和字符管道流(PipedReaderPipedWriter):它是IO技术和多线程技术的结合。在一条线程上写入的数据可以在另外一条线程上读取,它们是一对对配合使用的。如果在一条线程上使用管道读取和写入流会发生死锁的情况。

其使用步骤:

1.             分别定义写入和读取的Runnable接口子类,把相应的管道流作为构造参数传入给定义的私有管道流成员。

2.             将配对的管道流通过connect()方法连接起来。

3.             启动线程

代码示例:

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("读取前,没有数据就阻塞");
			int len = in.read(buf);
			System.out.println("读到数据,阻塞结束");


			String s = 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("piped lai le".getBytes());
			out.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("管道输出流失败");
		}

	}

}
class PipedStreamDemo 
{
	public static void main(String[] args) throws IOException
	{
		PipedOutputStream out = new PipedOutputStream();
		PipedInputStream in = new PipedInputStream();
		in.connect(out);
		
		Read r = new Read(in);
		Write w = new Write(out);

		new Thread(w).start();
		new Thread(r).start();

	}
}

RandomAccessFile(重点掌握)

RandomAccessFile,随机访问文件流对象,该类不是Io体系的成员,但是它在IO包中,因为它具备读写功能,其内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek方法改变指针的位置。它与其他IO流最大的不同在于:它既能读,又能写

它自身的特有功能:可以通过改变指针位置设置缓冲区字节数组的长度来访问文件中的任意一段字节。注意:如果希望能正确的读取,那么存入的数据最好是有规律的,比如8个字节为一段,其中后四个字节是int值(后四个字节就要用writeIntreadInt来读写)。延伸一下,就能发现,它能实现数据的分段写入(这与下载的原理:多线程下载类似)。

其实现原理:内部封装了字节输入流和输出流。

构造函数特点:通过构造函数看出,该类只能操作文件,而且操作文件还有模式:只读—“r”,读写---“rw”

注意:

1.             如果模式为只读r,不会创建文件,而会去读取一个已经存在的文件,如果该文件不存在,则会抛出异常。

2.             如果模式的读写rw,操作的文件不存在,会自动创建,如果存在则不会覆盖

 

import java.io.*;
class RandomAccessFileDemo 
{
	public static void main(String[] args) throws IOException
	{
		//writeFile_2();
		//writeFile();
		//readFile();
		RandomAccessFile raf = new RandomAccessFile("raf1.txt","rw");
		raf.write("hha".getBytes());

	}
	//读取,模式设置为“r”
	public static void readFile() throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("raf.txt","r");
		//调整对象中的指针,seek前后都能设置,所以比skipBytes使用范围广。
		//raf.seek(8*0);//里边存入的数据都是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="+name);
		System.out.println("age="+age);
		raf.close();
	}

	public static void writeFile_2()  throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
		raf.seek(8*0);//修改数据,网络分段下载原理,要重点掌握。
		raf.write("周期".getBytes());
		raf.writeInt(103);		
		raf.close();
	}
	//写入,模式设置为“rw”
	public static void writeFile()  throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
		raf.write("李四".getBytes());
//		raf.write(97);write(int x)方法只写入低8位。如果写入的数字在byte取值范围内,那么可以read()正常读取,如果超出,读取时就会出现数据错乱。
		raf.writeInt(97);//要把四个字节都写入,所以用writeInt
		raf.write("王五".getBytes());
		raf.writeInt(99);
		raf.close();
	}
}

操作[基本数据类型]的流对象

可以用于操作基本数据类型的数据的流对象,按照输入输出分为DataInputStreamDataOutputStream。这两个类提供了对8种基本数据类型的写入和读取的方法。

此外,它们有对应的wirtUTF-8(String str)readUTF-8()方法,来支持按照UTF-8修改版编码(与UTF-8稍有不同)来写入和读取字符串。

import java.io.*;
class DataStreamDemo 
{
	public static void main(String[] args)  throws IOException
	{
		//readData();
		//writeData();
		//writeUTFDemo();
		readUTFDemo();


		//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("GBK.txt"),"GBK");
		//osw.write("你好");
		//osw.close();
		
	}
	public static void readUTFDemo() throws IOException
	{
		DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
		String s = dis.readUTF();

		sop(s);
		dis.close();
	}
	public static void writeUTFDemo() throws IOException
	{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("utffdata.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();

		sop("num="+num+"   b="+b+"   d="+d);

		dis.close();
	}
	public static void writeData() throws IOException
	{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
		dos.writeInt(234);//4个字节
		dos.writeBoolean(true);//1个字节
		dos.writeDouble(988.755);//8个字节

		dos.close();
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

  

操作[字节数组]的流对象

按输入输出分为两个类

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

ByteArrayOutputStream:在构造的时候,不需要定义目的地,因为该对象内部已经封装了可变长度的字节数组,它就是目的地;它对外提供了toString()方法,可以把内部封装的字节数组按照默认的字符集或指定的字符集以字符串的形式返回。

注意

1.             因为这两个流对象都操作的是数组,并没有使用系统资源,所以,不用进行close关闭,即使你关闭了,它的其他方法还可以使用,而不会抛出IOException

2.             使用这对对象操作时,它的源和目的都是内存。

用途:这两个对象是在用流的思想来操作数组,当我们需要把一个文件中的数据加入内存中的数组时,就可以考虑用这个两个对象。此外,它还有writeToOutputStream os)可以把ByteArrayOutputStream对象内部定义的缓冲区内容,一次性写入os中。

操作字符数组、字符串的流对象类型与之相似,可以参与它们的使用方法。

import java.io.*;
class ByteArrayStream 
{
	public static void main(String[] args) 
	{
		//数据源---字节数组,在内存中
		ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFG".getBytes());

		//数据目的--bos内部封装的数组,在内存中
		ByteArrayOutputStream bos =new ByteArrayOutputStream();

		int by = 0;
		while((by =bis.read())!=-1)
		{
			bos.write(by);
		}

		System.out.println(bos.size());//返回缓冲区大小
	    System.out.println(bos.toString());//把缓冲区中的字节按照默认的编码转成为字符串返回。

		//bos.writeTo(new FileOutputStream("a.txt"));//把bos内部的byte数组内容一次性写入字节输出流对象中。
	}
}


 

操作[字符数组]的流对象

CharArrayReaderCharyArrayWriter

操作[字符串]的流对象

StringReaderStringWriter

编码问题

 

字符流的出现是为了方便操作字符数据,其方法操作的原因是因为内部加入了编码表。Java中能够实现字节根据指定编码表转成字符的,有四个类:InputStreamReaderOutputStreamWriterPrintStreamPrintWriter。它们都能够加构造时,指定编码表;但后两个是打印流,只能用于打印,使用有局限,所以相对而言,还是前两个转换流使用多一些。

编码表的由来

计算机只能识别二进制数据,早期是电信号。为了应用计算机方便,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并将文字与二进制数字一一对应,形成了一张表,这个表就是编码表。

常见的编码表

地域码表

1.      ASCII:美国码表,息交换码,用一个字节的7位表示。

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

3.      GB2312:中国中文编码表,它用两个字节表示,为兼容ASCII,它的两个字节的高位都是1,也即是两个负数;但与ISO8859-1冲突。大概有六七千个字。

4.      GBK:中国的中文编码表的升级版,扩容到2万多字。

通用码表

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

2.      UTF-8UnicodeTransform Format -8Unicode码把用一个字节能装下的文字,也用两个字节表示,有些浪费空间,对之进行优化的结果就是UTF-8UTF-8编码表,一个文字最用一个字节表示,最多用3个字节表示,并且每个字节开始都有标识头,所以很容易于其他编码表区分出来。

      UTF-8每次是如何判断是该读1个、2个还是3个字节呢?用过标示头信息,如下所示。

'\u0001''\u007F' 范围内的所有字符都是用单个字节表示的:

  位值
字节 1
0 位 6-0

null 字符 '\u0000' 以及从 '\u0080''\u07FF' 的范围内的字符用两个字节表示:

  位值
字节 1
1 1 0 位 10-6
字节 2
1 0 位 5-0


'\u0800''\uFFFF' 范围内的 char 值用三个字节表示:

  位值
字节 1
1 1 1 0 位 15-12
字节 2
1 0 位 11-6
字节 3
1 0 位 5-0

 

 

代码示例:

import java.io.*;

class EncodeStream 
{
	public static void main(String[] args)  throws Exception
	{
		//writeText();
		readText();
	}
	//按照指定的码表读取数据
	public static void readText() throws Exception
	{
		InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"UTF-8");
		char [] buf = new char[10];
		int len = isr.read(buf);
		System.out.println(new String(buf,0,len));
		isr.close();
	}
	//按照指定的码表写入数据
	public static void writeText() throws Exception
	{
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
		osw.write("你好");
		osw.close();
	}
}


 

编码问题的产生与解决

从上边的那些编码表可以看出,GBKUnicode都能识别中文,那么当一台电脑使用GBK,而另一台电脑使用Unicode时,虽然在各自的电脑上都能识别中文,但他们其中一方向另一方发送中文文字时,另一方却不能识别,出现了乱码。这是一万年GBKUnicode虽然都能识别中文,但对同一个中文文字,他们在两个编码表对应的编码值不同。这时,在解读别人传来的中文数据时,就需要指定解析中文使用的编码表了。

转换流就能指定编码表,它的应用可以分为:

1.      可以将字符以指定的编码格式存储。

2.      可以对文本数据以指定的编码格式进行解读。

它们指定编码表的动作是由构造函数完成的。

编码:字符串变成字节数组,Stringàbyte[],使用str.getBytes(charsetName)

解码:字节数组变成字符串,byte[]àString,使用new String(byte[] b, charsetName);

编码编错:是指你对一个文字进行编码时,使用了不识别该文字的编码表,比如你编码一个汉字,却使用了ISO8859-1这个拉丁码表,ISO8859-1根本就不识别汉字。编码编错时,你用任何方式对编码后的数据进行处理,都不可能再拿到这个汉字了。

解码解错:是指你对一个文字进行编码事,使用了正确的码表,编码正确,但在解码时使用了错误的码表,那么你还有可能拿到这个文字。这分为两种情况:

第一种情况:你使用的是GBK编码,解码时用的是ISO8859-1,因为GBK编译一个汉字,使用两个字节,ISO8859-1解码时是一个字节一个字节读取,虽然解码出现了乱码,但是这个汉字的二进制数据没有变化,那么你可以通过再次编译获取其原来的二进制数据,然后再次使用GBK编码,解码成功

第二种情况:你使用的是GBK编码,解码时用的却是UTF-8,因为这两个码表都识别汉字,那么你再次使用UTF-8编码时,就有可能把一个汉字的2个字节,变成3个,这时再用GBK解码时,得到的仍然是乱码,解码仍然失败

import java.io.*;
import java.util.*;
class  EncodeDemo
{
	//解决编码问题示例
	public static void main(String[] args) throws Exception
	{
		String s ="哈哈";
		byte[] b1= s.getBytes("GBK");//ruguo 
		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进行编码
		//byte [] b2 = s1.getBytes("ISO8859-1");
		byte [] b2 = s1.getBytes("UTF-8");
		System.out.println(Arrays.toString(b2));
		String s2 = new String(b2,"GBK");
		System.out.println("s2="+s2);


	}
}

 


“联通”的编码问题

         问题描述:打开记事本仅写入“联通”两个汉字,关闭后,再次打开会出现乱码。

 

class EncodeDemo2 
{
	public static void main(String[] args) throws Exception
	{
		String s = "联通";
		byte [] by = s.getBytes("GBK");
		for(byte b:by)
		{
			System.out.println(Integer.toBinaryString(b&255));
		/*	
		 * “联通”编码问题的原因:
		 //boBinaryString(int)它接受的是int,byte类型的b参与运算时会类型提升为int,我们需要的是提升后的低8位,所以&255
			
			 对联通的结果进行GBK编码时,其二进制码为:
			11000001
			10101010
			11001101
			10101000 
			 编码的结果符合UTF-8的格式,所以再次打开记事本时,它会把它按照UTF-8的格式进行解码,结果就是两个乱码。
		*/
		}
	}
}

IO综合练习:录入学生成绩并将信息存储在硬盘文件中。

 

/*
有5个学生,每个学生有三门课的成绩。
从键盘输入以上数据(包括姓名,三门课成绩);
输入的格式:如zhangsan,30,40,60计算出总成绩。
并把学生的信息和计算出的总分数,按由高到低顺序存在在磁盘文件stud.txt中。

1.描述学生对象。
2.定义一个学生对象的工具类

思想:
1.通过获取键盘录入的一行数据。并将该行数据中的信息取出,封装成学生对象。
2. 因为学生对象有很多,就需要存储使用的集合,因为要对学生的总分排序,
	所以可以使用TreeSet。
3.将集合中的信息写入到一个文件中。

*/
import java.io.*;
import java.util.*;
//使用TreeSet需要将其中的元素实现Comparable接口
class Student implements Comparable<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 =ma+cn+en;

	}
	//Comparable接口要实现compareTo方法。
	public int compareTo(Student s )
	{
		//注意,一般自然信息定义的都是升序,即成绩从低到高的顺序
		int num =new Integer(this.sum).compareTo(new Integer(s.sum));
		if(sum==0)
			return this.name.compareTo(s.name);
		return num;
	}
	public String getName()
	{
		return name;
	}
	public int getSum()
	{
		return sum;
	}
	//学生类也有可能存入HashSet中,所以要复写hashCode和equals方法。
	public int hashCode()
	{
		return name.hashCode()+sum*78;
	}
	public boolean equals (Object obj)
	{
		if(!(obj instanceof Student))
			throw new ClassCastException("类型不匹配");
		Student s = (Student)obj;
		return this.name.equals(s.name)&&this.sum==s.sum;
	}
	//复写toString方法,提供学生类特有的字符串表现形式。
	public String toString()
	{
		return "Student["+name+","+ma+","+cn+","+en+"]";
	}
}
//定义学生信息录入和存储工具类
class StudentInfoTool
{
	//函数重载,提供一个默认的方法,对学生对象按照定义的自然顺序进行排序
	public static Set<Student> getStudents()  throws IOException
	{
		return getStudents(null);
	}
	
	//定义录入工具,并将键盘录入的结果存入Set集合中,因为要排序,所以要用TreeSet。
	//这里加入比较器作为参数,是为了让集合可以按照不同的要求进行排序,比如按照某一单科成绩或总分从高到底
	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);
		//也可以用三元运算符进行优化
//		Set<Student>stus=(cmp==null)?(new TreeSet<Student>()):(new TreeSet<Student>(cmp));
		
//		循环读取录入的信息,注意定义结束标记。
		while((line =bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			//用","进行切割---其实这里可以将录入信息line用正则表达式过滤一下,对于非法的信息不写入,并进行提示,防止录入非法的数据。
			
			String [] info = line.split(",");
			Student stu =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(Student stu: stus)
		{
			bufw.write(stu.toString()+"\t");
			//这里写入的数据是int类型的值,并且write会截取其低8位,所以要把它转成字符串,否则会出现乱码
			bufw.write(stu.getSum()+"");
			bufw.newLine();//写入跨平台的换行符
			bufw.flush();//字符缓冲区一定要记得刷新动作
		}
		//关闭资源。
		bufw.close();
	}
}

class  StudentInfoTest
{
	public static void main(String[] args)  throws IOException
	{
		//通过Collections集合工具类的反转命名方法,获得一个逆序比较器
		Comparator<Student> cmp = Collections.reverseOrder();
		//如果没有传入逆序比较器,学生会按照自然顺,即总成绩从低到高的排序,这与我们的现实生活习惯不符合。
		Set <Student> stus = StudentInfoTool.getStudents(cmp);
		//将集合中的学生信息写入文件。
		StudentInfoTool.write2File(stus);

	}
}


 

 

 

 

 


————————————————————————————————————————————

------- android培训java培训、期待与您交流! ----------

你可能感兴趣的:(跟着老毕学Java之IO之File、Properties及其他流对象与编码问题)