高级程序员知识学习(java基础的知识点)

以下的都是本人个人整理相关的资料和参考其他书籍整理的有关于java的高级程序员的学习知识。希望大家能能够相互学习和相互的提高自己的技术。争取一个好的程序员。

java高级程序员的:java基础

1java基础学习的框架图

2java基本语法

3java关键字

4面向对象

5多线程

6集合

7java的API(常用工具类和方法)

8异常处理机制

9java的序列化和反序列化

10java的反射机制

11java Web的知识

12 java IO 流的知识

13java JDK8的性特性

14java基础的相关知识


1java基础学习的框架图

2java基本语法

Java 中的基本数据类型和包装类

基本类型

byte

boolean

char

short

int

float

long

double

应用类型

Byte

Boolean

Character

Short

Integer

Float

Long

Double

所占字节

1

1或者4

2

2

4

4

8

8

包装类和基本数据类型的区别是:

1、包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是

2、包装类型是引用的传递,基本类型是值的传递

3、声明方式不同:基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间

4、存储位置不同:基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们

5、初始值不同 :int的初始值为0、boolean的初始值为false 而包装类型的初始值为null

6、使用方式不同:基本数据类型直接赋值使用就好 ,而包装类型是在集合如 coolection Map时会使用

接口与抽象类的区别?

抽象类和接口的区别:一个类只能继承一个抽象类,而一个类却可以实现多个接口。(单继承多实现方式)

 

接口

抽象类

相同点

接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。实现接口或继承抽象类的普通子类都必须实现这些抽象方法

不同点

1只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现(但是在JDK1.8以后可以使用default和static关键字来修饰接口中定义的普通方法)

2接口中的成员变量只能是 public static final 类型的

3接口不能包含构造器

4接口里不能包含初始化块

1完全可以包含普通方法,接口中的普通方法默认为抽象方法

2抽象类中的成员变量可以是各种类型的

3抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。

4抽象类里完全可以包含初始化块。

== 和 equals 的区别是什么?

 

       基本数据类型

应用数据类型

==

比较是值是否相同

比较的是地址指向是否相同

equal

如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;但是

String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容

String str1 = "Hello"会在堆区存放一个字符串对象

String str2 = new String("Hello")会在堆区再次存放一个字符串对象

String str3 = str2这时候Str3和Str2是两个不同的引用,但是指向同一个对象。

根据这张图再来看上面的比较:

str1 == str2//意思是地址指向的是同一块地方吗?很明显不一样。

str1 == str3//意思是地址指向的是同一块地方吗?很明显不一样。

str2 == str3//意思是地址指向的是同一块地方吗?很明显内容一样,所以为true。

str1.equals(str2)//意思是地址指向的内容一样嘛?一样。

str1.equals(str3)//意思是地址指向的内容一样嘛?一样。

str2.equals(str3)//意思是地址指向的内容一样嘛?一样。

两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

String str1 = "精彩";//    String str2 = "笔记";

System. out. println(String. format("str1:%d | str2:%d", str1. hashCode(),str2. hashCode()));

System. out. println(str1. equals(str2));

执行的结果:

str1:1179395 | str2:1179395

false

String str="i"与 String str=new String(“i”)一样吗?

因为内存的分配方式不一样。String str=“i"的方式,Java 虚拟机会将其分配到常量池中,如果常量池中有"i”,就返回"i"的地址,如果没有就创建"i",然后返回"i"的地址;而 String str=new String(“i”) 则会被分到堆内存中新开辟一块空间。

String s1 = new String("woshi");
String s2 = new String("woshi");
//表示的是s1和s2的地址是不一样的,string不是基本的数据类型,==比较是的应用的是地址
System.out.println(s1 == s2);

操作字符串的类有:String、StringBuffer、StringBuilder

 

String

Stringbuffter

Stringbuilder

相同

都是继承与AbstractStringBuilder

区别

定长度的不变

会自动进行扩容工作,扩展为原数组长度的2倍加2。

效率最小

线程安全/效率其次

线程不安全/ 效率最高

区别:1、StringBuffer与StringBuilder 中的方法和功能完全是等价的,

2、只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全,而StringBuffer则每次都需要判断锁,效率相对更低

StringBuffer初始化及扩容机制

1.StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。

//Stringbuffer的扩容函数的操作
@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
//Stringbuilder的具体的扩容函数  
public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

3java关键字

Const/goto   保留关键字,没有具体含义

final 修饰的类叫最终类,该类不能被继承。修饰的方法不能被重写。final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改(相当于在c中的constant所修饰的量)。

final finally finalize区别

 final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。

finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

static的关键字:从JVM的类加载机制的角度讲,静态资源是类初始化的时候加载的,而非静态资源是类new的时候加载的。类的初始化早于类的new。静态变量先于静态代码块执行,整个执行顺序是:1.父类静态变量初始化。2.父类静态代码块。3.子类静态变量初始化。4.子类静态语句块。5.父类变量初始化。6.父类代码块。7.父类构造函数。8.子类变量初始化。9.子类语句块。10.子类构造函数

如果static要修饰一个类,说明这个类是一个静态内部类(注意static只能修饰一个内部类),也就是匿名内部类。像线程池ThreadPoolExecutor中的四种拒绝机制CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy就是静态内部类。

static修饰成员方法。相比于修饰成员属性,修饰成员方法对于数据的存储上面并没有多大的变化,static修饰成员方法最大的作用,就是可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
    
    public static void main(String[] args) {
        PrintHelper.print("Hello world");
    }
}

static是java中非常重要的一个关键字,主要有四种用法:

  • 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
  • 用来修饰成员方法,将其变为类方法,可以直接使用类名.方法名的方式调用,常用于工具类;
  • 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
  • 静态导包用法,将类的方法直接导入到当前类中,从而直接使用方法名即可调用类方法,更加方便。

4面向对象

Java 类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种:

public class Out {
    private static int a;
    private int b;
    public static class Inner {
    public void print() {
            System.out.println(a);
        }
    }
}

 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示:       

   Out.Inner inner =new Out.Inner();           inner.print();

成员内部类:定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外)。这是因为成员内部类是非静态的, 类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

public class Out {
private static int a;
private int b;
public class Inner {
      public void print() {
              System.out.println(a);
             System.out.println(b);
           }
       }
}

局部内部类:定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类

public class Out {
        private static int a;
        private int b;
        public void test(final int c) {
                final int d = 1;
        class Inner {
        public void print() {
                System.out.println(c);
             }
        }
    }
}

 匿名内部类:(要继承一个父类或者实现一个接口、直接使用new 来生成一个对象的引用)匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有 class 关键字,这是因为匿名内部类是直接使用 new 来生成一个对象的引用

public abstract class Bird {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public abstract int fly();
    }

    public class Test {
        public void test(Bird bird) {
            System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
        }

        public void main(String[] args) {
            Test test = new Test();
            test.test(new Bird() {
                public int fly() {
                    return 10000;
                }

                public String getName() {
                    return "大雁";
                }
            });
        }
    }

接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承实体类,但前提是实体类必须有明确的构造函数。

类的实例化创建/创建一个类的实例都有哪些办法?

第一种:使用new关键字
//创建对象方式1:使用new关键字
            User u1 = new User("1",2,"3");
            System.err.println(u1.toString());
//创建对象方式2:使用反射
//发射方式创建对象要求被创建的对象编写空构造
            try {
                User u2 = User.class.newInstance();
                System.err.println(u2.toString());
            } catch (InstantiationException | IllegalAccessException e) {
                System.out.println("反射创建失败"+e.getMessage());
            }
//使用clone方法创建对象:要求被创建或者被克隆的对象实现Cloneable接口
//(3)是在内存上对已有对象的影印,所以不会调用构造函数
            try {
                User u3 = (User) u1.clone();
                System.err.println("u3:"+u3.toString());
                System.out.println(u1==u3);//false
            } catch (CloneNotSupportedException e) {
                System.out.println("克隆创建失败"+e.getMessage());
            }

深拷贝和浅拷贝的原理

浅拷贝:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

深拷贝:除了对象本身被复制外,对象所包含的所有成员变量也将复制。

Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。

①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。

②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。

面向对象的多态机制理解

子类对象指向父类的应用。实现对父类方法的重写。多态机制是一种的动态的绑定技术。是指的是在执行期间而不是编译期间判断对象的实际类型调用相关的方法。多态就是一个行为具有多个不同的变现的形式或者是形态的能力。多态就是同一个接口使用不同实例进而实现不同的操作。

多态的概念:同一操作作用于不同对象,可以有不同的解释,有不同的执行结果,这就是多态,简单来说就是:父类的引用指向子类对象。Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用和接口引用调用的实现则有所不同。总体而言,当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。

5多线程

线程的状态有哪几种:

Java 中的线程有四种状态分别是:创建、就绪,运行、、挂起、结束。

进程间如何通讯:

信号量 内存共享 socket 管道通信 消息队列

线程间如何通讯:

使用 volatile 关键字、使用Object类的wait() 和 notify() 方法、使用JUC工具类 CountDownLatch、基本LockSupport实现线程间的阻塞和唤醒。

