黑马程序员---IO其他流

-----------android 培训 java培训 、java学习型技术博客、期待与您交流!---------

File类


      流在操作数据,而数据最明显的体现形式就是文件,文件又包含了很多属性和行为信息。File类就是用来描述文件的。

      

            File类用来将文件或者文件夹封装成对象,方便对文件和文件夹属性信息进行操作。


            File对象可以作为参数传递给流的构造函数。

 

      流只能操作数据,而想要操作数据封装成的一个文件信息,必须要用File对象。

 

      File方法介绍


            构造方法(不是创建,只是封装成对象。


                  1.   File(String pahtname)


                        例:File f = new File(“a.txt”);         //将a.txt封装成File对象


                        这个构造方法可以将已有的和未出现的文件或文件夹封装成对象。


                  2.   File(Streing parent , String child)   


                        例:File f = new File(“c:\\123”,”a.txt”);          //将a.txt文件封装成File对象存入C盘123文件夹中。


                              相比于前一个构造函数的好处:目录和文件名分开写。


                              可以将文件名设为参数,操作的目录不变,操作的目录下的文件改变。


                  3.   File(File parent , String child)

  

                        例:File f = new  File(d , “a.txt”);           //把a.txt文件封装成File对象,存入”d”这个引用对应的目录中去。


                        d的定义方式为:File d = new File(“c:\\123”);        //把“c:\\123”这个文件夹,封装成对象。

                           

                  再提一个小知识点


                        因为我是Windows系统,所以目录分隔符是\\ 但在别的系统上目录分隔符不是\\ 。

                  

                              所以File类提供了一个可跨平台的目录分隔符: File.separator


                              例:File f =new File(“c:” + File.separator + “abc” , “a.txt”); //就相当于直接把\\写为File.Separator

            

            常见方法


                  1.   创建


                        a.   boolean createNewFile()


                              在指定位置创建文件,如果该文件已经存在则不创建,返回false。


                              和输出流对象不一样,输出流对象一建立就会创建文件,而文件已经存在,会覆盖。


                        b.   boolean mkdir()


                              创建目录(文件夹),只能创建一级目录。


                              例:File f = new File(“e:\\abc”);         


                                    f.mkdir();                               //在e盘目录下创建abc文件夹。


                        c.   boolean mkdirs()


                              创建多级文件夹。


                  2.   删除


                        a.   boolean delet()


                              删除文件,如果文件删除成功返回true,失败返回false。


                              但当读到这条语句之前如果发生异常,将无法删除文件,所以要使用下面的方法。


                        b.   void deleteOnExit()


                              在程序退出时删除指定文件,要写在初始化对象语句后面,避免发生异常,读不到这句。


                  3.   判断


                        a.   boolean canExcute()


                              判断文件是否是可执行文件。


                        b.   boolean exists()


                              判断文件是否存在。


                              在流读取文件时,如果文件不存在会抛出异常,所以就用这个方法判断一下。这就是文件封装成对象的好处。


                        c.   boolean isDirector()


                              判断是否是目录(文件夹)


                        d.   boolean isFile()


                              判断是否是文件


                        记住:在判断文件对象是否是文件或目录时,必须要先判断该文件对象封装的内容是否存在,通过上面说过的exists方法判断。


                                  如果不先进行文件是否存在的判断,万一文件真的不存在,判断是否是目录,和是否是文件的方法都会返回false


                                  会混乱,不知道到底这个对象是什么。


                        e.   boolean is Hidden()


                              判断是否是隐藏文件。


                              Java无法访问隐藏文件,所以用读取流读取文件时要判断一下,以免发生异常。


                        f.   boolean isAbsolute()


                              判断是否是绝对路径。

                  

                  4.   获取信息


                        a.   String getName()


                              获取文件名称


                        b.   String getPath()


                              获取路径。封装的是什么获取到的就是什么,文件不存在也可以


                        c.   String getParent()


                              获取父目录。返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。


                              如果相对路径中有上一次层目录,那么该目录就是返回结果。


                        d.   String geiAbsolutePath()


                              获取绝对路径,文件不存在也可以


                        e.   File getiAbsoluteFile()


                              返回绝对路径,并封装成对象。


                        f.   long lastModified()


                              返回最后一次修改时间,可以用于判断文件是否是刚刚修改的。


                        g.   long length()


                              返回文件大小。其中文本按字节获取,所以也可用于取得文本的字节数。


                        h.   boolean renameTo(File dest)


                              重命名。加上路径可以改变文件位置。


                        5.   获取文件列表


                              a.   static File[] listRoots()


                                    列出可用的盘符


                              b.   String[] list()


                                    打印指定目录下的文件和文件夹名称,包含隐藏文件。


                                    调用list方法的File对象必须是封装了一个目录该目录还必须存在。


                              c.   File[] listFiles()


                                    返回指定目录下文件和文件夹对象。


                  常用方法代码演示

/*
File;类中常见方法演示

为了代码看起来简便就不进行处理了,直接抛出异常

正常情况下应该trycatch处理!正常情况下应该trycatch处理!正常情况下应该trycatch处理!
*/
import java.io.*;
class  FileDemo
{
	public static void main(String[] args) throws IOException
	{
		method_1();

		method_2();

		method_3();

		method_4();
	}

	//创建方法演示
	public static void method_1()throws IOException
	{
		//把路径成对象
		File f1 = new File("e:\\a.txt");

		//通过createNewFile方法创建文件。
		f1.createNewFile();		

		//将文件夹封装成对象
		File dir = new File("aaa\\bbb\\ccc\\ddd");
		//使用mkdirs方法,创建多级文件夹。
		dir.mkdirs();
	}

	//判断方法演示
	public static void method_2()throws IOException 
	{
		sop("判断方法");
		//先把文件封装成对象
		File f1 = new File("e:\\a.txt");

		//判断文件是否能执行并输出结果
		sop("e:\\a.txt文件是否能执行:"+f1.canExecute());

		//判断文件是否存在
		//如果存在
		if(f1.exists())
		{
			//判断是否是目录
			if(f1.isDirectory())
				//如果是,输出语句
				sop("e:\\a.txt是目录");
			//判断是否是文件
			if(f1.isFile())
				//如果是,输出语句
				sop("e:\\a.txt是文件");
		}
		//否则就是文件不存在,输出语句
		else
			sop("文件不存在");
	}

	//获取信息方法演示
	public static void method_3()throws IOException
	{
		sop("获取信息方法");
		//把文件目录封装成对象
		File dir = new File("e:\\java0318\\BlogDemo\\IO");

		//把文件封装成对象
		File f1 = new File(dir,"我要去黑马.txt");

		//获取文件名称,并打印
		sop("文件名:"+f1.getName());

		//获取文件路径,并打印
		sop("文件路径:"+f1.getPath());

		//获取父目录,并打印
		sop("父目录:"+f1.getParent());

		//获取绝对路径,并打印
		sop("绝对路径:"+f1.getAbsoluteFile());

		//返回文件大小,应为是纯文本文件,所以返回的是文件中的字节数
		//字节数除2就是文件中的字数
		sop("纯文本文件中字数:"+f1.length()/2);
	}

	//获取文件列表方法
	public static void method_4()
	{
		sop("获取文件列表");
		//将文件夹封装成对象
		File dir = new File("e:\\java0318\\BlogDemo");

		//将制定文件夹下的文件和文件夹名通过,list方法获取,并存入String数组中
		String[] arr = dir.list();

		//遍历String数组,打印数组中内容
		for(String s : arr)
		{
			sop(s);
		}
	
	}

	//输出方法
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}

}


                        运行结果




                  6.   列出目录下的所有内容


                        虽然我们可以通过list方法获取文件列表,但是目录中还有目录。我们要把目录中的目录中的文件也列出来。


                        可以使用同一个列出目录功能的函数完成。


                              在列出过程中出现还是目录的情况,可以接着调用自己的功能,列出目录中的文件列表。


                              也就是函数自身调用自身,这种表现形式(或编程手法)称为递归


                        递归要注意


                              1.   限定条件,要有判断和结束条件,方便递归停止。


                              2.   注意递归的次数,尽量避免内存溢出。因为每次调用自身的时候都会先执行下一次调用自己的方法,


                                    所以会不断在栈内存中开辟新空间,次数过多,会导致内存溢出。

 

                        练习一,列出目录下所有内容

