Java面试那些事儿

Java面试那些事儿

注意:下面大部分图片素材来自网络,非商用,如有侵权,请联系,立即删除!
注意:以下内容大多学习时整理,如果发现错误,参考阅读,有错误请委婉指正!

Java的JVM

JVM是什么?
Java visual mashion,Java的虚拟机,其中HotSpot比较成熟的一款虚拟机,虚拟机通常帮我们管理好对象的内存分配以及对象的回收,
Java跨平台?
Java的跨平台是基于字节码在JRE运行时实现跨平台。与C语言和c++不同,在编译步骤实现跨平台。
JVM执行过程?
Java虚拟机的构成分为三部分,类加载器子系统,执行引擎,以及JVM运行时数据区。
其中JVM运行时数据区包括了,堆,栈,本地方法区,程序计数器,方法区(元空间)。
在这里插入图片描述

线程私有?
栈+本地方法栈+程序计数器
栈 : 数据结构:存储内容:先进后出FILO
栈里存储的单位:栈帧,例如main帧。通常main帧是先进后出。
栈帧包含的内容?
局部变量变,操作数栈,方法出口,,,,

反汇编?
javap命令,对字节码文件进行反汇编。结果是让jvm可以识别的,以此实现Java的跨平台。

堆内存?
在这里插入图片描述

新生代内存占比1/3:伊甸园区,幸存区(from和to),内存占比:8:1:1
伊甸园区:对象被new的地方
老年代内存占比2/3:
为什么我会把元空间画在堆内存下面?这一点也不是完全没有理由,因为如果你知道内存调优,就会发现在打印GCdetails的时候,系统会将堆内存和元空间一起打印给你。虽然元空间在物理内存上不在堆内存中,但是在软件逻辑中,确实存在的。如下图就是在一次OOM触发的情况下获得的,直接对应着堆内存的数据。包含年轻代有三个区,老年代和元空间(Java8以后改名叫元空间,之前叫永久代)。
在这里插入图片描述

垃圾回收?

伊甸园区满了启动垃圾回收机制,进行根的可达性判定(gc roots),如果某个对象还是被使用的,就可能进入幸存区,不断的触发垃圾回收机制,幸存区进行交换,继续回收,不断的回收过后,15次之后依然存在于幸存区,那么如果还是被使用的对象就会从幸存区转移到老年代。如果连老年代也被占满了,则会触发full gc,会有一个STW(Stop To Work停机)的现象出现。
但是晋升到老年代还有一个情况,就是当创建的对象占用的内存直接大于新生代的50%以上,就会直接在老年代创建。
试跑如下代码,并打印出GCdetails,查看详情:

public class OOM {
    public static void main(String[] args) {
        String x = "ss";
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            x = x+x+x;
        }
    }
}

输出的GCdetails如下:
在这里插入图片描述
大家细看这里,一共触发了6次GC,以及3次Full GC,终究还是把内存撑爆了,提示了OOM(out of Memoery error:java heap space)
垃圾回收算法?(类似于磁盘整理的算法,计算机原理内容)

标记清除算法
复制清除算法
标记整理算法

JVM调优?

别人家的文章,详情请戳这里!

为什么Java需要性能调优?
1.Java的垃圾回收机制是自动的,只能认为提醒,而不能干涉
2.JVM运行的内存是基于物理内存之上的,节省资源,优化垃圾管理。

对象的内存布局

对象在内存中存储的布局可以分为3个区域,对象头、实例数据、对其填充。
在这里插入图片描述

  第一区域:对象头包含两部分信息,第一部分用于存储对象自身的运行时的数据,如哈希码,GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
数据长度在32位和64位虚拟机中分别为32bit和64bit,官方标称Mark Word。
在这里插入图片描述

  对象头的另一部分是类型指针,即对象指向它的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那么对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。
  第二区域:实例数据就是对象真正存储的有效信息,也是在程序代码中定义的各种类型字段内容,无论是从父类继承下来的还是在子类中定义的,都需要记录起来。这部分的存储顺序统一收到虚拟机分配策略参数和字段在Java源码总定义顺序的影响。HotSpot虚拟机默认的分配策略为。从分配策略中可以看出,相同宽度的字段总是被分配在一起,在满足这个前提的条件下,在父类定义的变量会出现在子类之前。如果CompactFields参数为true,那么子类中较窄的变量也可能插入到父类变量的空隙中。
  第三区域:是用来填充对其的,就是自动管理内存系统要求对象地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8的整数倍,而对象头正好是8字节的整数倍,因此,当对象实例数据部分没有对齐时,就需要通过补齐来实现。