线程的调度策略

线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:

(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利

(2)线程体中调用了 sleep 方法使线程进入睡眠状态

(3)线程由于 IO 操作受到阻塞

(4)另外一个更高优先级线程出现

(5)在支持时间片的系统中,该线程的时间片用完

在 Java 程序中怎么保证多线程的运行安全?

方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger

方法二:使用自动锁 synchronized。

方法三:使用手动锁 Lock。

说一下 synchronized 底层实现原理?

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 ,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

同步和异步有何异同,在什么情况下分别使用他们?举例说明。【基础】

答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率

synchronized关键字锁住的是什么东⻄?

无锁 --> 偏向锁(偏向锁实际上需要先获得轻量级锁,然后在锁重入时才会执行偏向锁优化) --> 轻量级锁(CAS设置markword+自旋) --> 重量级锁(OS层面,在升级为重量级锁时,若在多核cpu上,会出尝试多次自旋,若还是获取不到锁,才就会膨胀为重量级锁)其中无锁,偏向锁,轻量级锁都是JVM层面所做的工作; 而重量级锁是OS层面的,这就涉及到用户态到内核态的转换.轻量级锁连续自旋等待超过一定次数时(JVM默认设置为10次),为了避免CPU占用过高,会升级成重量级锁, 对于重量级锁, 在CPU层面是通过CAS指令来实现的.

synchronized在JVM层面是通过设置对象头来实现上述的锁升级/锁优化的,针对64bit的JVM,其对象头中的mark word格式如下:

https://img-blog.csdnimg.cn/20200223195850487.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvdmVteWxpZmUxMjM0,size_16,color_FFFFFF,t_70

多线程同步的五种方法:

通过Object的wait和notify

使用特殊域变量volatile实现线程同步

使用synchronized关键字 

使用局部变量来实现线程同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

使用阻塞队列实现线程同步

使用原子变量实现线程同步

线程池原理

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

1线程池状态

在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

volatile int runState;

static final int RUNNING    = 0;

static final int SHUTDOWN   = 1;

static final int STOP       = 2;

static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

下面的几个static final变量表示runState可能的几个取值。

  当创建线程池后,初始时,线程池处于RUNNING状态;

  如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

  如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

  当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2任务的执行

ThreadPoolExecutor类中其他的一些比较重要成员变量:

rivate final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务

private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小//、runState等)的改变都要使用这个锁

private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集

private volatile long  keepAliveTime;    //线程存货时间  

private volatile boolean allowCoreThreadTimeOut//是否允许为核心线程设置存活时间

private volatile int   corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)

private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数

private volatile int   poolSize;       //线程池中当前的线程数

private volatile RejectedExecutionHandler handler; //任务拒绝策略

private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程

private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数

private long completedTaskCount;   //用来记录已经执行完毕的任务个数

1)首先,要清楚corePoolSize和maximumPoolSize的含义;

2)其次,要知道Worker是用来起到什么作用的;

3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:

如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务

如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

3线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

4任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务

5任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

6线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

    shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

7线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

setCorePoolSize:设置核心池大小

setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

如果是不采用是这个那就在队列中的线程是不可能出队列的,就是如果是的非公平的锁的话那就永远不能出队列。那可能能执行不到该线程。

Synchronized 与volatile 关键字的区别?

Synchronized 解决执行控制问题,它会其它线程获取当前对象的监控锁,使得当前对象中被Synchronized关键字保护的代码块无法被并发执行,并且Synchronized 还会创建一个 内存屏障,保证了所有CPU操作结果都会直接刷到主存中去,从而保证了可见性。

内存可见:控制的是线程执行结果在内存中对其它线程的可见性,根据Java内存模型的实现,Java线程在具体执行时,会先拷贝主存中的数据 到线程本地(CPU缓存),操作完成后再把结果从线程刷到主存中。

volatile 解决了内存可见,所有对volatile关键字的读写都会直接刷到主存中去,保证了变量的可见性,适用于对变量可见性有要求而对读取顺序没有要求的场景。

两者区别:

1. volatile 不会造成线程的阻塞,Synchronized 会造成线程的阻塞。

2. volatile仅能使用在变量级别,Synchronized 可以使用在变量,方法,类级别上。

3. volatile 标记的变量不会被编译器优化,Synchronized标记的变量会被编译器优化。

4. Volatile 仅能保证变量的修改可见性,不能保证原子性,而Synchronized 可以保证变量修改的可见性和原子性。

 5. Volatile本质告诉JVM 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中去读取 ,Synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程不可以。