/*
File练习一,列出目录下所有内容

因为目录下还会有目录,所以使用递归

思路
1.将目录封装成对象
2.获取目录下的文件列表
3.判断文件列表中有没有目录,如果有继续获取这个目录下的文件列表
4.输出目录
*/
import java.io.*;
class  FileTest1
{
	public static void main(String[] args) 
	{
		//将文件夹封装成对象
		File dir = new File("e:\\java0318\\BlogDemo");
		
		//调用shouDir方法获取目录下的所有内容
		showFiles(dir,0);
	}
	//有层次输出方法
	public static String getLevel(int level)
	{
		//创建一个StringBuilder容器
		StringBuilder sb = new StringBuilder();
		//加入|--
		sb.append("|--");
		//循环,level为几,就往前面加几个|  。
		for(int x=0; x<level; x++)
		{
			sb.insert(0,"|  ");
		}
		//返回字符串形式
		return sb.toString();
	}
	//获取目录下文件的方法,参数接收一个目录对象,一个级别数
	public static void showFiles(File dir,int level)
	{
		//通过级别数输出列表层次和文件加名称	
		System.out.println(getLevel(level)+dir.getName());

		//因为要读取目录中信息了,所以多加一次列表层次
		level++;
		//获取指定目录下文件和文件夹对象
		File[] files = dir.listFiles();
		//遍历files数组
		for(File file : files)
		{
			//判断这个File对象是否是目录
			if(file.isDirectory())
				//如果是目录,按递归的方法,再把它下面的文件都取出来
				showFiles(file,level);
			//如果不是目录
			else
				//输出列表层次,和文件信息
				System.out.println(getLevel(level)+file);
		}
	}
}

                                              

                              运行结果



 

                        练习二,建立一个java文件列表的文件。 

