您的位置:首页 > 职场人生

黑马程序员——io流2(其它流)

2014-09-03 20:59 225 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1. 其他流

1.1. 序列流

也称为合并流。

1.1.1. SequenceInputStream

序列流,对多个流进行合并。

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

注意:

构造函数

SequenceInputStream(InputStream s1, InputStream s2)

SequenceInputStream(InputStream s1, InputStream s2)

合并两个流

使用构造函数SequenceInputStream(InputStream s1, InputStream s2)

privatestaticvoid testSequenceInputStream()
throws IOException {
FileInputStream fis1 =
new
FileInputStream("c:\\a.txt");
FileInputStream fis2 =
new
FileInputStream("c:\\b.txt");

SequenceInputStream s1 =
new
SequenceInputStream(fis1, fis2);
int len = 0;
byte[] byt =
newbyte[1024];

FileOutputStream fos =
new
FileOutputStream("c:\\z.txt");

while ((len = s1.read(byt)) != -1) {
fos.write(byt, 0, len);
}
s1.close();
}

合并多个流:

publicstaticvoid testSequenceInputStream()
throws Exception {
InputStream in1 =
new
FileInputStream("c:/a.txt");
InputStream in2 =
new
FileInputStream("c:/b.txt");
InputStream in3 =
new
FileInputStream("c:/c.txt");

LinkedHashSet<InputStream> set =
new LinkedHashSet<InputStream>();
set.add(in1);
set.add(in2);
set.add(in3);
final Iterator<InputStream> iter = set.iterator();

SequenceInputStream sin =
new SequenceInputStream(
new Enumeration<InputStream>() {
@Override
publicboolean hasMoreElements() {
return iter.hasNext();
}

@Override
public InputStream nextElement() {
return iter.next();
}
});

FileOutputStream out =
new
FileOutputStream("c:/z.txt");

for (int b = -1; (b = sin.read()) != -1;)
{
out.write(b);
}
sin.close();
out.close();
}

案例:将map3歌曲文件进行切割拷贝,并合并.

publicclass Demo2 {
publicstaticvoid main(String[] args)
throws IOException {

split(new File("c:\\a.mp3"), 10,
new File("c:\\"));
System.out.println("切割完毕");

LinkedHashSet<InputStream> hs =
new LinkedHashSet<InputStream>();

hs.add(new FileInputStream(new File("c:\\part.1.mp3")));
hs.add(new FileInputStream(new File("c:\\part.2.mp3")));
hs.add(new FileInputStream(new File("c:\\part.3.mp3")));
hs.add(new FileInputStream(new File("c:\\part.4.mp3")));
merage(hs,
new
File("c:\\merage.mp3"));
System.out.println("合并完毕");
}

privatestaticvoid merage(LinkedHashSet<InputStream> hs,
File dest)
throws IOException {

final Iterator<InputStream> it = hs.iterator();
FileOutputStream fos =
new
FileOutputStream(dest);
SequenceInputStream seq =
new SequenceInputStream(
new Enumeration<InputStream>() {

@Override
publicboolean hasMoreElements() {

return it.hasNext();
}

@Override
public InputStream nextElement() {
return it.next();
}
});
byte[] byt =
newbyte[1024 * 1024];
int len = 0;
while ((len = seq.read(byt)) != -1) {
fos.write(byt, 0, len);
}
seq.close();
fos.close();
}

// 1.
切割文件
/*
*
切割文件,切割份数,
切割后保存路径
*/
privatestaticvoid split(File src,
int count, File dir)
throws IOException {
FileInputStream fis =
new
FileInputStream(src);
FileOutputStream fos =
null
;
byte[] byt =
newbyte[1024 * 1024];
int len = 0;
for (int i = 1; i <= count; i++) {
len = fis.read(byt);
if (len != -1) {
fos = new FileOutputStream(dir +
"part." + i +
".mp3");
fos.write(byt, 0, len);
}

// fos.close();

}
fis.close();

}
}

1.2. 对象的序列化

当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同。可以使用对象的序列化。

对象的序列化: 将内存中的对象直接写入到文件设备中

对象的反序列化: 将文件设备中持久化的数据转换为内存对象

基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。

ObjectOutput

writeObject(Object obj)

将对象写入底层存储或流。

ObjectInput

readObject()

读取并返回对象。

1.2.1. ObjectOutputStream

1.2.2. ObjectInputStream

由于上述ObjectOutput和ObjectInput是接口,所以需要使用具体实现类。

ObjectOutput

ObjectOutputStream被写入的对象必须实现一个接口:Serializable

否则会抛出:NotSerializableException

ObjectInput

ObjectInputStream 该方法抛出异常:ClassNotFountException

ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。

案例:

序列化和反序列化Cat对象。

