基础阶段面试题
一,Map相关面试题
1> Map的四种遍历方式
1,keySet()方法遍历
2,entrySet()方法加迭代器方法遍历
3,entrySet()方法加增强for遍历
4,values()方法遍历
5,代码演示
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
/*
1,map集合遍历方式:keySet方法:
*/
Set<String> set = map.keySet();
for (String key : set) {
String value = map.get(key);
System.out.println(key + "---" + value);
}
System.out.println("*****************************************");
/*
2,使用entrySet方法获取键值对然后使用增强for,方式遍历map集合
*/
Set<java.util.Map.Entry<String, String>> entries = map.entrySet();
for (java.util.Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "---" + value);
}
System.out.println("*****************************************");
/*
3,使用entrySet方法获取键值对然后使用迭代器,方式遍历map集合
*/
Iterator<java.util.Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()) {//有就取出
java.util.Map.Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "---" + value);
}
System.out.println("*****************************************");
/*
4,使用values方法直接获取出值,缺点就是拿不到键
*/
Collection<String> values = map.values();
Iterator<String> iterator1 = values.iterator();
while (iterator1.hasNext()) {
String value = iterator1.next();
System.out.println(value);
}
}
2> map集合有哪些实现类
1,TreeMap:
2,HashTable和HashMap:
3,LinkedHashMap:
4,Properties
3> Map集合的实际应用情况
Properties:用于向文本中写入文件;
4> HashMap的特性
1,Map集合中的"键"是唯一的:
需要使用hashCode() 和 equls()方法保证唯一;
HashMap存储数据是底层是基于hash表结构进行存储的,也就是使用的数组加链表的存储方式;
当存储一个元素的时候HashMap首先回去计算key的hashCode,找到对应的存储位置;
如果位置上已经有了元素就会使用equals()方法比较"键的内容"是否相同;
如果不相等就会以链表的形式"挂"到相同位置上,如果相同则不能进行存储;
所以:当Map的"key"位置存储的是自定义对象的时候,我们最好重写hashCode和equls方法保证唯一性;
(截图.docx 1.0.png)
public class HashEquals {
public static void main(String[] args) {
HashMap<Person, String> map = new HashMap<>();
map.put(new Person("1", 23), "北京");
map.put(new Person("2", 23), "上海");
map.put(new Person("3", 23), "深圳");
map.put(new Person("4", 23), "天津");
map.put(new Person("4", 23), "青海");//key重写hashCode和equals方法之后就不能存储了
System.out.println(map);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
2,不保证元素存取的顺序
5> HashMap和HashTable的区别
相同点:
都是存储的键值对;
key都不能重复;
不同点:
1,HashMap允许存储null键和null值;Hashtable不允许存储null键和null值;
2,HashMap是不同步的,线程不安全,存储效率高;HashTable是同步的,线程安全,存储效率低;
3,出现的时间不同,Hashtable是jdk1.0出现的;HashMap是jdk1.2出现的;
同步单线程情况下我们一般使用HashMap,多线程下我们一般使用CurrentHashMap代替Hashtable;
但是Hashtable的子类Properties经常配合IO流进行配置文件的读取;
6> HashMap延伸
jdk8版本之后HashMap存储数据结构进行了优化,链表下挂的元素比较多的时候就会将链表提升为二叉树;
优点:查询速度更加快了;
(截图.docx 1.1.png)
7>应用场景
统计同一件商品出现的次数:
public class MapUse {
public static void main(String[] args) {
HashMap<Goods, Integer> map = new HashMap<>();
Goods goods0 = new Goods("饼干", 10);
Goods goods1 = new Goods("饼干", 10);
Goods goods2 = new Goods("糖果", 12);
Goods[] arr = new Goods[3];
arr[0] = goods0;
arr[1] = goods1;
arr[2] = goods2;
for (Goods goods : arr) {
if (!map.containsKey(goods)) {
map.put(goods, 1);
} else {
map.put(goods, map.get(goods) + 1);
}
}
//输出商品和数量
Set<Map.Entry<Goods, Integer>> entries = map.entrySet();
for (Map.Entry<Goods, Integer> entry : entries) {
Goods key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "----" + value);
}
}
}
class Goods {
private String name;
private int price;
public Goods(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" + "name='" + name + '\'' + ", price=" + price + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Goods goods = (Goods) o;
if (price != goods.price) return false;
return name != null ? name.equals(goods.name) : goods.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + price;
return result;
}
}
二,java虚拟机中的内存模型
1> java虚拟机中有几块内存空间
java虚拟机中主要有四块内存空间,分别是:堆内存,栈内存,方法区,寄存器;
"方法区"是字节码加载时所进入的空间,其内部又分为"静态区"和"非静态区";
"堆内存"中存储的都是我们"new"出来的东西,也就是我们的对象;
"栈内存"是"方法执行"所在的空间,还可以用于存放一些局部变量;
1,堆内存;
2,栈内存;
3,方法区;
本地方法区:加了"native"关键字的方法都是调用了本地方法区的方法;
4,寄存器;
执行流程:
当一个类加载的时候,首先会在方法区中加载这个类的字节码文件(.class),将此类中的静态方法,静态成员存储到静态区,不是静态的存储到非静态区;
然后当一个方法执行的时候会进入到栈内存中;
当方法中遇到new对象操作的时候,会首先加载这者对象的字节码文件到方法区,然后在堆内存中开辟一片空间,并将字节码中的非静态成员,以及非静态方法的地址存储到堆内存中,并对非静态成员进行初始化;
当方法执行完毕之后会将此方法从占内存中进行弹栈,这样一个方法就执行完毕了;
当程序执行完毕堆内存中开辟的空间就会成为垃圾等待垃圾回收机制的回收;
(截图.docx 2.0.png)
2> 问题扩展(线程内存)
1,每一个线程都有自己独立的栈内存空间;
2,堆内存中的数据是被多个线程所共享的;
当我们开启多个线程的时候,每调用一次start方法就会去开辟一块栈内存,去执行这个线程所对应的run方法;
但是run方法中使用的堆内存中的成员变量是共享的;
如果第一个线程修改了值,第二个线程再去调用的时候就会是改变后的值;
(截图.docx 2.1.png)
三,java异常处理机制的原理和应用
1> 什么是异常
异常就是程序的不正常,简单来说就是程序所发生的错误;
2> 异常的体系结构和分类
"体系结构":
-Throwable
-ERROR : 严重的错误,不是异常;(递归的时候不留出口就会出现"StackOverflowError")
-Exception : 异常的老祖宗
-RuntimeException
-运行时异常(只要是RuntimeException的子类都是运行时异常)
-!RuntimeException
-编译时异常(不是运行时异常就是编译时异常)
"分类主要有":
编译时异常和运行时异常;
"编译异常":就是在程序编译期间,编译器检查出某一段代码可能出现异常,需要我们提前对代码做出解决方案,否则程序编译不会通过;
"运行时异常":就是编译通过了,但是在运行的时候出现了异常,我们最常见的就是数组的索引越界问题,编译是通过的,但是运行的时候就会抛出"IndexOutOfBoundsException";
3> 异常产生的原理
这个问题首先得说一下java对异常的默认处理方式了,java默Q1认会将问题抛给上一级,
当我们的程序遇到异常的时候,首先java会根据错误信息产生异常类,创建该类的对象,底层在通过throw关键字
将异常不断抛出给上一级,直到抛给了jvm虚拟机之后,虚拟机就会在控制台打印出异常信息,告诉用户,你TM程序出异常了,我处理不了!!!
4> 异常的处理方式有哪些?
1,当我们遇到的异常自己能解决的时候使用try..catch进行处理;
try{}catch(){};try..catch..处理异常之后不会影响后续代码的运行;
2,当有一些自己处理不了的异常就使用throws进行抛出
需要注意的是:当使用throw抛出Exception异常的时候需要在方法使用throws声明,告知调用者此方法存在异常,
当throw 的是RuntimeException 的时候不需要使用throws声明
四,创建线程的几种方式
1> 继承Thread类
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo thread1 = new ThreadDemo();
thread1.setName("t1");
thread1.start();
ThreadDemo thread2 = new ThreadDemo();
thread2.setName("t2");
thread2.start();
}
}
class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
}
}
}
2> 实现Runnable接口
public class Runnable {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread t1 = new Thread(runnableDemo, "线程1");
t1.start();
Thread t2 = new Thread(runnableDemo, "线程2");
t2.start();
}
}
class RunnableDemo implements java.lang.Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
}
}
}
3> 实现Callable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//多态形式创建Callable接口实现类对象
Callable<Object> callable = new CallableDemo();
//FutureTask封装Callable接口实现类
FutureTask<Object> oneTask = new FutureTask<Object>(callable);
FutureTask<Object> twoTask = new FutureTask<Object>(callable);
//创建线程
Thread t1 = new Thread(oneTask, "线程1");
t1.start();
//获取返回值
System.out.println(oneTask.get());
Thread t2 = new Thread(twoTask, "线程2");
t2.start();
System.out.println(twoTask.get());
}
}
//实现Callable接口并覆盖call方法
class CallableDemo implements Callable<Object> {
@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
return Thread.currentThread().getName() + "--执行完毕";
}
}
4> 线程池方式
public class ThreadPool {
public static void main(String[] args) {
//创建线程池并指定创建线程的数量
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new ThreadPoolDemo());
pool.submit(new ThreadPoolDemo());
//关闭线程池
pool.shutdown();
}
}
class ThreadPoolDemo implements java.lang.Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
四种方式的优缺点:
"继承Thread类的方式":
优点:实现简单,代码量少;
缺点:每次开启线程都需要创建一个线程对象,浪费堆内存空间,因为java单继承的特点导致继承了Thread的子类没有其他的类可以再继承了;
"实现Runnable接口的方法":
优点:由于java单继承多实现的特点,还可以继承其他父类,统一实现改接口的实例可以共享资源;
缺点:实现代码相对复杂;
"实现Callable接口的方式":
优点:有返回值可以使用;
缺点:实现代码相对复杂;
"线程池方式":
优点:可以容纳多个线程,实现自动化装配,线程可以循环使用;
缺点:代码实现相对复杂;
5> Lock与synchronized相比优点是什么?
1,能够显示的获取和释放锁,运用灵活:
lock():加锁
unlock():释放锁
2,可以方便的实现公平锁:
公平锁就是哪个线程线程先执行到就先获得锁,非公平锁就是先到了也不一定获得锁,是靠抢的;
非公平锁可以大大提高线程执行效率;
6> 具体使用哪种方式实现多线程比较好?
这要看我们额需求了:
如果我们想要保证线程安全,那就别用多线程了;
如果想要提高使用效率,减少资源的浪费就使用"线程池"的方式;
如果你想要拿到线程执行的结果,我们就使用实现"Callable接口"的方式;
五,垃圾回收机制
1> 什么样的对象会被当做垃圾?
一个对象成为垃圾也就是没人要的时候;在Java中也就是没有变量指向这个对象的地址了,它也就成了垃圾;
2> 如何检验垃圾是否被回收
检验垃圾是否被回收的我们可以让对象继承Object并重写方法finalize();这个方法会在垃圾回器执行的时候自动调用;
进行垃圾的回收:
public class RubbishTest {
public static void main(String[] args) {
for (int i = 0; i < 5000000; i++) {
new RubbishDemo();
}
}
}
class RubbishDemo extends Object{
@Override
protected void finalize() throws Throwable {
System.out.println("垃圾太多了,爸爸帮你扫一下");
}
}
3> 怎样通知垃圾回收器来收垃圾
我们可以使用System的静态方法来实现;System.gc();
public class RubbishTellTest {
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Rubbish();
System.gc();//叫垃圾回收器来收垃圾!!(不需要等垃圾堆积的多了让回收器自动清扫)
}
}
}
class Rubbish extends Object{
@Override
protected void finalize() throws Throwable {
System.out.println("别叫了,爸爸来了!!!");
}
}
4> 垃圾回收机制扩展
注意事项:
1,我们在创建对象的时候,尽量不要在一个对象中存储太多的东西;因为垃圾回收器在回收垃圾的时候会先将垃圾进行压缩,
如果垃圾对象占用内存太大的话垃圾回收器,就不会进行压缩,因为这样会浪费太多的CPU处理时间;
2,不要频繁的new 生命周期很短的对象:
因为频繁的内存压缩回收,可能会导致很多的内存碎片;
垃圾回收器的检索垃圾的方法:
java虚拟机底层使用的是跟搜素的方法来判断一个对象是不是垃圾的;
(截图.docx 5.1.png)
六, HashCode()和euquls()的区别?
1> HashCode()和equals()的作用
hashCode()和euqals()方法都属于Object的方法:
hashCode()方法如果不进行重写返回的是对象的地址值;
equals()方法如果不进行重写返回的是对象的地址值是否相等;
如果对象进行了重写了这两个方法:
那么只要equals()方法比较是相等的,那么hashCode()方法肯定是相等的,也就是equals是绝对可靠的,这是因为两个对象进行比较是首会比较hashCode()是否相等,如果相等才会比较equals方法;
如果两个对象通过hashCode比较是相等的,但是equals()比较是不一定相等的;也就是说hashCode方法是不一定可靠地;
2> 通过hashCode和equals配合使用比较对象是如何提高效率的?
首先我们需要知道要想可靠的比较两个对象是否相等,如果只使用hashCode或者equals中的一个方法,那么我们就只能equals方法(绝对可靠);
但是对象之间重写equals之后是通过成员变量的进行比较的,当一个对象的成员变量比较多的时候我们使用equals比较效率就会比较低;
配合使用的情况下,两个对象会首先比较两个对象的hashCode进行比较两个对象的hash值,如果不相等就不再进行下面equals比较了,如果相等,再进行equals比较这样既能判断两个对象是否相等,又能提高效率;
3> HashSet集合如何保证元素的唯一性?
当添加元素的时候,底层首先会先计算出两个元素的hash值,如果hash值不相等就能进行添加;
如果hash值相等,再使用equals方法比较内容是否相等,如果相等就不能进行添加;
所以当我们向HashSet集合中存储对象元素的时候要想保证元素的唯一性就必须重写hsahCode和equals方法;
七,String StringBuilder,StringBuffer之间的区别?
String是一个最终类,只要对象创建就不会发生改变;
StringBuilder和StringBuffer是一个可以改变的字符串序列;
StringBuilder是jdk1.5之后出现的StringBuffer是jdk1.0出现的;
StringBuilder是不同步的,多线程不安全,Stringbuffer是同步的多线程安全;
我们在使用Sql语句的拼接的时候一般使用StringBuilder进行拼接,这样效率更高;
八,是否可以从一个静态方法内部发出对非静态方法的调用?
1> static关键字的特点
首先我们需要知道static关键字的特点:
1,静态成员被所在类的对象共享;也就是说,我们创建一个静态成员并赋值,那么我们创建的对象都可以访问这个成员变量,而且值都一样;
2,静态成员方法可以通过类名进行调用;
3,静态成员随着类的加载而加载;
4,静态成员优先于对象存在;
先进入内存的不能调用后进如内存的;
2> static方法的访问特点
静态方法只能调用静态成员(方法和成员变量)
非静态方法可以调用静态或者非静态成员;
原因:
静态方法是随着类的加载而加载,是属于类的方法,所以静态方法可以直接使用类名进行调用,非静态方法是属于对象的方法,
因为类优先于对象的存在,当静态方法加载进内存的时候非静态方法还没有加载进内存,先进内存的不能调用后进内存的,所静态方法无法访问非静态方法;想要访问非静态方法就需要创建对象使对象进行调用;
3> 静态方法以及private修饰的方法可以被重写吗?
我们知道父类的私有方法和静态方法是不能被重写的,原因在于:
私有方法只能在本类中使用访问,而静态方法在只能被继承,但也不能被重写,如果子类中有一个和父类一模一样的静态方法;
那么该子类就会把父类的中的方法引隐藏,简单来说,父类的静态方法和子类的静态方法是没有关系的;
4> 静态方法中能不能使用this关键字?
答案是不能的,因为this代表的是对象,静态方法随着类的加载而加载,也就是说静态成员优先于对象存在,所以静态方法不能使用this关键字;
5>抽象方法可以被static修饰吗?
不能:原因是抽象方法是让子类去重写的,而静态方法不能被重写;可以试想一下,如果一个抽象方法可以被static修饰,那么就可以使用类名进行调用,那么调用一个没有方法体的方法有什么意义呢?
6>静态内部类和非静态内部类的区别?
成员变量:
静态内部类:可以定义非静态和静态成员变量;
非静态内部类:只能定义非静态成员;
成员方法:
静态类内部类可以定义静态和非静态方法;
非静态内部类中只能定义非静态方法;
非静态方法的访问区别:
静态内部类:可以访问内部类的静态和非静态方法,但只能访问外部类的静态方法;
非静态内部类:可以访问内类的非静态方法,外部类的静态和非静态方法;
7> 静态代码块的特点及作用
特点:随着类的加载而加载,优先于构造方法执行,并且只执行一次;
作用:用于给类进行初始化,加载驱动等.
九,wait方法和sleep方法的区别
1> 区别
调用方式不同:
wait方法来自于Object必须由锁对象进行调用;
sleep方法来自于Thread类,可以直接使用类名.的方式进行调用;
sleep(毫秒值);wait(毫秒值):
如果调用的是wait的有参数的方法,其实和sleep方法效果是一样的,但是有一点就是wait方法必须由对象进行调用比较麻烦;锁对象还必须存在于同步代码块中;
如果调用的wait是无参数的那么这个就需要手动进行(notify())唤醒了,否则他就会一直等待下去,不会醒来;
需要注意的是,wait()无参方法会自动释放自己的锁对象,因为他是拿着锁进去的,只有释放了锁,CPU才会有机会切换到其他线程上,这样才能唤醒它;然而sleep方法在休眠的时候是不会释放锁的;
2>notify()和notifyAll()方法的区别
前者只会随机唤醒一个线程,后者会唤醒所有等待的线程;
十,String类相关面试题
1> String属于什么数据类型?
基本数据类型:byte,short,int,long,float,double,char,boolean;
引用数据类型:数组,类,接口等;
String是被final的引用数据类型;
2> String类中有哪些常用的方法?
boolean equals(Object obj);//比较两个字符串的内容是否相等;
int length();//获取字符串的长度,其实就是字符个数;
boolean contains(String s);//判断是否包含传入的字符串;
String substring(int start);//从开始索引截取到最后
String substring(int start,int end);//返回从start开始到指定索引的字符串;注意包含头不包含尾
String[] split(String regular);//根据指定规则截取字符串;
3> String创建对象有什么特点?
//首先我们已经创建字符串会保存在常量池中,程序结束后由系统释放;
问题: String str = new String("abc");这样创建会创建几个对象?
/*
这种情况要看我们常量池中是否已经存在了"abc"这样一个字符串,
如果已经存在那么new 之后就只会在堆内存中创建一个对象,
如果常量池中还没有这样一个字符串,那就是创建了两个对象;
*/
4> String应用场景有哪些?
/*
举例:
String会被用于系统中的登录注册相关功能等;一般牵扯到字符串的截取拼接,比较等都会使用String;
*/
十一,多线程中解决同步问题的方式
1,使用同步代码块方式;
2,使用同步方法方式;
3,lock锁方式;
十二,单例设计模式中懒汉式和饿汉式的的区别?
概念:
懒汉式:在类加载的时候不被初始化;
饿汉式:在类加载的时候就完成了初始化,但是加载比较慢,获取对象比较快;
饿汉式是线程安全的:在类创建好一个静态对象提供给系统使用,懒汉式模式在创建对象时不加上
synchronized 会导致对象的访问不是线程安全的.从实现方式来讲他们最大的区别就是懒汉式是延时加载,在需要的时候
才创建对象,而饿汉式在虚拟机启动的时候就会创建;
代码:
//饿汉式单例设计模式
public class Single01 {
private static Single01 single01 = new Single01();
public Single01() {
}
//获取实例
public static Single01 getInstance() {
return single01;
}
@Override
public String toString() {
return "饿汉式";
}
}
//单层同步懒汉式单例设计模式
public class Single02 {
private static Single02 single02 = null;
public Single02() {
}
//获取实例注意必须要加同步保证线程安全
public static synchronized Single02 getInstance() {
if (single02 == null) {
single02 = new Single02();
}
return single02;
}
@Override
public String toString() {
return "懒汉式";
}
}
//双重同步懒汉式单例设计模式
public class Single03 {
private static Single03 single03 = null;
public Single03() {
}
//获取实例注意必须要加同步保证线程安全
public static synchronized Single03 getInstance() {
if (single03 == null) {
synchronized (Single03.class) {
if (single03 == null) {
single03 = new Single03();
}
}
}
return single03;
}
@Override
public String toString() {
return "双重同步懒汉式";
}
}
//分别调用
public class SingletonDemo {
public static void main(String[] args) {
Single01 single01 = new Single01();
System.out.println(single01);
Single02 single02 = new Single02();
System.out.println(single02);
Single03 single03 = new Single03();
System.out.println(single03);
}
}
十三,解决前端工程访问后台工程的跨域问题
跨域,指的是浏览器不能执行其他网站的脚本。
它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。
所谓同源是指,域名,协议,端口均相同
18.1,通过注解的方式允许跨域
非常简单,我们可以在Controller类或其方法上加@CrossOrigin注解,来使之支持跨域。
举例:
@CrossOrigin(origins = "*", maxAge=3600)
@RestController@RequestMapping("/User")
public class UserController {
}
其中origins为CrossOrigin的默认参数,即跨域来源,*即任何来源,也可以是其他域名。即可以以以下形式:
@CrossOrigin("http://test.com")
@CrossOrigin(origins="http://test.com",maxAge=3600)
该注解用于方法上,写法相同,处理时,SpringMVC会对类上标签和方法上标签进行合并。
18.2, 通过配置文件的方式允许跨域
在web.xml中添加如下配置:
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用这个Filter即可让整个服务器全局允许跨域。
18.3,jsonp解决跨域
jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
1.AJAX直接请求普通文件存在跨域无权限访问的问题,不管是静态页面也好.
2.不过我们在调用js文件的时候又不受跨域影响,比如引入jquery框架的,或者是调用相片的时候
3.如果想通过纯web端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进js格式的文件里.
4.而json又是一个轻量级的数据格式,还被js原生支持
5.为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback 参数给服务端,
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'onBack'
}).then((res) => {
console.log(res);
})
18.4,nginx进行代理
前端发布在nginx,域名和端口是nginx域名和端口
后端接口通过nginx去转发,访问后端接口也是访问nginx域名和端口
18.5,通过gateway网关配置也可解决跨域问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46Zr03IT-1582816234142)(E:\文档\面试题总结\img\搜狗截图20200205222305.png)]
***javaWeb阶段面试题
1,Cookie和Session的区别:
Cookie和Session的区别其实还要从HTTP协议来说起,因为Http协议的无状态特点使得客户端和服务端交互变得麻烦了,所以CookieheSession就出现了,这样COKIE和Session的区别就好说了:
1,简单来说Cookie保存在客户端,Session保存服务器端;
2,一个浏览器最多可以创建20个Cookie,并且一个Cookie最多key保存4k的数据;
3,Cookie相对Session来说不是很安全,我们一般将比较敏感的数据保存在Session中,
4,Session会在服务器上保留一段时间就会占用服务器的内存,对系统的性能造成影响,所以我们可以将不重要的信息保存Cookie中这样可以减轻服务器的压力
2,http协议
/*
概念:
http协议超文本传输协议,它是一种基于请求和响应模式的应用层协议基于TCP/IP通信协议建立连接的,主要特点就是:简单快速,灵活,无连接,无状态;
所谓无连接就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,减少资源浪费;
所谓无状态就是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态;也就是说我们给服务端发送请求之后服务端会根据请求将数据返回给客户端,但是不记录下来状态,下次浏览器请求的时候如果还需要有前面的请求就需要重新发送,请求的携带的数据量就会更大;
请求包括请求行(),请求头,请求空行,请求数据(一包在post请求中使用)
响应包括:响应行(包括:http版本,状态码,等信息),响应头,响应空行,响应数据(封装返回的数据)
3,get和post请求的区别
/*
1,首先get和post都是http协议的两种请求方法;
2,get请求参在地址栏,post请求参数在请求体中;
3,get请求参数在地址栏相对不安全,post请求相对安全;
4,get请求因为参数在地址栏所以有大小限制,post请求你有大小限制;
*/
4,Servlet生命周期
1,加载和实例化;Servlet容器负责加载和实例化Servlet.
2,初始化;Servlet实例化之后会调用init()方法进行Servlet的初始化,并且只执行一次;
3,请求处理;Servlet容器调用Servlet的service()方法执行处处理请求;
4,服务终止;当容器检测到一个Servlet的实例应该从服务中被移除的时候会调用destroy()方法终止服务;
5,JSP和Servlet的区别
JSP是servlet技术的扩展,本质上就是Servlet的建议方式;
JSP编译后生成的就是”类servlet”;
区别就是:Servlet侧重于逻辑代码实现,JSP侧重于数据的动态显示;
3,get和post请求的区别
/*
1,首先get和post都是http协议的两种请求方法;
2,get请求参在地址栏,post请求参数在请求体中;
3,get请求参数在地址栏相对不安全,post请求相对安全;
4,get请求因为参数在地址栏所以有大小限制,post请求你有大小限制;
*/
4,Servlet 面试
/*
1,加载和实例化;Servlet容器负责加载和实例化Servlet.
2,初始化;Servlet实例化之后会调用init()方法进行Servlet的初始化,并且只执行一次;
3,请求处理;Servlet容器调用Servlet的service()方法执行处处理请求;
4,服务终止;当容器检测到一个Servlet的实例应该从服务中被移除的时候会调用destroy()方法终止服务;
*/
5,JSP和Servlet的区别
/*
首先收一下Servlet定义然后说一下JSP定义
最后讲解区别
*/