黑马程序员—java基础学习--IO ( Input Output )流(二)

之前的学习已经深入的了解到了IO流中的读取及写入的常用操作,但是单单有这些操作是不够的,还需要文件的信息,java的io包中当然也提供了文件或者文件夹的信息,通过这些类能让我们更加深入的探讨IO流的神奇。

*File类

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

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

File类的常见方法:

1,创建

boolean creatNewFile ( ) ; 在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立就会创建文件,而且文件已经存在会覆盖。

2,删除

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

void deleteOnExit ( ) ; 在程序退出时删除指定文件

3,判断

boolean canExecute ( ) ; 判断是否可执行

boolean canRead ( ) ; 是否可读

boolean exists ( ) ; 文件是否存在

boolean mkdir ( ) ; 创建文件夹

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

boolean isDirectory ( ) ; 判断是否为文件夹。记住,在判断文件是否是文件或者目录时,必须要判断该文件对象封装的内容是否存在

boolean isFile ( ) ;判断是否为文件。记住,在判断文件是否是文件或者目录时,必须要判断该文件对象封装的内容是否存在

boolean isHidden ( ) ; 判断是否为隐藏文件。

boolean isAbsolute ( ) ; 判断是否为绝对路径 

4,获取信息

getName ( ) ; 获取名称

getPath ( ) ; 获取路径

getParent ( ) ; 获取父目录。该方法返回的是绝对路径中的文件父目录,如果获取的是相对路径则返回null

getAbsolutePath ( ) ; 获取绝对路径 

long lastModified ( ) ; 获取最后一次修改时间

long length ( ) ; 获取文件大小


File入门方法演示代码:

import java.io.*;

class  FileDemo
{
	public static void main(String[] args) 
	{
		method_3();
		
	}
	public static void method_5()
	{
		File f1 = new File("c:\\a.txt");
		File f2 = new File("c:\\b.txt");	

		System.out.println("rename:"+f1.renameTo(f2));	//修改名称或者剪切文件
	}

	public static void method_4()
	{
		File f = new File("a.txt");
		System.out.println("path:"+f.getPath());	//获取相对路径
		System.out.println("abspath:"+f.getAbsolutePath());	//获取绝对路径
		System.out.println("parent:"+f.getParent());	//该方法返回的是绝对路径中的文件父目录,如果获取的是相对路径则返回null
														//如果相对路径中有上一层目录,那么该目录就是返回结果。
	}

	public static void method_3()
	{
		File f = new File("file.txt");
		
		//记住,在判断文件是否是文件或者目录时,必须要判断该文件对象封装的内容是否存在
		System.out.println("dir:"+f.isDirectory());
		System.out.println("file:"+f.isFile());
		System.out.println(f.isAbsolute());	//判断是否为绝对路径
	}

	public static void method_2()
	{
		File f = new File("file.txt");
		System.out.println("execute:"+f.canExecute());
		//创建文件夹
		File dir = new File("abc\\aa\\bb\\cc\\dd\\ee");
		System.out.println("mkdir:"+dir.mkdir());	//创建单级目录
	}

	public static void method_1()
	{
		File f = new File("file.txt");
		f.deleteOnExit();
		System.out.println("creat:"+f.creatNexFile());
		System.out.println("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");
		
		System.out.println("f1:"+f1);
		System.out.println("f2:"+f2);
		System.out.println("f3:"+f3);
	}
}


例题:使用File类遍历文件夹。包含子目录中的内容,也就是列出指定目录下的所有的内容。

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

递归要注意:

1,限定条件,如果不限定条件,则有可能成为死递。

2,要注意递归的次数,否则会出现内存溢出等情况。

import java.io.*;

class FileDemo3 
{
	public static void main(String[] args) 
	{
		File dir = new File("f:\\java");
		
		//将dir文件夹传入到遍历文件夹方法中
		listDirs(dir);


	}
	public static void listDirs(File dir)
	{
		//注意,直接打印dir对象,File对象的toString方法返回的是文件的路径名字符串
		System.out.println(dir);

		//使用文件数组接受传入的文件夹中的文件
		File[] files = dir.listFiles();

		//对数组进行遍历。
		for(File file : files)
		{
			if(file.isDirectory())
			{
				//使用到递归,如果是文件夹便重新走此流程
				listDirs(file);
			}
			else
				//如果不是目录,打印其路径
				System.out.println(file);
		}
	}
}

