Java记录一

1、数据输入

int rd = System.in.read();
char c = (char)rd;

char[] buffer = new char[n];
try {
    System.in.read(buffer);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
import java.io.BufferedReader;
import java.io.InputStreamReader;

char
[] buffer = new char[n]; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); br.read(buffer); br.readLine();
import java.util.Scanner;

Scanner scan = new Scanner(System.in); if(scan.hasNextLine()) { String str = scan.nextLine(); } Scanner sc = new Scanner(System.in); if (sc.hasNext()) { String str = sc.next(); } if (sc.hasNextInt()) { int value = sc.nextInt(); }

 

2、数据输出

StringBuilder sb = new StringBuilder();
sb.append("this array is: ");
sb.append('[');
sb.append(1);
sb.append(',');
sb.append(3.14);
sb.append(']');
System.out.println(sb.toString());

 

3、命名

number_1     ✅
number_2     ✅
$1234        ✅
_number      ✅
1_number     ❌
number.value ❌

 

4、java成员访问权限修饰词: public, protected, private,包访问权限

若不提供任何访问权限修饰词,则为包访问权限,表示当前包中所有其他类对那个成员都有访问权限,但包外的类无法访问;

类既不可以是private也不可能是protected;对于类的访问权限,仅有包访问权限和public

public成员可以被所有其他类访问;

protected成员可以被子类访问,并提供包访问权限;若没有protected修饰,对于从另一个包继承的类,它唯一可以访问的是源包的public成员;

private成员只能被包含该成员的类访问。

 

5、内部类

闭包是一个记录了一些信息的可调用对象,这些信息来自于创建它的作用域

内部类是面向对象的闭包,它不仅包含外围类对象的信息(它拥有其外围类的所有元素的访问权),还自动秘密捕获一个指向外围类对象的引用。

在拥有外部类对象之前,不可能创建内部类对象,因为没有外围类对象连接,但如果创建的是嵌套类,就不需要对外围类对象的引用。

内部类在访问外围类成员时,就是用那个引用来选择外围类的成员。

成员内部类可以由public, protected, private修饰,它和外部类的成员访问权限一致。

局部内部类放在代码块或方法中,不能有访问控制修饰符,不能用static修饰。

定义内部类的目的:

1)用内部类实现某类型的接口,然后在外围类中创建该内部类对象,就能用到该接口的实现

2)想创建一个类来辅助解决一个复杂的问题,但不希望该类是公共可用的。

定义过程:

1、在方法中定义类(局部内部类) 2、在作用域中定义类(局部内部类) 3、定义一个实现了接口的匿名类 4、扩展了有非默认构造器的类  5、匿名类中执行字段初始化  6、匿名类通过实例初始化实现构造

内部类即使是在作用域中被定义,但它是和其它类一起被编译的,在作用域之外,该内部类就不可用了。

匿名内部类看起来似乎是你正要创建一个基类对象, 实际上指的是创建一个继承自基类的匿名类的对象,并且返回的引用被自动向上转型为对基类的引用。

匿名内部类没有构造器,它实际上调用了基类的构造器,即使在创建匿名内部类对象时传递了参数,该参数也被传递给了匿名类的基类的构造器,并不会在匿名类内部被直接使用。

匿名内部类在访问外部定义对象时,编译器希望该参数引用是final的

匿名内部类既可以扩展类,也可以实现接口,但不能二者兼备;如果实现接口,也只能实现一个接口。

嵌套类:将内部类声明为static

嵌套类对象不需要其外围类的对象;不能从嵌套类对象中访问非静态的外围类对象。

接口中的类:

在正常情况下,不能在接口内部放置任何代码,但不包括嵌套类。接口中的任何类自动时public和static。若想创建某些公共代码,它们可被某个接口的不同实现共用。

 

6、继承自基类方法和接口方法的visibility

不能降低继承方法的可视化程度

class Basis {
    void f() {}
}
class A extends Basis {
    @Override
    //private void f() {}   ❌
    //void f() {}           ✅ 
    //public void f() {}    ✅
}
//接口中的域都是隐式地为public,static和final,方法都是隐式地为public,,故在实现接口的类中方法必须被定义为public
interface Interface {         
    void f();
}
class B implements Interface{
    @Override
    //private void f() {}    ❌
    //void f() {}            ❌
    //public void f() {}     ✅  
}

 