publicclass Demo3 {
publicstaticvoid main(String[] args)
throws IOException,
ClassNotFoundException {
Cat cat = new Cat("tom", 3);
FileOutputStream fos =
new
FileOutputStream(new File("c:\\Cat.txt"));
ObjectOutputStream oos =
new
ObjectOutputStream(fos);
oos.writeObject(cat);
System.out.println(cat);
oos.close();
//
反序列化
FileInputStream fis =
new
FileInputStream(new File("c:\\Cat.txt"));
ObjectInputStream ois =
new
ObjectInputStream(fis);
Object readObject = ois.readObject();
Cat cat2 = (Cat) readObject;
System.out.println(cat2);
fis.close();
}

class
Cat implements Serializable {
public String
name;
publicintage;

public Cat() {

}

public Cat(String name,
int age) {

this.name = name;
this.age = age;
}

@Override
public String toString() {
return"Cat [name=" +
name +
", age=" + age +
"]";
}

}

例子关键点:

1. 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。

2. 新建Cat对象。

3. 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。

4. 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。

5. writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。

6. 反序列化就是调用ObjectInputStream的readObject()方法。

7. 异常处理和流的关闭动作要执行。

1.2.3. Serializable:

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

所以需要被序列化的类必须是实现Serializable接口,该接口中没有描述任何的属性和方法,称之为标记接口。

如果对象没有实现接口Serializable,在进行序列化时会抛出:NotSerializableException 异常。

注意:

保存一个对象的真正含义是什么?如果对象的实例变量都是基本数据类型,那么就非常简单。但是如果实例变量是包含对象的引用,会怎么样?保存的会是什么?很显然在Java中保存引用变量的实际值没有任何意义,因为Java引用的值是通过JVM的单一实例的上下文中才有意义。通过序列化后,尝试在JVM的另一个实例中恢复对象,是没有用处的。

如下:

首先建立一个Dog对象,也建立了一个Collar对象。Dog中包含了一个Collar(项圈)

现在想要保存Dog对象,但是Dog中有一个Collar,意味着保存Dog时也应该保存Collar。假如Collar也包含了其他对象的引用,那么会发生什么?意味着保存一个Dog对象需要清楚的知道Dog对象的内部结构。会是一件很麻烦的事情。

Java的序列化机制可以解决该类问题,当序列化一个对象时,Java的序列化机制会负责保存对象的所有关联的对象(就是对象图),反序列化时,也会恢复所有的相关内容。本例中:如果序列化Dog会自动序列化Collar。但是,只有实现了Serializable接口的类才可以序列化。如果只是Dog实现了该接口,而Collar没有实现该接口。会发生什么?

Dog类和Collar类

import java.io.Serializable;

publicclass
Dog implements Serializable {
private Collar
collar;
private String
name;

public Dog(Collar collar, String name) {

this.collar = collar;
this.name = name;
}

public Collar getCollar() {
returncollar;
}

}

class Collar {
privateintsize;

publicint getSize() {
returnsize;
}

public Collar(int size) {
this.size = size;
}

}

序列化

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

publicclass Demo4 {
publicstaticvoid main(String[] args)
throws IOException {
Collar coll = new Collar(10);
Dog dog = new Dog(coll,
"旺财");

FileOutputStream fis =
new
FileOutputStream(new File("c:\\dog.txt"));
ObjectOutputStream os =
new
ObjectOutputStream(fis);
os.writeObject(dog);
}
}

执行程序,出现了运行时异常。

Exception in thread "main" java.io.NotSerializableException: Collar

所以我们也必须将Dog中使用的Collar序列化。但是如果我们无法访问Collar的源代码,或者无法使Collar可序列化,如何处理?

两种解决方法:

一:继承Collar类,使子类可序列化

但是:如果Collar是final类,就无法继承了。并且,如果Collar引用了其他非序列化对象,也无法解决该问题。

transient

此时就可以使用transient修饰符,可以将Dog类中的成员变量标识为transient

那么在序列化Dog对象时,序列化就会跳过Collar。

publicclass Demo4 {
publicstaticvoid main(String[] args)
throws IOException,
ClassNotFoundException {
Collar coll = new Collar(10);
Dog dog = new Dog(coll,
"旺财");
System.out.println(dog.getCollar().getSize());

FileOutputStream fis =
new
FileOutputStream(new File("c:\\dog.txt"));
ObjectOutputStream os =
new
ObjectOutputStream(fis);
os.writeObject(dog);

//
反序列化
FileInputStream fos =
new
FileInputStream(new File("c:\\dog.txt"));
ObjectInputStream ois =
new
ObjectInputStream(fos);
Object readObject = ois.readObject();
Dog dog2 = (Dog) readObject;
// Collar未序列化。
dog2.getCollar().getSize();
}
}

这样我们具有一个序列化的Dog和非序列化的Collar。

此时反序列化Dog后,访问Collar,就会出现运行时异常

10

Exception in thread "main" java.lang.NullPointerException

注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。

基本数据类型可以被序列化

publicclass Demo5 {
publicstaticvoid main(String[] args)
throws IOException {
//
创建序列化流对象
FileOutputStream fis =
new
FileOutputStream(new File("c:\\basic.txt"));
ObjectOutputStream os =
new
ObjectOutputStream(fis);
//
序列化基本数据类型
os.writeDouble(3.14);
os.writeBoolean(true);
os.writeInt(100);
os.writeInt(200);

//
关闭流
os.close();

//
反序列化
FileInputStream fos =
new
FileInputStream(new File("c:\\basic.txt"));
ObjectInputStream ois =
new
ObjectInputStream(fos);

System.out.println(ois.readDouble());
System.out.println(ois.readBoolean());
System.out.println(ois.readInt());
System.out.println(ois.readInt());

fos.close();
}
}

serialVersionUID

用于给类指定一个UID。该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。

只要是这些成员没有变化,那么该值每次运算都一样。

该值用于判断被序列化的对象和类文件是否兼容。

如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。

定义方式:static final long serialVersionUID = 42L;

1.3. Properties.

可以和流相关联的集合对象Properties.

Map

|--Hashtable

|--Properties

Properties:该集合不需要泛型,因为该集合中的键值对都是String类型。

1,存入键值对:setProperty(key,value);

2,获取指定键对应的值:value getProperty(key);

3,获取集合中所有键元素:

Enumeration propertyNames();

在jdk1.6版本给该类提供一个新的方法。

Set<String> stringPropertyNames();

4,列出该集合中的所有键值对,可以通过参数打印流指定列出到的目的地。

list(PrintStream);

list(PrintWriter);

例:list(System.out):将集合中的键值对打印到控制台。

list(new PrintStream("prop.txt")):将集合中的键值对存储到prop.txt文件中。

5,可以将流中的规则数据加载进行集合,并称为键值对。

load(InputStream):

jdk1.6版本。提供了新的方法。

load(Reader):

注意:流中的数据要是"键=值" 的规则数据。

6,可以将集合中的数据进行指定目的的存储。

store(OutputStram,String comment)方法。

jdk1.6版本。提供了新的方法。

store(Writer ,String comment):

使用该方法存储时,会带着当时存储的时间。

注意:

Properties只加载key=value这样的键值对,与文件名无关,注释使用#

练习:记录一个程序运行的次数,当满足指定次数时,该程序就不可以再继续运行了。

通常可用于软件使用次数的限定。

publicstaticvoid sysPropList()
throws IOException {
Properties prop = System.getProperties();

// prop.list(System.out);//
目的是控制台。
//
需求是:将jvm的属性信息存储到一个文件中。
prop.list(new PrintStream("java.txt"));
}

publicstaticvoid sysProp() {
Properties prop = System.getProperties();

Set<String> keys = prop.stringPropertyNames();

for (String key : keys) {
System.out.println(key +
":" + prop.getProperty(key));
}
}

Properties类与配置文件

Map

|--Hashtable

|--Properties

注意:是一个Map集合,该集合中的键值对都是字符串。该集合通常用于对键值对形式的配置文件进行操作.

配置文件:将软件中可变的部分数据可以定义到一个文件中,方便以后更改,该文件称之为配置文件。

优势: 提高代码的维护性。

Properties: 该类是一个Map的子类,提供了可以快速操作配置文件的方法

load() : 将文件设备数据装载为Map集合数据

get(key): 获取Map中的数据

getProperty()获取Map中的数据特有方法

案例:

/*
*
将配置文件中的数据通过流加载到集合中。
*/
publicstaticvoid loadFile()
throws IOException {

// 1,创建Properties(Map)对象
Properties prop =
new
Properties();

// 2.使用流加载配置文件。
FileInputStream fis =
new
FileInputStream("c:\\qq.txt");

// 3。使用Properties
对象的load方法将流中数据加载到集合中。
prop.load(fis);

//
遍历该集合
Set<Entry<Object, Object>> entrySet = prop.entrySet();
Iterator<Entry<Object, Object>> it = entrySet.iterator();
while (it.hasNext()) {
Entry<Object, Object> next = it.next();
Object key = next.getKey();
Object value = next.getValue();
}
//
通过键获取指定的值
Object object = prop.get("jack");
System.out.println(object);

//
通过键修改值
prop.setProperty("jack",
"888888");

//
将集合中的数据写入到配置文件中。
FileOutputStream fos =
new
FileOutputStream("c:\\qq.txt");

//
注释:
prop.store(fos,
"yes,qq");

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

}

获取记录程序运行次数:

publicclass Demo6 {
publicstaticvoid main(String[] args)
throws IOException {
int count = 0;
Properties pro =
new
Properties();

File file = new File("c:\\count.ini");
FileInputStream fis =
null
;
if (!file.exists()) {
file.createNewFile();
}
fis = new FileInputStream(file);
pro.load(fis);
String str = pro.getProperty("count");
if (str !=
null) {
count = Integer.parseInt(str);
}
if (count == 3) {
System.out.println("使用次数已到,请付费");
System.exit(0);
}

count++;
System.out.println("欢迎使用本软件"
+ "你已经使用了:" + count +
" 次");

pro.setProperty("count", count +
"");
FileOutputStream fos =
new
FileOutputStream(new File("c:\\count.ini"));
pro.store(fos,
"请保护知识产权");

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

}
}

1.4. 打印流

PrintStream可以接受文件和其他字节输出流,所以打印流是对普通字节输出流的增强,其中定义了很多的重载的print()和println(),方便输出各种类型的数据。

1.4.1. PrintStream

PrintWriter

1,打印流。

PrintStream:

是一个字节打印流,System.out对应的类型就是PrintStream。

它的构造函数可以接收三种数据类型的值。

1,字符串路径。

2,File对象。

3,OutputStream。

publicstaticvoid main(String[] args)
throws IOException {
PrintStream ps = System.out;

//
普通write方法需要调用flush或者close方法才会在控制台显示
// ps.write(100);
// ps.close();

//
不换行打印
ps.print(100);
ps.print('a');
ps.print(100.5);
ps.print("世界");
ps.print(new Object());
System.out.println("--------------");
//
换行
ps.println(100);
ps.println('a');
ps.println(100.5);
ps.println("世界");
ps.println(new Object());

//
重定向打印流
PrintStream ps2 =
new
PrintStream(new File("c:\\a.txt"));
System.setOut(ps2);
//
换行
ps2.println(100);
ps2.println('a');
ps2.println(100.5);
ps2.println("世界");
ps2.println(new Object());

//
printf(); 格式化
ps2.printf("%d,%f,%c,%s", 100, 3.14,
'中',
"世界你好!!!");
ps2.printf("%4s和%8s
打价格战",
"京东",
"苏宁");

} }

