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

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------


1.初识IO流

        IO是Input%Output的缩写,即输入和输出。简单理解,就是通过IO流可以实现对硬盘数据或者键盘录入数据(内存数据)进行操作。

         那么,IO流的体系?

        从流向分,IO里有写入流(Writer和OutputStream:WO)和输出流(Read和InputStream:RI)。

        按操作数据类型分,有字符流(Writer和Read:W、R)和字节流(OutputStream和InputStream:O、I)。

        而由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。

        字符流和字节流相比,有两个不同,一个是流式传输的时候传输协议不同,因为传输的数据单位改变了;二是字符流还带有编解码的功能,我们的字符数据在 java 程序里面是以 unicode 编码的形式存在,字符流可以对我们程序中的字符再编码,编码为 utf-8 、gbk 等,可以对其他设备传过来的数据解码,把 utf-8、gbk 等编码解码为 unicode 码。

2.代码示例            

 1、先用FileWriter来简单了解下IO流的基本操作。

		// 创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录
		// 下。如果该目录下已有同名文件,默认将被覆盖。其实该步就是在明确数据要存放的目的地。File类关于文
		// 件创建有更多的方式
		FileWriter fw = new FileWriter("Test.txt");
		// 调用write方法,将字符串写入到流中。
		fw.write("abcde");
		// 刷新流对象中的缓冲中的数据。
		// 将数据刷到目的地中。
		fw.flush();
		// 关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。
		// 将数据刷到目的地中。
		// 和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
		fw.close();

close()flush()的区别:

flush():将缓冲区的数据刷到目的地中后,流可以使用。

close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。

io异常的处理方式:io一定要写finally

注:FileWriter可以对已有文件进行续写,构造方法为

FileWriter fw=new FileWriter ("Demo.txt",true);

2. 复制txt文件

示例代码中,演示了IO流的异常处理方式,并用了三种方法来执行文本的复制

//导包动作必须做,否则会出现大片错误提示
import java.io.*;
 
class FileReaderDemo
{
       publicstatic void main(String[] args)
       {
              //创建文件读取流和写入流对象,初始化为null。
              FileReader fr = null;
			  FileWriter fw=null;
			try  
			{  
				fr= new FileReader("source.txt");  //源
				fw=new FileWriter ("dest.txt");//目的

				//方法一:通过字符读取
				int ch = 0;
				while((ch=fr.read())!=-1)//调用读取流对象的read方法。返回值为-1时,表示结束到末尾。
				{
					 fw.write(ch);
				}

				//方法二:通过自定义字符数组读取
				char[]buf = new char[1024];  

				int len = 0;  

				while((num=fr.read(buf))!=-1)  
				{  
					fw.write(buf,0,len);  
				}  

				//方法三:字符流缓冲区
				//用此方法时,在finnaly中需要关闭的流是bufr和bufw
				BufferedReader bufr=new BufferedReader (fr);
				BufferedWriter bufw=new BufferedWriter(fw);

				String line =null;

				while ((line=bufr.readLine())!=null)
				{
					bufw.write(line);
					bufw.newLine();
					bufw.flush();//用到缓冲流就需要刷新
				}
			}  
			catch(IOException e)  
			{  
				throw new RuntimeException("复制文件失败");  
			}  
			finally  //必须执行关流动作
			{  
				 try  
				 {  
					if(fr!=null)  
						fr.close();
				 }  
				 catch(IOException e)  
				 {  
					 throw new RuntimeException("读取文件失败");  
				 }  
				 try  
				 {  
					 if(fw!=null)
						fw.close();					
				 }  
				 catch(IOException e)  
				 {  
					 throw new RuntimeException("写入文件失败");  
				 }  
       }
}

注:此处示例中方法三用到了缓冲字符流,因为这里是txt文件。如果需要复制图片、mp3、视频等需要使用的缓冲流为缓冲字节流,即:BufferedOutputStream和BufferedInputStream。

3.自定义字节流的缓冲区

import java.io.*;
 
class MyBufferedInputStream
{
       private InputStream in;
 
       private byte[] buf = new byte[1024*4];
            
       private int pos = 0,count = 0;
     
       MyBufferedInputStream(InputStream in)
       {
              this.in= in;
       }
 