6集合

Array 和 ArrayList 有何区别?

Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。

Array 是指定固定大小的,       而 ArrayList 大小是自动扩展的。

Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 。

6.10Arraylist和Linkedlist的区别

    Arraylist:底层是基于动态数组,根据下表随机访问数组元素的效率高,向数组尾部添加元素的效率高;但是,删除数组中的数据以及向数组中间添加数据效率低,因为需要移动数组。

Linkedlist基于链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。

对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和优缺点?

ArrayList:首先ArrayList底层是基于数组实现的,数组属于线性表中的顺序结构的线性表,物理表现即为内存为连续的空间。那么java在定义数组的时候需要指定数组长度,那ArrayList的默认长度是多少呢? 它的默认初始化长度为10;同时它也是支持动态扩容的,底层调用的就是 Arrays.copyOf(elementData, newCapacity),通过这个方法进行动态扩容新增加的容量大小为原容量大小的50%,具体算法如下图:

LinkedList:可知该链表是双向链表,即可以从头遍历到尾,也可以从尾遍历到头。

同样它也是线程不安全的,在这里最可能的造成的并发原因就是链表成环,以该LinkedList 类的linkFirst方法为例下面是源代码:

HashMap原理介绍:

Hashmap中解决hash冲突的方式的是采用还数组+链表的结构。在链表的插入操作的时候是在头部进行插入的时候更快的。而不是在链表的尾部,因为链表是需要遍历才能找到尾部的。在JDK1.7中的时候hashmap中的采用的结构是数组+链表,但是还是存在效率的问题。就是就是在遍历的链表的时候需要很长的时间。如果是需要采用的二叉树或者是排序树的时候可能有存在当二叉树退化为为链表的时候和严重的性能时间。所以JDK1.8中采用的是数组+链表+红黑树一种数据结构来。

Hashmap中的扩容机制:

装载因子,是一个0-1之间的系数,根据它来确定需要扩容的阈值,默认值是0.75 当数据的大于真个数组的0.75倍的时候真个时候扩大为原来的2倍在将原来的数据复制到性的数据中。在JDK1.7中的采用的是头部插入法,但是有可能出现的是的闭环单链表。但是JDk1.8中应用的是尾部插入的方法。但是会存在数据丢失的问题。

Hashmap在动态扩容的时候出现的问题:

那么JDk1.7中使用头插法新的hash桶会倒置原hash桶中的单链表,插入在多个线程同时扩容的情况下就可能导致产生一个存在闭环的单链表,从而导致死循环。

改进的方法:JDK1.8中的采用的是在尾部进行增加的元素。而不是在链头部。由于红黑树的这样可以节省到达尾部的时间,当链表的长度大于在8的时候这时候后就将链表装换为红黑树。

JDK1.8中将原hash桶中单链表上的节点按照尾插法插入到loHead和hiHead所引用的单链表中。由于使用的是尾插法,不会导致单链表的倒置,所以扩容的时候不会导致死循环。但是可能出现的是的数据丢失的问题。

改进办法:使用分段锁的机制来保证线程安全。分段锁是确保每一段的数据都是数据的安全。这样全局都是安全按的。这样在数据做扩容的或者是移动的时候是不予许有其他线程的使用。从而能够保证数据安装,这个不会出现死循环链表的情况。

6.4 HashSet的原理和不重复的原理

HashSet实现了Set接口,它不允许集合中有重复的值(主要是允许key相同,那么在通过控制key相同的时候就不存储在hashset中即可),HashSet的构造方法其实就是在内部实例化了一个HashMap对象。HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写。

6.5TreesSet原理:

TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。

Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的, 自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。

在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序。

比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

6.6ConcurrentHashMap原理

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成, Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。ConcurrentHashMap 是一个Segment数组Segment 通过继承ReentrantLock 来进行加锁,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。JDk1.7的时候

JDK1.8在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

ConcurrentHashMap 和 Hashtable 的区别?

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

    底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

实现线程安全的方式(重要):

在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

6.7HashTable原理

Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。 Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

6.8Set的原理

HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素

LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。

TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

6.9TreeMap原理

TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用 TreeMap 时, key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
Hasmap的原理

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

6.11Hashmap的扩容机制

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;           // HashMap初始容量大小(16)

static final int MAXIMUM_CAPACITY = 1 << 30;               // HashMap最大容量

transient int size;                                       // The number of key-value mappings contained in this map

static final float DEFAULT_LOAD_FACTOR = 0.75f;              // 负载因子

HashMap的容量size乘以负载因子[默认0.75] = threshold;     // threshold即为开始扩容的临界值

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;     // HashMap的基本构成Entry数组

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。当然也不是无线的扩容。

6.12HashSet和HashMap的区别

*HashMap*

*HashSet*

HashMap实现了Map接口

HashSet实现了Set接口

HashMap储存键值对

HashSet仅仅存储对象

使用put()方法将元素放入map中

使用add()方法将元素放入set中

HashMap中使用键对象来计算hashcode值

HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

HashMap比较快,因为是使用唯一的键来获取对象

HashSet较HashMap来说比较慢

如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

你了解重新调整HashMap大小存在什么问题吗?(多线程的情况下,可能产生条件竞争(race condition))

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢。

解决方法是采用的是currenthashmap来完成的。

为什么的currenthashmap是线程的安全的?同样是线程类,ConcurrentHashMap 和 HashTable 在线程同步上有什么不同

ConcurrentHashMap类(是Java并发包java.util.concurrent中提供的一个线程安全且高效的HashMap实现)HashTable是使用synchronize关键字加锁的原理(就是对对象加锁)而针对ConcurrentHashMap,在JDK1.7 中采用分段锁的方式,JDK1.8 中直接采用了CAS(无锁算法)+ synchronized。

HashMap & ConcurrentHashMap 的区别?

除了加锁,原理上无太大区别。另外,HashMap的键值对允许有null,但是ConcurrentHashMap 都不允许。

HashMap 和 Hashtable 有什么区别?

 

Hashtable (老)

HashMap

继承父类

Dictionary

Abstractmap

线程安全

安全

不安全

空值

不允许

允许

Hash值

使用对象的hashcode

重新计算

实现扩容

默认是11( 2^n+1)

默认是16(2^n)

遍历方式

Enumerate

Iterator

contains方法

包含

不包含

HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。在jdk8中,HashMap处理“碰撞”增加了红黑树这种数据结构,当碰撞结点较少时,采用链表存储,当较大时(>8个),采用红黑树(特点是查询时间是O(logn))存储(有一个阀值控制,大于阀值(8个),将链表存储转换成红黑树存储)

https://img2018.cnblogs.com/blog/1184092/201902/1184092-20190220113622746-1879492177.png

为什么 ConcurrentHashMap 比 HashTable 效率要高?

HashTable使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;ConcurrentHashMap :JDK1.7使用分段锁(ReentrantLock + Segment + HashEntry)相当于把一个HashMap分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于Segment,包含多个HashEntry。JDK1.8使用CAS + synchronized + Node + 红黑树。 锁粒度:Node(首结点)(实现Map.Entry<K,V>)。锁粒度降低了。

HashMap的遍历方式及效率

Entryset

HashMap<String,Integer> map=new HashMap<String,Integer>();

Set<Entry<String, Integer>> entrySet=map.entrySet();

Set<Entry<String, Integer>> entrySet=map.entrySet();

for (Entry<String, String> entry : entrySet) {

            System.out.println(entry.getKey()+":"+entry.getValue());

        }

或者是使用迭代器

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();

        while (iterator.hasNext()) {

            Map.Entry<String, String> entry = iterator.next();

            System.out.println(entry.getKey()+":"+entry.getValue());

        }

Keyset

for (String key : map.keySet()) {

            System.out.println(key+":"+map.get(key));

        }

Collection<String> collection = map.values();

System.out.println(collection);

7java的API(常用工具类和方法)

String类

charAt():返回指定索引处的字符。

replace():字符串替换。

trim():去除字符串两端空白。

split():分割字符串,返回一个分割后的字符串数组。

getBytes():返回字符串的 byte 类型数组。

length():返回字符串长度。

toLowerCase():将字符串转成小写字母。

toUpperCase():将字符串转成大写字符。

substring(a,b):截取字符串。截取的是范围是[a,b)的范围 所以如果是要[a,b]那么需要时subString(a,b+1)这才能表示范围是[a,b]。

如何将字符串反转使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

Arrays类

Araays.sort(数组)用来排序的

Araays.aslist() 数组转List

如何实现 Array 和 List 之间的转换?

 Array 转 List: Arrays. asList(array) ;

 List 转 Array:List 的 toArray() 方法。

如何实现数组和 List 之间的转换?

List转数组:toArray(arraylist.size())方法

数组转List:Arrays的asList(a)方法

Math工具类

Math.MAX(a,b)获取a ,b中最大的数字

Math.Min(a,b)获取a ,b中最小的数字

Math.abs(a,b)差的绝对值

HashMap/Hashset类

    HashMap.containkey()

    HashMap.containvalue()

    HashMap.put()

HaspMap.get();

HaspMap.keySet();

HaspMap.clear();

StringBuild和Stringbuffer类

 表示的是字符串的倒序输出

StringBuffer sb = new StringBuffer("abcd");

System.out.println(sb.reverse().toString());

String[] aa = {"a", "b", "c", "d", "e"}

new StringBuilder(Arrays.toString(aa)).reverse()。

Collections类

Collections.Min(list) 获取list中的最小值。

Collections.Max(list) 获取list中的最大的值。

Collections.reverse(arrayList); 将数组进行反转:

Stack类

Stack.empty()查看stack是否为空

Stack.peek()获取栈顶元素,但是不删除。

Stack.pop() 获取栈顶元素,但是删除,弹出栈顶元素。

8异常处理机制

程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误

编译错误是因为程序没有遵循语法规则。throws 用在函数上,后面跟的是异常类,可以跟多个; 而 throw 用在函数内,后面跟的是异常对象。

运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。(空指针异常、数组越界异常、SQL异常、非法参数异常、找不到类文件异常等)

逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。

package CodingTest;

public class test20200508 {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        try {
            return 0;
        } catch (Exception e) {
            e.printStackTrace();
            return 1;
        } finally {
            return 2;
        }
    }
}
结果是2

1、不管有没有异常,finally中的代码都会执行。

2、当try、catch中有return时,finally中的代码依然会继续执行。

3、finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。如果是操作不是基本类型会怎么样?

4、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值。

public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(test());
    }
   public static int test(){
       int i = 1; 
       try{
           i++;
           System.out.println("try block, i = "+i);
           return i;
       }catch(Exception e){
           i ++;
           System.out.println("catch block i = "+i);
           return i;
       }finally{
           i = 10;
           System.out.println("finally block i = "+i);
       }
   }
结果输出如下:

  try block, i = 2
  finally block i = 10
  2
package xyz;

public  class Person {
    public String shengao;
    public String tizhong;
    public String xingbie;
    
    //public String hello(String input);
    
    public String getShengao() {
        return shengao;
    }

    public void setShengao(String shengao) {
        this.shengao = shengao;
    }

    public String getTizhong() {
        return tizhong;
    }

    public void setTizhong(String tizhong) {
        this.tizhong = tizhong;
    }

    public String getXingbie() {
        return xingbie;
    }

    public void setXingbie(String xingbie) {
        this.xingbie = xingbie;
    }


    public String mmp(String fuck){
        System.out.println("person : mmp");
        System.out.println("shengao:"+this.shengao);
        System.out.println("tizhong:"+this.tizhong);
        System.out.println("xingbie:"+this.xingbie);
        return fuck;
    }
    
}
输出结果是:
try block
finally block
person : mmp
shengao:172cm---try block
tizhong:null
xingbie:女
fuck

从上面可以看出,在finally中的set的性别 女  生效了,而在之前用基本类型,在finally中修改它的值不生效。为什么呢?我们知道基本类型在栈中存储,而对于非基本类型是存储在堆中的,返回的是堆中的地址,因此内容被改变了

9java的序列化和反序列化

为什么要序列化:有序在不同的虚拟机中的,我们常常设计到A中的程序调用B中的程序,这个时候会出现问题的是怎么样A中没有B的对象。需要通过网络传输才能得到B的对象,或者内存数据。

怎么来解决问题:将B中的序列化的数据经过序列化处理,传输到A中的,再利用但序列化的操作,生成一个B中的对象。并可以实现在A中的程序中的调用B中的方法。

实现的方法:有java中自带的serializable的接口。利用的protocol buffer的序列化分方式。第三是采用湿thirft的序列化的方式。

序列换的基本的原理是:

JDK的序列化的方式:

步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\object.out"));

步骤二:通过对象输出流的writeObject()方法写对象:

oos.writeObject(new User("xuliugen", "123456", "male"));

JDK类库中反序列化的步骤

步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:

ObjectInputStream ois= new ObjectInputStream(new FileInputStream("object.out"));

步骤二:通过对象输出流的readObject()方法读取对象:

User user = (User) ois.readObject();

说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。将内存中的数据的利用序列化的数据的用于存储和用于传输的一种方式。

10java的反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。

原理是:要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个class对象就保存了这个类的一切信息。反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。有三种方法获得类的Class对象:Class.forName(String className)、className.class、实例对象.getClass();

Java的反射机制:操作的就是这个对象的.class文件,首先加载相应类的字节码(运行eclipse的时候,.class文件的字节码会加载到内存中),随后解剖(反射 reflect)出字节码中的构造函数、方法以及变量(字段),或者说是取出。

反射机制有哪些性能问题?

反射机制的应用场景有哪些?反射是框架设计的灵魂。

例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring等框架也大量使用到了反射机制。

举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

Java获取反射的三种方法:1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制

11java Web的知识

session 和 cookie 有什么区别?

session:是一种将会话状态保存在服务器端的技术。Cookie是一种在浏览器的技术。

Cookie :Web 服务器保存在用户浏览器(客户端)上的小文本文件,它可以包含有关用户的信息。无论何时用户链接到服务器,Web 站点都可以访问 Cookie信息 。

存储位置不同session 存储在服务器端;cookie 存储在浏览器端。

安全性不同:cookie 安全性一般,在浏览器存储,可以被伪造和修改。

容量和个数限制:cookie 有容量限制,每个站点下的 cookie 也有个数限制。

存储的多样性:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie只能存储在浏览器中。

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。session 只是依赖 cookie 存储 sessionid,如果 cookie 被禁用了,可以使用 url 中添加 sessionid 的方式保证 session 能正常使用。

GET和POST区别

 

如果客户端禁止 cookie 能实现 session 还能用吗?

Cookie 与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。

但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。

假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:1手动通过URL传值、隐藏表单传递Session ID。2用文件、数据库等形式保存Session ID,在跨页过程中手动调用。

跨域有以下几种方案

1 将本域中的cookie写入其他的域中,src的发送到需要访问的域名中。

2 P3P协议

3 CORS的请求头设置

在web.xml文件中的listener,servlet,filter的作用的区别:

1、servlet:

   创建并返回一个包含基于客户请求性质的动态内容的完整的html页面;

   创建可嵌入到现有的html页面中的一部分html页面(html片段);

   读取客户端发来的隐藏数据;

   读取客户端发来的显示数据;

   与其他服务器资源(包括数据库和java的应用程序)进行通信;

   通过状态代码和响应头向客户端发送隐藏数据。

2、filter:

   filter能够在一个请求到达servlet之前预处理用户请求,也可以在离开servlet时处理http响应:

   在执行servlet之前,首先执行filter程序,并为之做一些预处理工作;

   根据程序需要修改请求和响应;

   在servlet被调用之后截获servlet的执行

filter(拦截器)

它只是修改对某一资源的请求,或者修改从某一的响应。Servlet中的过滤器Filter,主要的用途是过滤字符编码、做一些业务逻辑判断等。其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作;同时还可进行逻辑判断,如用户是否已经登陆、有没有权限访问该页面等等工作。它是随你的web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当你的web应用停止或重新部署的时候才销毁。它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。它与Servlet的区别在于:它不能直接向用户生成响应。完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,Filter再对服务器响应进行后处理。

Filter有如下几个用处。

在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。

根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。

在HttpServletResponse到达客户端之前,拦截HttpServletResponse。

根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

Listen监听器:

listener:监听器主要用来监听只用。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。比如spring 的总监听器 会在服务器启动的时候实例化我们配置的bean对象 、的 session 的监听器会监听session的活动和生命周期,负责创建,关闭session等活动。

Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是: 做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。

区别是:

1,servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在 业务处理之前进行控制.

2,filter 流程是线性的,url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,servlet的功能主要用来主导流程。

filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存

3, servlet,filter都是针对url之类的,而listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。可用来进行:Spring整合Struts,为Struts的action注入属性,web应用定时任务的实现,在线人数的统计等

4,interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在web.xml,并且不是针对URL的,而是针对action,当页面提交action时,进行过滤操作,相当于struts1.x提供的plug-in机制,可以看作,前者是struts1.x自带的filter,而interceptor 是struts2 提供的filter.

与filter不同点:(1)不在web.xml中配置,而是在struts.xml中完成配置,与action在一起 (2) 可由action自己指定用哪个interceptor 来在接收之前做事 

 

 

servlet的生命周期?

实例化---初始化---服务--销毁

实例化:当用户第一次发送请求当时候,容器判断是否已经创建过servlet对象,由于是第一次,所以没有创建Tomcat进行创建

初始化:创建完毕,调研init方法初始化

第二次请求,判断已经存在则无需进行实例化喝初始化

服务:初始化完毕,调用service方法,进行doget dopost方法去处理相应当请求

销毁:tomcat关闭,调用destory方法销毁servlet对象

1.request对象

     客户端的请求信息被封装在request对象中,通过它才能了解到客户的需求,然后做出响应。它是HttpServletRequest类的实例。

2.response对象

     response对象包含了响应客户请求的有关信息,但在JSP中很少直接用到它。它是HttpServletResponse类的实例。

3.session对象

     session对象指的是客户端与服务器的一次会话,从客户连到服务器的一个WebApplication开始,直到客户端与服务器断开连接为止。它是HttpSession类的实例.

4.out对象

     out对象是JspWriter类的实例,是向客户端输出内容常用的对象

5.page对象

     page对象就是指向当前JSP页面本身,有点象类中的this指针,它是java.lang.Object类的实例

6.application对象

     application对象实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在;这样在用户的前后连接或不同用户之间的连接中,可以对此对象的同一属性进行操作;在任何地方对此对象属性的操作,都将影响到其他用户对此的访问。服务器的启动和关闭决定了application对象的生命。它是ServletContext类的实例。

7.exception对象

 exception对象是一个例外对象,当一个页面在运行过程中发生了例外,就产生这个对象。如果一个JSP页面要应用此对象,就必须把isErrorPage设为true,否则无法编译。他实际上是java.lang.Throwable的对象

8.pageContext对象

pageContext对象提供了对JSP页面内所有的对象及名字空间的访问,也就是说他可以访问到本页所在的SESSION,也可以取本页面所在的application的某一属性值,他相当于页面中所有功能的集大成者,它的本 类名也叫pageContext。

9.config对象

config对象是在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所要用到的参数(通过属性名和属性值构成)以及服务器的有关信息(通过传递一个ServletContext对象)

12 java IO 流的知识

13java JDK8的性特性

Lambda表达式

函数式接口

方法引用和构造器调用

Stream API

在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。jdk1.8并行流使用的是fork/join框架进行并行操作。

接口中的默认方法和静态方法

在接口中可以使用default和static关键字来修饰接口中定义的普通方法。

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

新时间日期API:LocalDate 、 LocalTime 、 LocalDateTime

14java基础的相关知识

说一下堆栈的区别?

栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。 

堆(heap):用于存储对象.

 

功能方面

堆是用来存放对象的

栈是用来执行程序的

共享性

堆是线程共享的

栈是线程私有的

空间大小

描述一下链式存储结构

 

存储方式

适用于

顺序存储

要求存储空间连续

频繁的查询

链式存储

空间可以不连续

频繁的插入和删除

java内存泄漏的根本原因是?/怎么避免内存的泄露

内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露

静态集合类引起内存泄露:像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量1、生命周期和应用程序一致。他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。例: Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }// 在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null

2、当集合里面的对象属性被修改后再调用remove()方法时不起作用。(需要思考的问题)

3、监听器 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

4、各种连接 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

5、内部类和外部模块等的引用 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。

6、单例模式 不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露。

java幂等性的解决方案:幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的。

幂等场景:

1、查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作;

2、删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) ;

3、唯一索引:防止新增脏数据。比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录。要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可);

4、token机制:防止页面重复提交。

原理上通过session token来实现的(也可以通过redis来实现)。当客户端请求页面时,服务器会生成一个随机数Token,并且将Token放置到session当中,然后将Token发给客户端(一般通过构造hidden表单)。

下次客户端提交请求时,Token会随着表单一起提交到服务器端。

服务器端第一次验证相同过后,会将session中的Token值更新下,若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的Token没变,但服务器端session中Token已经改变了。

5、悲观锁

获取数据的时候加锁获取。select * from table_xxx where id='xxx' for update; 注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的;悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;

6、乐观锁——乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。乐观锁的实现方式多种多样可以通过version或者其他状态条件:

1. 通过版本号实现update table_xxx set name=#name#,version=version+1 where version=#version#;

2. 通过条件限制 update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0要求:quality-#subQuality# >= ,这个情景适合不用版本号,只更新是做数据安全校验,适合库存模型,扣份额和回滚份额,性能更高;

7、分布式锁

如果是分布式系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供);

什么是泛型

在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,泛型的产生是为了提高代码的安全性和代码的代码的可读性。

List arrayList = new ArrayList();

arrayList.add("aaaa");

arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){

    String item = (String)arrayList.get(i);

    Log.d("泛型测试","item = " + item);

}

Python的装饰器(相当于是在java的AOP的概念)

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。装饰器的作用就是为已经存在的函数或对象添加额外的功能

内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。

Python中的闭包

就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。闭包存在有什么意义呢?为什么需要闭包?闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。

def make_printer(msg):

    def printer():

        print msg  # 夹带私货(外部变量)

    return printer  # 返回的是函数,带私货的函数

printer = make_printer('Foo!')

printer()

猜你喜欢

转载自blog.csdn.net/weixin_41605937/article/details/105959934