/*
File练习二

需求
将目录中所有的java文件的绝对路径写入到一个文件中。建立一个java文件的列表

思路
1.将指定目录封装对象
2.定义一个集合,用于存储文件和文件夹对象
3.定义一个方法,用于获取文件列表,
4.将文件存入集合中
5.将集合中的元素通过输出流写入文件中
*/
import java.io.*;
import java.util.*;

class FileTest2 
{
	public static void main(String[] args) 
	{
		//将文件夹封装成对象
		File dir = new File("e:\\java0318\\BlogDemo");

		//定义集合,用于接收File对象
		List<File> list = new ArrayList<File>();

		//调用获取全部java文件路径的方法
		getJavaFiles(dir,list);

		//创建字符流缓冲区的引用
		BufferedWriter bufw = null;
		//处理异常
		try
		{
			//建立字符读取流缓冲区对象,并将字符流对象作为参数传入,关联目的文件
			bufw = 
				new BufferedWriter(new FileWriter("e:\\java0318\\BlogDemo\\java文件列表.txt"));
			
			//遍历数组
			for(File file : list)
			{
				//使用write方法,将数组中File对象的绝对路径写入文件中
				bufw.write(file.getAbsolutePath());
				//换行
				bufw.newLine();
				//刷新流
				bufw.flush();
			}
		}
		//异常处理
		catch (IOException e)
		{
			throw new RuntimeException("写入失败");
		}
		finally
		{
			try
			{
				if(bufw!=null)
					//关闭资源
					bufw.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("缓冲区关闭失败");
			}
		}
	}

	//获取全部java文件方法,参数列表为,目录对象和数组
	public static void getJavaFiles(File dir , List<File> list)
	{
		//获取dir对象目录下的文件列表对象
		File[] files = dir.listFiles();

		//遍历列表
		for(File file : files)
		{
			//判断file是不是文件夹
			if(file.isDirectory())
			{
				//如果是,就把这个对象的绝对路径存入数组中
				list.add(file.getAbsoluteFile());
				//然后使用递归,再获取它里面的全部java文件
				//也存入list数组中
				getJavaFiles(file,list);
			}
			//如果不是目录,那说明是文件
			else
			{
				//获取文件名,判断是否是以.java结尾
				if(file.getName().endsWith(".java"))
					//如果是,就将文件存入list集合中
					list.add(file);
			}
		}
	}
}


                              最后获取的java文件列表



 

      

