本文发表于KuTear's Blog,转载请注明
字节流与字符流的区别
在字节流中输出数据主要是使用OutputStream完成
输入使的是InputStream
在字符流中输出主要是使用Writer类完成
输入流主要使用Reader类完成。
实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件
public static void test1() {
File f = new File("G:" + File.separator + "test.txt"); // 声明File对象
OutputStream out = null;
try {
out = new FileOutputStream(f);
String str = "Hello World!!!";
byte b[] = str.getBytes();
out.write(b);
} catch (Exception e) {
e.printStackTrace();
}
}
//test1()正常输出到文件
public static void test2() {
File f = new File("G:" + File.separator + "test.txt"); // 声明File对象
try {
Writer out = new FileWriter(f);
out.write("Hello World!!!");
//out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//test2没有输出到文件
所以在使用字符流的时候,是没有直接对文件或者其他的I/O设备进行直接的处理,而是先在内存中操作,待操作完成之后一次性输出到I/O设备(close()
或flush()
).
为什么匿名内部类和局部内部类只能访问final变量
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来源与创建它的作用域.
下面通过例子说明
public class Test{
abstract class Run{
abstract void run();
}
public void func(final/* Java 8 final 可以省 */ String str){
new Run(){
void run(){
System.out.println(str);
}
};
}
}
通过反编译最终可以得到3个类
//Compiled from "Test.java"
public class Test {
public Test();
public void func(java.lang.String);
}
//Compiled from "Test.java"
abstract class Test$Run {
final Test this$0;
Test$Run(Test);
abstract void run();
}
//Compiled from "Test.java"
class Test$1 extends Test$Run {
final java.lang.String val$str;
final Test this$0;
Test$1(Test, java.lang.String);
void run();
}
我们的匿名内部类Test$1
持有外部对象this$0
的引用和内部方法要使用的参数val$str
,但是是分离了外部类Test
的,要想访问Test
或方法中的参数,只有通过传值,所以在Test$1
的构造器中就传递了必须的参数,并将其保存到它自己的内部.
Java 常见类型的字节数
类型 | 字节(byte) | 比特位(bit) |
---|---|---|
byte | 1 | 8 |
short | 2 | 2*8 |
int | 4 | 4*8 |
long | 8 | 8*8 |
float | 4 | 4*8 |
double | 8 | 8*8 |
char | 2 | 2*8 |
boolean 类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true 或 false
对于boolean,虽然一个bit 就能用,但可惜,最小的内存寻址单元就是byte,所以占用一个byte.
System下的in/out/err的类型和I/O重定向
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
标准I/O重定向
FileOutputStream fileOutputStream = new FileOutputStream("G:/text.txt");
PrintStream fileOut = new PrintStream(fileOutputStream);
System.setOut(fileOut);
System.out.println("这是测试文字");
运行之后会在
G:/text.txt
写入这是测试文字
java
调用系统命令
Process process = new ProcessBuilder().command("ping", "www.kutear.com").start();
InputStream inputStream = process.getInputStream();
int c;
while ((c = inputStream.read()) != -1) {
System.out.print((char) (c));
}
文件通道与缓冲器
文件读取
//第一步:获取通道
FileInputStream fin = new FileInputStream( "G:/test.txt" );
//第二步:创建缓冲区
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
//第三步:将数据从通道读到缓冲区
fc.read( buffer );
buffer.flip();
CharBuffer charBuffer = buffer.asCharBuffer();
while (charBuffer.hasRemaining()) {
System.out.print(charBuffer.get());
}
文件写入
FileOutputStream outputStream = new FileOutputStream("G:/test.txt");
FileChannel channel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(2);
CharBuffer charBuffer = buffer.asCharBuffer();
charBuffer.put('C');
channel.write(buffer);
buffer.flip();
Buffer的结构
基本函数说明
- flip()
此方法用于准备从缓冲区读取已经写入的数据
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
- rewind()
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
- clear()
复写缓冲区,有下面的实现可以看见并没有清除数据
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
- reset()
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- mark()
public final Buffer mark() {
mark = position;
return this;
}
由源码可知,reset()
就是恢复到mark()
的位置.有下面例子说明
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{1,2,3});
buffer.mark(); //此时pos在0
System.out.print(buffer.get()); //此时pos在1
buffer.reset();//pos回到mark的0;
System.out.print(buffer.get()+" ");
输出: 1 1
内存映射文件与文件锁
内存映射简单的使用
int LENGTH = 1 * 1024;
MappedByteBuffer out = new RandomAccessFile("/home/kutear/test.txt", "rw")
.getChannel().map(FileChannel.MapMode.READ_WRITE, LENGTH / 2, LENGTH);
for (int i = 0; i < LENGTH / 2; i++) {
out.put((byte) 'A');
}
由于可以指定映射文件的初始位置和长度,意味着我们可以修改某个文件的其中一小部分,并且相比使用原始的流,映射在读写的性能上都有很大的提升。
文件加锁
文件锁主要涉及的类就是FileChannel
,FileLock
,这里主要用到的就是FileChannel
的tryLock()
或者lock()
以及FileLock
的release()
方法。下面是一个例子:
FileInputStream f = new FileInputStream("/home/kutear/test.txt");
FileChannel channel = f.getChannel();
FileLock lock = channel.tryLock();
if (lock != null) {
//TODO 文件操作
lock.release();
}
f.close();
这里使用的tryLock()
是非阻塞式的,而lock()
是阻塞式的。另外还可以使用带参数的lock(...)
或trylock(...)
来对文件的一部分加锁,而无参数的默认是对整个文件加锁。
对象持久化
持久化意味着一个对象的生存周期并不取决于程序是否正在执行,他可以生存于程序的调用之间。
基本的使用方法在Android开发艺术探索-第二章-Android常用进程间通信这里有说过,在这里就不再说了。
- Externalizable接口
public static class Bean implements Externalizable {
private String args;
public Bean() {
System.out.println("调默认构造器");
}
public void setArgs(String args) {
this.args = args;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(args);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.args = (String) in.readObject();
}
}
public static void write() throws Exception {
Bean bean = new Bean();
bean.setArgs("这是测试");
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("/home/kutear/test.txt"));
out.writeObject(bean);
}
public static void read() throws Exception {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("/home/kutear/test.txt"));
Bean bean = (Bean) in.readObject();
System.out.println(bean.args);
}
public static void main(String[] args) throws Exception{
write();
read();
}
输出如下
调默认构造器
调默认构造器
这是测试
由上面可以知道,在使用Externalizable
反序列化的时候回调用默认的构造器。而使用Serializable
则不会。
- transient(瞬时)关键字
使用该关键字来使某些字段不序列化
public static class Bean implements Serializable {
private String args;
private transient String args2;
public Bean(String args, String args2) {
this.args = args;
this.args2 = args2;
}
}
public static void write() throws Exception {
Bean bean = new Bean("111","222");
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("/home/kutear/test.txt"));
out.writeObject(bean);
}
public static void read() throws Exception {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("/home/kutear/test.txt"));
Bean bean = (Bean) in.readObject();
System.out.println(bean.args);
System.out.println(bean.args2);
}
输出
111
null
但是如果对上面的Bean
做一定的修改,就可以得到不同的结果
public static class Bean implements Serializable {
private String args;
private String args2;
public Bean(String args, String args2) {
this.args = args;
this.args2 = args2;
}
private void writeObject(ObjectOutputStream out) throws IOException{
System.out.println("write called");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
System.out.println("read called");
}
}
此时输出
write called
read called
null
null
非常奇怪,添加这两个与类无关的方法为什么会使序列化失效,原因就是在ObjectOutputStream.writeObject()
中会通过反射检测类中是否含有签名是
private void writeObject(ObjectOutputStream out) throws IOException{}
的函数,如果有,会主动调用。接着我们在做一下修改
private void writeObject(ObjectOutputStream out) throws IOException{
System.out.println("write called");
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
System.out.println("read called");
in.defaultReadObject();
}
此时得到的结果是
write called
read called
111
222
这说明通过out.defaultWriteObject()
会写入数据。但是不会写入带有transient
的字段。这点可以通过修改字段
private String args;
private transient String args2;
得到的结果证实
write called
read called
111
null
继续再此基础上修改
private void writeObject(ObjectOutputStream out) throws IOException{
System.out.println("write called");
out.defaultWriteObject();
out.writeObject(args2);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
System.out.println("read called");
in.defaultReadObject();
args2 = (String) in.readObject();
}
即主动将带有transient
关键字的字段保存,可以看见结果是真的可以保存的
write called
read called
111
222
反序列化后的数据测试
Bean bean = new Bean("111", "222");
System.out.println("原始数据:" + bean);
ByteArrayOutputStream out1 = new ByteArrayOutputStream();
ObjectOutputStream objOut1 = new ObjectOutputStream(out1);
objOut1.writeObject(bean);
objOut1.writeObject(bean);
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
ObjectOutputStream objOut2 = new ObjectOutputStream(out2);
objOut2.writeObject(bean);
ObjectInputStream objIn1 = new ObjectInputStream(new ByteArrayInputStream(out1.toByteArray()));
System.out.println("1号数据流-1:" + objIn1.readObject());
System.out.println("1号数据流-2:" + objIn1.readObject());
ObjectInputStream objIn2 = new ObjectInputStream(new ByteArrayInputStream(out2.toByteArray()));
System.out.println("2号数据流:" + objIn2.readObject());
输出
原始数据:com.kutear.design.pattern.IO$Bean@7f31245a
1号数据流-1:com.kutear.design.pattern.IO$Bean@61bbe9ba
1号数据流-2:com.kutear.design.pattern.IO$Bean@61bbe9ba
2号数据流:com.kutear.design.pattern.IO$Bean@610455d6
由上可以看出,在同一流中的反序列化,只会出现一个对象,而不同流之间反序列化后的结果是不同的,这里对于单例需要特别注意。
Tips
- 记得在
finally
子句中调用close()
函数