如下图代码所示,解释递归原理。

黑马程序员—java基础学习--IO ( Input Output )流(二)_第1张图片

练习:

将一个指定目录下的java文件的绝对路径,储存到一个文本文件中。

建立一个java文件列表文件

思路:

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

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

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

4,将集合中的数据写入到一个文件中

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

class  javaFileList
{
	public static void main(String[] args) 
	{
		File dir = new File("f:\\java");
		File file = new File(dir,"java.txt");
		List list = new ArrayList();

		fileToList(dir,list);

		writeToFile(list,file.toString());//此处file的toString方法返回的为File对象的路径字符串形式
	}
	
	//结合IO流将集合中的对象遍历并使用字符流写入到文件中
	public static void writeToFile(List list,String javaListName)
	{

		BufferedWriter bufw = null;
		try
		{
			bufw = new BufferedWriter(new FileWriter(javaListName));
			for (File f: list )
			{
				String path = f.getAbsolutePath();
				bufw.write(path);
				bufw.newLine();
				bufw.flush();
			}

		}
		catch (IOException e)
		{
			throw new RuntimeException("文件读写失败");
		}
		finally
		{
			try
			{
				if(bufw!=null)
					bufw.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("关闭流失败");
			}
			
		}
	}
	
	//遍历文件夹,并向集合中添加文件对象
	public static void fileToList(File dir,List list)
	{
		File[] files = dir.listFiles();
		for(File file : files)
		{
			if(file.isDirectory())
			{
				fileToList(file,list);
			}
			else
			{
				if(file.getName().endsWith(".java"))
				{
					list.add(file);
				}
			}
		}
	}
}

*Properties

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

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

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

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

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

class  PropertiesDemo
{
	public static void main(String[] args) throws IOException
	{
		loadDemo();
	}
	
	//Properties中有自己特有的方法,使得读取文件并非method_1方法麻烦
	public static void loadDemo() throws IOException
	{
		FileInputStream fis = new FileInputStream("info.txt");
		Properties prop = new Properties();

		//将流中的数据加载进集合
		prop.load(fis);
		
		//修改流中的数据(内存)
		prop.setProperty("wangwu","39");

		FileOutputStream fos = new FileOutputStream("info.txt");
		//将修改的数据存入到文件中
		prop.store(fos,"aa");

		//将流中的数据列出来
		prop.list(System.out);

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

	//想要将info.txt中键值数据存储到集合中进行操作
	/*
	1,用一个流和info.txt文件关联。
	2,读取一行数据,将该行数据用“=”进行切割。
	3,等号左边作为键,右边作为值,存入到Properties集合中即可
	*/
	public static void method_1() throws IOException
	{
		BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
		String line = null;
		Properties prop = new Properties();
		while((line = bufr.readLine())!=null)
		{
			String[] arr = line.split("=");
			prop.setProperty(arr[0],arr[1]);
		}
		bufr.close();

		System.out.println(prop);
	}
	
	//Properties的基本使用方法
	public static void setAndGet()
	{
		Properties prop = new Properties();
		prop.setProperty("zhangsan","30");
		prop.setProperty("lisi","39");

		String value = prop.getProperty("lisi");
		System.out.println(value);
		
		Set names = prop.stringPropertyNames();
		for(String name:names)
		{
			System.out.println(name+":"+prop.getProperty(name));
		}
	}
}

Properties练习:

用于记录应用程序运行次数,如果使用次数已到,那么给出注册提示。
分析得知:需要定义计数器,如果将计数器定义在程序中,随着程序的运行而在内存中存在并自增,可是随着应用程序的退出,该计数器也在内存中小时了。

下一次再启动该程序,又重新开始从0计数,这样不是我们想要的。

我们需要程序即使结束,该计数器的值也要存在,下次程序启动在会先加载该计数器的值并加1后的重新存储起来,所以要建立一个配置文件,用于记录该软件的使用次数。

该配置文件使用键值对的形式,这样便于阅读数据并操作数据。

键值对数据是Map集合,数据是以文件形式存储,使用io技术,那么Map加IO即为Properties。

配置文件可以实现应用程序数据的共享。

代码如下:

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

class  RunCount
{
	public static void main(String[] args) throws IOException
	{
		Properties prop = new Properties();
		
		File file = new File("count.properties");
		
		//程序的健壮性,若文件不存在则创建配置文件
		if(!file.exists())
			file.createNewFile();
		
		FileInputStream fis = new FileInputStream(file);
		//将prop对象与流相关联
		prop.load(fis);
		
		//定义计数器
		int count = 0;
		String value = prop.getProperty("time");
		
		if(value!=null)
		{
			count = Integer.parseInt(value);
			if(count>=5)
				System.out.println("5次试用已到期");
				throws
		}
		count++;
		
		//将配置文件信息输入到文件中
		prop.setProperty("time",count+"");
		
		//建立输出流,需要与prop对象想关联
		FileOutputStream fos = new FileOutputStream(file);
		
		//关联文件,持久化储存
		prop.store(fos,"");
	
		//关流
		fos.close();
		fis.close();
	}
}

*打印流

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

字节打印流PrintStream

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

1,file对象:File

2,字符串路径:String

3,字节输出流:OutputStream

字符打印流PrintWriter

1,file对象:File

2,字符串路径:String

3,字节输出流:OutputStream

4,字符输出流:Writer

打印流的应用如下:

import java.io.*;

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();
	}
}