Properties类


      Properties类是HashTable的子类,也就是说它具备了Map集合的特点,而且里面储存的键值对都是字符串。


      不需要泛型,是集合中和IO技术相结合的集合容器

 

      特点


            可以用于键值对形式的配置文件(配置文件:软件的一些基本配置信息存储在文件中)


            不仅可以操作键值对,而且可以操作硬盘上的键值对信息。

 

      常用方法


            1.   设置和获取元素


                  a.   String setProperty(String key , String value)


                        设置键值对(打印时默认用 = 链接)如果键相同会覆盖。


                  b.   String  getProperty(String key)


                        根据键取值


                  c.   Set<String> StringProprtyNames()


                        获取Properties集合中所有的键。


            2.   获取配置文件中的数据


                  最重要的步骤,读取文件中的一行数据。将该数据用 = 进行切割,使用的是String类中的split方法。


                        String[] split(String regex)        


                  在1.6版本后出现的新功能


                        void load(InputStream  inStream)


                             从字节输入流中读取键值对。


                        void load(Reader  reader)


                             从字符输入刘中读取键值对。


                              例:FileReader  fr  =  new  FileReader("e:\\123.txtz");


                                    Properties prop = new Properties();


                                    prop . load(fr);                  //将读取流中的数组,存入Properties集合中


            3.   Properties也可以与输出流相关联


                  void list(PrintStream out)


                  void list(PrintWriter out)


                        将属性列表输出到指定的输出流。


            4.   修改输入流内的内容


                  void store(OutputStream out , String comments)


                        OutputStream是输出流,String comments是注释信息,加不加都可以。


                  用setProperty方法修改了内存中的Properties集合后,使用store方法将修改后的数据(内容结果)存入输出流中,从而改变文件中的数据。

 

            凡是配置文件每一行前加 # 的都是注释信息,不会被Properties所加载。Properties加载的信息必须是键值对形式的。


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

        

      下面来看一个练习

/*
Properties类练习

记录应用程序使用次数,如果次数已到,则要向用户收费

思路
1.使用读取流关联软件的配置文件,如果配置文件存在,直接读取,如果不存在创建
2.程序每次运行都从配置文件获取运行次数,如果次数小于5就继续运行程序并次数+1
	如果次数>=5了,就提示用户要收费
3.将次数小于5情况,+1之后的计数器的次数再存入配置文件中。
*/
import java.io.*;
import java.util.*;

class PropertiesTest 
{
	public static void main(String[] args) throws IOException
	{
		//创建Properties对象
		Properties prop = new Properties();

		//将配置文件封装成对象
		File file = new File("info.ini");
		//判断info.ini文件是否存在
		if(!(file.exists()))
			//如果不存在,则创建这个文件
			file.createNewFile();
		
		//创建读取流对象,将配置文件与其关联
		FileInputStream fis = new FileInputStream(file);

		//将读取流中的数据存入Properties集合中
		prop.load(fis);

		//定义一个计数器
		int count = 0;

		//使用getProperty方法取出集合中time键对应的值
		String value = prop.getProperty("time");

		//判断value是否为null
		if(value!=null)
		{
			//如果不为null,说明time已经有对应的值了,程序运行过
			//因为Properties中存储的键和值都是String类型的,
			//所以要把value转回int型,并用count记录住
			count = Integer.parseInt(value);
			//判断count的值是否大于等于5
			if(count>=5)
			{
				//如果真的大于等于5,那么就输出信息,让用户缴费
				System.out.println("使用次数到了,交钱吧~~");
				return;
			}	
		}
		//计数器自增
		count++;
		System.out.println("运行"+count+"次");
		//调用setProperty方法,将time和count的值传入,如果没有则新建键值对,有了则覆盖键值对
		prop.setProperty("time",count+"");

		//创建一个输出流,将配置文件指为目的地
		FileOutputStream fos = new FileOutputStream(file);

		//调用store方法,将prop中的信息存入FileOutputStream流中
		prop.store(fos,"");

		//关闭流资源,并把流中的数据冲入目的地中
		fis.close();
		fos.close();
	}
}


            运行结果



 

打印流


     包括PrintStream和PrintWriter

     

     该流提供了打印方法,可将各种类型的数据都原样打印。

 

     字节打印流:PrintStream(在进行对象建立时能直接操作文件,凡是和文件相关的流对象都是比较重要的)


          构造函数可以接收的参数类型


               1.   File对象:File


               2.   字符串路径:String


               3.   字节输出流:OutputStream

 

     字符打印流:PrintWriter       比较常用


          构造函数可以接受的参数类型


               1.   File对象:File


               2.   字符串路径:String


               3.   字节输出流:OutputStream


               4.   字符输出流:Writer

 

               PrintWriter out= new PrintWriter(System.out);     //打印在控制台上。

 

          特殊构造函数


               PrintWriter(OutputStream out , booleanautoFlush)


                    和上述的第三个构造方法一样,就是多了一个boolean类型的参数。


                    如果传入的参数为true,那么该类中的println,printf或format方法将会刷新。


                    也就是说当传入的参数为true是,不用手动刷新,在调用这三个方法的时候会自动刷新流。


                    是不是很方便~~

 

     下面来根据练习看一看打印流如何使用