注意: 打印流的三种方法

void print(数据类型变量)

println(数据类型 变量)

printf(String format, Object... args)

可以自定数据格式

print 和println方法的区别在于,一个换行一个不换行

print 方法和write方法的却别在于,print提供自动刷新.

普通的write方法需要调用flush或者close方法才可以看到数据.

JDK1.5之后Java对PrintStream进行了扩展,增加了格式化输出方式,可以使用printf()重载方法直接格式化输出。但是在格式化输出的时候需要指定输出的数据类型格式。

1.4.2. PrintWriter

是一个字符打印流。构造函数可以接收四种类型的值。

1,字符串路径。

2,File对象。

对于1,2类型的数据,还可以指定编码表。也就是字符集。

3,OutputStream

4,Writer

对于3,4类型的数据,可以指定自动刷新。

注意:该自动刷新值为true时,只有三个方法可以用:println,printf,format.

如果想要既有自动刷新,又可执行编码。如何完成流对象的包装?

PrintWrter pw =

new PrintWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);

如果想要提高效率。还要使用打印方法。

PrintWrter pw =

newPrintWriter(new BufferdWriter(new OutputSteamWriter(

newFileOutputStream("a.txt"),"utf-8")),true);

publicstaticvoid testPrintWriter()
throws Exception {
PrintWriter pw =
new
PrintWriter("c:/b.txt",
"gbk");

// pw.append("xxx");
// pw.println(55);
// pw.println('c');
// pw.printf("%.1s与%4s打价格战, %c",
"京东","苏宁", 'a');

