1 基本概括
2 主要介绍
2.1 ByteArrayOutputStream、ByteArrayInputStream介绍
ByteArrayOutputStream:字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数 据保存在该字节数组缓冲区中。实际作用就是通过write()将对象各个字段写入一个字节数组,然后再使用toByteArray()将字节数据取出来,通过tcp传输给服务器。
ByteArrayInputStream:字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。实际就是将客户端发送过来的消息转成byte数组,存入内存,再分批次读取数据。
2.2 ByteArrayOutputStream、ByteArrayInputStream介绍
1 FileInputStream、FileOutputStream、FileReader、FileWriter是存储在硬盘上的硬盘上的资源java虚拟机是无权直接访问的,必须借助操作系统,java虚拟机借助完了之后要通知操作系统释放资源
2 把源头换成电脑上的一块内存(字节数组),既然是一块内存那么java就可以直接访问,因为是java虚拟机的一块内存。不用关闭(释放)
3 所有的东西都可以转成字节数组(字符串转成字节数组、任何一个数据(包括12、包括3.14、包括一个一个的对象都可以转成字节数组))
4 文件可以无限制地往里面加内容,但是内存速度快、量小,所以内存(字节数组)不允许不建议量特别的大
2.3 浅拷贝与深拷贝
2.3.1浅拷贝
概念
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象
实现方式
继承自java.lang.Object类的Cloneable接口,实现clone()方法
案例
package com.xxg;
//浅拷贝
public class ShallowCopy {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
teacher.setName("Delacey");
teacher.setAge(29);
Student student1 = new Student();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
Student student2 = (Student) student1.clone();
System.out.println("拷贝后");
System.out.println("student2: "+student2.getName());
System.out.println("student2: "+student2.getAge());
student1.setAge(20);
System.out.println("属性值修改后");
System.out.println("student1: "+student1.getAge());
System.out.println("student2: "+student2.getAge());
System.out.println("student2.teacher_name: "+student2.getTeacher().getName());
System.out.println("student2.teacher_age: "+student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------");
// 修改老师的信息
teacher.setName("Jam");
System.out.println("student1: "+student1.getTeacher().getName());
System.out.println("student2: "+student2.getTeacher().getName());
}
}
class Teacher implements Cloneable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
示意图
2.3.2深拷贝
概念
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
实现方式
一种是通过继承java.lang.Object类的Cloneable接口,重写clone方法;
另外一种是通过实现对象序列化的方式,使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝,原先的对象依然在JVM里面)写到一个流里,再从流里读出来,便可以重建对象。
案例
1 重写clone方法
public Object clone() throws CloneNotSupportedException
{
// 浅复制时:
// Object object = super.clone();
// return object;
// 改为深复制:
Student2 student = (Student2) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher) student.getTeacher().clone());
return student;
}
2 序列化示意
public Object deepClone() throws Exception
{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
示意图
3 简单用例
3.1从内存流读出信息,创建内存流时,就把数据存入到内存中
@Test
public void t1() throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream("我是内存流测试".getBytes());
byte[] b = new byte[1024];
int len;
while((len = bais.read(b)) != -1){
System.out.println(new String(b, 0, len));
}
}
3.2 将数据写入内存流
@Test
public void t2() throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("我是内存流测试".getBytes());
//输出方式一
byte[] b = baos.toByteArray();
System.out.println(new String(b));
//输出方式二
String s = baos.toString();
System.out.println(s);
}
4 源码
4.1 ByteArrayOutputSream源码
public class ByteArrayOutputStream extends OutputStream {
/**
* 存储字节数组的缓存字节数组、存放在内存中.
*/
protected byte buf[];
/**
* 缓存字节数组中有效字节数
*/
protected int count;
/**
* 创建一个带有初始大小为32字节的字节缓存字节数组的字节数组输出流。
* 当缓存字节数组存满时会自动增加一倍容量。
*/
public ByteArrayOutputStream() {
//默认缓存字节数组大小为32位。
this(32);
}
/**
* 创建一个指定初始大小的字节数组输出流。
* 参数不能为负。
*/
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
//将传入的size设为缓存字节数组的大小
buf = new byte[size];
}
/**
*将一个指定的字节写入到字节数组输出流中、存放在字节数组输出流的缓存字节数组即buf中。
*缓存字节数组满时会自动扩容。
*/
public synchronized void write(int b) {
int newcount = count + 1;
//当缓存字节数组存满时、自动增加一倍容量。
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (byte)b;
count = newcount;
}
/**
* 将一个指定的字节数组从下标off开始len长个字节写入到缓存字节数组中。
* 缓存字节数组满时会自动扩容。
*/
public synchronized void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newcount = count + len;
//当缓存字节数组存满时、自动增加一倍容量。
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
System.arraycopy(b, off, buf, count, len);
count = newcount;
}
/**
* 调用另一个OutputStream 的 write(byte[] b, 0, count);方法、将此字节数组输出流的缓存字节数组中的全部字节写入到
* 另一个OutputStream指定的目的地中去。
*/
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
/**
* 重置指定字节写入缓存字节数组的位置、使后面写入的字节重新从缓存字节数组buf的下标0开始写入到缓存字节数组buf中。
*/
public synchronized void reset() {
count = 0;
}
/**
* 将此输出流的缓存字节数组转换成程序中指定代表此流中所有字节的字节数组。
*/
public synchronized byte[] toByteArray() {
return Arrays.copyOf(buf, count);
}
/**
* 返回此字节数组输出流的缓存字节数组的size、即其中的字节的个数
*/
public synchronized int size() {
return count;
}
/**
* 将当前缓存字节数组中所有字节转换成程序指定的表示他的String。
*/
public synchronized String toString() {
return new String(buf, 0, count);
}
/**
* 根据给定的编码将当前缓存字节数组中所有字节转换成程序指定的表示他的String。
*/
public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
return new String(buf, 0, count, charsetName);
}
/**
* 关闭此流、没有什么用!
* 关闭之后方法还是能用!
*/
public void close() throws IOException {
}
}
4.2 ByteArrayInputSream源码
/**
* 一个字节数组包含一个内置缓存数组、该缓存数组中的字段就是供ByteArrayInputStream读取到程序中的流。
*/
public class ByteArrayInputStream extends InputStream {
/**
* 构造方法传入的一个字节数组、作为数据源、read()读取的第一个字节应该是
* 这个数组的第一个字节、即buf[0]、下一个是buf[pos];
*/
protected byte buf[];
/**
* 下一个从输入流缓冲区中读取的字节的下标、不能为负数、
* 下一个读入的字节应该是buf[pos];
*/
protected int pos;
/**
* 当前输入流的当前的标记位置、默认是开始、即下标是0.
*/
protected int mark = 0;
/**
* 注意:这里count不代表buf中可读字节总数!
*
* 此值应该始终是非负数,并且不应大于 buf 的长度。
* 它比 buf 中最后一个可从输入流缓冲区中读取的字节位置大一。
*/
protected int count;
/**
* 创建一个使用buf[]作为其缓冲区数组的ByteArrayInputStream、
* 这个buf[]不是复制来的、并且给pos、count赋初始值。
* 传入的是作为源的字节数组。
* 赋给的是作为缓存区的字节数组。
*/
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
//初始化buf的长度
this.count = buf.length;
}
/**
* 创建一个使用buf[]的起始下标offset,长度为length的字节数组作为其缓冲区数组的ByteArrayInputStream、
* 这个buf[]不是复制来的、并且给pos、count赋初始值。
* 传入的是作为源的字节数组。
* 赋给的是作为缓存区的字节数组。
* 将偏移量设为offset
*/
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
//初始化buf的长度、取min的意义在于剔除不合理参数、避免出现异常、
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
/**
* 开始读取源字节数组中的字节、并将字节以整数形式返回
* 返回值在 0 到 255 之间、当读到最后一个的下一个时返回 -1.
*/
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
/**
* 从源字节数中下标为pos开始的len个字节读取到 b[]的从下标off开始长度为len的字节数组中。
* 同时将下一个将要被读取的字节下标设置成pos+len。这样当循环读取的时候就可以读取源中所有的字节。
* 返回读取字节的个数。
* 当读到最后一个的下一个时返回 -1.
*/
public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
//如果buf中没有字节可供读取、返回-1;
if (pos >= count) {
return -1;
}
//如果buf中剩余字节不够len个、返回count-pos;这个值是作为实际读取字节数返回的
if (pos + len > count) {
len = count - pos;
}
//如果传入的要求读取的字节数小于0、则返回0。
if (len <= 0) {
return 0;
}
//将buf 下标为pos的之后的字节数组复制到b中下标从b开始长度为len的数组中
System.arraycopy(buf, pos, b, off, len);
//设置下一个被读取字节的下标。
pos += len;
return len;
}
/**
* 返回实际跳过的字节个数、同时将pos后移 “ 实际跳跃的字节数” 位
* 下次再读的时候就从pos开始读取。那此时就会忽略被跳过的字节。
*/
public synchronized long skip(long n) {
//如果要跳过的字节数大于剩余字节数、那么实际跳过的字节数就是剩余字节数
if (pos + n > count) {
n = count - pos;
}
if (n < 0) {
return 0;
}
//最后设置偏移量、即下一个被读取字节的下标。
pos += n;
return n;
}
/**
* 返回当前字节数组输入流实际上还有多少能被读取的字节个数。
*/
public synchronized int available() {
return count - pos;
}
/**
* 查看此输入流是否支持mark。
*/
public boolean markSupported() {
return true;
}
/**
* 设置当前流的mark位置、
* 值得注意的是 readAheadLimit这个参数无效
* 只是把pos的当前值传给mark。
* mark取决于创建ByteArrayStream时传入的byte[]b, int offset ...中的offset、若未使用这个构造方法、则取默认值0。
*/
public void mark(int readAheadLimit) {
mark = pos;
}
/**
* 将mark的值赋给pos、使流可以从此处继续读取。下次读取的时候还是从pos下标开始读取
* 也就是继续接着上次被mark的地方进行读取。
*/
public synchronized void reset() {
pos = mark;
}
/**
* 关闭此流、释放所有与此流有关的资源。调用此方法没有任何效果
*/
public void close() throws IOException {
}
}