/*
打印流使用示例

键盘录入一些数据,将字母变为大写,写入一个文件中。

思路
1.建立键盘录入
2.创建PrintWriter,将字符输出流作为参数传递进去,并添加true参数,使流自动刷新
3.读取键盘录入信息,将字母变为大写,使用println方法写入到文件中
3.关闭资源

*/
import java.io.*;
class  PrintWriterTest
{
	public static void main(String[] args) 
	{
		//建立对象的引用
		BufferedReader bufr = null;
		PrintWriter out = null;
		//异常处理
		try
		{
			//创建键盘录入
			bufr = new BufferedReader(new InputStreamReader(System.in));
			//创建PrintWriter,将字符输出流作为参数传递进去,并添加true参数
			out = new PrintWriter(new FileWriter("e:\\小写变大写.txt"),true);

			//定义一个String类型变量,用于接收键盘录入的数据
			String line = null;
			//建立循环readLine方法返回值不会null为循环条件
			while((line = bufr.readLine())!=null)
			{
				//判断输入的字符串是否是over,如果是,程序结束
				if("over".equals(line))
					break;
				//将字母变成大写,通过PrintWriter中的println方法,将数据写入流中
				out.println(line.toUpperCase());
				//因为前面传入了true参数,所以会自动刷新,不用我们刷新
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读写失败");
		}
		finally
		{
			try
			{
				//关闭资源
				if(bufr!=null)
					bufr.close();
				if(out!=null)
					out.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("资源关闭失败");
			}
		}
		
	}
}


          运行结果




 

序列流

      

      SequenceInputStream:多个流进行合并(串联),可以把多个流内的数据合并在一起输出

 

      构造函数


            合并多个流对象:SequenceInputStream(Enumeration<? extends InputStream>  e)


            合并两个流对象:SequenceInputStream(InputStream  s1 , InputStream  s2)

        

      合并多个流对象构造方法使用步骤


            1.   创建一个流的集合


                  Vector<FileInputStrea>  v = new  Vector<FileInoutStream>();


            2.   往集合内添加流对象


                  v.add(new  FileInoutStream(“1.txt”));


                  v.add(new  FileInoutStream(“2.txt”));


                  v.add(new  FileInoutStream(“3.txt”));


            3.   把集合内的元素存入Enumeration对象中


                  Enumeration<FileInputStream> en  =  v.elments();             //Enumeration是Vector特有的迭代方式,枚举


            4.   将Enumeration对象作为参数传入SequenceInputStream对象的构造函数中,完成多个流的合并


                  SequenceInputStream sis = new SequenceInputStream(en);

 

      说道合并,就不能不提到切割啦!


            切割文件


                   自定义数组,长度为1024*1024= 1M。通过存1M到一个文件中的方式来完成切割。


                   切割完的文件扩展名为part。然后可以用序列流合并文件。


                   在文件合并时可以用ArrayList数组,因为它比Veotr效率要高。


                   在取出元素时可以使用迭代器+枚举的方法。

 

      下面我们通过练习来看切割文件之后再合并在一起的代码演示

/*
SequenceInputStream序列流合并文件示例

思路
1.我们先切一个文件
2.定义一个ArrayList数组。
3.将切割完的文件和输入流相关联,并把输入流对象存入数组中
4.通过迭代器+枚举的方法,将输入流存入Enumeration对象中
5.将Enumeration对象作为参数传入SequenceInputStream构造函数的参数中。
6.将SequenceInputStream中的数据读取出来,通过输出流存入一个新的文件中。

为了代码演示清晰,我就不处理异常了。
*/
import java.io.*;
import java.util.*;
class SplitFileTest 
{
	public static void main(String[] args) throws IOException
	{
		//调用切割文件方法
		splitFile();
		//调用合并文件方法
		merge();
	}