7、Random

Random rand = new Random();
rand.nextInt(N); //int的数据范围控制在[0,N)之间

 

8、成员初始化

对于局部变量,java以编译时错误来保证使用前得到初始化;

类的基本数据类型成员被自动赋予了0初始值(后面会讲理由);类的非基本数据类型成员(即对象)在没有初始化前,获得特殊值null,若未初始化就尝试使用它,会出现运行时错误(即异常);

初始化可以在定义类成员变量的地方,也可以在构造器内初始化;其中,变量定义会在任何方法调用之前(包括构造器)得到初始化,且变量定义的先后顺序决定了初始化的顺序;

变量定义的初始化包括静态实例的初始化和非静态实例的初始化。其中,初始化的顺序是先静态对象,而后是非静态对象;

静态初始化只有在必要时刻才会进行,即当首次生成一个类的对象(构造器实际上也是静态方法),或者首次访问属于该类的静态数据成员或静态方法时,静态初始化的代码才执行,且只执行一次;

非静态实例的初始化在静态初始化之后,构造器执行之前;

创建对象过程的初始化顺序:

1)当首次创建该类的对象,或者类的静态方法/静态数据首次被访问时,java加载器查找用户类路径,并定位该类的.class文件。

2)载入.class文件,给静态域分配空间,执行静态初始化操作。

3)当用new创建对象时,先在堆上为该对象分配充足的存储空间

4)将存储空间清零(故对象中所有基本类型数据都被设置成默认值,而引用被设置为null);

5)执行所有非静态字段定义处的初始化。

6)执行构造器。

 

9、构造器的调用顺序和构造器内部的多态行为

当创建一个类时,总是在继承,它会自动得到基类中所有的域和方法;但导出类只能访问自己的成员,不能访问基类的private成员,只有基类的构造器才有权限对自己的元素进行初始化。因此,必须令所有的构造器都得到调用,否则不能正确构造完整对象。若不能调用基类构造器(例如基类构造器为private访问权限),编译器会报错。当创建了导出类对象,该对象即包含了基类的子对象

总的来说,导出类的构建过程从基类向外扩散,基类在导出类构造器可以访问它之前,就已经完成了初始化。

调用导出类构造器的顺序是:

1)将分配给对象的存储空间初始化为0;

2)先调用基类构造器(若没有默认的基类构造器,可在导出类构造器中显式地调用带参数基类构造器,调用基类构造器必须是导出类构造器总要做的第一件事);

3)按声明顺序调用导出类成员的初始化块;

4)再调用导出类构造器(2,3步遵循类成员初始化的原理(该文章第八点));

构造器内部的多态:

class A{
    void draw() {print("A.draw()");}  
    A() { draw();}
}
class B extends A{
    void draw() {print("B.draw()");}
    B() {}
}
class Test{
    public static void main(String[] s){
        new B();
    }
}
/*Output:
B.draw();
*///

 其中,基类的draw()方法被覆盖,这种覆盖是在导出类中发生的,但是基类构造器本来是想调用它的这个方法,结果变成了对导出类draw()方法的调用。

因此,在构造器中调用方法是非常不合适的行为,它使对象产生不稳定因素。在构造器中唯一能够安全调用的方法是final方法,因为这些方法不能被覆盖。

 

总结:使用类(new 一个新的对象)之前有三个步骤要做:

1)加载:由类加载器通过一个类的全限定名来获取定义此类的二进制字节流(即 查找字节码),(可能是.class文件,也可能是其它),载入并创建一个class对象,若该类有超类,先加载超类的.class文件;类的加载方式分为隐式加载和显式加载,用new创建对象时会隐式加载,用class.forName()是显式加载;

2)链接:1、检查:待加载的字节码的正确性,比如是否包含不良java代码;2、准备:为静态域分配存储空间3、解析:符号引用转换成直接引用;

3)初始化:为该类的超类初始化(先执行超类的静态初始化器),之后执行该类的静态初始化构造器(由静态域的赋值和静态代码块合并产生)。

三个必要步骤做完了,现在开始new一个对象:

4)先创建超类的对象,为非静态域分配存储空间;

5)超类执行非static的初始化块;

6)超类调用构造器;

7)再创建子类对象,为非静态域分配存储空间;

8)子类执行非static的初始化块;

9)子类调用构造器;

class A {
    public A(){
        System.out.println("construct class A");
    }
    {
        System.out.println("initialize class A");    
    }
    static{
        System.out.println("static class A");
    }
}
public B extends A {
    public B(){
        System.out.println("construct class B");  
    }
    {
        System.out.println("initialize class B");
    }
    static{
        System.out.println("static class B");
    }
}    
/*Output:
static class A
static class B
initialize class A
construct class A
initialize class B
construct class B
*///

 

10、数组

int[] a;    //✅
int a[];    //✅

编译器不允许指定数组的大小。现在拥有的只是对数组的一个引用,且没有给数组对象本身分配任何空间;在java中可以将一个数组直接赋值给另一个数组,因为它只是复制了一个引用。

int[] a = {1,2,3};    //✅
int[] b ;
b = {1,2,3};          //❌
int[] c;
c = new int[]{1,2,3}; //✅

用特殊初始化表达式对数组初始化,数组的存储空间的分配由编译器负责,且必须在定义数组的地方才能使用该方法;还可以用new创建数组。数组元素的基本数据类型会自动初始化为0,非基本数据类型会获得null),这是由于分配存储空间后有将存储空间清零的步骤。

Arrays.toString()方法属于java.util库,将产生一维数组的可打印版本。

 

11、方法重载

每个重载方法都必须有一个独一无二的参数类型列表;

若方法参数列表声明了相同的参数,但顺序不同,也为不同

方法接受实际较小的基本类型作为参数,若传入的实参较大,得将实参执行窄化转换,否则编译器报错。

若写的类没有构造器,编译器会自动创建一个和其类具有相同访问权限的默认构造器;若自定义了一个构造器,编译器将不再创建默认构造器。

若类中有多了构造器,且想在某个构造器中调用另一个构造器,用this,但this不能调用两个构造器,且必须将构造器的调用置于起始位置,否则编译器报错。

除构造器外,编译器禁止在其它任何方法中调用构造器。

class A{
    int a;
    A(String msg){}
    A(int T){}
    A(){                               //
        this.A("test");
        a = 10;
    }
    A(){                               //
        a = 10;
        this.A("test");
    }
    A(){                               //
        this.A("test");
        this.A(10);
    }
    void f(){                          //
        this.A();
    }
}

 

12、Thread类

实现多线程有三种方式:

1)继承Thread类,重写run()方法。Thread类实现了Runnable接口,且继承了Thread类就不能继承其它类了;

2)实现Runable接口,实现run()方法;

3)实现Callable接口,线程结束后有返回值;

 

13、持有对象(对象集合)

集合框架图(不包含Queue)                                               简化版(去除抽象类和弃用类)

   

接口:

Collection:独立元素的序列;

List:按照插入顺序保存元素,不关心重复;

Set:不能有重复元素;

Queue:按照排序规则产生顺序;

Map:键值对;Map允许将null作为键,也允许将null作为值,对于每一个键,Map只接受一次存储

Collection.addAll()只接受另一个Collection对象作为参数,而Collections.addAll()和Arrays.asList()接受可变参数列表作为参数;Collections.fill()只能替换已有的元素,不能添加新的元素;

Arrays.asList()的输出为List,但其底层表示的是数组,不能调整尺寸;若试图add()或delete(),将得到运行时异常;Collections.unmodifiableList()产生不可修改的List列表;

List<int[]> list1 = Arrays.asList(new int[] {2,3,4});
List<Integer> list2 = Arrays.asList(2,3,4);

Collection:有add, addAll, remove, removeAll, contains,containsAll, retainAll(两个List的交集), clear, toArray, isEmpty, size (不包括随机访问get)

将Collection转化为对应Array数组:

// 以List为例
List<Integer> list = new ArrayList<Integer>();
Integer[] arr = (Integer[])list.toArray(new Integer[]{});

对Array数组进行内置排序

int[] arr = new int[]{1,3,2};
Arrays.parallelSort(arr);

