@liayun
2016-06-20T15:25:43.000000Z
字数 23093
阅读 2179
java基础
File类的构造方法
创建File对象
第①种方法,将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");
打印时,new File()
封装的是什么,就打印什么,即以下语句
System.out.println("f1:"+f1);
System.out.println("f2:"+f2);
System.out.println("f3:"+f3);
会依次输出:
f1:a.txt
f2:c:\abc\b.txt
f3:c:\abc\c.txt
separator
字段
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。该字段是跨平台的,可这样创建File对象:
File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");
创建
boolean createNewFile()
:在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立就创建文件,而且文件已经存在,会覆盖。
File f = new File("file.txt");
System.out.println("create:"+f.createNewFile());
boolean mkdir()
:创建文件夹,但只能创建一级目录。
File dir = new File("abc");
System.out.println("mkdir:"+dir.mkdir());
boolean mkdirs()
:创建多级文件夹。
File dir = new File("abc\\kkk\\a\\a\\dd\\ee\\qq\\aaa");
System.out.println("mkdirs:"+dir.mkdirs());
删除
boolean delete()
:删除失败返回false。
File f = new File("file.txt");
System.out.println("delete:"+f.delete());
void deleteOnExit()
:在程序退出时删除指定文件,告诉JVM,一会退出时删除文件(一般是临时文件),即使发生异常。
File f = new File("file.txt");
f.deleteOnExit();
code(); // 省略一些代码,这些代码可能会抛出异常
System.out.println("delete:"+f.delete());
判断
boolean exists()
:文件是否存在。
File f = new File("file.txt");
System.out.println("exists:"+f.exists());
boolean canExecute()
:测试应用程序是否可以执行此抽象路径名表示的文件。
File f = new File("FileDemo.java");
System.out.println("execute:"+f.canExecute());
isDirectory()
:是否是一个目录。isFile()
:是否是一个标准文件。 exists()
判断。isAbsolute()
:是否为绝对路径,文件不存在也可以判断。
File f = new File("c:\\file.txt");
System.out.println(f.isAbsolute());
isHidden()
:是否是一个隐藏文件。获取信息
getName()
:获取文件或目录的名称。getPath()
:返回的就是File类构造函数里面的字符串,不管什么绝对或相对路径,你给我什么,我就返回什么。getParent()
:该方法返回的是绝对路径中的父目录,如果获取的是相对路径,返回null。如果相对路径中有上一层目录,那么该目录就是返回结果。getAbsolutePath()
:该方法法返回的是绝对路径。
File f = new File("abc\\file.txt");
System.out.println("path:"+f.getPath()); // abc\file.txt
System.out.println("abspath:"+f.getAbsolutePath()); // E:\MyJava\Java_Basic\day17\abc\file.txt
System.out.println("parent:"+f.getParent()); // abc
long lastModified()
:文件最后一次被修改的时间。long length()
:文件的长度。重命名
boolean renameTo(File dest)
:在同目录下就相当于重命名,在不同目录下就相当于剪切和复制。
File f1 = new File("c:\\1.jpg");
File f2 = new File("f:\\hahaha.jpg");
System.out.println("rename:"+f2.renameTo(f1));
static File[] listRoots()
:列出可用的文件系统根。
File[] files = File.listRoots();
for (File f : files) {
System.out.println(f);
}
会输出计算机可用盘符:
C:\
D:\
E:\
F:\
G:\
String[] list()
:返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。注意:调用list()方法的file对象必须是封装了一个目录。该目录还必须存在。
File f = new File("c:\\");
String[] names = f.list();
for(String name : names) {
System.out.println(name);
}
File[] listFiles()
:返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。如果目录为空,那么数组也将为空。如果此抽象路径名不表示一个目录,或者发生I/O错误,则返回null。
File dir = new File("c:\\");
File[] files = dir.listFiles();
for (File f : files) {
System.out.println(f.getName()+"::"+f.length());
}
String[] list(FilenameFilter filter)
:返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。用途:专门找指定目录下指定后缀名(例如:.java/.jpg/.mp3/.log)的文件
public static void listDemo_2() {
File dir = new File("E:\\MyJava\\Java_Basic\\day16");
String[] arr = dir.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
// System.out.println("dir:"+dir+".....name:"+name);
// 谁会这样写代码呢?显然很麻烦
/*
if(name.endsWith(".java"))
return true;
else
return false;
*/
// 简写
return name.endsWith(".java");
}
});
System.out.println("len:"+arr.length);
for (String name : arr) {
System.out.println(name);
}
}
例,列出指定目录下文件或者文件夹,包含子目录中的内容。也就是列出指定目录下的所有内容。
解:
分析:因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。这种表现形式或者编程手法,称为递归。
定义一个列出目录功能的递归函数:
public static void showDir(File dir) {
System.out.println(dir);
File[] files = dir.listFiles();
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory())
showDir(files[x]);
else
System.out.println(files[x]);
}
}
调用代码:
public static void main(String[] args) {
File dir = new File("C:\\myclass");
showDir(dir);
}
若要分层级列出指定目录下的所有内容,可这样做:
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(getLevel(level)+files[x]);
}
}
调用代码:
public static void main(String[] args) {
File dir = new File("C:\\myclass");
showDir(dir, 0);
}
递归要注意:
java.lang.StackOverflowError
)接下来要对递归有更深入的理解,举例说之:
例1. 十进制数转二进制数,代码如下:
public static void toBin(int num) {
if(num > 0) {
toBin(num / 2);
System.out.print(num % 2);
}
}
图解如下:
例2. 求和,代码如下:
public static int getSum(int n) {
if(n == 1)
return 1;
return n + getSum(n - 1);
}
图解如下:
思考:删除一个目录的过程是如何进行的?
解:
删除原理:在windows中,删除目录从里面往外删除的。既然是从里往外删除,就需要用到递归。
import java.io.*;
class RemoveDir {
public static void main(String[] args) {
File dir = new File("d:\\java");
removeDir(dir);
}
public static void removeDir(File dir) {
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文件的绝对路径,存到一个文本文件中,建立一个java文件列表清单。
解:
思路:
①②③步骤:对指定的目录进行递归,获取递归过程中所有的java文件的路径,将这些路径存储到集合中。
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 writeToFile(List<File> list, String javaListFile) throws IOException {
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(javaListFile));
for (File f : list) {
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
throw e;
} finally {
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
throw e;
}
}
}
测试代码:
public static void main(String[] args) throws IOException {
File dir = new File("E:\\MyJava\\Java_Basic");
List<File> list = new ArrayList<File>();
fileToList(dir, list);
File file = new File(dir, "javalist.txt");
writeToFile(list, file.toString());
}
Properties是HashTable的子类,也就是说它具备map集合的特点,而且他里面存储的键值对都是字符串,没有泛型定义。它是集合和IO技术相结合的集合容器。该对象的特点:可以用于键值对形式的配置文件。那么在加载数据时,需要数据有固定格式,通常是键=值。
设置和获取元素
setProperty(String key, String value)
:调用Hashtable的方法put,即设置元素。getProperty(String key)
:用指定的键在此属性列表中搜索属性,即获取元素。
Properties prop = new Properties();
prop.setProperty("zhangsan", "30");
prop.setProperty("lisi", "39");
System.out.println(prop);
String value = prop.getProperty("lisi");
System.out.println(value);
Set<String> stringPropertyNames()
:返回此属性列表中的键集。
Properties prop = new Properties();
prop.setProperty("zhangsan", "30");
prop.setProperty("lisi", "39");
// 修改属性值
prop.setProperty("lisi", 89+"");
Set<String> names = prop.stringPropertyNames();
for (String s : names) {
System.out.println(s+":"+prop.getProperty(s));
}
演示,如何将流中的数据存储到集合中。(也即是Properties类中load(InputStream in)
方法的原理)
解:
想要将info.txt
中的键值对数据存到集合中进行操作,必须按照以下步骤进行:
等号左边作为键,右边作为值,存入到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);
}
load()与store()
方法
load(InputStream inStream)
:从输入流中读取属性列表(键和元素对)。store(OutputStream out, String comments)
:以适合使用 load(InputStream)
方法加载到Properties表中的格式,将此Properties表中的属性列表(键和元素对)写入输出流。comments
为属性列表的描述。
public static void loadDemo() throws IOException {
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");
// 将流中的数据加载进集合
prop.load(fis);
prop.setProperty("wangwu", "39");
FileOutputStream fos = new FileOutputStream("info.txt");
prop.store(fos, "haha");
// 将此Properties表中的属性列表(键和元素对)输出到控制台
prop.list(System.out);
fos.close();
fis.close();
}
练习二:用于记录应用程序运行次数,如果使用次数已到,那么给出注册提示。
解:
分析:很容易想到的是计数器,可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增,可是随着该应用程序的退出,该计数器也在内存中消失了,下一次再启动该程序,又重新开始从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.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) {
System.out.println("您好,使用次数已到,拿钱!");
return;
}
}
count++;
prop.setProperty("time", count+"");
FileOutputStream fos = new FileOutputStream(file);
prop.store(fos, "");
fos.close();
fis.close();
}
}
当然配置文件还有另外一种格式保存,那就是XML
,格式如下:
<persons>
<person id="001">
<name>zhangsan</name>
<age>30</age>
<address>bj</address>
</person>
<person id="002">
<name>zhangsan1</name>
<age>31</age>
<address>bj</address>
</person>
</persons>
一个用来解析XML格式的文件的工具:dom4j
,即dom for(four的谐音) java。
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
构造函数可以接收的参数类型:
构造函数可以接收的参数类型:
字符输出流。(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); //不需要写刷新代码out.flush();会自动刷新。
String line = null;
while((line=bufr.readLine()) != null) {
if("over".equals(line))
break;
out.println(line.toUpperCase());
// out.flush();
}
out.close();
bufr.close();
}
}
SequenceInputStream
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
SequenceInputStream
有两个构造函数:
SequenceInputStream(Enumeration<? extends InputStream> e)
,通过记住参数来初始化新创建的SequenceInputStream,该参数必须是生成运行时类型为InputStream对象的Enumeration型参数。SequenceInputStream(InputStream s1, InputStream s2)
,通过记住这两个参数来初始化新创建的SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取s2),以提供从此SequenceInputStream读取的字节。例,合并文件,将3个文件中的数据合并到1个文件中。
解:
import java.io.*;
import java.util.*;
class SequenceDemo {
public static void main(String[] args) throws IOException {
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<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();
}
}
有合并文件,那么就有切割文件。
例,切割一张图片,再合成为一张完整的图片。
解:
先切割文件(一张图片):
public static void splitFile() throws IOException {
FileInputStream fis = new FileInputStream("c:\\DSC_0199.JPG");
FileOutputStream fos = null;
byte[] buf = new byte[1024*1024*2]; // 按照每2MB数据大小来切割图片
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();
}
再合并图片:
public static void merge() throws IOException {
// 使用Vector的效率很低下,所以使用List集合
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int x = 1; x <= 3; x++) {
al.add(new FileInputStream("c:\\splitFiles\\"+x+".part"));
}
// 匿名内部类访问局部变量,所以要用final修饰
final Iterator<FileInputStream> it = al.iterator();
/*
枚举,匿名内部类
虽然见过Enumeration,但是从来没这么写过,这是用ArrayList来实现一个Enumeration。
即把数据存到ArrayList集合里面去(Vector效率低),使用Enumeration取出。
*/
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();
}
Serializable
接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用transient
关键字进行声明。static
修饰的静态属性也不能被序列化,序列化的只是堆内存中对象的属性。ANY-ACCESS-MODIFIER(任意的访问修饰符) static final long serialVersionUID = 42L;
,语句的作用:给类定义一个固定标识,为了序列化方便,新的类还能操作曾经被序列化的对象。
要序列化的Person类:
import java.io.*;
class Person implements Serializable {
// 给类定义一个固定标识,为了序列化方便,新的类还能操作曾经被序列化的对象。
public static final long serialVersionUID = 42L;
private String name;
transient int age; // transient保证其值在堆内存中存在,不被序列化到文件。
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;
}
}
序列化一个Person对象,即将对象内容保存到文件中:
public static void writeObj() throws IOException {
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi0", 339, "kr"));
oos.close();
}
反序列化一个Person对象,即从文件中读取到一个对象:
public static void readObj() throws Exception {
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
管道流的主要作用是可以进行两个线程间的通信。反正管道流结合的是多线程技术,可以查看JDK帮助文档。
创建一个Read
的线程:
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("管道读取流失败");
}
}
}
创建一个Write
的线程:
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); // 使此管道输入流(in)连接到管道输出流(out)
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
getFilePointer
获取指针的位置,同时可以通过seek
改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件,而且操作文件还有模式:只读r,读写rw等。
向文件中写入数据:
public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
raf.close();
}
从文件中读取数据:
public static void readFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "r"); //只读模式,屏蔽掉写的动作
// raf.write("haha".getBytes());
// 调整对象中的指针
// raf.seek(8*1);
// 跳过指定的字节数,只能往下跳,不能往前跳。
raf.skipBytes(8); // 等同于raf.seek(8*1);
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("ran.txt", "rw"); // 和输出流不一样,不覆盖文件,直接在原文件写数据
raf.seek(8*0);
raf.write("周七".getBytes());
raf.writeInt(103);
raf.close();
}
图解原理:
可以用于操作基本数据类型的数据的流对象。
向文件中写入数据:
public static void writeData() throws IOException {
DataOutputStream dos =
new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9887.543);
dos.close();
}
从文件中读取数据:
public static void readData() 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="+num);
System.out.println("b="+b);
System.out.println("d="+d);
}
writeUTF(String str)
:以与机器无关方式使用UTF-8修改版编码将一个字符串写入基础输出流。
public static void writeUTFDemo() throws IOException {
DataOutputStream dos =
new DataOutputStream(new FileOutputStream("utfdata.txt"));
dos.writeUTF("你好");
dos.close();
}
使用writeUTF()
写,必须使用相应的readUTF()
方法来读:
public static void readUTFDemo() throws IOException {
DataInputStream dis =
new DataInputStream(new FileInputStream("utfdata.txt"));
String s = dis.readUTF();
System.out.println(s);
dis.close();
}
如果使用转换流OutputStreamWriter
写入数据到文件,如下:
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("utf.txt"), "UTF-8");
osw.write("你好");
osw.close();
如果还使用readUTF()
方法来读,那么会报java.io.EOFException
异常。
用于操作字节数组的流对象。
ByteArrayInputStream
:在构造的时候,需要接收数据源,而且数据源是一个字节数组。ByteArrayOutputStream
:在构造的时候,不用定义数据目的地,因为该数据对象已经内部封装了可变长度的字节数组。这就是数据目的地。
因为这两个流对象都操作的是数组,并没有使用系统资源。所以,不用进行close()关闭。
在流操作规律讲解时:
System.in
FileStream
(文件流)ArrayStream
System.out
FileStream
(文件流)ArrayStream
import java.io.*;
class ByteArrayStream {
public static void main(String[] args) {
// 数据源
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")); // 也可将数据源写入到一个文件中
}
}
结论:用流的读写思想来操作数组。
字符流的出现是为了方便操作字符,更重要是的加入了编码转换,通过两个转换流来完成,InputStreamReader
和OutputStreamWriter
,在两个对象进行构造的时候可以加入字符集。
可以将字符以指定编码格式存储,可以对文本数据指定编码格式来解读,指定编码表的动作由构造函数完成。
由UTF-8编码,自然要通过UTF-8来解码;同理,由GBK来编码,自然也要通过GBK来解码,否则会出现乱码。
先用UTF-8编码,再通过UTF-8来解码
public static void writeText() throws IOException {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("utf.txt"), "utf-8");
osw.write("你好");
osw.close();
}
public static void readText() throws IOException {
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();
}
会输出你好
。
先用UTF-8编码,再通过GBK来解码
public static void writeText() throws IOException {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("utf.txt"), "utf-8");
osw.write("你好");
osw.close();
}
public static void readText() throws IOException {
InputStreamReader isr =
new InputStreamReader(new FileInputStream("utf.txt"), "gbk");
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf, 0, len);
System.out.println(str);
isr.close();
}
会输出浣犲ソ
。
图解原理:
先用GBK编码,再通过GBK来解码
public static void writeText() throws IOException {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk");
osw.write("你好");
osw.close();
}
public static void readText() throws IOException {
InputStreamReader isr =
new InputStreamReader(new FileInputStream("gbk.txt"), "gbk");
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf, 0, len);
System.out.println(str);
isr.close();
}
会输出你好
。
先用GBK编码,再通过UTF-8来解码
public static void writeText() throws IOException {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk");
osw.write("你好");
osw.close();
}
public static void readText() throws IOException {
InputStreamReader isr =
new InputStreamReader(new FileInputStream("gbk.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();
}
会输出??
。
图解原理:
计算机只能识别二进制数据,早期由来是电信号来表示,为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。
ASCII
:美国标准信息交换码,用一个字节的7位可以表示。ISO8859-1
:拉丁码表或欧洲码表,用一个字节的8位表示。GB2312
:中国的中文编码表。GBK
:中国的中文编码表升级,融合了更多的中文文字符号。Unicode
:国际标准码,融合了多种文字,所有文字都用两个字节来表示,Java语言使用的就是unicode。UTF-8
:最多用三个字节来表示一个字符。str.getBytes(charsetName);
解码:字节数组变成字符串(byte[]--->String)。方法为new String(byte[], charsetName);
同理,由UTF-8编码,自然要通过UTF-8来解码;同理,由GBK来编码,自然也要通过GBK来解码,否则会出现乱码。
String s = "你好";
byte[] b1 = s.getBytes("GBK"); // 默认编码方式是GBK
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "GBK"); // 通过GBK来解码
System.out.println("s1="+s1);
会输出你好
。
String s = "你好";
byte[] b1 = s.getBytes("GBK"); // 默认编码方式是GBK
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
System.out.println("s1="+s1);
会输出乱码???
。怎么不是像上面那样输出??
两个问号呢?
String s = "你好";
byte[] b1 = s.getBytes("UTF-8"); // 编码方式是UTF-8
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
System.out.println("s1="+s1);
会输出你好
。
String s = "你好";
byte[] b1 = s.getBytes("UTF-8"); // 编码方式是UTF-8
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "GBK"); // 通过GBK来解码
System.out.println("s1="+s1);
会输出浣犲ソ
。
有一种特殊的情况:
先对字符串你好
进行GBK编码,再通过ISO8859-1解码:
String s = "你好";
byte[] b1 = s.getBytes("GBK"); // 默认编码方式是GBK
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "ISO8859-1"); // 通过ISO8859-1来解码
System.out.println("s1="+s1);
此时会发现输出乱码????
。如果要输出正确的字符串内容,可对s1
进行进行ISO8859-1编码,然后再通过GBK解码。
// 对s1进行iso8859-1编码
byte[] b2 = s1.getBytes("ISO8859-1");
System.out.println(Arrays.toString(b2));
String s2 = new String(b2, "GBK");
System.out.println("s2="+s2);
图解原理:
思考:先对字符串你好
进行GBK编码,再通过UTF-8解码:
String s = "你好";
byte[] b1 = s.getBytes("GBK"); // 默认编码方式是GBK
System.out.println(Arrays.toString(b1));
String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
System.out.println("s1="+s1);
此时也会输出乱码???
,如果要输出正确的字符串内容,可不可以对s1
进行进行一次UTF-8编码,然后再通过GBK解码呢?答案是不可以。
// 对s1进行UTF-8编码
byte[] b2 = s1.getBytes("UTF-8");
System.out.println(Arrays.toString(b2));
String s2 = new String(b2, "GBK");
System.out.println("s2="+s2);
此时依然输出乱码锟斤拷锟?
图解原理:
一个非常特殊的例子:新建一个文本文档,输入"联通"二字,保存并退出,再次打开此文本文档时,会显示乱码!!!。
String s = "联通";
byte[] by = s.getBytes("gbk");
for (byte b : by) {
System.out.println(Integer.toBinaryString(b&255));
}
会输出"联通"二字的二进制表现形式:
11000001
10101010
11001101
10101000
图示原理:
有五个学生,每个学生有3门课的成绩,从键盘输入以上数据(包括姓名,三门可成绩),输入的格式:如:zhangsan,30,40,60,计算出总成绩,并把学生的信息和计算出的总分数按高低顺序存放在磁盘文件"stud.txt"中。
解:
分析:
思路:
描述一个学生类,因为学生对象要存储到TreeSet集合中,所以要实现Comparable
接口,学生对象也有可能存储到HashSet集合中,所以最好覆写hashCode
和equals
方法。
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;
}
public int compareTo(Student s) {
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num == 0)
return this.name.compareTo(s.name);
return num;
}
public String getName() {
return name;
}
public int getSum() {
return sum;
}
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;
}
public String toString() {
return "student["+name+", "+ma+", "+cn+", "+en+"]";
}
}
定义一个可以操作学生对象的工具类。一个函数功能是通过获取键盘录入的一行数据,并将该行中的信息取出封装成学生对象,存储进集合。一个函数功能是将集合的信息写入到一个文件中。
class StudentInfoTool {
// 使用默认排序
public static Set<Student> getStudents() throws IOException {
return getStudents(null);
}
// 使用传入的比较器排序
public static Set<Student> getStudents(Comparator<Student> cmp) throws IOException {
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
TreeSet<Student> stus = null;
if(cmp == null)
stus = new TreeSet<Student>(); // 使用默认排序
else
stus = new TreeSet<Student>(cmp); // 使用传入的比较器排序
while((line=bufr.readLine()) != null) {
if("over".equals(line))
break;
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");
bufw.write(stu.getSum()+""); // 注意有可能写出的是一个整数的最后8位
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}
测试代码:
class StudentInfoTest {
public static void main(String[] args) throws IOException {
Comparator<Student> cmp = Collections.reverseOrder();
Set<Student> stus = StudentInfoTool.getStudents(cmp);
StudentInfoTool.write2File(stus);
}
}