	//合并文件方法
	public static void merge()throws IOException
	{
		//定义一个ArrayList数组,用于接收读取流对象
		ArrayList<FileInputStream> list = new ArrayList<FileInputStream>();

		//因为我们切割了三个文件,所以定义一个循环,把读取三个文件的输入流对象都添加到list数组中
		for(int x = 1 ; x<=3 ; x++)
		{
			list.add(new FileInputStream("e:\\java0318\\BlogDemo\\IO\\splitfile\\"+x+".path"));
		}
		//通过迭代器+枚举的方法将输入流存入Enumeration对象中
		//首先通过iterator()方法获得Iterator对象,用finally修饰是因为一会儿内部类要访问Iterator对象的引用型常量。
		final Iterator<FileInputStream> it = list.iterator();		
		//因为枚举是Vector的特有迭代方法,ArrayList无法使用,所以创建一个Enumeration的匿名内部类
		//复写Enumeration中的方法,把迭代器的方法体写入,这样调用Enumeration中方法时,其实是在调用
		//Iterator中的方法。获取的元素,也是ArrayList中的元素。
		Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
		{
			//复写hasMoreElements方法
			public boolean hasMoreElements()
			{
				//返回的是迭代器的hasNext方法的结果
				return it.hasNext();
			}
			//复写nextElement
			public FileInputStream nextElement()
			{
				//返回的是迭代器的next方法的返回结果
				return it.next();
			}
		};
		//这样我们就将list集合内的输入流对象存进了Enumeration对象中,将它传入序列流的构造函数中
		SequenceInputStream sis = new SequenceInputStream(en);
		//定义一个输出流对象,关联目的文件
		FileOutputStream fos = 
			new FileOutputStream("e:\\java0318\\BlogDemo\\IO\\splitfile\\吧特然弗莱.mp3");		
		//下面的输出流动作我就不介绍了
		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("e:\\java0318\\BlogDemo\\IO\\splitfile\\Butterfly.mp3");
		//定义一个字节输出流对象的引用。
		FileOutputStream fos = null;
		//定义一个长度为1M的字节数组
		byte[] arr = new byte[1024*1024];
		//定义两个int类型变量,一个用于接收read返回值,
		//一个计数器用于定义文件名用的
		int len = 0 , count = 1;
		//创建循环,使用read方法,将数据存入数组中
		while((len = fis.read(arr))!=-1)
		{
			//定义输出流对象,关联目的文件,因为是切割文件,所以名字要与count关联,使文件名不同
			fos = 
				new FileOutputStream("e:\\java0318\\BlogDemo\\IO\\splitfile\\"+count+".path");
			//将数组内的数据存入输出流中
			fos.write(arr,0,len);
			//关闭输出流资源
			fos.close();
			//计数器自增
			count++;
		}
	}
}


            切割完的文件



        

 

可操作对象的流


      ObjectInputStream  和  ObjectOutputStream

       

      ObjectOutputStream


            构造函数


                  ObjectOutputStream(OutputStreamput)


            特有方法


                  void  write(int val)            写入一个字节


                  void  writeInt(int val)       写入一个32个二进制位的int值


                  上面两个方法的区别:write方法写入的是后八个二进制位,也就是一个字节


                  void  writeObject(Object obj)      将对象写入ObjeceOutputStream


                        例:oos.writeObject(new Person(“lisi” , 12));


                        被写入ObjectPutStream中的对象必须实现Serializbale接口,以启用其序列化功能。

 

      ObjectInputStream


            构造函数


                  ObjectInputStream(InputStream  in)


            特有方法


                  Object  readObject()                   从ObjectInputStream中读取对象。


                        例:Person p = (Person)ois.readObject();

 

      用以上介绍的方法,可以实现把对象信息存入到文件中


            但当Person类改变时,将无法进行读取文件中对象信息的操作,因为这时对象和类的UID不同


            UID是根据类中成员来获取的,多加一个成员,或将已有成员用修饰词修饰都会改变UID,使程序运行失败。


            所以要想改变类,并且可以使用原来建立的对象,就可以自己写一个UID,写在自定义对象中


                        public static final  longserialVersionUID = 42L;                 //前面可加任何修饰符


            自己写UID是为了固定标识,使对象序列化方便。


            静态是不能被序列化的。

 

 

管道流


      PipedInputStream 和 PipedOutputStream.

        

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