ArrayList:底层由数组支持,有get, set, indexOf, subList方法;

LinkedList:底层由双向链表实现;除了List,还实现了Deque接口(继承自Queue),添加了用作栈、队列、双端队列的方法;有getFirst, element, peek; removeFirst, remove, poll; addFirst, add; addLast, offer, removeLast;  

Set:具有与Collection完全一样的接口;

Set和List之间的转换:

Set<Integer> set = new HashSet<Integer>();
List<Integer> list = new ArrayList<Integer>();
set.addAll(list);                 //
Collections.addAll(set, list);    //
list.addAll(set);                 //
Collections.addAll(list, set);    //

Map:有containsKey, containsValue, keySet, values, entrySet, get, putAll;Map的键是Set, 值是Collection;

HashMap:使用了特殊的值,即hashcode来取代了对键的缓慢搜索。hashcode是相对唯一的,它通过将对象的部分信息转换生成的。所有java对象都能产生hashcode,若使用object的hashcode(),它默认使用对象的地址计算hashcode。任何键都具有equals()方法,若用于HashMap,则键还具有hashcode()方法;

HashMap的结构:一个hashcode查询数组(通常称为bucket)和多个LinkedList列表构成,其中Linkedlist的结点是Map.Entry ;通过键对象生成一个hashcode值,将其作为数组的下标,找到散列数组对应的槽位,每个槽位对应一个LinkedList列表,遍历该列表,对每一个结点使用equals()方法完全确定对象的身份。

因为散列表是一个数组,当HashMap和HashSet的负载情况达到负载因子时,容量将自动增加其槽位,实现方式是重新申请一个更大的数组,将现有对象分布到新的bucket中(再散列)。

BitSet:高效率地存储大量“开/关”信息,最小容量是long64位,若存储内容较小,则比较浪费空间,速度比本地数组稍慢。

 

14、对List<List<T>>进行排序

//即使元素类型为基本数据类型的包装类,但list.sort(null)和Collections.sort(list,null)
//只能对List<T>排序,不能对List<List<T>>排序
//若想对其排序,只能实现Comparator接口,自定义比较规则
List<List<Integer>> result = new ArrayList<List<Integer>>();
result.sort(new Comparator<List<T>>() {
    @Override
    public int compare(List<T> l1, List<T> l2) {
        return l1.get(0).compareTo(l2.get(0));     ////     if(l1.get(0)<=l2.get(0)) return 0;            ////     else return 1;

//     if(l1.get(0)<l2.get(0))return -1;              ////     else if(l1.get(0)==l2.get(0))return 0;
//     else return 1;
    }    
});              

 

15、反射

查询类型信息:instanceof和isInstance()的结果一样,指的是“你是这个类或这个类的派生类吗?”考虑了继承;而==和equals()的结果一样,指的是确切的某个实际对象类型,没有考虑继承。

对于RTTI来说,编译器在编译时就要打开和检查.class文件,而对于反射来说,.class文件在编译时不可取,在运行时才打开和检查.class文件。

java.lang.reflect类库包含field\method\constructor类,这些类型对象由jvm在运行时创建;

 

16、代理

代理是基本的设计模式,代理模式在访问实际对象时引入了一定程度的间接性,在这个间接性中,可以织入多种用途;其特征是代理类委托类同样的接口。代理类的对象本身不真正实现服务,而是通过调用委托类对象的方法提供服务

静态代理:代理类和委托类实现了同样的接口,代理类中包含接口对象,且通过构造器实现了委托类对象的依赖注入;

interface Interface{
    void doSomething();  
}
class realObject implements Interface{
    public void doSomething(){}
}
class OneProxy implements Interface{
    private Interface proxied;
    public OneProxy(Interface proxied){
        this.proxied = proxied;
    }
    public void doSomething(){
        proxied.doSomething();
    }
}    

动态代理:在动态代理上的所有调用都会被重定向到单一的调用处理器InvocationHandler上,该接口有一个invoke方法,该方法会传递进一个代理对象,,在该invoke方法内部,该方法内部会实现对代理的调用。

class DynamicProxyHandler implements InvocationHandler{
    private Object proxied;      //委托类对象
    public DynamicProxyHandler(Object proxied){
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args){
        return method.invoke(proxy,args);   //完成proxy类对象的请求转发
    }
}

class OneDynamicProxy{
    public void doConfiguration(){
        RealObject real = new RealObject();
        //Proxy.newProxyInstance()创建动态代理,它需要一个接口类的类加载器,一个希望实现的接口列表,一个InvocationHandler接口的实现
        Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[]{Interface.class}, new DynamicProxyHandler(real));
    }
}

 

17、Java内存结构和内存溢出异常

Java运行时数据区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区、运行时常量池;

1)程序计数器:记录当前执行的字节码指令的地址,字节码解释器可通过控制该值选取下一条指令;每条线程独占一个程序计数器,各线程计数互不干扰;若正执行native方法,则程序计数器值为空;

2)虚拟机栈:每个方法创建一个栈帧,存储局部变量表、操作数栈、动态链接(栈帧包含一个指向运行时常量池中方法的符号引用,转化为直接引用即为动态链接)、方法出口(返回地址)等;局部变量表编译时可知,栈帧分配的局部变量空间完全确定,运行时不变化;规定了两种异常:

  (1) 若线程请求的栈深度大于允许深度,抛出StackOverFlowError;(栈内存溢出)一般是由于程序中存在死循环或者深度递归调用造成的,

  (2)若虚拟机栈动态扩展时无法申请足够内存,抛出OutOfMemoryError; 即栈大小设置太小。

3)本地方法栈:和虚拟机栈差不多,但只为native方法服务;

4)堆:存储对象实例、数组和多个划分的线程私有的分配缓冲区TLAB;有异常:

  (1) java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,一般由于内存泄露或者堆的大小设置不当引起。内存泄漏:查看泄露对象到GCRoots对象的引用链,该引用链导致GC无法回收泄露对象。

5)方法区:存储类加载信息,常量、静态变量、即时编译器编译后的代码,主要用于对class类型的装卸,回收常量池;

  (1) java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,有大量的Class信息存储于方法区,另外,过多的常量尤其是字符串也会导致方法区溢出。

6)运行时常量池:方法区的一部分,存储编译期生成的字面值常量和符号引用(放在常量池中),解析出来的直接引用,运行时新的常量;

7)直接内存:不受Java堆大小的限制;

 

使用过程:当jvm遇到一个new指令,先检查该指令参数能否定位常量池中的一个类的符号引用,并检查该类是否被加载、解析、初始化,若无,则执行这些步骤。然后为类实例分配存储空间,并将该新分配的空间清零。

对象的访问:程序通过栈上的引用信息找到堆上具体对象地址,访问包括句柄访问和直接指针访问;

1)句柄访问:堆中划分一块内存作为句柄池,栈上引用存储句柄池中的句柄地址,句柄包含实例数据地址(指向堆中的实例)和类型数据地址(指向方法区中类信息),共三次指针定位开销;

2)直接指针访问:栈上引用存储堆中对象地址,该地址存放了类型数据的地址和实例数据本身,共两次指针定位开销。

 

18、Java垃圾收集器与内存分配策略

强引用、软引用、弱引用、虚引用;

可达性分析:以GCRoots对象(栈中的引用、类静态对象、常量对象)为起始点,确认其引用链不可达的对象是不可用的;

两次标记:第一次发生在GC发现引用链不可达的对象,做好标记,并将对象放入f_queue;第二次发生在GC遍历f_queue中的对象,做好标记;

回收方法区:废弃常量和无用类(无用类包括该类的所有实例均被回收,该类的类加载器被回收,该类的class对象没被引用)可以回收。

 

垃圾收集算法:

1)标记-清除算法:标记完成后统一回收对象,该处理将产生大量不连续内存碎片

2)标记-整理算法:标记完成后,将存活对象移动至一侧的边界,清理掉其它内存;

3)复制算法:将内存划分为大小相等两块,一次只使用一个内存块,当块中内存分配完后,将存活对象复制到另一块,然后对整个内存半块进行清理回收;或者内存划分为新生代区和老年代区,其中新生代区分为一个eden区和二个survivor区,比例为8:1:1,每次只使用eden和一个survivor(另一个survivor作为轮换备份),回收时,将存活对象一次性复制到survivor区,当该survivor区空间不足时,老年代区分配担保;

4)分代收集:新生代用复制算法,老年代用标记-整理\清除算法;

 

MinorGC和FullGC的触发条件:https://blog.csdn.net/qq_28165595/article/details/82633308

Major GC 是清理永久代,Full GC 是清理整个堆空间—包括年轻代和永久代,但许多 Major GC 是由 Minor GC 触发的;

19、jvm类加载机制

jvm加载类的过程:

当我们执行一个java程序时,

1)java.exe帮助找到jre,接着找到jre内部的jvm.dll,这是一个jvm,然后加载动态库,激活jvm;

2)jvm激活后,进行初始化;初始化完成后,产生第一个类加载器-Bootstrap Loader启动类加载器;

3)Bootstrap Loader加载Launcher.java中的ExtClassLoader扩展类加载器,并将其parent设为null;

4)Bootstrap Loader加载Launcher.java中的AppClassLoader应用程序类加载器,并将其parent设为ExtClassLoader;

加载对应类:

1)Bootstrap Loader加载存放于%JAVA_HOME%lib中的类库;Bootstrap Loader无法被java程序直接引用;

2)ExtClassLoader加载存放于%JAVA_HOME%libext中的类库;java程序可使用;

3)(默认)AppClassLoader加载用户类路径classpath上指定的类库;java程序可使用;

类加载器:将类加载阶段获取二进制字节流的动作放到虚拟机外部去实现,实现该动作的代码称为“类加载器”;

对于任意一个类,由该类和加载它的类加载器共同确立其在jvm中的唯一性;比较两个类是否相等,只有在这两个类是同一类加载器加载才有意义;否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要类加载器不同,这两个类必定是不相等的。这里的“相等”,包括使用instanceof关键字做对象所属关系判定等情况。

双亲委派模型(父子类加载器关系使用的是组合而非继承):启动类加载器<-扩展类加载器<-应用程序类加载器(默认)<-自定义类加载器 

若一个类加载器收到加载请求,先把该请求委派给父类加载器,递归到顶层,只有当父加载器反馈无法加载请求,再将请求向下传递,子类加载器尝试加载;实现双亲委派的代码都在java.lang.ClassLoader的loadClass()方法先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如java.lang.Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,那系统中将会出现多个不同的Object类,程序将混乱。如果开发者尝试编写一个与核心类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

破坏双亲委派模型:当用户使用代码热替换、模块热部署的技术,每个程序模块bundle都有一个自己的类加载器(应用程序类加载器(用户指定classpath上的类库)),当需要更换bundle时,把bundle连同类加载器一起换掉。

 

20、jvm执行引擎、热点代码、热点检测

hotspot虚拟机使用解释器jit编译器并存的架构,当程序需要迅速启动和执行时,解释器首先发挥作用,立即执行;在程序运行后,随着时间的推移,jit编译器逐渐发挥作用,把热点代码编译成本地代码(本地代码 即 特定的处理器硬件平台对应的指令代码,又叫原生型指令码),以获取更高的执行效率。解释器执行可节约内存空间,jit编译器执行可提升运行时的执行效率;

jit编译器:花费少许的编译时间来节省稍后相当长的执行时间;

参考文章:JVM即时编译(JIT)

 

21、Cookies和Session的区别

Session和Cookies同样都是针对单独用户的变量(或者说是对象好像更合适点),不同的用户在访问网站的时候都会拥有各自的Session或者Cookies,不同用户之间互不干扰。

Session 是一个抽象概念,将 User Agent 和 Server 之间一对一的交互,抽象为“会话”; Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
Cookies是一个实际存在的东西,Http 协议中定义在 Header 中的字段,它是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式(Session 因为 Session id 的存在,通常要借助 Cookies 实现);

 

22、状态码301和302的区别

共同点:用户都可以看到原url被重定向到一个一个新的url,然后发出请求;

差异:301是永久重定向,而302是临时重定向;

301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一;除非额外指定,否则这个响应也是可缓存的。

302 Found 请求的资源现在临时从不同的URI响应请求,客户端应当继续向原有地址发送以后的请求;只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。

 

posted @ 2019-05-07 16:07  thePacer  阅读(262)  评论(0编辑  收藏  举报