参考自 https://www.bilibili.com/video/BV1Qb411g7cz?from=search&seid=7373469111232659208
程序:是为了完成特定的目的,用某种语言编写的一种指令的集合
进程:是程序的一次运行后者是一个正在运行的程序,进程是资源分配的基本单位
线程:线程是进程的进一步细分,是程序内的一条执行路径。线程是调度和执行的单位,每个线程……
在java的虚拟机JVM中,每个线程都有自己的虚拟机栈和程序计数器,方法区和堆是进程独有的。即多个线程共享进程中的方法区和堆,这就存在安全隐患。
单核cpu上的多线程是假的多线程,只是一种快速的线程切换,同一个时刻只能执行一个线程。多核CPU才是真的多线程。
一个java应用程序至少有三个线程,main()主线程,gc()垃圾回收线程,异常处理线程。
并行(单核,时间片)与并发(多核)
单核CPU多线程实际比单线程慢,线程切换要花时间,但是多线程有他自己的优点,提高应用程序的响应,提高用户体验。提高计算机系统CPU的利用率。也可以改善程序结构,不用将冗长复杂的进程分为多个线程独立运行,利于理解和修改。
何时需要多线程 ?需要同时执行多个数据 (多功能的引用)或者需要一些等待的任务(加载图片) 或者一些后台运行的程序(垃圾回收)。
创建线程的两种方法:
方法一;继承Thread类并重写run方法,new出对象之后调用start方法。只能start一次,否则会出异常或者重新new一个对象再调用start方法。也可以用匿名子类。
new Thread(){ @Override public void run() { System.out.println("test"); } }.start();
Thread.currentThread()一个静态方法,返回当前执行代码的线程。
yield()方法:释放当前线程的cpu执行,但是有可能下一时刻又拿到执行权。
join()方法:在线程A中调用线程B的join方法,线程A进入阻塞状态,直到线程B完全执行完以后,A结束阻塞状态。
sleep()方法:阻塞当前线程具体的时间
isAlive() 判断当前线程是否还存活。
线程有优先级1-10,默认是5
第二种创建线程的方法,实现Rnnable 接口,并重写run方法。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
}).start();
两种方式优先选择Runnable的方式,应为实现的方式没有单继承的限制,更适合用来处理共享数据的问题。两种方法都需要重写run方法,将线程要执行的逻辑写在其中。
线程分为两类:守护线程和用户线程
线程的生命周期,状态:新建、就绪、运行、阻塞、死亡