            要把这两个管道流的对象放到不同线程对象中,不然会发生死锁。


            因为read是一个阻塞式方法,两个管道流对象放到了同一个线程对象中,如果read方法读取不到数据线程就会停止,转到别的线程也一样会阻塞。


            所以将两个管道流对象放入不同线程对象中,如果PipedInputStream的线程先执行,找不到要被读取的文件,就等待。


            就转到了PipedOutputStream的线程先运行。

 

      可以理解为输入流从输出流中读取数据,输出流把数据写入输入流中。

        

      特有方法


            void  connect(PipedOutputStream src)            使此管道输入流连接到管道输出流 src。连接管道流

                  

      根据练习学习管道流的使用。

/*
管道流使用示例
*/

import java.io.*;

//定义读类实现Runnable接口
class Read implements Runnable
{
	//构造函数接收管道读取流对象
	private PipedInputStream in;
	Read(PipedInputStream in)
	{
		this.in = in;
	}
	//复写run方法
	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("管道读取流失败");
		}
	}
}
//定义写类实现Runnable接口
class Write implements Runnable
{
	//构造函数接收管道输出流对象
	private PipedOutputStream out;
	Write(PipedOutputStream out)
	{
		this.out = out;
	}
	//复写run方法
	public void run()
	{
		//处理异常
		try
		{
			System.out.println("线程等待6秒后开始写入数据。");
			Thread.sleep(6000);
			//将字符串变为字节,写入管道输出流中
			out.write("用电脑控制挖掘机炒菜".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();
		//调用connect方法连接管道流
		in.connect(out);
		//建立读写对象,将管道流对象作为参数传入
		Read r = new Read(in);
		Write w = new Write(out);

		//创建线程,并启动线程
		new Thread(r).start();
		new Thread(w).start();
	}
}


            运行结果



 

 

RandomAccessFile

     

      自身具备读写方法

 

      该类不算是IO体系中的子类,他直接继承Object,但是它是IO包中的成员。因为它具备读和写功能。


            RandomAccessFile内部封装了一个数组,而且通过指针对数组中的元素进行操作,可以通过getFilePointer获取指针位置。


            可以通过seek方法改变指针位置,完成读写的原理。

 

      RandomAccessFile内部封装了输入流和输出流

 

      构造函数


            RandomAccessFile(Filefile , String mode)


                  创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。


            RandomAccessFile(String name , String mode)


                  创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

 

      通过RandomAccessFile的构造函数可以看出,该类只能操作文件,而且操作文件还有模式,模式的值不同含义也不同,下面我来介绍两种常用模式


            1.   “r”:以只读的方式打开,调用结果对象的任何write方法都会发生异常。(就是只能看不能写)


                  在这个模式下,会去读取一个已存在的文件,如果该文件不存在则会出现异常。


            2.    “rw”:打开以便读写。读写都可以的,调用结果对象的任何write方法都不会发生异常


                  在这个模式下,该对象的构造函数要操作的文件不存在,会自动创建,如果存在则不会覆盖。

 

      类中方法讲解


            void  write(int b)


                  只能写入int类型数字二进制的后八位,会造成数据丢失。


            void  writeInt(int v)


                  方法会写入32个二进制位,不会造成数据丢失


            int  readInt()


                  读取出4个字节,也就是32个二进制位,转成int型返回。


            int  skipBytes(int n)


                  跳过指定的字节数,只能往后跳。

 

                  这个方法可以实现多线程下载。

 

 

操作基本数据类型的类


      DataInputStream  和  DataOutputStream

 

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


            使用writeInt方法和readInt方法来读取和写入数据,数据类型可以是可以是任何基本数据类型,这里我用int型做的比喻。

 

            在读取时,要按写入的顺序,不然会发生错乱

 

      特有方法


            void writeUTF(String str)


                  把传入的字符串使用UTF-8修改版码表进行编码,使读取文件时要用readUTF方法。


                   就是说writeUTF()写进去的,readUTF()读出来

 

      根据练习来学习怎么使用操作基本数据类型的类

/*
操作基本数据类型数据的类方法应用示例
*/
import java.io.*;
class DataStreamDemo 
{
	public static void main(String[] args) throws IOException
	{
		//调用读写方法
		writeData();
		readData();
	
		//调用使用UTF-8编码表的读写方法
		writeUTFDemo();
		readUTFDemo();
	}
	//读取字节,通过UTF-8编码方法
	public static void readUTFDemo()throws IOException
	{
		//创建DataInputStream对象,并指定要读取的文件
		DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
		//调用readUTF方法,将字节进行解码
		String s = dis.readUTF();
		//将获取的数据打印
		System.out.println(s);
		//关闭资源
		dis.close();
	}

	//使用UTF-8码表写入数据方法
	public static void writeUTFDemo()throws IOException
	{
		//创建DataOutputStream对象,并指定目的文件
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("utf.txt"));
		//使用writeUTF方法,将数据使用UTF-8编码,写入流中
		dos.writeUTF("都凌晨一点了啊");
		//关闭资源
		dos.close();
	}
	
	//读取数据方法
	public static void readData()throws IOException
	{
		//创建DataInputStream对象,关联要读取的文件
		DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));

