Java基础 笔记(三)
引用拷贝,浅拷贝与深拷贝
引用拷贝:仅仅拷贝指向对象的内存地址。
浅拷贝:被复制对象的所有属性都与原来的对象相同,且对象中对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
深拷贝:是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
I/O
序列化:将数据结构或对象转换成二进制字节流
反序列化:将序列化生成的二进制字节流转换成数据结构或对象
序列化目的:将数据结构或对象状态转换成可取用的格式(如,存成文件,存于缓存,或经由网络发送)以留待后续在相同或另一台计算机环境中。
Object——> Bytes——>(File/DB/Memory/Cloud)——> Bytes——> Object
transient可修饰字段不被序列化,且在反序列化后被置成类型的默认值。只能修饰变量,不能修饰类和方法。static变量不属于对象,不会被序列化。
IO流的分类
IO(Input And Output)在编程中是一个很常见的需求,IO即意味着我们的java程序需要和"外部"进行通信,这个"外部"可以是很多介质
1) 本地磁盘文件、远程磁盘文件
2) 数据库连接
3) TCP、UDP、HTTP网络通信
4) 进程间通信
5) 硬件设备(键盘、串口等)
按照流向:
- 输入流:把程序(内存)中的内容输出到磁盘、光盘等存储设备中。
- 输出流:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
按操作单元:
- 字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码。
- 字符流:每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文。
按流的角色:
- 节点流
- 处理流
1.1 IO
即输入(Input)、输出(Output)。都是以Java程序为参照
输入:把持久设备上的数据读取到内存中。
输出:就内存中的数据存储到持久化设备上。
1.2 IO流
IO流用来处理设备之间的数据传输;
Java对数据的操作是通过流的方式;
Java用于操作流的类都在IO包中。
流按 流向 分为两种:输入流、输出流。
流按 操作类型 分为两种:
- 字节流:可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的。
- 字符流:只能操作纯字符数据。
IO流的常用父类:
-
字节流的抽象父类:
InputStream
OutputStream
-
字符流的抽象父类:
Reader
Writer
IO 程序书写:
- 使用前,导入IO包中的类
- 使用时,进行IO异常处理
- 使用后,释放资源
1.3 字节输出流 OutputStream
方法介绍:
- void close() :关闭此输出流并释放与此流有关的所有系统资源。
- void write(byte[] b) :将
b.length
个字节从指定的 byte 数组写入此输出流。 - void write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量
off
开始的len
个字节写入此输出流。 - abstract void write(int b) :将指定的字节写入此输出流。
1.3.1 FileOutputStream
作用:写入数据到文件。
1.3.2 写入数据到文件示例
public class FileOutputStreamDemo {
public static void main(String[] args) {
File file = new File("e:\\test.txt");
try {
FileOutputStream fos = new FileOutputStream(file);
byte[] data = "zxcvbn".getBytes();
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3.3 给文件续写与换行
public class FileOutputStreamDemo {
public static void main(String[] args) {
File file = new File("e:\\test.txt");
try {
FileOutputStream fos = new FileOutputStream(file, true);
byte[] data = "\nzxcvbn".getBytes();
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3.4 IO异常处理
public class FileOutputStreamDemo {
public static void main(String[] args) {
File file = new File("e:\\test.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
byte[] data = "\nzxcvbn".getBytes();
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意事项:
- 流对象的构造方法,可以创建文件。
- 如果文件存在,直接覆盖。
1.4 字节输入流 InputStream
方法介绍:
- abstract int read()
- int read(byte[] b)
- int read(byte[] b, int off, int len)
- close()
1.4.1 FileInputStream
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("e:\\test.txt");
int read = 0;
while ((read = fis.read()) != -1) {
System.out.print((char)read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- read(byte[])
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("e:\\test.txt");
byte[] b = new byte[2];
int read = 0;
while ((read = fis.read(b)) != -1) {
System.out.print(new String(b, 0, read));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.5 字节流练习
使用读写操作完成文件的复制。
1.5.1 复制文件
原理:读取一个已有的数据,并将这些读到数据写入到另一个文件中。
public class CopyFile {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("E:\\图片\\大数据导论.jpeg");
fos = new FileOutputStream("E:\\image\\copy.jpeg");
int read = 0;
while ((read = fis.read()) != -1) {
fos.write(read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上述复制文件的代码,每次都从源文件中读取一个,然后写入到指定文件中,接着再读取下一个字节,然后再写下一个,直到文件复制完成。效率极低。
1.5.2 缓冲数组方式复制
public class CopyFile {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("E:\\图片\\大数据导论.jpeg");
fos = new FileOutputStream("E:\\image\\copy.jpeg");
byte[] buf = new byte[1024];
int read = 0;
while ((read = fis.read(buf)) != -1) {
fos.write(buf, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2 字符流
2.1 字符编码表
编码表:就是生活中字符和计算机二进制的对应关系表。
- ASCII表
- ISO-8859-1:拉丁码表。
- GB-2312:简体中文码表。
- GBK:最常用的中文码表。
- Unicode:国际标准码表。用两个字节存储。
- UTF-8:基于Unicode。用一个字节存储。
能够识别中文的码表:GBK、UTF-8。
编码:文字 -> 数字。
解码:数字 -> 文字。
2.2 字符输入流 Reader
方法:
- int read()
- int read(char[] cbuf)
public static void readCN() throws IOException {
FileReader reader = new FileReader("e:\\test.txt");
int read = 0;
while((read = reader.read()) != -1) {
System.out.print(read + " => ");
System.out.println((char)read);
}
}
2.3 字符输出流 Writer
方法:
- write(char[] cbuf)
- write(char[] cbuf, int off, int len)
- write(String str)
public static void writeCN() throws IOException {
FileWriter fw = new FileWriter("e:\\test.txt", true);
fw.write(new char[] {'你', '好', '呀'});
fw.flush();
fw.close();
}
2.4 字符流复制文件
private static void conyByCharStream(String src, String copy) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(src);
fw = new FileWriter(copy);
int len = 0;
char[] buf = new char[1024];
while ((len = fr.read(buf)) != -1) {
fw.write(buf, 0, len);
fw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
内部类
将类写在其他类的内部,可以写在其它类的成员位置和局部位置。这时写在其它类内部的类称为内部类,其他类称为外部类。
什么时候使用内部类
在描述事务时,若一个事物内部还包含了其他可能包含的事物。比如在描述汽车的时候,汽车中还包含发动机,这时,发动机就可以使用内部类来描述。
class car{ // 外部类
class enginee{ // 内部类
}
}
内部类的分类
- 成员内部类
- 局部内部类
定义一个内部类时,就是一个正常定义类的过程,同样的包含了各种修饰符、继承与实现关系等。在内部类中可直接访问外部类的所有成员。
成员内部类
定义在外部类中的成员位置。与类中的成员变量相似。可通过外部类对象进行访问。
定义格式:
class 外部类{
修饰符 class 内部类{
}
}
访问方式:
外部类名.内部类 变量名 = new 外部类名().new 内部类名();
class Body{
private boolean life = true;
public class Heart{
public void jump(){
System.out.println("jump jump");
// 访问外部类成员变量
System.out.println("生命状态"+ life);
}
}
}
// 访问内部类
public static void main(String[] args){
Body.Heart heart = new Body().new Heart();
heart.jump();
}
局部内部类
定义在外部类方法中的局部位置,与访问方法中的局部变量相似,可通过调用方法进行访问。
定义格式:
class 外部类{
修饰符 返回值类型 方法名(参数列表){
class 内部类{
}
}
}
访问方式:
在外部类方法中,创建内部类的对象,进行访问
public Party{ // 外部类
public void pullBall(){ // 吹气球
class Ball{ // 内部类, 气球
public pull(){
System.out.println("气球膨胀");
}
}
new Ball().pull();
}
// 访问内部类
public static void main(String[] args){
Party p = new Party();
p.pullBall();
}
}
内部类的实际应用——匿名内部类
内部类是为了应对更加复杂的类间关系。最常用到的内部类就是匿名内部类,是局部内部类的一种。
定义匿名内部类有两个含义:
- 临时定义某一指定类型的子类
- 定义后即刻创建刚刚定义的这个子类的对象
定义匿名内部类的作用与格式
作用:匿名内部类是创建某个指定类型子类对象的快捷方式。
格式:
new 父类或接口{
// 进行方法重写
}
代码演示:
public abstract class Person{
public abstract void eat();
}
Person p = new Person{
public void eat(){
System.out.println("狂吃狂吃");
}
}
// 调用方法
p.eat;
使用匿名对象的方式,将定义子类与创建子类对象两个步骤由一个格式完成。
new Person{
public void eat(){
System.out.println("狂吃狂吃");
}
}.eat;
匿名对象:没有名字的对象(没有引用)
在没有指定其引用变量时,只能使用一次
匿名对象可以作为方法的接收参数,方法的返回值使用。
包(package)
通常情况下,包名使用的是公司网址反写 如:com.yzhiedu.se
包名小写,多层包之间用.连接。
代码块
局部代码块:定义在方法中,用来限制变量的作用范围
构造代码块:定义在类中,方法外,用来给对象中的成员初始化赋值
静态代码块:定义在类中,方法外,用来给类的静态成员初始化赋值
权限修饰符,用来修饰类,方法,变量
public:公共访问
protected;受保护访问
default:默认
private:私有访问
abstract 不能与private, static, final同时使用
类中 | 本包 | 子类 | 外包 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
default | √ | √ | ||
private | √ |
String & StringBuffer & StringBulider
String 对象是不可变的(String类中使用final和private修饰字符数组来保存字符串,不能被继承且没有提供修改的方法)
StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用 final
和 private
关键字修饰,最关键的是这个 AbstractStringBuilder
类还提供了很多修改字符串的方法比如 append
方法。
线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是 StringBuilder
与 StringBuffer
的公共父类,定义了一些字符串的基本操作,如 expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象。StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
Java 反射
Java程序在运行时操作类中的属性和方法的机制,称为反射机制。
把Java类中的各个成分映射成一个个的Java对象。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性。
反射机制的主要功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(通过反射甚至可以调用private修饰的方法)
- 生成动态代理
反射的用途:
- 在IDE中,当我们输入一个对象或类,想要调用它的属性或方法时,一加点号,IDE会自动列出它的属性或方法,这里就会用到反射。
- **反射最重要的用途就是开发各种通用框架。**很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
实现反射机制的类(反射相关的类都在 java.lang.relfect 包里):
-
Class类:代表类的实体,在运行的Java应用程序中国表示类和接口
-
Field类:代表类的成员变量(类的属性)
-
Method类:代表类的方法
-
Constructor类:代表类的构造方法
-
在IDE中,我们输入一个对象或类,并想xiao’yong’ta’de’shu’xing
反射的基本运用:
获得Class对象:
-
使用Class类的forName(“类的全路径名”)方法,可能抛出 ClassNotFoundException 异常
Class<?> myclass = Class.forName("Student");
-
直接获取某个对象的class,仅适合在编译前就已经明确要操作的 Class
Class<?> myclass = Student.class;
-
调用某个对象的getClass方法,需要new一个对象
Class<?> myclass = student.getClass();
实例化类对象:
-
通过Class对象的newInstance( )方法
Student student = (Student)myclass.newInstance();
-
通过Constructor对象的newInstance方法(),可带参数
Constructor con = myclass.getConstructor();
Student student = (Student)con.newInstance("name", "age");
反射的缺点:
- 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
- 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。