       //一次读一个字节,从缓冲区(字节数组)获取。
       public int myRead()throws IOException
       {
              //通过in对象读取硬盘上数据,并存储buf中。
              if(count==0)
              {
                     count= in.read(buf);
                     if(count<0)
                            return-1;
                     pos= 0;
                     byte b = buf[pos];
 
                     count--;
                     pos++;
                     return b&255;//取最低八位
              }
              else if(count>0)
              {
                     byte b = buf[pos];
                     count--;
                     pos++;
                     return b&0xff; //十六进制的255
              }
              return -1;
 
       }
       public void myClose()throws IOException
       {
              in.close();
       }
}

4.读取键盘录入

需求:

通过键盘录入数据。

当录入一行数据后,就将该行数据进行打印。

如果录入的数据是over,那么停止录入。

import java.io.*;
class ReadIn
{
       publicstatic void main(String[] args)throws IOException
       {
              InputStreamin = System.in;
              StringBuildersb = newStringBuilder();
 
              while(true)
              {
                     intch = in.read();
                     if(ch=='\r')
                            continue;
                     if(ch=='\n')
                     {
                            Strings = sb.toString();
                           if("over".equals(s))
                                   break;
                           System.out.println(s.toUpperCase());
                           sb.delete(0,sb.length());
                     }
                     else
                           sb.append((char)ch);
 
              }
       }
}

5、上述的键盘录入方法是按字节来读取的,比较慢,而readLine方法又属于字符流的派系,那么我们就需要引入转换流的概念

读取转换流InputStreamReader

InputStreamReader是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

每次调用InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。

为了达到最高效率,可要考虑在BufferedReader 内包装InputStreamReader。例如:

 BufferedReader in  = new BufferedReader(new InputStreamReader(System.in));

----------------------------------------------------------------------------------------------------

写入转换流OutputStreamWriter

OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。

为了获得最高效率,可考虑将OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。例如:

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));


红色的两行代码是在获取键盘录入时最常用的方法。

3.流对象总结

流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

流的操作规律:

1,明确源和目的。

       数据源:就是需要读取,可以使用两个体系:InputStreamReader

       数据汇:就是需要写入,可以使用两个体系:OutputStreamWriter

2,操作的数据是否是纯文本数据?

       如果是:数据源:Reader

                  数据汇:Writer

       如果不是:数据源:InputStream

                    数据汇:OutputStream

3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

       明确操作的数据设备。

       数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)

       数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)

4,需要在基本操作上附加其他功能吗?比如缓冲。

       如果需要就进行装饰。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于字节流 +编码表。没有转换,没有字符流。

发现转换流有一个子类就是操作文件的字符流对象:

InputStreamReader

       |--FileReader

OutputStreamWriter

       |--FileWrier

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK

FileReader fr = new FileReader("a.txt");

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

以上两句代码功能一致,

如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。

如果需要制定码表,必须用转换流。

转换流 = 字节流+编码表。

转换流的子类File =字节流 +默认编码表。

凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

四、File类

用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作,File对象可以作为参数传递给流的构造函数

了解file类中的常用方法。

separator(跨平台的目录分隔符==\\)与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。

File类常见方法:

1,创建

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

static File   createTempFile(String prefix,String suffix)在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。

static File   createTempFile(String prefix,String suffix,File directory)在指定文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。和输出流不一样,输出流对象一建立创建文件,而且文件已经存在,会覆盖

boolean mkdir();创建文件夹

boolean mkdirs();创建多级文件夹

2,删除

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

void deleteOnExit();在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。在程序退出时删除指定文件

3,判断

boolean compareTo(File pathname)按字母顺序比较两个抽象路径名。

boolean exists():文件是否存在

isfile();测试此抽象路径名表示的文件是否是一个标准文件。

isDirectory();测试此抽象路径名表示的文件是否是一个目录

isHidden();/测试此文件是否是一个隐藏文件。

isAbsolute();测试此抽象路径名是否为绝对路径名。

4,获取信息

getName();获取名称

getPath()将此抽象路径名转换为一个路径名字符串。

getParent();返回此抽象路径名父目录的路径名字符串,如果此路径名没有指定父目录,则返回null.

getParentFile()返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回null.

getAbsolutePath();返回的是绝对路径名字符串

getAbsoluteFile();返回绝对路径名形式

long  length()返回由此抽象路径名表示的文件的长度。

long  lastModified() 返回最后一次被修改的时间。

5、List

static File listRoots():列出可用的文件系统根,

String[]list(FilenameFilter filter):返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录

list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。

listFiles():返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。

File[] listFiles(FileFilter filter):返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。

File[] listFiles(FilenameFilter filter):返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。

FilenameFilter:接口,实现此接口的类实例可用于过滤器文件名

boolean accept(File dir,String name):测试指定文件是否应该包含在某一文件列表中。


函数递归

函数递归又叫函数嵌套,是指在函数定义的时候调用函数自己

注意:递归时一定要明确结束条件。 
应用场景:当某一功能要重复使用时,可以使用函数递归,函数递归的好处是简化了算法的描述方法,但是是因为函数压栈弹栈,和循环相比降低了程序的运行效率

递归要注意:
  1、限定条件。
  2、要注意递归的次数。尽量避免内存溢出。

函数递归使用实例一:列出指定目录下文件或者文件夹,包含子目录中的内容。

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