volatile关键词

JMM(Java内存模型),JVM是软件虚拟的,就需要一套完整的运行环境。JSR133内存规则,规定了线程不能操作主内存。由此可以产生出线程内存和主内存的区分,那么数据之间就需要交互与共享,就有了可见性一说。
在这里插入图片描述
volatile会触发lock,内存回写
在这里插入图片描述
volatile读操作每次都是从主内存去读。
在这里插入图片描述
有序性禁止了指令重排
指令重排
为什么要指令重排呢?
{顺序执行:效率较低}

Java字节码层面
openjdk编译层面
编译器优化,会优化无效代码,并且进行简单的运算以及进行指令重排
使用volatile和不使用的字节码是完全一致的。加了volatile的类属性会发生变化access flag。
三大特性:可见性、原子性、有序性

new过程

HashMap

键值对存取方式
hashmap的存取方法:
put(Key,value)的方法存入
get(key)方法获取

JDK1.7:数组和链表
JDK1.8:数组+链表+红黑树

数组 : 是一段连续的存储单元的来存储数据。特点是,指定容量和下标。删除插入时间复杂度o(N),查询就是o(1)。其中Java中的ArrayList底层就是数组。

链表 : 链表是物理存储单元上非连续的非顺序的结构。插入和删除复杂度o(1),查找的时间复杂度o(N)。其中Java的LinkedList就是采用链表作为底层。

怎么存储?Hash算法
 也叫散列算法。把任意长度值的key通过散列算法变换成固定长度的key(地址),通过这个地址进行访问数据结构,它通过关键码值映射到表中一个。位置来访问记录,以加快查找速度。

以一个对象来存储:

Entry{
      key;
      value;
      hash;
      next;

hash碰撞:index= hash&(n-1);可能碰撞。如果张三和刘一计算的index都是4,那么会在4的位置替换上张三,但是张三的next的属性会指向刘一。
hash算法
如果下一个hash计算出来的值还在4位置,那么依次移动,然后指针继续正向上一个数据。
在这里插入图片描述
查询思路?get方法,首先也是通过哈希计算,获取存储的位置,但是如果发现相应的存储位置上的值的哈希值不等于自己的,就按照链表的顺序依次向下查找,直到哈希值匹配上。例如上面要查找“张三”,那么相同的key计算方法,得到存储位置为4,但是发现当前4号位置的hash值为8749,不匹配,只能按照链表继续查询,到张三时就完成匹配。

为什么JDK8之后要用红黑树呢?因为链表的插入和删除很快,但是查找的时候会非常慢,时间复杂度为o(N),那么效率极低。采用红黑树就可以有效解决查询的问题。但是并不是在JDK8直接使用红黑树,而是在链表长度超过8(实际上是大于等于8-1等于7的时候进行使用红黑树的) 的时候才使用红黑树结构。
在这里插入图片描述
在这里插入图片描述
并发的情况,只是用get方法就可以是线程安全的。

Spring结构源码

Spring中的IOC

Spring中的AOP
什么是AOP? 面向切面编程:与OOP对比,AOP是处理一些横切性的问题,这些横切性的问题不会影响主逻辑的实现,但是会散落在代码的各个部分,难以维护,AOP就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。
自上而下

执行接口是怎么做到的?

Spring中的MVC

Java中的十种排序方式(代码都是升序)

排序
基于比较的排序:冒泡、选择、插入、并归、快速、希尔、堆排序
排序所发稳定性:如果两个值相等,排序后顺序发生了变化,则不稳定,反之稳定。
稳定性
常见递推式与复杂度:
在这里插入图片描述

1.冒泡排序

执行顺序:从头开始,如果第一个数比第二个数小,则不动,如果比第二个数比较小,就交换这两个数据。一次往后移动,这样循环一次就可以将最大的数移动到最后位置。下一次循环就忽略上一次排序的最大数,在剩下的元素里再进行循环。连续多次就可以实现。(因为这样的排序,比较大或者比较小的数会连续的想后移动,一次排序一个,形似冒泡。)
冒泡排序的时间复杂度是十分不确定的,对于已有顺序的复杂度很小,但是对于大量随机数据,是可能会更高的,因为一旦if语句判定成功,就要进行数据交换。

class Sort{
    //冒泡排序:
    public int[] BubbleSort(int [] in ){
        int end = in.length;//缩小排序范围,将已经排序的数据排除在循环外
        //boolean isSort = false;//判断是不是已经有序
        int endnum = 1;//记录最后一次的交换位置,这个值后的表示已经有序,当数组完全有序,第一轮扫描就结束
        while (end>=0 /*&& (!isSort)*/) {
            endnum--;//先减。如果已经排序,那么if语句不执行,end将直接等于0.
            for (int i = 0; i < end-1; i++) {
                //isSort = true;
                if(in[i]>in[i+1]){
                 //   isSort = false;
                    int m = in[i];//m是实现数据交换的中间变量
                    in[i] = in[i+1];
                    in[i+1] = m;
                    endnum = i+1;//记录最后一次交换的位置
                }
            }
            end=endnum;
        }
        return in;
    }
}
2.选择排序

多次循环,每轮跳出最大值,将最大值(升序情况,降序相反)放到最后一个未排序的位置,与其交换值。
相比于冒泡排序,时间复杂度可能更高o(n^2),但是,交换的次数更少,如果是处理随机数据,选择排序可能比冒泡排序更好。因为他并不会像冒泡一样,if判定为真就执行交换,而是遍历一次才交换一次数据。
注意:数组选择排序不能保证绝对的稳定性。如果多组相等值,可能会导致混乱,例如:
1,2,3,5,10,10,2,3,2.
虽然可以把第二个10放在最后,但是最后一个2被放在第二个2前面了。

以上仅是数组的选择排序,但是链表的选择排序是稳定的

    public int[] SelectSort(int[] in){
        int end = in.length-1;
        int index = 0;
        int max = in[0];
        while (end>=0){
            for (int i = 0; i <= end; i++) {//找出每轮最大值
                if(max<=in[i]){//保证稳定性,这里要相等,使最大的为最后一个最大的数
                //比如:123525214,需要让最大的值为第二个5.但不能保证绝对稳定。
                    max = in[i];
                    index = i;
                }
            }
            in[index] = in[end];//交换为最后一值的大小;
            in[end] = max;//把最大值放在最后
            max = 0;
            end--;
        }
        return in;
    }
3.堆排序(如果你不知道堆,就不用看了,看了也不懂)

堆排序可以认为是对选择排序的优化。从数组中挑一个最大的。不过优化了选择最大的时间复杂度进行了优化。
在这里插入图片描述

步骤:原地建堆
原地建堆
大顶堆:最大值就会在堆顶,将堆第一个数据与最后一个数据进行交换,再把最后一个元素放到堆顶。对堆顶进行siftDown操作。一直重复,直到堆只有一个元素。siftDown的时间复杂度是o(n*logn)。空间复杂度o(1)。
堆排序不是稳定的排序。

    public int[] Heapsort(int[] in){
        int heapsize = in.length ;
        //原地建堆
        for (int i =(heapsize>>1)-1; i >=0; i--) {
            in = siftDown(i,in,heapsize);
        }
        while(heapsize>1){
            //交换堆顶元素
            --heapsize;
            int tem = in[0];
            in[0] = in[heapsize];
            in[heapsize] = tem;
            //对0元素进行siftDown,恢复堆的性质
            in = siftDown(0,in,heapsize);
        }
        return in;
    }
    private int[] siftDown(int index,int[] arr,int heapsize){
        Integer element = arr[index];
        int half = heapsize >>1;
        while (index<half){
            int childindex = (index<<1)+1;
            Integer child = arr[childindex];
            int rightindex = childindex+1;
            if(rightindex<heapsize && arr[rightindex]-child>0){
                child = arr[childindex = rightindex];
            }
            if(element-child>=0) break;
            arr[index] = child;
            index = childindex;
        }
        arr[index] = element;
        return arr;
    }

4.插入排序(扑克牌玩过没?)

这样
在执行过程中,插入排序将序列分为两部分,头部是已经排好序的,尾部待排序。是稳定的排序。
插入排序的交换次数比较多,数量比较小的情况下比较适合。
**时间复杂度:**插入排序的时间复杂度跟逆序对正相关的。
在这里插入图片描述

    public int[] InsertSort(int[] in){
        //类似于扑克牌 ,前面部分相当于在手里的牌,
        //i指向的牌相当于拿起来的牌,然后遍历手里的牌,
        // 找个合适的位置放进去
        for (int i = 1; i < in.length; i++) {
            int j = i;
            //重点是与前一个值比较
            while(in[j]<in[j-1]){
                int tem = in[j];
                in[j] = in[j-1];
                in[j-1]=tem;
                j--;
            }
        }
        return in;
    }

【优化交换次数】:将待插入的数据放在旁边,将前面有序的比待插入的数大的都往后挪一位,然后将待插入元素放到正确位置就可以。

    public int[] InsertSort(int[] in){
        //类似于扑克牌 ,前面部分相当于在手里的牌,
        //i指向的牌相当于拿起来的牌,然后遍历手里的牌,
        // 找个合适的位置放进去
        for (int i = 1; i < in.length; i++) {
            int j = i;
            int insert = in[j];
            //重点是移动前面大的
            while(j>0&&insert<in[j-1]){
                in[j] = in[j-1];
                j--;
            }
            in[j] = insert;
        }
        return in;
    }

【优化查找】还可以使用二分查找的方法进行查找插入。能将该方法进一步进行优化。二分查找的方法代码如下:

    public int[] InsertSort2(int[] in){
        for (int i = 1; i < in.length; i++) {
            int j = i;
            int insert = in[j];
            int index = search(in,insert,j);//查找插入位置
            while(j>index){//重点是移动前面大的
                in[j] = in[j-1];
                j--;
            }
            in[j] = insert;
        }
        return in;
    }
    //二分查找进行优化,返回第一个大于x的元素索引,插入位置
    public int search(int [] in,int x){
        if(in == null||in.length==0) return -1;
        int left = 0;
        int right = in.length;
        while(left!=rigth){
            int mid = (left+right)>>1;
            if(x<in[mid]){
                right = mid;
            } else{
                left = mid+1;
            }
        }
        return left;
    }
5.归并排序(Merge Sort)

不断地将当前序列分割成两个子序列,直到两个子序列不能再分。再将子序列不断的合并成一个有序序列,直到最后只剩下一个有序序列。
000
如果不考虑空间问题,则可以直接将数组拆分为两个数组进行组合就可以了。如下图。
在这里插入图片描述
合并的时候需要先拷贝整个数组的左半边数组,为了节省空间,就可以在输入数组上直接进行比较填入。例如下图,第一个元素,左边较小,就先填入左边的数据。依次后推。
在这里插入图片描述
具体的实现代码如下:(使用递归的,不太好理解!!!)

    //归并排序
    public int[] MergeSort(int[] in){
        in = divide(in,0,in.length);
        return in;
    }
    private int[] divide(int[] in,int begin,int end){
        //利用递归,不断地拆分序列,直到长度为1,然后调用merge方法,
        //进行合并序列,直到所有的序列都被合并在最后的序列中
        if(end-begin<2) return in;
        int mid = (begin+end)>>1;
        in = divide(in,begin,mid);//前半部分
        in = divide(in,mid,end);//后半部分
        in = merge(in,begin,mid,end);//合并
        return in;
    }
    private int[] merge(int[] in,int begin,int mid,int end){
        int [] leftarr = new int[in.length>>1];//得到左半部分数据,进行保留
        int li = 0,le = mid-begin;//li为左侧指针,le为左侧截止位置
        int ri = mid,re = end;//ri为右侧指针,re为右侧截止位置
        int ai = begin;//ai表示最终返回数组的,当前填入数据的指针
        for (int i = li; i <le ; i++) {
            leftarr[i] = in[begin+i];//获得左边数组
        }
        while (li<le){//在左边数组没填完之前都需要进入
            if(ri<re && in[ri]<leftarr[li]){//如果右侧数组小于左侧数组指针的数
                in[ai++] = in[ri++];//在返回数组填入小的右侧数据
            }else{
                in[ai++] = leftarr[li++];//反之填入左侧数据
            }
        }
        return in;
    }
休眠排序(请不要使用此方法,纯属娱乐)

用线程的休眠机制进行排序。。。(适用于数字型的排序),给出一个数组,然后遍历数组,循环建立线程,对相应建立的线程进行sleep睡眠相应的时间进行输出。实现排序。

    //休眠排序(娱乐)
    public void SleepSort(int[] in){
        for (int i : in) {
            new SortThread(i).start();
        }
    }
class SortThread extends Thread{
    private int value;
    public SortThread(int value){
        this.value =value;
    }
    public void run(){
        try{
            Thread.sleep(value);
            System.out.println(value);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
6.快速排序(Quick Sort)(不稳定)

执行流程:
1.首先选择一个轴点元素;
2.按照轴点元素将所有 的元素分割为两个子序列;
 将大于轴点元素放在右边;
 小于轴点的放左边;
 等于的元素随便放置都可以。
3.对子序列进行继续切分操作,直到不能再切分。
如下图,带颜色的是轴点元素。
kuai

在这里插入图片描述
具体代码实现如下:

    //快速排序
    public int[] QuickSort(int[] in){
        quickSort(in,0,in.length);
        return in;
    }
    public int [] quickSort(int[] in,int begin,int end){
        if(end-begin<2) return in;
        int mid = indexOfpivot(in,begin, end);
        quickSort(in,begin,mid);
        quickSort(in,mid+1,end);
        return in;
    }
    public int indexOfpivot(int[] in,int begin,int end){
        int value = in[begin];
        boolean change = true;//改变运行方向,左边换右边,右边换左边。两边依次执行
        end--;
        while (begin<end){
            if(change) {
                if (in[end] <= value) {//如果右边的值小于轴点。就将右边这个值交换到begin位置
                    // (在左边,其实是个无效位,因为轴点已经取出了)
                    in[begin++] = in[end];//并且将begin指向下一个有效位,
                    // 注意此时end指向的位置是无效位,已经把值赋值到begin了                    
                    change =false;//改变为true。下次执行右端数据。从begin开始判断
                } else {
                    end--;
                }
            }
            else{
                if(in[begin]>=value){//如果左边的值大于轴点值,那么以为它需要放在右边,
                    // 因为此时end位为无效位,所以将这个数值放去end位最合适
                    in[end--] = in[begin];//end--,是指向下一个有效位
                    change = true;//改变为true。下次执行右端数据。从end开始判断
                }else {
                    begin++;
                }
            }
        }
        in[begin] = value;
        return begin;
    }
7.希尔排序

把序列看做一个矩阵。分为m列,逐列进行排序,m从某个整数逐渐减为1,当m为1的时候,整个序列将完全有序。
因此希尔排序也被称为递减增量排序。矩阵的列数取决于步长序列。
希尔本人给出的步长序列是n/2k,比如n为16,步长序列为1,2,4,8。希尔不认的步长序列最差复杂度是o(n2)。目前科学家研究的最佳步长序列算法见下文:
希尔
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
疑惑吗?如果上来直接分为1列不就直接排好了?
其实不是,前面的几次排列,每次逆序对的数量在不断减少的。到这里请返回去看插入排序,在每一次的步长排序中,逆序对都减少,那么底层就很适合用插入排序来实现。所以也有人认为希尔排序实际是插入排序的一种优化。

    //希尔排序
    public int[] ShellSort(int[] in){
        ArrayList<Integer> arr = new ArrayList<>();//希尔步长序列
        int inistep = in.length>>1;
        while (inistep>0){
            arr.add(inistep);
            inistep>>=1;
        }//产生希尔步长
        for (Integer step:arr) {//步长
            for (int col = 0; col < step; col++) {//每一列col+step*i
                for (int begin = col+step; begin < in.length; begin+=step) {//排序
                    int cur =begin;
                    while(cur>col && in[cur]<in[cur-step]){//此处跟插入排序一致,还可以优化。
                        int tem = in[cur-step];
                        in[cur-step] = in[cur];
                        in[cur] = tem;
                        cur -=step;
                    }
                }
            }
        }
        return in;
    }

在这里插入图片描述
________________________________________________________________________

以上都是基于比较的排序,时间复杂度最低都是o(n*logn)。下面的方法都是基于比较的排序方式,属于典型的利用空间换时间,某些时候,平均时间复杂度可以更小。

8.计数排序(Count Sort针对一定范围的整数排序)

核心思想是统计整数在序列中出现的次数,进而推导出有序的索引。时间复杂度是o(N);
在这里插入图片描述
数组的长度取决于序列中最大的数的大小。统计完成后直接从小到大的依次输出就可以得到排序的结果。
这个实现很简单,就不加注释了,如下!

//计数排序
    public int[] CountSort(int[] in){
        int max = 0,min = Integer.MAX_VALUE;
        for (int i = 0; i <in.length ; i++) {
            max = Math.max(max,in[i]);//最大值
            min = Math.min(min,in[i]);//最小值
        }
        int[] out = new int[max+1-min];//指引大小
        for (int i = 0; i < in.length; i++) {
            out[in[i]-min]++;
        }
        int count = 0;
        for (int i = 0; i < out.length; i++) {
            while(out[i]>0){
                in[count] = i+min;//存入真实值
                out[i]--;//次数减1
                count++;//in的index加1
            }
        }
        return in;
    }
9.基数排序(Radix Sort)

适用于整数排序(尤其是非负整数)。
执行流程:依次对个位数、十位数、百位数、千位数、、、等进行排序。(从低到高)
在这里插入图片描述
针对每个位上的数进行一次计数排序。可以理解为把计数排序的索引更换成数字的每个位。这样都是在0-9之间的数。

    //基数排序
    public int[] Radixsort(int[] in ){
        int[] count = new int[10];//计数内存
        int[] out = new int[in.length];//中间缓存空间
        int max = 0;//最大值
        for (int i = 0; i <in.length ; i++) {
            if(in[i]>max){
                max = in[i];//获取最大值,用来判断最大数有几位,作为循环条件
            }
        }
        for (int divider= 1; divider <= max; divider=divider*10) {//获取除数,用来获取每个位上的数字
            for (int i = 0; i < count.length; i++) {
                count[i] = 0;//清空coun,因为是复用的,必须清空
            }
            for (int j = 0; j <in.length ; j++) {
                count[in[j]/divider%10]++;//计算每个位,除以一个数,再模10就可以得到各位数
            }
            for (int i = 1; i <count.length ; i++) {
                count[i]+= count[i-1];//统计每个位的出现次数,累计起来
            }
            for (int i = in.length-1; i >=0; i--) {
                out[--count[in[i]/divider%10]] = in[i];//恢复每个数位置,存入每个数
            }
            for (int i = 0; i <in.length ; i++) {
                in[i]=out[i];//将数据存回输入数组
            }
        }
        return in;
    }
10.桶排序(Bucket Sort)

执行流程:创建一定数量的桶(数组,链表),按照一定的规则将序列中的元素均匀分配到桶里面,分别对桶每个桶进行排序。将所有非空的桶合并成有序数列。桶排序没有具体的规范式,需要自己进行规划。代码大家自己操作吧!!!(实际上是我写了,没跑对!)

注解(annotation)

注解注解和注释是不一样的,注释仅限于人进行识别,但是注解不仅是人用来解析的,Java虚拟机也可以进行识别。注解是Java5开始的技术,不是程序本身,可以对程序作出解释,可以被程序(如编译器)读取。最常见的注解如:

@override//(重写)
@Deprecated//(不建议使用)
@FunctionalInterface//(函数式接口)
@SuppressWarning("all")//(忽略警告)可以多个参数

可以使用在Package、class、metho、field等上面使用,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程来实现对这些元数据的访问。
元注解
Java中定义了4个标准的meta-annotation类型,被用来提供对其他annotation使用。
四个元注解为:

@target(enum ElementType value)//可以传递的参数名叫value,类型是枚举类型,具体可以参考源码。表示注解可以用在哪些地方
@Retention(value)//参数表示注解在什么生命周期有效,runtime>class>sources
@Document//标示注解将生成在Java的document中
@Inherited//子类可以继承父类的注解

自定义注解
使用@interface自定义注解,自动继承了java.lang.annontation.Annontation类

@Target({ElementType.TYPE,ElementType.METHOD})//标注有用地方
@Retention(RetentionPolicy.RUNTIME)//标注有用生命周期
@interface MyAnnontation{//里面的不是方法,只是参数
    //注解的参数:类型+ 参数名+ 默认值(可以没有)
    String name() default "小张";
    int age()  ;//如果没有默认值,在使用注解时就需要赋值
    int id() default -1;//如果默认值为-1,代表不存在
}

反射(Reflection)(比较慢)

Java可以称为准动态语言,就是因为Java中有反射机制,反射是Java被视为动态语言的关键,反射机制允许Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class x = Class.forName("java.lang.String")

加载完类以后,在堆内存的方法去中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构,这个对象就像是一面镜子,透过这个镜子可以看到类的结构,所以我们形象的称之为反射。getclass方法在object类中。
反射
在这里插入图片描述
获取Class类的实例
1.通过对象获取:Class c1 = 对象.getClass();
2.通过包名获取:Class c1 = Class.forName(“包名”);
3.通过类名获取:Class c1 = 类名.class;
4.基本类型的包装类都有一个TYPE属性返回类型:Class c1 = Integer.TYPE;
获得父类
Class c1 = 类名.class;
Class c2 = c1.getSuperclass();

多线程安全

高并发利器NIO

网络七层协议:
在这里插入图片描述

网络IO的流程:网络数据是优先到网卡,因为应用程序不能直接调用硬件,需要内核接口才可以访问到数据。
在这里插入图片描述
阻塞IO需要等数据处理完成,才可以访问下一个数据IO。
在这里插入图片描述
非阻塞IO可以将收到的数据进行接收维护,可以进行多个Socket建立连接。占用资源,可能引起CPU异常。

Linux内核IO模型:
在这里插入图片描述

同步和异步:强调的是结果返回的形式。举个例子,如果A向B借钱,B直接把钱借给A那么就是同步借钱;另外一种情况是A去借钱,B在忙,没有立即把钱给A,于是A回去了,等B忙完了,主动再把钱拿给A,就是异步。
阻塞和非阻塞:强调对调用者的影响。假如AB都去去找C借钱,但是A先到C家,C还在犹豫(还没处理完),然后B也到C家了,并且都借到钱了,就是非阻塞。如果B到C门口被挡住了,要等A借完钱才可以进入,那么就是阻塞。

套接字(Socket)

设计模式(GoF23)

不是语法规定,是一套用来提高代码的可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性、和多态性以及类的关联关系和组合关系的充分理解。
重点:
模式名称
问题
解决方案
效果
设计模式
面向对象(OOP)的七大原则:
1.开闭原则:对扩展开放,对修改关闭(总纲)
2.里氏替换原则:继承必须确保超类所拥有的性质在子类中依然成立
3.依赖倒置原则:要面向接口编程,不要面向实现编程
4.单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性
5.接口隔离原则:要为各个类建立他们所需要的专用接口
6.迪米莱法则:只与你的直接朋友交谈,不跟“陌生人”说话(降低耦合度)
7.合成复用原则:尽量先使用组合或者聚合等关系来实现,其次才考虑使用继承关系来实现

单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。(全局使用一个实例)

原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。(每次调用都是一个新的实例)

工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。

抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。(中介可以做到房东做不到的事情)

适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。

外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。

观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

数据结构之各种树

树的基本概念:树是n个节点的有限集。n=0时为空树。在任意一颗非空树中:(1)有且仅有一个特定的节点叫做根(root);
(2)当n>1时,其余节点可分为多个不可相交的有限集,没有一个子集都是一颗树,叫做子树。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值