*序列流

序列流的用途:将多个读取流合并成一个读取流。

例题:将c盘下的多个txt文件合并为一个文件。

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

class SequenceDemo 
{
	public static void main(String[] args) throws IOException
	{
		Vector v = new Vector();
		v.add(new FileInputStream("c:\\1.txt"));
		v.add(new FileInputStream("c:\\2.txt"));
		v.add(new FileInputStream("c:\\3.txt"));
		
		//使用Enumeration接受Vector集合中的数据
		Enumeration en = v.elements();
		
		//序列流接受Enumeration类型的对象
		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);
		}
		
		//流的关闭动作
		sis.close();
		fos.close();
	}
}

*操作对象

ObjectInputStream 与ObjectOutputStream

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

注意:此流必须成对出现使用,可以将对象存入到文件中长久保存。代码如下:

import java.io.*;

class  Person implements Serializable
{
	//自定义UID进行标记,而非使用默认的UID。
	//给类定义固定标示,为序列化方便,静态不能序列化。
	public static final long serialVersionUID = 42L;

	String name;
	//被transient修饰后便不可以序列化。
	transient int 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;
	}
}


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

	public static void readObj() throws Exception
	{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"));
		
		//读取对象
		Person p = (Person)ois.readObject();

		System.out.println(p);

		//关闭流
		ois.close();
	}

	public static void writeObj() throws IOException
	{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"));
		
		//将对象存入文件中
		oos.writeObject(new Person("lisi",39));
		
		//关闭流
		oos.close();
	}

}

*管道流

PipedInputStream和PipedOutputStream

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

import java.io.*;

class Read implements Runnable
{
	private PipedInputStream in;
	Read(PipedInputStream in)
	{
		this.in = in;
	}
	//将多线程代码写入到run方法中
	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);
			
			//关闭管道流
			in.close();
		}
		catch (IOException e)
		{
			throw new RuntimeException("管道读取失败");
		}
	}
}

class Write implements Runnable
{
	private PipedOutputStream out;
	Write(PipedOutputStream out)
	{
		this.out = out;
	}
	
	//多线程技术将多线程代码写入到run方法中
	public void run()
	{
		try
		{
			//线程等待6s
			Thread.sleep(6000);
			//写入数据
			out.write("piped 来了".getBytes());
			out.close();
		}
		//因涉及到多线程,所以抛出的异常并非只有IOException
		catch (Exception e)
		{
			throw new RuntimeException("管道输出失败");
		}
	}
}

class  PipedStreamDemo
{
	public static void main(String[] args) 
	{
		//建立管道流对象
		PipedInputStream in = new PipedInputStream();
		PipedOutputStream out = new PipedOutputStream();
		
		Read r = new Read(in);
		Write w = new Write(out);
		//开启线程
		new Thread(r).start();
		new Thread(w).start();

	}
}

*RandomAccessFile

此类文件的示例支持对随机文件的读取和访问,该类不算是IO体系中子类,而是直接继承自Object。

但是它是IO包中的成员,因为它具备读写功能,内部封装了一个数组,而且通过指针对数组的元素进行操作。

可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。

其实完成读写的原理就是内部封装了字节输入流和输出流。

通过构造函数可以看出,该类只能操作文件。

而且操作文件还有模式:只读 r   读写rw等

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

如果模式为只读r,则不会创建文件,会读取一个已存在文件,如果文件不存在,则会出现异常。

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

import java.io.*;

class  RandomAccessFileDemo 
{
	public static void main(String[] args) throws IOException
	{
		writeFile_2();
	}
	
	public static void readFile() throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("ran.txt","R");
		//此处出现异常,因为模式选择的为读取并非写入。
	//	raf.write("haha".getBytes());

		//调整对象中的指针。
		//raf.seek(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+":"+age);

		raf.close();
	}

	public static void writeFile() throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");

		raf.write("李四".getBytes());
		//使用write方法是写入的是对应的码
		raf.writeInt(97);
		
		raf.write("wangwu".getBytes());
		raf.writeInt(99);

		raf.close();
	}

	public static void writeFile_2() throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
		
		//调整指针位置,并可以对对象进行修改。
		raf.seek(8*3);

		raf.write("周七".getBytes());
		raf.write(103);
		raf.close();
	}


}

*DataInputStream和DataOutputStream

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

import java.io.*;

class  DataStreamDemo
{
	public static void main(String[] args) throws IOException,Exception
	{
		writeFile();
	//	readFile();
		writeUTFDemo();
		readUTFDemo();
	}

	public static void readUTFDemo() throws Exception
	{
		DataInputStream dis = new DataInputStream(new FileInputStream("utfdate.txt"));
		//必须使用本方法才可读出writeUTF方法写入的字符串
		String s = dis.readUTF();
	
		System.out.println(s);
		
		//关闭流
		dis.close();
	}

	public static void writeUTFDemo()throws IOException
	{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdate.txt"));
		//此方法写入的是utf-8的修改版,使用utf-8的字符集读不出来需要指定的方法才能读出
		dos.writeUTF("你好");
		//关闭流
		dos.close();
	}

	public static void writeFile()throws IOException
	{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
		
		//对基本数据类型进行写入
		dos.write(123);
		dos.writeBoolean(true);
		dos.writeDouble(9228.223);
		
		//对流的关闭
		dos.close();

	}

	public static void readFile()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);
		System.out.println(b);
		System.out.println(d);

		//流关闭
		dis.close();
	}
}

*操作数组的流对象

可以用于直接操作字节数组数据的流对象。

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

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

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

在刘操作规律讲解时:

源设备:

键盘System.in  硬盘FileStream  内存ArrayStream

目的设备:

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

用流的读写思想来操作数组。

操作字符数组的流:CharArrayReader和CharArrayWrite

操作字符串的流:StringReader与StringWriter

import java.io.*;

class  ByteArrayStream
{
	public static void main(String[] args) 
	{
		//数据源
		ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFG".getBytes());
		//数据目的
		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());
		//无需关闭资源和异常处理,因为未调用系统资源。
	}
}

*字符编解码

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

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

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

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

解码选择错误如何修正?比如解码选择了iso8859-1时,但是需要注意的是,如若解码选择的UTF-8,则此方法行不通

代码如下:

import java.util.*;

class  EncodeDemo
{
	public static void main(String[] args) throws Exception
	{
		String s = "你好";
		byte[] b1 = s.getBytes("gbk");	//编码,不传入编码集时默认编码为gbk
		System.out.println(Arrays.toString(b1));	
	
		//解码操作
		String s1 = new String(b1,"iso8859-1");
		System.out.println(s1);
		
		//再次进行编码操作
		byte b2 = s1.getBytes("iso8859-1");
		System.out.println(Arrays.toString(b2));	

		//使用正确的解码集进行解码
		String s2 = new String(b2,"gbk");
		System.out.println(s2);

	}
}


你可能感兴趣的:(黑马程序员—java基础学习--IO ( Input Output )流(二))