//要求:列出指定目录下文件或者文件夹,包含子目录中的内容
import java.io.*;
public class FileDemo3 
{
	public static void main(String[] args)
	{
	   File dir=new File("d:\\java123");
	   showDir(dir,0);
	   /*  toBin(6);
	   int n=getSum(10);
	   System.out.println("n:"+n);*/
	}
	public static String getLevel(int level)
	{
		StringBuilder sb=new StringBuilder();
		sb.append("|--");
		for(int x=0;x<level;x++)
		{
                            //sb.append("!===");
			sb.insert(0, "!--");
		}
		return sb.toString();
	}
	public static void showDir(File dir,int level)
	{ 
		System.out.println(getLevel(level)+dir.getName());
		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(files[x]);
		}
	}
}

函数递归使用实例二 删除一个带内容的目录
import java.io.*;
public class RemoveDir {
	public static void main(String[] args) {
		File dir=new File("d:\\testdir");
		removeDir(dir);
	}
	public static void removeDir(File dir)
	{  // if(dir.exists())
		File[] files=dir.listFiles();
		for(int x=0;x<files.length;x++)
		{
			if(/*!files[x].isHidden()不是隐藏的&&*/files[x].isDirectory())
				removeDir(files[x]);
			//else
			System.out.println(files[x].toString()+":file:"+files[x].delete());
		}
		System.out.println(dir+"::dir::"+dir.delete());
	}
}

递归使用实例三:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中,建立一个java文件列表文件

/*
思路:

    1,对指定的目录进行递归

    2,获取递归过程所有的java文件的路径

    3,将这些路径存储到集合中。

    4,将集合中的数据写入到一个文件中。
*/
import java.io.*;
import java.util.*;
public class JavaFileList {
	public static void main(String[] args) {
		File dir=new File("d:\\java123");
		List<File>list=new ArrayList<File>();
		fileToList(dir,list);
                  //System.out.println(list.size());
		File file=new File(dir,"javalist.txt");
		writerToFile(list,file.toString());
	}
	public static void fileToList(File dir,List<File> list)
	{
		File[] files=dir.listFiles();
		for(File file:files)
		{
			if(file.isDirectory())
				fileToList(file,list);
			else
			{
				if(file.getName().endsWith(".java"))
					list.add(file);
			}
		}
	}
	public static void writerToFile(List<File>list,String javaListFile)
	{
		BufferedWriter bufw=null;
		try {
			bufw = new BufferedWriter(new FileWriter(javaListFile));
			for (File f : list) {
				String path = f.getAbsolutePath();
				bufw.write(path);
			}
		} catch (IOException e) {
			throw new RuntimeException();
		}
		finally{
		try {	
				if(bufw!=null)
					bufw.close();
			} catch (Exception e2) {
				throw new RuntimeException();
			}
		}
	}
}

5.配置文件

要求:定义配置文件,记录软件使用次数,超过五次给出提示

import java.util.*;
import java.io.*;
public 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");//time键获取值。
		if(value!=null)
		 {count=Integer.parseInt(value);//count记录次数。
		    if(count>=5)
		    {
		    	System.out.println("你好,使用次数已到");
		    	return;
		    }
		 }
		count++;
		prop.setProperty("time", count+"");
		FileOutputStream fos=new FileOutputStream(file);//写回去。
		prop.store(fos, "");//注册信息。
		fos.close();
		fis.close();
	}
}

6.序列流: SequenceInputStream,对多个流进行合并

操作对象

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

SequenceInputStream 表示其他输入流的逻辑串联,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。多个文件合并成一个文件。

练习:文件分割程序


import java.io.*;
import java.util.*;
public class SequenceDemo {
	public static void main(String[] args) throws IOException
	{
		Vector<FileInputStream> v=new Vector<FileInputStream>();
		v.add(new FileInputStream("c:\\1.tdt"));
		v.add(new FileInputStream("c:\\2.tdt"));
		v.add(new FileInputStream("c:\\3.tdt"));
		Enumeration<FileInputStream>en=v.elements();
		SequenceInputStream sis=new SequenceInputStream(en);//将多个读取流合并成一个流
		FileOutputStream fos=new FileOutputStream("c:\\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.util.*;
import java.io.*;
public class splitFile {
	public static void main(String[] args)throws IOException
	{     merge();
	//	splitFile();
	}
	public static void merge() throws IOException
	{ ArrayList<FileInputStream>al=new ArrayList<FileInputStream>();
	  for(int x=1;x<=3;x++)
	  {
		  al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
	  }
	 final Iterator<FileInputStream>it=al.iterator();
	  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("c:\\splitfiles\\0.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("c:\\1.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("c:\\splitfiles\\"+(count++)+".part");
		   fos.write(buf,0,len);//写入流里面去。
		   fos.close();
	   }
	   fis.close();
   }
}

对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。

注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。

如何将非静态的数据不进行序列化?transient 关键字修饰此变量即可。

Serializable用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值