pw.close();

}

Scanner

publicstaticvoid testScanner()
throws Exception {
// Scanner scanner = new Scanner(new File("c:/test.txt"));
Scanner scanner =
new
Scanner(System.in);

System.out.println(scanner.nextInt());
System.out.println(scanner.nextBoolean());

scanner.close();
}

1.5. 操作数组的流对象

1.5.1. 操作字节数组

ByteArrayInputStream

以及ByteArrayOutputStream

toByteArray();
toString();
writeTo(OutputStream);
publicstaticvoid testByteArrayInputStream()
throws Exception {
InputStream in =
new
ByteArrayInputStream(newbyte[] { 65, 66, 67 });
ByteArrayOutputStream out =
new ByteArrayOutputStream();

for (int b = -1; (b = in.read()) != -1;)
{
out.write(b);
}

in.close();
out.close();

System.out.println(Arrays.toString(out.toByteArray()));
System.out.println(out);
}

1.5.2. 操作字符数组

CharArrayReader
CharArrayWriter
对于这些流,源是内存。目的也是内存。
而且这些流并未调用系统资源。使用的就是内存中的数组。
所以这些在使用的时候不需要close。
操作数组的读取流在构造时,必须要明确一个数据源。所以要传入相对应的数组。
对于操作数组的写入流,在构造函数可以使用空参数。因为它内置了一个可变长度数组作为缓冲区。
publicstaticvoid testCharArrayReader()
throws Exception {
CharArrayReader reader =
new
CharArrayReader(newchar[] {
'A', 'b',
'c' });
CharArrayWriter writer =
new
CharArrayWriter();

for (int b = -1; (b = reader.read()) !=
-1;) {
writer.write(b);
}

reader.close();
writer.close();

System.out.println(writer.toCharArray());
}

这几个流的出现其实就是通过流的读写思想在操作数组。
类似的对象同理:

StringReader

StringWriter。

