------------android培训、java培训、期待与您交流! -------------
知识点总结
1、File类的由来、三种常见的构造方法。
文件也有自己对应的属性信息,比如所在目录、文件类型、大小、建立修改日期。Java通过面向对象的方法对其进行封装,方便对其进行操作。
File(String pathname)
File(File parent ,String child)
File(String parent,String child)
2、File的几种操作方法:创建、判断、删除、获取信息。
1,创建。
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立创建文件。而且文件已经存在,会覆盖。
boolean mkdir():创建文件夹。
boolean mkdirs():创建多级文件夹。
2,删除。
boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回falsel。
void deleteOnExit();在程序退出时删除指定文件。解决了在占用文件时删除不 了的问题。
3,判断。
boolean exists() :文件是否存在.非常常用。
isFile():【注意】:在判断文件对象是否是文件或者目录时,
亦一般要先通过exists先判断文件对象封装内容是否存在。
isDirectory();
isHidden();
isAbsolute();
4,获取信息。
getName():
getPath():
getParent()://只要没有明确指定父目录,返回为空;
否则返回文件的父目录(上级目录)
String getAbsolutePath();//返回绝对路径,字符串类型
File getAbosoluteFile();返回对象
long lastModified() //返回最近一次修改时间
long length() //返回该文件的长度
5.设置
public boolean renameTo(File dest)
重新命名此抽象路径名表示的文件。
可以将文件从一个目录转移到另一个目录。
但是,重命名操作无法将一个文件从一个文件系统移动到另一个文件系统,
该操作不是不可分的,如果已经存在具有目标抽象路径名的文件,那么该操作可 能无法获得成功。应该始终检查返回值,以确保重命名操作成功。
6、列出内容:public String[]list(); 只传入实现FileNameFilter接口的对象
列出内容:public File[]listFiles(),既可以传入FileNameFilter,又可以传FileFilter
FileNameFilter 中只有 booleanaccept(File dir, String name) 方法
测试指定文件是否应该包含在某一文件列表中。
FileFilter 中只有 booleanaccept(File pathname)
测试指定抽象路径名是否应该包含在某个路径名列表中。
- 示例代码:
- public class FileDemo2 {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- listDemo();
- listDemo1();
- listDemo_2();
- }
- public static void listDemo()
- {
- File f = new File("D:\\JAVA\\code");
- String[] names = f.list();//调用list方法的file对象必须是封装了一个目录。该目录还必须存在。
- for(String name : names)
- {
- System.out.println(name);
- }
- }
- public static void listDemo_2()
- {
- File dir = new File("d:\\java\\code");
- String[] arr = dir.list(new FilenameFilter()
- {
- public boolean accept(File dir,String name)
- {
- return name.endsWith(".java");
- }
- });
- System.out.println("len:"+arr.length);
- for(String name : arr)
- {
- System.out.println(name);
- }
- }
- public static void listDemo1(){
- File dir = new File("d:\\java\\code");
- File [] arr = dir.listFiles(new FilenameFilter(){
- public boolean accept(File dir,String name){
- return name.endsWith(".java");
- }
- });
- for(File f :arr){
- Print.sop(f);
- }
- }
- }
3、列出指定目录下文件或者文件夹,包含子目录中的内容。
也就是列出指定目录下所有内容。
因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。
在列出过程中出现的还是目录的话,还可以再次调用本功能。
也就是函数自身调用自身。
这种表现形式,或者编程手法,称为递归。
递归要注意:
1,限定条件。
2,要注意递归的次数。尽量避免内存溢出。
- public class ListFileDemo {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- File f = new File("d:"+File.separator+"java");
- getFile(f,0);
- }
- public static void getFile(File f,int level){
- if(!f.exists())//先判断文件或目录是否存在
- new IOException("找不到文件");
- if(f.isDirectory()){//判断是不是目录
- File[] files = f.listFiles();//是就获取目录下的文件
- for(File f1 : files){
- if(f1.isDirectory()){//在循环目录的过程中判断是否还是目录,是 //的话递归调用本方法
- level++;
- getFile(f1,level);
- }
- System.out.println(getLevel(level)+f1);//不是目录就打印
- }
- }
- System.out.println(getLevel(level)+f);//不是目录就打印
- }
- //获取层级目录,打印出层次效果,毕老师想的,牛!
- 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();
- }
- }
4、删除一个带内容的目录
- /*
- 删除一个带内容的目录。
- 删除原理:
- 在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);
- }
- }
5、打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
字节打印流:
PrintStream
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
字符打印流:
PrintWriter
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
4,字符输出流,Writer。
打印流有自己的PrintWriter.println();等方法。
把标准输入流写到文件中
- 示例代码:
- 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();
- }
- }
6、Properties是hashtable的子类。
也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。
是集合中和IO技术相结合的集合容器。
该对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值。
常用方法:
Object setProperty(String key,String value):
调用Hashtable的put方法,设置键值对
String getProperty(String key):
Set<String> stringPropertyNames:
获取集合中所有的键
void load(InputStream in):
从输入流中读取属性列表(键和元素对)。
void load(Reader reader):
按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
void list(PrintStream out)
将属性列表输出到指定的输出流。
voidlist(PrintWriter out)
将属性列表输出到指定的输出流。
Store(OutputStream out,String comments):
Store(Writer writer,String comments)
- 练习代码:
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileReader;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.util.Properties;
- public class PropertiesDemo1 {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- //getSystemInfoToFile();
- getPropertiseAndChange();
- }
- public static void getPropertiseAndChange(){//测试加载流方法load
- File sysInfo = new File("D:"+File.separator+"Systeminfo.txt");
- Properties prop = new Properties();
- PrintWriter pw= null;
- //prop.setProperty("userName", "caoRuiXiang");
- try {
- prop.load(new FileReader(sysInfo));
- prop.setProperty("userName", "caoRuiXiang");//添加一个键值对
- System.out.println(prop);
- pw= new PrintWriter(new File("d:"+File.separator+"sys.txt"));
- prop.store(pw, null);
- /*从上面的例子中可以看出,setProperty方法只是修改了内存中的值,而硬盘中的值并没有改变,
- * 所以Properties集合中海油一种方法,store,这种方法存到流中,进而存到硬盘中去,
- * */
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }finally{
- pw.close();
- }
- }
- public static void getSystemInfoToFile() { //测试list方法
- Properties prop = System.getProperties();
- PrintWriter pw = null;
- try {
- pw = New PrintWriter(newFile("D:\\Systeminfo.txt"));
- prop.list(pw);
- pw.flush();
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }finally{
- if(pw!=null)
- pw.close();
- }
- }
- }
7、练习:限制程序运行次数。当运行次数到达5次时,给出,请您注册的提示。并不再让该程序执行。
- /*用于记录应用程序运行次数。
- 如果使用次数已到,那么给出注册提示。
- 很容易想到的是:计数器。
- 可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。
- 可是随着该应用程序的退出,该计数器也在内存中消失了。
- 下一次在启动该程序,又重新开始从0计数。这样不是我们想要的。程序即使结束,该计数器的值也存在。下次程序启动在会先加载该计数器的值并加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);
- }
- }
8、
合并流
SequenceInputStream是能对多个流进行合并成一个读取流,它在构造时需要传入Enumeration,而这个只用Vector中有,所以这个多个读取流要加入Vector集合中。
注意:它只是对读取流进行合并。
它使用步骤:
1. 创建Vector<InputStream>
2. 将要合并的InputStream加入Vector
3. 通过Vector获取Enumeration
4. 创建SequenceInputStream,将Enumeration作为参数传入。
代码:
- public class SequenceDemo {
- /**
- * @param args
- * @throws Exception
- */
- public static void main(String[] args) throws Exception {
- // TODO Auto-generated method stub
- File file1 = new File("D:\\abc.txt");
- File file2 = new File("D:\\def.txt");
- File file3 = new File("d:\\java.txt");
- //将三个文件封装到vector集合中
- Vector<FileInputStream> vec = new Vector<FileInputStream>();
- vec.add(new FileInputStream(file1));
- vec.add(new FileInputStream(file2));
- vec.add(new FileInputStream(file3));
- //返回Enumeration
- Enumeration<FileInputStream> enu = vec.elements();
- SequenceInputStream seq = new SequenceInputStream(enu);
- PrintStream ps = new PrintStream("d:\\合并.txt");
- byte[] ch = new byte[1024];
- int len = 0;
- while((len = seq.read(ch))!= -1){
- ps.write(ch, 0, len);
- }
- seq.close();
- ps.close();
- }
- }
- public class SplitAndMergeFile {
- /**
- * @param args
- * @throws IOException
- */
- public static void main(String[] args) throws IOException {
- // TODO Auto-generated method stub
- // File pic = new File("d:\\pic.jpg");
- // splitFile(pic);
- mergeFile();
- }
- public static void mergeFile() throws IOException{
- //Vector效率低,可以改用ArrayList
- ArrayList<FileInputStream> arr = new ArrayList<FileInputStream>();
- for(int x = 1;x<6;x++){
- arr.add(new FileInputStream("d:\\pic"+(x++)+".part"));
- }
- //因为it要被Enumeration的匿名内部类对象使用,所以要加final
- final Iterator<FileInputStream> it = arr.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);
- byte [] buf = new byte[1024];
- int len = 0;
- PrintStream ps = new PrintStream("d:\\合并得到的.jpg");
- while((len = sis.read(buf))!=-1){
- ps.write(buf, 0, len);
- }
- sis.close();
- ps.close();
- }
- public static void splitFile(File fNeedS) throws IOException{ //切割文件
- FileInputStream fis = new FileInputStream(fNeedS);
- PrintStream ps = null;
- //指定切割后每个文件的大小或者缓冲区的大小
- byte[] buf = new byte[1024*512];
- int len = 0;
- int count = 1;
- while((len = fis.read(buf))!=-1){
- //创建每个分割文件。
- ps = new PrintStream("d:\\pic"+(count++)+".part");
- ps.write(buf, 0, len);
- ps.close();
- }
- fis.close();
- }
- }
10、
对象序列化
数据可以封装成对象,对象运行时是在堆内存中的,如果对象的数据需要存储在硬盘上,那么就要用到对象的序列化流。对象序列化(也叫对象的可串行性)其实就是对象持久化,把内存中的对象,变成硬盘上的文件内容。IO*对象序列化的流对象为ObjectInputStream和ObjectOutputStream。
注意:
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.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class ObjectStreamDemo {
- /**
- * @param args
- * @throws Exception
- */
- public static void main(String[] args) throws Exception {
- // TODO Auto-generated method stub
- // WriteObject();
- ReadObject();
- }
- public static void ReadObject() throws Exception{
- //通过ObjectInputStream读取序列化后的对象
- ObjectInputStream ois =
- new ObjectInputStream(new FileInputStream("D:\\caoRuiXiang.object"));
- Person cao = (Person) ois.readObject();
- System.out.println(cao.getInfo());
- }
- public static void WriteObject() throws IOException{
- //通过ObjectOutputStream将对象序列化
- ObjectOutputStream oop =
- new ObjectOutputStream(new FileOutputStream("D:\\caoRuiXiang.object"));
- oop.writeObject(new Person("曹睿翔",23,"hongkang"));//country为静态,不能序列化,所以,写入文件中的不是“HongKang”,而是CN
- oop.close();
- }
- }
- class Person implements Serializable{
- public static final long serialVersionUID = 12L;
- private String name;
- private int age; //age如果不想序列化,可以在前边加 transient 关键字,保证其值在堆内存中存在而不在文本文件中存在。
- static String county = "cn";
- public Person(String aName,int aAge,String cCounty){
- this.name = aName;
- this.age = aAge;
- this.county = cCounty;
- }
- public String getInfo(){
- return this.name + this.age + county;
- }
- }
- //Serializable serializable Serializable Serializable Serializable Serializable
- //serialVersionUID serialVersionUID serialversionUID
11、用于操作字节数组的流对象。
ByteArrayInputStream :在构造的时候,需要接收数据源,。而且数据源是一个字节数组。
ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。
这就是数据目的地。
注意:
1. 因为这两个流对象都操作的是数组,并没有使用系统资源,所以,不用进行close关闭,即使你关闭了,它的其他方法还可以使用,而不会抛出IOException。
2. 使用这对对象操作时,它的源和目的都是内存。
用途:
这两个对象是在用流的思想来操作数组,当我们需要把一个文件中的数据加入内存中的数组时,就可以考虑用这个两个对象。此外,它还有writeTo(OutputStream 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数组内容一次性写入字节输出流对象中。
- }
- }
12、管道流
管道流分为字节管道流(PipedInputStream和PipedOutputStream)和字符管道流(PipedReader和PipedWriter):它是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 la".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();
- in.connect(out);
- Read r = new Read(in);
- Write w = new Write(out);
- new Thread(r).start();
- new Thread(w).start();
- }
- }
13、RundomAccessFile(重点,即可读又可写)
此类的实例支持对随机访问文件的读取和写入。
该类不是算是IO体系中子类。
而是直接继承自Object。
但是它是IO包中成员。因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。
通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读r,,读写rw等。
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式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();
- }
- }
编码的练习及总结:
字符流的出现是为了方便操作字符数据,其方法操作的原因是因为内部加入了编码表。Java中能够实现字节根据指定编码表转成字符的,有四个类:InputStreamReader和OutputStreamWriter,PrintStream和PrintWriter。它们都能够加构造时,指定编码表;但后两个是打印流,只能用于打印,使用有局限,所以相对而言,还是前两个转换流使用多一些。
编码表的由来
计算机只能识别二进制数据,早期是电信号。为了应用计算机方便,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并将文字与二进制数字一一对应,形成了一张表,这个表就是编码表。
常见的编码表
地域码表
1. ASCII:美国码表,息交换码,用一个字节的7位表示。
2. ISO8859-1:欧洲码表,拉丁码表,用一个字节的8位表示,最高位1
3. GB2312:中国中文编码表,它用两个字节表示,为兼容ASCII,它的两个字节的高位都是1,也即是两个负数;但与ISO8859-1冲突。大概有六七千个字。
4. GBK:中国的中文编码表的升级版,扩容到2万多字。
通用码表:
1. Unicode:国际标准码,融合多种语言文字。所有的文字都用两个字节表示,Java默认使用的就是Unicode。
2. UTF-8:UnicodeTransform Format -8。Unicode码把用一个字节能装下的文字,也用两个字节表示,有些浪费空间,对之进行优化的结果就是UTF-8。UTF-8编码表,一个文字最用一个字节表示,最多用3个字节表示,并且每个字节开始都有标识头,所以很容易于其他编码表区分出来。
代码示例:
- 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();
- }
- }
编码问题的产生与解决
从上边的那些编码表可以看出,GBK和Unicode都能识别中文,那么当一台电脑使用GBK,而另一台电脑使用Unicode时,虽然在各自的电脑上都能识别中文,但他们其中一方向另一方发送中文文字时,另一方却不能识别,出现了乱码。这是一万年GBK和Unicode虽然都能识别中文,但对同一个中文文字,他们在两个编码表对应的编码值不同。这时,在解读别人传来的中文数据时,就需要指定解析中文使用的编码表了。
而转换流就能指定编码表,它的应用可以分为:
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解码时,得到的仍然是乱码,解码仍然失败。
- class EncodeDemo
- {
- //解决编码问题示例
- public static void main(String[] args) throws Exception
- {
- String s ="黑马训练营";
- byte[] b1= s.getBytes("GBK");
- 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);
- }
- }