黑马程序员——Java基础知识——IO(Properties、打印流、IO其他类、字符编码)

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!

一、Properties

       Properties是Hashtable的子类。它具备Map集合的特点。它里面存储的都是键值对,都是字符串,无泛型定义,是集合中和IO技术相结合的集合容器。Properties可用于键值对形式的配置文件。在加载时,需要数据有固定的格式:键=值。

       常用方法:

        1.设置元素

            Object  setProperty(String key,String value) 设置键和值,

        2.获取值

            String   getProperty(String key) 指定key搜索value

            Set<String>  stringPropertyName() 返回属性列表的键集,存入Set集合

        3.加载

             void   load(InputStream in/Reader r) 从输入流中读取属性列表。可以理解为将流中的数据加载进集合。

        4.输出

              void  list(PrintStream ps/PrintWriter pw) 将属性列表输出到指定输出流。

         5.存储

              void  store(OutputStream out/Writer w,String comments)将属性列表写入输出流。comments为属性列表的描述。

        下面通过一段简单程序进行方法的演示。如下:

class  PropertiesDemo
{
	public static void main(String[] args) throws IOException
	{
		//建立Properties对象
		Properties prop=new Properties();
	    //建立流对象,和文件相关联,加入缓冲
		BufferedReader br=new BufferedReader(new FileReader(new File("F:\\123.txt")));
		BufferedWriter bw=new BufferedWriter(new FileWriter(new File("F:\\124.txt")));
	    //加载读取流中的数据进内存
		prop.load(br);
		//设置键值对
		prop.setProperty("color", "white");
		//获取值,并打印
		System.out.println(prop.getProperty("name"));
		//输出到写入流中,存入文件。
	    prop.store(bw, "ok");
	    
	}
}

   再通过一个程序使用次数的程序来学习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("F:\\count.ini");
		if(!file.exists())
			file.createNewFile();
	//定义读取流,与文件相关
		FileReader fis = new FileReader(file);
       //将流加载进Properties
		prop.load(fis);
		
      //定义计数器
		int count = 0;
		String value = prop.getProperty("time");
		if(value!=null)
		{ count = Integer.parseInt(value);
			if(count>=5)
			{
				System.out.println("您好,使用次数已到,请充值");
				return ;
			}
                 
		}
     //使用一次 count自增一次
		count++;
                System.out.println("您好,您的使用次数还有"+(5-count)次);      
    //设置使用次数
		prop.setProperty("time",count+"");
    //定义写入流
		FileOutputStream fos = new FileOutputStream(file);
   //存入写入流
		prop.store(fos,"");
  //关闭流资源
		fos.close();
		fis.close();
		
	}
}

二、打印流

         在前面的学习中已经接触到打印流(PrintStream和PrintWriter),打印流提供了打印方法,可将各种数据类型的数据原样打印。

         字节打印流PrintStream,构造方法中接收的参数类型:

          1.File对象 File

          2.字符串路径 String

          3.字节输出流 OutputStream

         字符打印流PrintWriter,构造方法中接收的参数类型:

          1.File对象 File

          2.字符串路径 String

          3.字节输出流 OutputStream

          4.字符输出流:Writer

         字符流的使用很常见,通过打印流的print和println方法可以提高写入效率,传入流对象时,可以在构造函数中设置自动刷新缓冲。

   下面这个程序就是运用了打印流:

/**
需求:将键盘录入写入到文件中,通过打印流,效率较高
*/

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("123.txt"),true);

		String line = null;
                 //进行读写操作
	 while((line=bufr.readLine())!=null)
         {
			if("over".equals(line))
				break;
			out.println(line);
			
         }
                //关闭流资源
		out.close();
		bufr.close();

	}	
}

三、功能型流对象

    学习了基本的流对象之后,下面对功能型的流对象进行学习。

   1.序列流SequenceInputStream

         该流可对多个字节流进行合并,所以也被称为合并流。下面通过一个程序展示该流的使用过程:  

            //创建Vector集合
		Vector<FileInputStream> v = new Vector<FileInputStream>();

            //添加输入流对象		
		v.add(new FileInputStream("c:\\1.txt"));
		v.add(new FileInputStream("c:\\2.txt"));
		v.add(new FileInputStream("c:\\3.txt"));
            //创建Enumeration对象
		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();

     2.对象的序列化

           对象的序列化,就是将堆内存中的对象存入硬盘,保留对象中的数据,也称之为对象的持久化。对象的持久化中常使用的两个类是:ObjectInputStream和ObjectOutputStream。构造函数中传入流对象。

             特有方法:readObject()从流中读取对象

                              writeObject() 将对象写入流中

              在使用这些方法时,要注意被操作都必须实现Serializable接口(标记接口)

       Serializable接口:这个接口没有方法,称之为标记接口。在序列化运行时使用一个名称为serialVersionUID的版本号与每个可序列化类相关联,用于验证序列化对象的发送者和接收者是否为同一个对象,如果接收者加载的该对象的类的serialVersionUID与对应的发送者的类的版本号不同,则会导致InvalidClassException,可序列化类可通过声明为"serialVersionUID"字段(该字段必须是静态的)声明自己的serialVersionUID,固定格式如下:

                        public static final long serialVersionUID=42L;

  下面是一个从将对象写入文件,再读取的程序:

/**
定义Person类,将其存入文件中,再在控制台打印。
*/
 class ObjectDemo
{
	public static void main(String[] args) throws Exception
	{
	      writeObj();
		readObj();
	}
	//从硬盘上读取对象
	public static void readObj()throws Exception
	{   //定义流对象
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.object"));
         //向下转型
		Person p = (Person)ois.readObject();
        //打印对象
		System.out.println(p);
		ois.close();
	}
    //将对象写入文件中
	public static void writeObj()throws IOException
	{   //定义流对象
		ObjectOutputStream oos = 
			new ObjectOutputStream(new FileOutputStream("Person.object"));
        //写入对象
		oos.writeObject(new Person("小明",15));

		oos.close();
	}
}
//定义要操作的对象,实现Serializable接口
class Person implements Serializable
{
	//自定义serialVersionUID 号
	public static final long serialVersionUID = 42L;

	private String name;
	//transient修饰的成员不能被序列化
	transient int age;
	//静态成员不能被序列化
	static String country = "cn";
	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	public String toString()
	{
		return name+":"+age+":"+country;
	}
}	

      注意:对象中静态成员不能序列化;非静态成员如果被transient修饰,也不被序列化,这样是保证非静态成员保存在堆内存中,不能写入文件。

 

      3.RandomAccessFile类

       RandomAccessFile类的实例支持对随机访问文件的读取和写入。自身具备读写方法。该类不算IO体系中的子类,而是直接继承Objcet,但它是IO包成员,因为它具备读写功能,内部封装了一个数组,通过getFilePointer方法获取指针位置,来对数组中的元素进行操作,同时还可以通过seek方法改变指针的位置。RandomAccessFile能完成读写的原理是它内部封装了字节输入流和输出流。

       Random的构造函数:RandomAccessFile(File file ,String mode),该类只能操作文件,而且操作文件有模式(r:只读;rw:读写等四种模式) ,如果模式为r,不会创建文件,会去读一个已存在的文件,若不存在,出现异常;如果为rw,文件不存在就会创建,存在不会覆盖。 

       特有方法:

        1.seek(long pos):调整指针,来进行指定位置的数据读取和写入,要求数据要有规律,如果设置的 指针位置已有数据,写入时会将其修改,用seek可以表示随机读写访问。

       2.skipBytes(int  n)跳过指定字节数。

        RandomAccessFile中也可对基本数据类型进行读写的方法,还有readLine()方法等。

         下面是一段用RandomAccessFile操作的程序。例如:

               //读取方法
	public static void readFile()throws IOException
	{
              //建立RandomAccessFile对象,关联文件,模式为只读。
             RandomAccessFile raf = new RandomAccessFile("F:\\abc.java","r");
	      //调整对象中指针
              raf.seek(8*1);
              //跳过指定的字节数
              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()throws IOException
	{
		RandomAccessFile raf = new RandomAccessFile("F:\\abc.java","rw");
		raf.seek(8*0);
		raf.write("你好".getBytes());
		raf.writeInt(103);
                raf.close();
	}
}

   该类的对象可以实现数据的分段写入,可以结合多线程写入数据(例如用多线程技术下载,可以实现同时多段下载)。

   4.操作基本数据类型的流对象

       DataInputStream和DataOutputStream,这两个对象可以用于操作基本数据类型的流对象。包含读写各种基本数据类型的方法,例如:readByte()/writeByte(byte b)、readDouble()/writeDouble(double d).

     还有两个特殊的方法:

       String readUTF()  以UTF-8修改版解码读取文件中的内容       

       String writeUTF()   以UTF-8修改版编码将字符串写入文件。

   

   5.操作字节数组的流对象

         ByteArrayInputStream和ByteArrayOutputStream,这两个对象并没有调用底层资源,所以不用关闭流资源,即时关闭后,仍可调用。内部包含缓冲区,相当于以内存作为流操作源和目的,不会产生任何IO异常。对象中封装了数组。该对象就是用流的思想操作数组。

          ByteArrayInputStream:构造函数需要接收一个字节数组

          ByteArrayOutputStream:不用定义目的,因为该对象内部已经封装了可变长度的数组,这就是数据的目的地。它有一个特殊的方法writeTo(OutputStream os),将内容写入到指定的输出流中,这个方法用到了字节输出流,需要对异常进行操作。还有size()方法可以返回当前缓冲区的大小,toString()方法,通过解码字节将缓冲区内容转为字符串。如下:

		//定义数据源。
		ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".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());
                //写入指定文件
          	bos.writeTo(new FileOutputStream("a.txt"));
           操作字符数组的流对象:CharArrayReader,CharArrayWriter

           操作字符串的流对象:StringReader ,StringWriter


四、字符编码

       字符流的出现为了方便操作字符。而转换流加入了编码的转换,在两个对象对象构造时,可以加入字符集(即编码表)。

       可传入编码表的流对象:

      (1)转换流:InputStreamReader/OutputStreamWriter

      (2)打印流:PrintWriter/PrintStream

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

       常用的编码表:

         (1)ASCII 美国标准信息交换码表,用一个字节的7位来编码 

         (2)GBK:中国中文编码表,用打头的是两个高位为1的两个字节来编码。

         (3)UTF-8:国际标准码。用一到三个字节来编码。其具体格式为:

                          一个字节:0开头。

                          两个字节:字节一  110开头

                                           字节二  10开头

                          三个字节:字节一  1110开头

                                           字节二  10开头

                                           字节三  10开头

   转换流的编码应用:

          可以将字符以指定编码格式来存储,可以对文本数据指定解码格式来解读,指定编码的动作由转换流的构造函数完成。下面的程序就是应用到转换流,并指定编码表,来完成数据的读写:

class IODemo{
	public static void main(String[] args) throws IOException 
	{
			writeText();
			readText();
	}

	public static void readText()throws IOException 
	{   //建立转换读取流对象,关联文件,并指定UTF-8编码表来解码
		InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"UTF-8");
          
		char[] buf = new char[10];
		int len = isr.read(buf);

		String str = new String(buf,0,len);

		System.out.println(str);

		isr.close();
	}
	public static void writeText()throws IOException 
	{   //建立转换输出流对象,关联目的文件,并指定UTF-8编码表来编码。
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");

		osw.write("你好");

		osw.close();
	}

}
  在这个程序中,如果我们在读取时指定的字符集是GBK,和写入时指定的UTF-8不同,那么在读取时就会出现错误。原理如下图:


     编码和解码的方法:

      (1)编码:字符串变成字节数组

               默认字符集:byte[]   getBytes()   

               指定字符集:byte[]    getBytes(String charsetName)

      (2)解码:字节数组变成字符串

               默认字符集:String  new String(byte[])

               指定字符集:String  new String(byte[],String charsetName)

      对于编码和解码的字符集,转换时要注意:

      1.如果编码失败,解码就没意义了。

      2.如果编码成功,解码出来是乱码,则需二次编码(用解码的编码表编),然后再通过正确的编码表解码(主要针对ISO8859-1). 

如图:


      但是注意如果用的是GBK编码,UTF-8解码,此时通过再次编码后解码的方式,就不能成功了,因为UTF-8也支持中文,在UTF-8解码时,会将对应的字节改变,所以这种方法不会成功。

       还有个小特例:中文的"联通"二字,它的二进制位的开头正好和在UTF-8中两个字节的开头相同,所以在文本文件中,如果单独写“联通”或和满足UTF-8编码格式的字符一起保存时,记事本就会用UTF-8进行解码动作,会显示乱码。


 -------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值