publicstaticvoid testStringReader()
throws Exception {
StringReader reader =
new
StringReader("test
中国");
StringWriter writer =
new
StringWriter();

for (int b = -1; (b = reader.read()) !=
-1;) {
writer.write(b);
}

reader.close();
writer.close();

System.out.println(writer.toString());
}

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

1.6.1. DataInputStream

以及DataOutputStream

查看API文档DataInputStream的信息。发现从底层输入流中读取基本 Java 数据类型。查看方法,有读一个字节,读一个char读一个double 的方法,

DataInputStream 从数据流读取字节,并将它们转换为正确的基本数据类型值或字符串。

该流有操作基本数据类型的方法.

有读的,那么必定有对应的写的就是DataOutputStream 将基本类型的值或字符串转换为字节,并且将字节输出到数据流。

DataInputStream类继承FilterInputStream类,并实现了DataInput接口。DataOutputStream

类继承FilterOutputStream 并实现了DataOutput 接口。

例如:

DataInputStream
操作基本数据类型的方法:
int readInt():一次读取四个字节,并将其转成int值。
boolean readBoolean():一次读取一个字节。
short readShort();
long readLong();
剩下的数据类型一样。
String readUTF():按照utf-8修改版读取字符。注意,它只能读writeUTF()写入的字符数据。

DataOutputStream

DataOutputStream(OutputStream):

操作基本数据类型的方法:

writeInt(int):一次写入四个字节。

注意和write(int)不同。write(int)只将该整数的最低一个8位写入。剩余三个8位丢弃。

writeBoolean(boolean);

writeShort(short);

writeLong(long);

剩下是数据类型也也一样。

writeUTF(String):按照utf-8修改版将字符数据进行存储。只能通过readUTF读取。

测试: DataOutputStream

使用DataOutputStream写数据文件。

publicstaticvoid testDataInputStream()
throws Exception {
DataOutputStream out =
new
DataOutputStream(new FileOutputStream(
"c:/a.txt"));

out.writeBoolean(true);
out.writeByte(15);
// 0x05 1 个字节
out.writeBytes("abc");
// 0x 0041 2个字节
out.writeChar('X');
// ??
out.writeChars("xyz");
out.writeLong(111);
out.writeUTF("中国");

out.close();

DataInputStream in =
new
DataInputStream(
new FileInputStream("c:/a.txt"));
System.out.println(in.readBoolean());
System.out.println(in.readByte());

System.out.println(in.readByte());
System.out.println(in.readByte());
System.out.println(in.readByte());

System.out.println(in.readChar());

System.out.println(in.readChar());
System.out.println(in.readChar());
System.out.println(in.readChar());

System.out.println(in.readLong());

System.out.println(in.readUTF());
in.close();
}

2. 编码

什么是编码?

计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,a 1 等字符

计算机中是没有存储字符的,但是我们却看到了。计算机在存储这些信息的时候,根据一个有规则的编号,当用户输入a 有a对映的编号,就将这个编号存进计算机中这就是编码。

计算机只能识别二进制数据。

为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

例如:汉字 中

有一种编码:

中字在utf 8中对映的编码

utf-8 -->100

在gbk中呢?有可能就不是100了

gbk --> 150

很显然同一个信息在不同的编码中对映的数字也不同,

不同的国家和地区使用的码表是不同的,

gbk 是中国大陆

bjg5 是台湾同胞中的繁体字。所以如果给big5一个简体字是不认识的。

还有ASCII 美国标准信息交换码

2.1. 码表

常见的码表如下:

ASCII: 美国标准信息交换码。用一个字节的7位可以表示。

ISO8859-1: 拉丁码表。欧洲码表,用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,

藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。

GB2312: 中国的中文编码表。

GBK: 中国的中文编码表升级,融合了更多的中文文字符号。

Unicode: 国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。

UTF-8: 最多用三个字节来表示一个字符。

(我们以后接触最多的是iso8859-1、gbk、utf-8)

查看上述码表后,很显然中文的‘中’在iso8859-1中是没有对映的编码的。或者一个字符在2中码表中对应的编码不同,例如有一些字在不同的编码中是有交集的,例如bjg5 和gbk 中的汉字简体和繁体可能是一样的,就是有交集,但是在各自码表中的数字不一样。

例如

使用gbk 将中文保存在计算机中,

中 国

对映 100 200 如果使用big5 打开

可能 ? ...

不同的编码对映的是不一样的。

很显然,我们使用什么样的编码写数据,就需要使用什么样的编码来对数据。

ISO8859-1:一个字节

GBK: 两个字节包含了英文字符和扩展的中文 ISO8859-1+中文字符

UTF-8 万国码,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间。

2.2. 编码:

字符串---》字节数组

String类的getBytes() 方法进行编码,将字符串,转为对映的二进制,并且这个方法可以指定编码表。如果没有指定码表,该方法会使用操作系统默认码表。

注意:中国大陆的Windows系统上默认的编码一般为GBK。在Java程序中可以使用System.getProperty("file.encoding")方式得到当前的默认编码。

2.3. 解码:

字节数组---》字符串

String类的构造函数完成。

String(byte[] bytes) 使用系统默认码表

String(byte[],charset)指定码表

注意:我们使用什么字符集(码表)进行编码,就应该使用什么字符集进行解码,否则很有可能出现乱码(兼容字符集不会)。

//
编码操作与解码操作。
publicstaticvoid main(String[] args)
throws Exception {
String value = System.getProperty("file.encoding");
System.out.println("系统默认的编码为
" + value);

String str =
"中";

//
编码操作
byte[] bytes = str.getBytes();
byte[] bytes2 = str.getBytes("gbk");//
d6d0
byte[] bytes3 = str.getBytes("utf-8");//
e4b8ad

System.out.println(Arrays.toString(bytes));
// [-42, -48]
System.out.println(Arrays.toString(bytes2));// [-42, -48]
System.out.println(Arrays.toString(bytes3));// [-28, -72, -83]

//
解码操作
//
编码gbk,解码utf-8乱码。
String str2 = new String(bytes2,
"utf-8");
System.out.println(str2);

//
编码utf-8
解码gbk,乱码
str2 = new String(bytes3,
"gbk");
System.out.println(str2);
//
gbk兼容gb2312所以,没有问题。
str = new String("中国".getBytes("gb2312"),
"gbk");
System.out.println(str);
}

存文件时可以使用各种编码,但是解码的时候要对映的采用相同的解码方式。

我们的字符流自动的做了编码和解码的工作,写一个中文,字符流进行了编码,存到了计算机中读到了一个字符,字符流进行了解码,我们可以看到字符。因为文件存的都是二进制。

但是拷贝图片时,是纯二进制,不是有意义的字符,所以码表无法转换。

字符流的弊端:

一:无法拷贝图片和视频。

二:拷贝文件使用字节流而不使用字符流,因为字符流读文件涉及到解码,会先解码,写文件的时候又涉及到编码,这些操作多余,而且读和写的码表不对应还容易引发问题。

例如FileReader 读文件,我们没有指定编码时,默认是按照系统编码gbk进行操作,如果读到utf-8的文件也是按照gbk编码进行解码,那就会出现问题。

2.4. 字节流读取中文

publicclass TestIo {
publicstaticvoid main(String[] args)
throws IOException {
readFileByInputStream2("c:\\a.txt");
}

privatestaticvoid readFileByInputStream2(String path)
throws IOException {
FileInputStream fis =
new
FileInputStream(path);
int len = 0;

while ((len = fis.read()) != -1) {
System.out.print((char) len);
}

}
}

这个方法读取文本文件,中文是无法正确显示的。

很显然这些字节需要解码,可以将字节输入流读取的信息保存在字节数组中,指定对应的码表进行解码即可。

publicclass TestIo {
publicstaticvoid main(String[] args)
throws IOException {
readFileByInputStream("c:\\a.txt");
}

privatestaticvoid readFileByInputStream(String path)
throws IOException {
FileInputStream fis =
new
FileInputStream(path);
int len = 0;
byte[] buffer =
newbyte[1024];
while ((len = fis.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, len,
"gbk"));
}

}
}

注意:如果指定的编码表和解码表不对应就会出现问题

publicclass TestIo {
publicstaticvoid main(String[] args)
throws IOException {
//
该文件默认是gbk编码
readFileByInputStream("c:\\a.txt");
}

privatestaticvoid readFileByInputStream(String path)
throws IOException {
FileInputStream fis =
new
FileInputStream(path);
int len = 0;
byte[] buffer =
newbyte[1024];
while ((len = fis.read(buffer)) != -1) {
//
使用utf-8
解码,解错。
System.out.println(new String(buffer, 0, len,
"utf-8"));
}

}
}

2.5. 字节流写出中文

需要编码,可以指定码表。就需要自己把字符串进行编码操作后,把得到的二进制内容通过字节流写入到文件中

使用String的getBytes方法,无参数的会使用系统默认的码表进行编码,也可以指定码表

系统默认编码

publicclass TestIo {
publicstaticvoid main(String[] args)
throws IOException {

String path =
"c:\\test.txt";
writeFileByOutputStream(path,
"世界你好");
readFileByInputStream(path);
}

privatestaticvoid writeFileByOutputStream(String path, String
content)
throws IOException {
FileOutputStream fos =
new
FileOutputStream(path);

//
把字符串进行编码操作,系统默认编码
byte[] bytes = content.getBytes();
//
内容通过字节流写入到文件中。
fos.write(bytes);
fos.close();
}

privatestaticvoid readFileByInputStream(String path)
throws IOException {
FileInputStream fis =
new
FileInputStream(path);
int len = 0;
byte[] buffer =
newbyte[1024];

while ((len = fis.read(buffer)) != -1) {
//
二进制解码,使用系统默认编码
System.out.println(new String(buffer, 0, len));
}

}
}

使用utf-8进行编码

publicclass TestIo {
publicstaticvoid main(String[] args)
throws IOException {

String path =
"c:\\test.txt";
writeFileByOutputStream(path,
"世界你好");
readFileByInputStream(path);
}

privatestaticvoid writeFileByOutputStream(String path, String
content)
throws IOException {
FileOutputStream fos =
new
FileOutputStream(path);

//
把字符串进行编码操作
byte[] bytes = content.getBytes("utf-8");
//
内容通过字节流写入到文件中。
fos.write(bytes);
fos.close();
}

privatestaticvoid readFileByInputStream(String path)
throws IOException {
FileInputStream fis =
new
FileInputStream(path);
int len = 0;
byte[] buffer =
newbyte[1024];

while ((len = fis.read(buffer)) != -1) {
//
二进制解码,使用系统默认编码
System.out.println(new String(buffer, 0, len,"utf-8"));
}

}
}

在明白了字节流也可以正确的处理中文字符之后,就应该明白字符流其实就是字节流在加上系统默认的码表。自动进行了编码和解码的操作。底层还是使用字节流读取文件。通过转换流的学习就可以明白这些道理。

2.6. 转换流

InputStreamReader

查看API文档,发现是字节流通向字符流的桥梁。查看构造,可以传递字节流,可以指定编码,该流可以实现什么功能?很显然可以包装我们的字节流,自动的完成节流编码和解码的工作。该流是一个Reader的子类,是字符流的体系。所以将转换流称之为字节流和字符流之间的桥梁。

InputStreamReader 是字节流通向字符流的桥梁

测试InputStreamReader:

第一步: 需要专门新建以GBK编码的文本文件。为了便于标识,我们命名为gbk.txt

和以UFT-8编码的文本文件,命名为utf.txt

第二步: 分别写入汉字”中国”

第三步:编写测试方法,用InputStreamReader 分别使用系统默认编码,GBK,UTF-8编码读取文件.

publicclass Demo4 {
publicstaticvoid main(String[] args)
throws IOException {
File file = new File("c:\\a.txt");
File fileGBK = new File("c:\\gbk.txt");
File fileUTF = new File("c:\\utf.txt");
//
默认编码
testReadFile(file);
//
传入gbk编码文件,使用gbk解码
testReadFile(fileGBK,
"gbk");
//
传入utf-8文件,使用utf-8解码
testReadFile(fileUTF,
"utf-8");

}

//
该方法中nputStreamReader使用系统默认编码读取文件.
privatestaticvoid testReadFile(File file)
throws
IOException {
FileInputStream fis =
new
FileInputStream(file);
InputStreamReader ins =
new
InputStreamReader(fis);
int len = 0;
while ((len = ins.read()) != -1) {
System.out.print((char) len);
}
ins.close();
fis.close();
}

//
该方法使用指定编码读取文件
privatestaticvoid testReadFile(File file, String encod)
throws IOException {
FileInputStream fis =
new
FileInputStream(file);
InputStreamReader ins =
new
InputStreamReader(fis, encod);
int len = 0;
while ((len = ins.read()) != -1) {
System.out.print((char) len);
}
ins.close();
}
}

注意:码表不对应

分别测试:

使用系统默认编码读取utf-8编码文件
使用utf-8编码读取gbk编码文件
使用"gbk”编码读取utf-8文件.
发现都会出现乱码的问题.
// 使用系统默认编码读取utf-8
testReadFile(fileUTF);
//
传入gbk编码文件,使用utf-8解码
testReadFile(fileGBK,
"utf-8");
//
传入utf-8文件,使用"gbk解码
testReadFile(fileUTF,
"gbk");

类 OutputStreamWriter

OutputStreamWriter

有了InputStreamReader 可以转换InputStream

那么其实还有OutputStreamWriter 可以转换OutputStream

OutputStreamWriter 是字符流通向字节流的桥梁

测试OutputStreamWriter

一: 分别使用OutputStreamWriter使用系统默认编码,GBK,UTF-8相对应的默认编码文件,GBK编码文件,UTF-8编码文件中写出汉字”中国”.

二: 在使用上述案例中的readFile方法传入相对应码表读取.

publicclass TestIo {
publicclass Demo4 {
publicstaticvoid main(String[] args)
throws IOException {
File file = new File("c:\\a.txt");
File fileGBK = new File("c:\\gbk.txt");
File fileUTF = new File("c:\\utf.txt");

//
写入
//
使用系统默认码表写入
testWriteFile(file);
//
使用gbk编码向gbk文件写入信息
testWriteFile(fileGBK,
"gbk");
//
使用utf-8向utf-8文件中写入信息
testWriteFile(fileUTF,
"utf-8");

//
读取
//
默认编码
testReadFile(file);
//
传入gbk编码文件,使用gbk解码
testReadFile(fileGBK,
"gbk");
//
传入utf-8文件,使用utf-8解码
testReadFile(fileUTF,
"utf-8");

}

//
使用系统码表将信息写入到文件中
privatestaticvoid testWriteFile(File file)
throws IOException {
FileOutputStream fos =
new
FileOutputStream(file);
OutputStreamWriter ops =
new
OutputStreamWriter(fos);
ops.write("中国");
ops.close();
}

//
使用指定码表,将信息写入到文件中
privatestaticvoid testWriteFile(File file, String encod)
throws IOException {
FileOutputStream fos =
new
FileOutputStream(file);
OutputStreamWriter ops =
new OutputStreamWriter(fos, encod);
ops.write("中国");
ops.close();
}

//
该方法中nputStreamReader使用系统默认编码读取文件.
privatestaticvoid testReadFile(File file)
throws IOException {
FileInputStream fis =
new
FileInputStream(file);
InputStreamReader ins =
new
InputStreamReader(fis);
int len = 0;
while ((len = ins.read()) != -1) {
System.out.print((char) len);
}
ins.close();

}

//
该方法适合用指定编码读取文件
privatestaticvoid testReadFile(File file, String encod)
throws IOException {
FileInputStream fis =
new
FileInputStream(file);
InputStreamReader ins =
new
InputStreamReader(fis, encod);
int len = 0;
while ((len = ins.read()) != -1) {
System.out.print((char) len);
}

ins.close();
}

}

注意: 码表不对应的问题

分别测试:

向GBK文件中写入utf-8编码的信息

向utf文件中写入gbk编码的信息

发现文件都有问题,无法正常的读取了.

publicstaticvoid main(String[] args)
throws IOException {
File file = new File("c:\\a.txt");
File fileGBK = new File("c:\\gbk.txt");
File fileUTF = new File("c:\\utf.txt");

//
写入
// //
使用系统默认码表写入
// testWriteFile(file);
// //
使用gbk编码向gbk文件写入信息
// testWriteFile(fileGBK, "gbk");
// //
使用utf-8向utf-8文件中写入信息
// testWriteFile(fileUTF, "utf-8");

testWriteFile(fileGBK);
//
向GBK文件中写入utf-8编码的信息
testWriteFile(fileGBK,
"utf-8");

// 向utf文件中写入gbk编码的信息
testWriteFile(fileUTF,
"gbk");

//
读取
//
默认编码
testReadFile(file);
//
传入gbk编码文件,使用gbk解码
testReadFile(fileGBK,
"gbk");
//
传入utf-8文件,使用utf-8解码
testReadFile(fileUTF,
"utf-8");

}

InputStreamReader:字节到字符的桥梁。

OutputStreamWriter:字符到字节的桥梁。

它们有转换作用,而本身又是字符流。所以在构造的时候,需要传入字节流对象进来。

构造函数:

InputStreamReader(InputStream)

通过该构造函数初始化,使用的是本系统默认的编码表GBK。

InputStreamReader(InputStream,String charSet)

通过该构造函数初始化,可以指定编码表。

OutputStreamWriter(OutputStream)

通过该构造函数初始化,使用的是本系统默认的编码表GBK。

OutputStreamWriter(OutputStream,String charSet)

通过该构造函数初始化,可以指定编码表。

注意:

操作文件的字符流对象是转换流的子类。

Reader
|--InputStreamReader
|--FileReader
Writer

|--OutputStreamWriter
|--FileWriter

注意:

在使用FileReader操作文本数据时,该对象使用的是默认的编码表。

如果要使用指定编码表时,必须使用转换流。

如果系统默认编码是GBK的:

FileReader fr = new FileReader("a.txt");//操作a.txt的中的数据使用的本系统默认的GBK。
操作a.txt中的数据使用的也是本系统默认的GBK。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
这两句的代码的意义相同。
但是:如果a.txt中的文件中的字符数据是通过utf-8的形式编码。使用FileReader就无能为力,那么在读取时,就必须指定编码表。那么转换流必须使用。
InputStreamReader isr =

new InputStreamReader(new FileInputStream("a.txt"),"utf-8");

3. 递归

递归做为一种算法在程序设计语言中广泛应用。是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。

(自己调用自己,有结束条件)

注意:递归时一定要明确结束条件。

数学中递归运算.

对于任何正整数N ,N! (读作N的阶乘)的值定义为1-N(包括N)的所有的整数的成绩.因此3! 就是 3!=3*2*1 =6;

5! 定义为5!=5*4*3*2*1=120

那么整数N 的阶乘 N! 可以表示为

1!=1

N!=N*(N-1)! for N>1

若果N 等于1 那么1的继承就是1,其他所有N! =N*(N-1)!,例如:50!=50*49!

49!=49*48! 48!=48*47! 一直持续到1出现.

如何使用Java程序计算阶乘?

publicstaticlong recursion(int
n) {
if (n == 1) {
return 1;
} else {
return n *
recursion(n - 1);
}
}

3.1. 案例:

1,列出指定目录中所有的子孙文件与子孙目录名,只需要列出名称即可。

2,列出指定目录中所有的子孙文件与子孙目录名,要求名称前面要有相应数量的空格:

第一级前面有0个,第二级前面有1个,第三级前面有2个...,以此类推。

3,列出指定目录中所有的子孙文件与子孙目录名,要求要是树状结构,效果如下所示:

|--src

| |--cn

| | |--itcast

| | | |--a_helloworld

| | | | |--HelloWorld.java

| | | |--b_for

| | | | |--ForTest.java

| | | |--c_api

| | | | |--Student.java

|--bin

| |--cn

| | |--itcast

| | | |--i_exception

| | | | |--ExceptionTest.class

| | | |--h_linecount

| | | | |--LineCounter3.class

| | | | |--LineCounter2.class

| | | | |--LineCounter.class

|--lib

| |--commons-io.jar

答案:

案例一:

// 1,列出指定目录中所有的子孙文件与子孙目录名,只需要列出名称即可。
privatestaticvoid
listFile(File file) {

File[] listFiles = file.listFiles();

for (File f : listFiles) {
if (f.isFile()) {
System.out.println(f.getName());
} elseif (f.isDirectory()) {
System.out.println(f.getName());
listFile(f);
}

}
}

publicstaticvoid main(String[] args) {
File file =
new
File("c:\\abc");
listFile(file);
}

案例二

// 2,列出指定目录中所有的子孙文件与子孙目录名,要求名称前面要有相应数量的空格:
privatestaticvoid
listFile2(File file, String str) {

File[] listFiles = file.listFiles();

for (int i = 0; i < listFiles.length;
i++) {
File f = listFiles[i];
System.out.println(str + f.getName());

if (f.isDirectory()) {
listFile2(f, str +
"-");
}
}
}

publicstaticvoid main(String[] args) {
File file =
new
File("c:\\abc");
String str =
"-";
listFile2(file,
str);

案例三:

// 列出指定目录中所有的子孙文件与子孙目录名,要求要是树状结构
privatestaticvoid listFile3(File file, String str) {

File[] listFiles = file.listFiles();

for (File f : listFiles) {
System.out.println(str + f.getName());
if (f.isDirectory()) {
listFile3(f,
"| " + str);
}

}
}

publicstaticvoid main(String[] args) {
File file =
new
File("c:\\abc");
file = new File("c:\\day18ide");
file = new File("c:\\MyIo");
str =
"|-";

listFile3(file, str);

3.2. 练习:

1,删除一个非空的目录。

2,移动一个非空的目录到另一个地方(剪切)。

3,把File类中的重要方法设计代码测试一遍。

// 1,删除一个非空的目录。并加强健壮性
privatestaticvoid deleteFile(File file) {
if (!file.exists()) {
System.out.println("路径不存在");
return;
}
if (!file.isDirectory()) {
System.out.println("不是目录");
return;
}
//
如果当前目录中有子目录和文件,先删除子目录和文件
File[] listFiles = file.listFiles();
for (File f : listFiles) {
if (f.isFile()) {
f.delete();
} elseif (f.isDirectory()) {
deleteFile(f);
}
}
//
删除当前目录
file.delete();

}

0o

练习2:

使用File类的renameTo 方法和递归实现非空目录的剪切.

publicstaticvoid main(String[] args)
throws IOException {
//
重命名文件(成功)
// File
src = new File("c:\\aaa.txt");
// File
dest = new File("c:\\bbb.txt");
// src.renameTo(dest);

// //移动文件(成功)
// File
src = new File("c:\\aaa.txt");
// File
dest = new File("d:\\aaa.txt");
// src.renameTo(dest);

//
移动一个空目录(失败)
// File
src = new File("c:\\aaa");
// File
dest = new File("d:\\aaa");
// System.out.println(src.renameTo(dest));

//
使用File类和递归实现文件的剪切.
File src = new File("c:\\abc");
File dest = new File("d:\\");
cutFile(src, dest);

}

//
移动一个非空的目录到另一个地方(剪切)。
privatestaticvoid cutFile(File srcDir, File dest)
throws IOException {
if (!srcDir.exists() || !dest.exists()) {
System.out.println("指定的源目录或者目标目录不存在");
return;
}
if (!srcDir.isDirectory() || !dest.isDirectory()) {
System.out.println("指定的源目录或者目标目录不是目录");
return;
}

//
得到源目录名
String srcDirName = srcDir.getName();
// abc
//
根据源目录名创建新目录名
File destDir = new File(dest + srcDirName);
// d:\\abc dest 为父路径

// srcDirName 为子路径
//
创建目标目录
destDir.mkdir();

//
遍历源目录
File[] listFiles = srcDir.listFiles();

for (File f : listFiles) {
//
如果是子源文件,使用renameTo方法,移动至目标目录中(该方法同时会删除源目录中的文件)
if (f.isFile()) {
f.renameTo(new File(destDir, f.getName()));
// 指定目标文件的父目录,文件名(根据源文件名生成).
} elseif (f.isDirectory()) {
//
如果是子目录,执行重复动作.
将源子目录 ,
目标目录(父目录+//)
cutFile(f,
new File(destDir, File.separator));
// 指定源目录,指定目的路径d:\\abc\\
}
}
//
删除源目录
srcDir.delete();

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: