java 高级复习

参考自 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(“代理中”);可以简单理解为一个切片。

发表评论

电子邮件地址不会被公开。 必填项已用*标注