		//通过read不同数据类型的方法,读取文件中的数据
		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对象,并关联目的文件
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
		//通过write不同数据类型的方法,往文件中写入数据
		dos.writeInt(123321);
		dos.writeBoolean(true);
		dos.writeDouble(3.1415926);
		
		//关闭流
		dos.close();	
	}
}


            运行结果


 


 

操作数组和字符串的类


       操作字节数组的类


                     ByteArrayInputStream  和  ByteArrayOutputStream

       

                            这个类不调用底层资源,只是在操作数组,所以不会产生IO异常,不用关闭流。

 

                     ByteArrayInputStream


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

 

                     ByteArrayOutputStream


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

 

                     在流操作规律讲解时说过的源设备和目的设备对应的对象分别是


                                 源设备:         键盘   System.in            硬盘 FileStream      内存 ArrayStream


                                 目的设备:     控制台 System.out         硬盘 FileStream      内存 ArrayStream

 

                     这个类中,唯一一个抛出异常的方法


                            void  writeTo(OutputStream out)              把字节数组写到一个输出流中,只有它有异常。

 

       操作字符数组的类


              CharArrayReader 和 CharArrayWriter

 

       操作字符串的类

              StringReader和 StringWriter

 

             操作字符数组和操作字符串的4个类的用法,和上面操作字节数组的类大同小异。

 

 

字符编码

      

       字符流的出现是为了方便操作字符,更重要的是加入了编码转换,通过子类转换流来完成。


              InputStreamReader和 OutputStreamWriter。在这两个类构造时可以加入字符集(编码表)。

 

       编码表的由来


              计算机智能识别二进制数据,早期由来是电信号,为了方便应用计算机,让他可以识别各个国家的文字,


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

 

       常见编码表


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


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


              GB2312:中国的中文编码表


              GBK:中文编码表升级,融合了更多的中文文字符号,2个字节一个字


              Unicode:国际标准码,融合了多种文字,所以文字都用2个字节表示,Java语言使用的就是Unicod


              UTF-8:最多用3个字节来表示一个字符(字节能用一个就用一个,不够了再多用)

 

       编码


              字符串变成字节数组:String——>>byte[ ]


                     str.getBytes(“编码表”);

        

       解码


              字节数组变成字符串:byte[]——>>String


                      new String(byte[],”编码表”);

 

         个人理解:编码解码就是用不同的编码表完成字符串到字节数组,字节数组到字符串的转化,这就是编码解码。

 

       还有一点要注意


              用什么编码表编的码,就用什么编码表解码。什么编的就用什么解。


                     不管中间用了那个编码表,解出了什么乱码,最后要想返回正常的文字,还是要用编码时候用的编码表。


                     因为比如我用GBK变了”你好”这个字符串的码,就变成了二进制数据,用ASCII解码之后肯定是乱码,因为二进制对应的字不同,


                     但是文字对应的还是这段二进制数,再用ASCII编码之后又成了二进制数,不管用了什么编码表,一直都是这段二进制数。


                     数据不会改变,但是想看到正常的结果,还是要用编码时候的编码表进行二进制数据的解码。

 

       注意:因为GBK和UTF-8都识别中文,所以在G编,U解,U编,G解 的过程中会发生错误。


                   GBK和IOS8859-1进行上面的动作就不会发生错误。

 

 

 

 


 

谢谢大家的观看!~

-----------android培训java培训、java学习型技术博客、期待与您交流!---------


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值