线程的同步为了解决线程安全问题,原因在于操作共享数据为完成时,其他线程也进来操作数据。
同步代码块 synchronized(同步监视器){},同步监视器就是锁,任何一个类的对象都可以充当锁,要求多个线程共用同一个锁。解决了线程安全问题,但效率上有一点损失。在Runnable实现的方式可以用this(代表当前对象)作为锁。继承Thread的方法不可以,可以用类对象充当锁,Test.class。
同步方法如果一个操作共享数据的代码在一个方法中,直接用同步方法就可以。非静态同步方法的默认持有锁是this,Runnable方式好用,静态方法的同步监视器是当前的类对象,继承Thread方式好用。
用线程同步解决懒汉式单例模式的安全隐患。将getInstance方法直接声明为同步方法,但是效率不高。提高效率如下:
class SingleTon {
private SingleTon(){}
private static SingleTon instance;
public static SingleTon getInstance(){
if (instance == null) {
synchronized (SingleTon.class){
if (instance == null)
instance = new SingleTon();
}
}
return instance;
}
}
死锁,不同线程分别持有对方所需要的同步资源,都在等待对方释放资源,形成了死锁。
JDK5新增两种解决线程安全的方式,Lock锁。(ReentrantLock lock = new ReentrantLock();)也要保证里lock的唯一性。
lock.lock() lock.unlock() 夹住同步代码,可以结合try 和 finally
sychronized 和 lock的异同。两者都可以解决线程安全问题,sychronized自动释放锁,lock需要手动释放锁。lock的方式更灵活,其次是同步代码块,再是同步方法。
线程的通信涉及三个方法
wait()方法,当前方法进入阻塞状态,并释放锁
notify()方法,唤醒被wait的优先级最高的一个线程
notifyAll()方法,唤醒所有被wait的的线程
tip:三个方法必须用在同步代码块或者同步方法中,调用这三个方法必须由锁对象进行调用。这三个方法都定义在Object类中。
sleep方法和wait方法的异同:都可以时当前线程进入阻塞状态,两个方法声明的位置不同,sleep在Thread类中,wait在Object类中,sleep可以在任何需要的时候调用,wait必须在同步方法或者同步代码块中调用。如果两个方法都使用在同步代码中,wait方法会释放锁。
消费者和生产者问题的多线程设计
创建线程的方法三:实现Callable接口,结合FutureTask和Thread类。
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return null;
}
});
new Thread(futureTask).start();
try {
Object obj = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Callable接口比Runnable方法好?
- call方法有返回值,可以利用futureTask任务的get方法取得
- call方法可以抛出异常,被外面的操作捕获,获取异常的信息
- callable支持泛型
创建线程的方法四:线程池(避免频繁的创建和销毁线程,e.g. 安卓listview的item图片加载。实现重复利用) 好处:1.提高响应速度2.降低资源消耗3.便于线程管理
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
});
executorService.shutdown();
创建多线程的四种方式
方法导致状态变化(多线程可以作为例子),状态变化导致某些方法执行(回调方法,安卓activity中很常见)
java 常用类
String 是一个final类,不能被继承。实现了serializable和comparable接口,可序列化,可比较。内部定义了final char[]进行存储字符串数据。String代表不可变的序列。
Stirng可以通过字面量的定义方式,String对象引用指向的内容是存在jvm方法区的常量池中。而常量池会维护其中常量的唯一性。重新赋值,连接操作,或者修改操作时,都是在常量池中进行新建一个字符串常量的。
String两种实例化方式的区别。new 的方式如果常量池中没有定义的话是创建了两个对象
String a = "abc";
String b = new String("abc");
System.out.println(a == b); //false
System.out.println(a.equals(b)); //true
System.out.println(b.equals(a)); //true
常量和常量(final 修饰的变量)的拼接,结果在常量池且常量池中不会有相同的内容。
只要有一个变量,那么结果就在堆中。除非结果调用了intern方法,结果就在常量池中。
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a"+"b";
String s5 = s1 + "b";
String s6 = "a" + s2;
String s7 = s1 + s2;
String s8 = (s1 + "b").intern();
final String s9 = "a";
String s10 = s9 + "b";
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //false
System.out.println(s3 == s8); //true
System.out.println(s3 == s10); //true
JVM内部结构是随着版本进行优化,也会有不同企业公司面对不同落地需求,优化虚拟机内部结构版本的JVM。有名的就是sun公司的java hotspot JVM。
Jvm规范中将堆分别(青年区、老年区和永久区),其中永久区可以认为就是方法区。
JDK6 常量池在方法区,JDK7的常量池在堆里,JDK8常量池有回到永久区,但是永久区名称叫成元空间了。
字符串的常用方法很多,需要铭记一点String是不可变的,任何产生字符串改变的方法都不会改变原来的字符串,都是生成新的对象。
String与基本数据类型的转换,parseXXX与valueOf方法。
String与char数组类型转换 String.toCharArray() 与 new String(char [] arr))
String与Byte数组的转换 String.getBytes() —— new String(byte[]) 使用特定的字符集编码将字符串转换为byte数组,abc之类的就是ASCII值,如果字符串中出现了中文,utf-8会将中文转换为3为的byte,gbk转换成2位字节。
编码:字符串(看得懂)->字节(看不懂) 解码:字节->字符串
乱码的出现就是由于编码和解码的字符集不同。
String、StringBuffer 和 StringBuilder的区别。
- StringBuffer类:可变的字符序列,线程安全,效率低。 底层用char[]实现
- StringBuilder类:可变的字符序列,线程不安全,效率高。 底层用char[]实现
- String类:不可变的字符序列,底层用char[]实现
StringBuffer默认初始化16个长度的char数组,或者初始化长度+16,每次扩容为原来的2倍+2,或者最小扩容数量。也可以在初始化的时候定制初始化长度。如果应用场景需要频繁修改字符串,StringBuffer的效率肯定高于String。
单线程效率:StringBuilder > StringBuffer > String
StringBuffer的一些方法返回值是this,可以实现方法链调用,这些方法对StringBuffer自身对象进行了修改,如append,delete等方法。返回值是String的方法则对本身不进行修改,如substring方法。
StringBuilder只是多数方法上加了sychronized 关键字。
java.util.Date 是 java.sql.Date的父类,后者和数据库交互的时候用,
java.util.Date转换为java.sql.Date类型的对象
simpledateformat类的构造函数,format和parse方法
jdk8 的localdatetime、instant和datetimeformatter这三个类关于时间日期操作更好用,符合人们认知和使用习惯
如果对象的引用需要能够用> >= < <=等进行比较时,需要使用Comparable接口,实现类需要重写compareTo方法,实现一种自然的排序。compareTo方法当前对象小返回负数,相等返回零,否则返回正数。这种比较方式实现(集成)在类的内部,类在定义的时候就需要制定,不够灵活。
Comparator接口也是一种比较器,可以实现定制排序。实现类需要实现compare方法。
String [] arr = {"bac", "acd", "ddk", "bcd"};
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
System.out.println(Arrays.toString(arr));
两种方式对比,Comparable接口一旦制定,类在任何时候都可以比较。Comparator具有临时性。
System类代表系统,构造器是私有的,成员变量和方法基本都是静态的。如out,currentTimeMillions()、gc()……
Math类提供了一系列静态方法用于科学计算
BigInteger可以表示不可变的任意精度的整数。BigDecimal表示数字精度比较高的数字。
定义一组常量的时候,可以使用枚举类。
自定义枚举类可以利用final关键字实现,jdk5 以后可以使用enum关键字,更方便了。
enum Season{ Spring("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天"); private String season; private Season(String season){ this.season = season; } }
注解 Annotation 注解是一种趋势
框架 = 注解 + 反射 + 设计模式
java中的注解
生成文档的相关注解如@author @version @param等
编译时进行格式检查 @Override @Deprecated @SuppressWarnings
如果方法上有@Override注解的话,编译过程会去校验方法的定义是否符合重写的规则,否则会报错,编译无法通过。@Deprecated 过时方法,但能用,为了兼容老代码。@SuppressWarnings 抑制编译器的一些警告,如变量没有使用,没有声明泛型等。
跟踪代码依赖性,代替配置文件的功能 servelet spring junit等框架提供的注解
注解可以认为是和类、接口、枚举并列的结构。也可以自定义。

注解具体实现什么功能?需要通过反射机制来完成。自定义的注解必须配合具体信息处理流程才有意义。
元注解是用来修饰其他注解的,主要有四种
Retention 表示所修饰注解的生命周期,取值Source表示在编译之前生效,class表示在会生成字节码文件(默认),runtime表示会被类加载器所加载到运行中,通过反射生效处理流程。
Target用于制定注解可以修饰的结构,不写默认都可以用,如类、接口、枚举、方法、构造器、变量、局部变量等
Documented表示所修饰的注解可以被解析到javadoc中
Inherited表明注解有继承性
通过反射可以获取注解信息,例子
Class clazz = Student.class;
Annotation [] annotations = clazz.getAnnotations();
jdk8 中关于注解的新特性
这里理解不是很深,后期再回来看看吧
集合框架
java从服务器数据库读取的list,为啥用json传,因为json是字符串,字符串是可序列化的。
集合框架分为collection接口 (collection又分为list接口 和set 接口)和map接口两个系列
list表示有序可重复集合,即动态数组,set表示无序不可重复集合
向实现collection接口的类对象中添加自定义类的时候,这个自定义类最好实现equals方法。因为很多方法都需要比较元素。
ArrayList.contains(), containsAll, romove, removeAll(删除交集),retainAll(求交集)方法比较的是对象的内容,调用equals方法。两个new的相同的字符串会返回true,如果自定义类没有重写eqauls方法,调用OBject类中的==比地址,则返回false。
数组和list的转换
- Collection coll = Arrays.asList(12, 34, 56);
- Object [] obj = coll.toArray();
Iterator迭代器接口(设计模式的一种,提供访问容器中各个元素的方式,而又不暴露容器的细节),通常使用hasNext方法和next方法结合使用。内部定义了Iterator在遍历的同时,remove删除当前元素。这比for循环靠谱,因为动态删除的时候数组的长度变了。
JDK 5 增强for循环,用于遍历集合对象(collection)和数组。底层就是用迭代器实现的。
for (Object obj : coll) {}
List接口的三种实现类
- ArrayList:作为List接口的主要实现类,线程不安全,效率高,底层用Object[]存储
- LinkedList:底层使用双向链表进行存储,对频繁对插入和删除操作效率高
- Vector:List接口的古老实现类,线程安全,效率低,底层用Object[]存储
ArrayList源码分析JDK7:
默认创建长度为10的Object[](饿汉),扩容每次为原来的1.5倍。建议new的时候制定容量,扩容移动数据会消耗资源。
JDK8中默认创建为{},第一次调用add操作的时候(懒汉),才创建长度为10的数据,后续其他操作 一致。
LinkdedList源码分析:定义了私有静态内部类Node,first和last属性,Node内部有prev和next两个引用,双向链表。
Vector默认创建10,扩容两倍,但是通常已经不用这个类了。
List常用方法,增删改查插长度遍历
注意list中remove方法的重载,以及list底层的Object数组,多态以及自动装箱。
ArrayList<Integer> al = new ArrayList<>();
al.add(1);
al.add(2);
al.add(3);
al.remove(2);
System.out.println(al); // 1,2
al.remove(new Integer(2));
System.out.println(al); //1
Set接口的主要实现类(无序,不可重复)
- HashSet:作为Set的主要接口实现类, 线程不安全,可以存储null值
- LinkedHashSet:作为HashSet的子类,遍历内部元素时,可以按照添加的顺序遍历‘
- TreeSet类:可以按照添加元素制定的属性进行排序
Set接口没有额外定义新方法,都是Collection中的。
无序性理解:是不按照添加的顺序,不是随机性。底层虽然是用数组实现的,但是添加到数组中的位置是有hashcode决定的
不可重复性理解:保证添加的元素用hashcode和equal方法判断时,不能返回true
HashSet底层时用数组实现的,向其中添加元素时,首先算元素的Hashcode值,在通过散裂函数算存储的位置,如果该位置没有元素,添加成功。如果有元素,先比较hash值,不同则添加成功,相同再比较equals方法,false则添加成功。
多个元素通过链表进行存储,JDK7 和 JDK8中 (JDK8中旧的在原位)
向Set中添加的元素,需要重写hashcode方法和equals方法,相同元素一定要有相同的hashcode。
HashSet的底层是用HashMap来实现的。
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据额外维护了两个prev和next指针(引用),对于比较频繁的遍历操作,效率高于HashSet,空间换时间。并且遍历顺序是按照添加顺序。
TreeSet中添加的元素,要求是相同类的对象。添加的对象必须有比较的方法,实现comparable接口或者定制comparator,定制的newTreeSet的时候传入。底层用红黑树进行实现。用campare/campareTo方法的返回值判断是不是唯一元素,不再用equals方法了。
List 和 Set 中都有很多方法涉及到重写equals方法,除了TreeSet.
一道有难度的题目,考察hashset中add方法的底层实现
public class HashSetTest {
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<>();
Person p1 = new Person(15, "Tom");
Person p2 = new Person(15, "Amy");
hs.add(p1);
hs.add(p2);
p1.setName("Jack");
System.out.println(hs);
hs.add(new Person(15, "Jack"));
System.out.println(hs);
hs.add(new Person(15, "Tom"));
System.out.println(hs);
}
}
class Person{
private int age;
private String name;
Person(int age, String name) {
this.age = age;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person persino = (Person) o;
return age == persino.age &&
Objects.equals(name, persino.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

Map接口 双列数据,存储key-value对的数据。
Hashmap是map的主要实现类,非线程安全,可以存null的key和value,LinkedHashMap是hashMap的子类,提高了遍历的效率,且遍历顺序按照添加顺序。
Treemap内部元素按照key排序(自然、定制),底层用红黑树。
Hashtable古老的实现类,线程安全,效率低,不能存null,不常用了。有一个子类是Properties,配置文件,key value都是String
Hashmap JDK7 数组+链表 JDK8 数组+链表+红黑树
Map内部结构的理解:
key 无序,不可重复,value 无序,不可重复。key-value pair用Entry对象进行存储。key对象需要重写hashcode和equals方法。value要重写equals方法。
Hashmap的底层实现原理
初始化长度为16的Entry[] table.
put元素的时候,key.hashcode 散裂(& length-1) 确定位置,没冲突则装成Entry放入。冲突,则对比hashcode,在比key.equals(方法). 一样修改,不一样添加(头插法)。
每次扩容为原来的2倍。超过threshold并且索引位置不为空的时候扩容。
JDK8中Node[],而不是Entry[](本质一样,改个名罢了),首次put的时候才创建(懒汉),底层结构多了红黑树(数组+链表+红黑树)。当一个索引位置上元素个数大于8,且数组长度大于64,转换成红黑树,提高查找效率。五大常量:
- DEFAULT_INITIAL_CAPACITY
- DEFAULT_LOAD_FACTOR
- threshold
- TREEITY_THRESHOLD
- MIN_TREEIFY_CAPACITY
LinkedHashMap就是把Node类改造了一下(继承),加了两个指针(引用)。
HashSet底层就是用Hashmap实现的,只存key,value统一指向一个静态的Object类的对象。
hashMap的遍历方法,keySet() / values() / entrySet() + iterator/增强for
面试题:负载因子的大小对Hashmap有什么影响?
负载因子表示Hashmap的数据密度。太大会怎样,太小会怎样?
treemap需要key是同一种类型的对象,并且要求该类实现comparable接口
Arrays是操作数组的工具类,Collectiions是操作集合和Map的工具类。
collection和collections的区别。
Collections常用方法包括reverse、shuffle、sort、swap、max、min(可以定制Comparator)、frequency、copy
List src = Arrays.asList(new Integer[]{1,2,3});
List des = Arrays.asList(new Object [src.size()]);
Collections.copy(des, src);
Collections.sychronizedXxx方法返回线程安全的List、Set和Map,底层就是用同步代码块包裹了一下原来的方法。
数据结构的真实结构(数组、链表)和抽象结构(线性表(数组、链表、栈、队列)、树、图、其他),抽象结构都是在真实结构的基础上进行构建的。
泛型可以和集合框架结合,保证集合内部数据类型的一致性和类型检查,避免了强制转换带来的潜在异常。还可以用在Iterator、Comparator等接口,很方便。
Set<Map.Entry<String, Integer>> entrySet = hm.entrySet();
泛型必须是类,不能是基本数据类型,泛型不制定,则默认是java.lang.Object.
自定义泛型,继承的时候子类可以指明父类的泛型,子类就不是泛型类了。如果子类不指明,则子类还是泛型类。静态方法不能用泛型(从生命周期的角度很好理解),异常类也不能用泛型。
泛型方法:在方法中出现了泛型的结构,与类的泛型没有关系,类不是泛型类也没关系。泛型方法可以是静态的。
public <E> List<E> copy(E[] arr){} //public 后面的 <E> 是为了防止编译器认为E是一个类
例子:DAO(data access object)数据访问对象 就可以定义泛型,DAO中定义操作数据库表的共性操作的方法,T就可以是数据库中的表对于的类。
泛型在继承方面的体现。
类A是类B的父类,G<A>和G<B>不具有子父类关系,而是两个并列的结构。这会导致某些操作不太方便,因此引入通配符,二者的共同父类是G<?>
通配符: ?
public void print(List<?> list) {
Iterator <?> iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
}
像上面写不太好,不能再像list中添加任何元素了,除了null值。因为?具体表示什么类型不确定,只能读了,并且读出来的元素应该复制给Object类。
有限制的通配符。源码中很常见
?extends Person 实际上就是小于等于,这种方式添加数据不行
?super Persion 实际上就是大于等于,这种方式添加数据OK的
文件与IO流
文件File类,IO流主要有InputStream、OutputStream、Reader、Writer四个基本抽象类,任何其他的流操作都是在这四个类上继承而来的。
inputstream和outputstream是字节流,reader和writer是字符流
输入和输出都时相对与内存来说的
流可以分为节点流(例如直接作用在文件上)和处理流(作用在流上,如buffered)
流通常需要处理关闭close,否则可能会有内存泄露问题
FileReader作用于File节点,可以用char[]数组存读取的数据
FileWriter 文件不存在会自动创建,默认是覆盖模式,可以在File构造的时候制定为append模式。
FilieReader和FileWriter不能复制二进制文件(如图片等),因为是字符流
FileInputStream和FileOutputString可以复制二进制文件,字符文件当然也可以。读的过程用byte[]字节数组进行存储。
缓冲流是一种处理流,内置缓冲区,可以加速读写操作
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
字符流可以直接使用简单的方法,String line = br.readline(), 不包含换行符, bw.write(line) bw.newline()
转换流也是一种处理流,InputStreamReader和OutputStreamWriter,从后缀来看属于字符流,可以将字节输入流转换成字符流,将输出字符流转换成字节流。
转换流就设计到了编码和解码。
new InputStreamReader() 默认是用系统默认的字符集进行解码,当然可以显式地指定字符集。
字符集:
ASCII码(8位)、ISO8859-1(8位,欧洲)、GB2312(中文,最多两字节)、GBK(中文、最多两个字节,首位是0就是一个英文字母字节,是1就是两个字节中文)、
Unicode(所有语言的字符集,两个字节,实际没有落地) Unicode的问题,1.英文一个字节表示就够了,纯英文文件的话,文件有两倍大。2.如果英文的字符用一个字节进行编码,那么怎么区分一个字节字符和两个字节字符?3..如果英文采用首位0标识,那么能编码的字符数量就少了一半,不够用。
UTF-8(UCS transfer format)-8表示每8位传输。变长的字节编码方式,Unicode的一种实现方案,用前缀表示字符的长度,0xxxxxxx 、110xxxxx 10xxxxxx、1110xxxx 10xxxxxx 10xxxxxx、 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
ANSI编码标准,英文系统用 ISO8859-1 编码,中文系统默认用GBK。
标准输入输出流 默认是键盘输入控制台输出。可以通过方法重定向流,setIn,setOut。
System.in 是InputStream, 可以先包转换流,再包缓冲流,然后用readline读取System.out是PrintStream
打印流PrintStream和PrintWrite都是输出的,提供了一系列的重载的print()和println()方法。可以配合System.out实现输出的重定向。
数据流DateinputStream和DateOutputStream是为了方便处理基本数据类型或者字符串。当然dos写到文件的数据对人来说通常是不可读,应该用dateinputstream进行配套对应读取。这两个类没什么意思。
装饰设计模式和代理设计模式的区别。
对象流。 ObjectOutputStream,序列化 ObjectInputStream 反序列化。需要保证对象所属于的类是可序列化的。
对象序列化机制:允许把内存中的加了对象转化成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上或者通过网络,将这种二进制流传到另一个网络的一个节点上。当其他程序获得了,这种二进制流可以恢复成原来的java对象。
自定义类需要实现序列化Serializable接口,并提供serialVersionID值并且类中所有的属性都应该是可序列化(String和基本数据类型是可序列化的)才可以序列化。
serialVersionID理解: 就是一个类的标识,如果不显示指定,虽然会自动生成,但是如果你修改了类反序列化的时候可能不会保存,显式制定后再修改类反序列化的时候不会出问题。
static transit 成员变量不可序列化。
RandomAccessFile类,可以读可以写,写内容的时候可以从头覆盖,原来内容多可能留下来。
new RandomAccessFile(new File(), mode) mode可以是r rw rwd等
内部有位置指针seek(), 如果要实现插入的功能就比较麻烦了。多线程断点续传。
NIO(New IO、None-blocking IO)是面向缓冲区的,基于通道。是一种更加高效的读写文件的方式。Path类(可以当成File类的升级版)Paths类, Files类。
网络编程两个问题:如何定位主机以及主机上的应用?(IP和端口)如何进行可靠高效的数据传输?(网络通信协议)
IP区分主机,端口号区分应用
网络通信协议:OSI参考模型(理想7层,无法落地)TCP/IP参考模型(4层,应用层、传输层、网络层、数据链路层,实际实现的网络通信协议)
IPv4、IPv6、公网IP、私有IP。IP(不容易记忆)与域名(容易记忆,通过DNS服务器解析)
本地回路地址:127.0.0.1——-localhost
端口号和进程挂钩(程序/应用),同主机端口不能冲突。
在Java中ip和端口被封装在Socket类里面。
TCP和UDP是两个传输层协议,TCP建立连接三次握手,断开连接四次挥手,是可靠的
UDP不用建立连接,不可靠连接,效率比TCP快,e.g.直播
java开发中可以使用Socket做客户端,ServerSocket做服务器。
read是阻塞式方法,客户端可以调用socket.shutdown()方法通知服务器以及发送完数据了,不用再等了。
UDP的话用DatagramSocket和DatagramPacket类就行了。
URL 协议+主机名+端口+路径+参数 URL类 HttpURLConnection类
Java反射
反射是动态语言的关键,允许程序在执行期间取得类的任何内部信息。甚至是私有的结构都可以。java实际上是静态语言,有了反射,使java称为准动态语言。
反射机制和封装性的矛盾。实际上不矛盾,封装是设计上的思想,私有实际是内部使用,不推荐外部使用。
通过直接new和反射的方式都可以调用公共结构,应用场景各别是?
编译的时候无法确定要创建哪一个对象,可以用反射。WEB应用中很常见,因为用户的请求总是动态的,而服务器早就在运行态了。
对Class类的理解:类的加载过程,javac后会生成多个.class字节码文件,java.exe会对某一个类进行解释执行,将某个字节码文件加载到内存中。次过程为类的加载,加载到内存中的类称为运行时类,是Class类的一个实例。加载到内存中的运行时类,会缓冲一定时间,在此时间内,可以通过不同的方式获取此运行时类。
获取Class的实例的方式。方式三最常用,方式1写死了,方式2逻辑不对,先有对象
//调用运行时类的属性
Class clazz1 = Person.class;
//通过运行时类的对象的getClass方法
Person p = new Person("Amy", 20);
Class clazz2 = p.getClass();
//调用Class的静态方法
Class class3 = Class.forName("java.lang.String");
//使用类的加载器 Test是当前类
ClassLoader classLoader = Test.class.getClassLoader();
Class class4 = classLoader.loadClass("java.lang.String");
Class的实例可以是那些结构,类、接口、注解、数组、基本数据类型、void、枚举
如果程序主动使用某个类,如果该类还未被加载到内存中,则系统会通过如下三个步骤对内进行初始化。Load、Link、Initialize。




可以用ClassLoder系统类加载器加载配置文件,和Property的文件流加载方式的功能一样,默认路径在工程下的src目录。
Class的方法,newInstance()调用空参数构造函数(有权限 public)创建对象。
通常在javabean中要求提供一个public的空参构造器,便于通过反射,创建运行时的类,同时便于子类继承时,子类调用super()。
运行时类的属性结构:
getFields():获取当前类和父类中声明为public的属性
getDeclaredFields(): 多去当前类中声明的所有属性
getMethods(): 获取当前运行时类和父类中声明的所有public方法
getDeclaredMethods():获取当前类中声明所有方法
获取方法声明的注解(框架中很常用): method.getAnnotations()
还可以获得运行时类的带有泛型的父类的泛型,在DAO有用到
class StudenDao extends Dao<Student>{}
获得运行时类的注解 clazz.getAnnotations() 注解应该是RUNTIME的
获取运行时类实现的接口 clazz.getInterfaces() 在动态代理中可以用。
通过反射操作实例的属性:field.set(对象,name)
Class clazz = Person.class;
Person p = new Person();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
name.set(p, "Tome");
通过反射调用实例的方法:field.set(对象,name)
Class clazz = Person.class;
Person p = new Person();
Method show = clazz.getDeclaredMethod("show");
show.setAccessible(true);
show.invoke(p);
创建类的对象的方式:
- new + 构造器
- XXX XXXs XXXFactory XXXBuilder中的静态方法
- 反射
动态代理

静态代理:代理类和被代理费都要实现同一套的接口,所有的功能都由代理类来完成,当代理内完成不了的时候就用被代理类的方法。如房屋中介和租房子的人,他们时都要实现找房子的这个接口,而房屋中介就是代理类,租房子的人就是被代理类。又比如明星和经纪人的关系,他们都要实现明星这个结果,你的明星所有的事物都是由机器人来完成的,当明天要上课的时候,直接就调用明星的唱歌方法。
静态的意思就是,房屋中介和租房子的人在编译之前就应该写好两个类的代理关系,明星和经纪人也是一样的,如果一个工程上的代理的代理对的关系比较多,那么工程下的类的数目就会变得非常多。
下面这个动态代码挺有意思的
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxy = (Human) ProxyFactory.getProxyInstance(superMan);
proxy.eat("beef");
System.out.println(proxy.getBelief());
}
}
interface Human{
String getBelief();
void eat(String food);
}
class SuperMan implements Human{
@Override
public String getBelief() {
return "help others";
}
@Override
public void eat(String food) {
System.out.println("eat " + food);
}
}
class ProxyFactory{
public static Object getProxyInstance(Object obj) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),myInvocationHandler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public void bind(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理中");
return method.invoke(obj, args);
}
}
输出:
代理中
eat beef
代理中
help others
动态代理和AOP(Aspect Orient Programming, 面向切片编程)

上面代码中的invoke方法的System.out.println(“代理中”);可以简单理解为一个切片。