• java开发总体知识复习


    上一篇发了一个找工作的面经, 找工作不宜, 希望这一篇的内容能够帮助到大家.

    对于这次跳槽找工作, 我准备了挺长的时间, 其中也收集了很多比较好的笔试面试题, 大都是一些常用的基础, 很多都是由于时间原因没有来得及给出答案, 但是题目大都是比较经典实用的, 现在都放到这里, 希望对正处于找工作的博友有一定的帮助.


    第一部分: Java基础(此部分面试题题目来自:http://www.hollischuang.com/archives/10 答案是搜集与互联网)
    (为了方便,我把他们分了类,有一些是必看的,我用!标注,有一些进阶型的我用%标注,有一些需要了解的,我用?标注。)

    一:继承、抽象类与接口区别、访问控制(private, public, protected,默认)、多态相关
    !1、interface和 abstract class的区别
    interface是接口,abstract class是抽象类。

    1,语法层次
    抽象类中可以拥有任意范围的成员数据,可以定义非抽象方法。而接口中只能拥有静态的不能修改的成员数据,同时所有的方法必须是抽象的。
    所以说接口是抽象类的一种特例。它们都不能new,但是可以声明数据结构,接受子类对象赋值

    2,跨域不同
    a,抽象类是对类的整体进行抽象,包括类的属性和行为。接口是对类的局部(行为)进行抽象。
    b,抽象类是is-a,跨域的是具有相似特点的类。接口是like-a,可以跨域不同的类。(域是指同一个物种)
    例如猫、狗可以抽象一个动物类的抽象类,具备叫的方法。鸟、飞机可以实现Fly接口,具备飞的行为。

    3,设计层次
    抽象类是自下而上的一种设计思想,而接口是自顶而下的一种设计思想。 
    抽象类中我们要知道子类才能抽象出父类。而接口不同,它只需要定义一个规则即可。

    !2、是否可以继承多个接口,是否可以继承多个抽象类
    java可以实现多个接口,对于类是单继承体系结构。

    %3、Static Nested Class 和 Inner Class的不同

    静态内部类没有了指向外部的引用,可以直接被实例化而不需要依附与外部类的实例化。                                                                                            

    非静态内部类保留了指向外部的引用,必须依附于外部类的实例化才能够实例化内部类。(内部类中不许有静态成员)
    静态嵌套类内部(就是静态内部类)中:内部类不能访问外部类的非静态成员。外部类不能直接访问静态类中的属性,需要通过内部类去访问。(可以有)
    非静态内部类中:内部类可以直接访问外部类的属性成员。外部类不能直接访问静态类中的属性,需要通过内部类去访问。
    延伸:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个类,所以无论外围类是否已经继承某个类,对内部类都没有影响。
    内部类:成员内部类(直接在外部类中,地位与方法,成员并列)、局部内部类(内部类在方法或者作用域中,地位与局部变量并列)、嵌套内部类(static 修饰的内部类,地位与类变量并列)、匿名内部类(参考21天学通java

    这里匿名内部类是没有名字的内部类,它的定义为new A(){  },这里A是匿名内部类的父类或继承的接口,关于更加详细的匿名内部类请参考21天学通java,  匿名类是没有默认构造器的类,而其他内部类具有默认构造器,因此匿名类要初始化它的局部变量i时,要在块域中,即new A(){{i;} fangfa(){} }              同时要记住:内部类可以直接调用外部类的成员是因为内部类隐藏了外部类的this,而外部类调用内部类时要通过内部类实例对象来间接实现

    这里需要说明一点:局部内部类和匿名内部类访问类的所有(不止所在方法的)局部变量时为何必须加final关键字?
    局部变量的生命周期与局部内部类的对象的生命周期的不一致性。例如内部类innerClass在方法f()中,而方法f()中定义局部变量i且被内部类使用。
    当方法f()运行结束后,局部变量i就已经死亡不存在了,但局部内部类对象可能还存在(直道没有人再引用该对象才会消亡),这时出现一种情况就是
    局部内部类要访问一个已经不存在的局部变量,可能引起异常。所以需要局部变量被final修饰,这时局部变量的作用域同时它不会改变(起到安全性)。

    !4、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
    overload是一个类多态性的表现,override是父类与之类多态性的不同表现。
    override:子类中定义与父类相同的名称及签名. overload:方法相同方法签名不同。
    注意:不能通过访问权限、返回类型、抛出的异常进行重载

    !5、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

    都不可以,因为abstract申明的方法是要求子类去实现的,abstract只是告诉你有这样一个接口,你要去实现,至于你的具体实现可以是native和synchronized,也可以不是,抽象方法是不关心这些事的,所以写这两个是没有意义的。然后,static方法是不会被覆盖的,而abstract方法正是要子类去覆盖它,所以也是没有意义的。所以,总的来说,就是java语法不允许你这样做,事实上,也没有意义这样做。

    static 修饰的方法不能够被重写。


    !6、是否可以继承String类
    不可以,String是被final修饰的类。

    !7、构造器Constructor是否可被override?
    构造器不能被继承,所以也不能够被重写。

    !8、作用域public,protected,private,以及不写时的区别?这里主要体现类中成员被使用的权限,而类的权限只能是public,abstract,final
    不写时默认是default,这里主要说明这几个作用域的使用范围。
    作用域               当前类A 同一package 子孙类 其他package        (要知道继承权限与访问权限是两个概念)
    public                    √             √            √            √
    protected              √             √            √            ×  (这个子孙类在其他包中)(这个说明,其他包的类B不能访问这个变量,但是B能够继承类A)
    friendly(default)     √             √            ×            ×      
    private                   √            ×            ×            ×
    这里需要说明的是在同一个package, public、protected、friendly使用范围一致。
    而在其他package中,只有子孙类中protected才能被访问。


    二:collections相关的数据结构及API
    http://cmsblogs.com/?p=106
    !1、列举几个Java Collection类库中的常用类
    此处应该有Collection类图。
    Collection是java.util 中的一个接口。继承自Iterable。
    子接口:List、Set、Queue...
    实现类:ArrayList、LinkedList、HashSet、TreeSet、Vector、Stack
    其他相关类:Iterator、TreeMap、HashTable、HashMap
    Collection接口是最基本的集合接口,它不提供直接的实现,Java SDK提供的类都是继承自Collection的"子接口"
    如List和Set。Collection所代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。
    如有些允许重复而有些则不能重复、有些必须要按照顺序插入而有些则是散列,有些支持排序但是有些则不支持。

    !2、List、Set、Map是否都继承自Collection接口?
    List、Set继承自Collection接口,而Map不是。
    (1)List 所代表的是有序的Collection。实现List接口的集合主要有:ArratList、LinkedList、Vector、Stack。
    (2) Set是一种不包括重复元素的Collection。实现了Set接口的集合有:EnumSet、HashSet、TreeSet。
    (3)Map与List、Set接口不同,它是由一系列键值对组成的集合,提供了key到Value的映射。同时它也没有继承Collection。
    实现map的有:HashMap、TreeMap、HashTable、Properties、EnumMap。

    !3、HashMap和Hashtable的区别
    需查看源码。
    1、历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 。
    2、同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 。
    3、值:只有HashMap可以让你将空值作为一个表的条目的key或value 。

    1.HashTable的方法是同步的,在方法的前面都有synchronized来同步,HashMap未经同步,所以在多线程场合要手动同步
    2.HashTable不允许null值(key和value都不可以) ,HashMap允许null值(key和value都可以)。
    3.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
    4.HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。
    5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
    6.哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    而HashMap重新计算hash值,而且用与代替求模:

    1 int hash = hash(k);

    2 int i = indexFor(hash, table.length);

    3 static int hash(Object x) {

    4 h ^= (h >>> 20) ^ (h >>> 12);

    5 return h ^ (h >>> 7) ^ (h >>> 4);

    6 }

    &&延展:
    A、HashMap与HashSet的关系与区别
    1、HashSet底层是采用HashMap实现的:

    public HashSet() {

    map = new HashMap<E,Object>();

    }

    2、调用HashSet的add方法时,只是存储value,HashMap使用put添加而存储key-value

    private static final Object PRESENT = new Object();

    public boolean add(E e) {

    return map.put(e, PRESENT)==null;

    }

    public boolean remove(Object o) {

    return map.remove(o)==PRESENT;

    }

      

    B、HashMap 和 ConcurrentHashMap 的关系
    关于这部分内容建议自己去翻翻源码,ConcurrentHashMap 也是一种线程安全的集合类,他和HashTable也是有区别的,主要区别就是加锁的粒度以及如何加锁,ConcurrentHashMap 的加锁粒度要比HashTable更细一点。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    %4、HashMap中是否任何对象都可以做为key,用户自定义对象做为key有没有什么要求?
    用户自定义的对象当作key需要实现Map中的hashCode和Equals方法。
    HashMap用可以的哈希值来存储和查找键值对。
    当插入一个Entry时,HashMap会计算Entry Key 的哈希值。Map会根据这个哈希值把Entry插入到相应的位置。
    查找时,HashMap通过计算Key的哈希值到特定的位置查找这个Entry。
    如果我们在使用自定义对象做为Key时,我们需要保证当改变对象的状态的时候,不改变它的哈希值。

    !5、Collection 和 Collections的区别
    Collection 是一个接口,它是各种集合结构的父接口。
    Collections是一个包装类,它包含有各种有关集合操作的静态方法。Collcetions不能被实例化,它的构造函数是私有的。
    例如:
    sort():专门针对LIST进行排序,在实际开发中,我们确实经常需要对一个装有一些对象的LIST进行排序!
    min/max():如果,我们想取一个集合中的最小、最大值,如何快速的取到呢?上面的方法将帮我们实现。
    reverse():如果,我们仅仅需要得到一个LIST的相反顺序!
    Shuffle():通过这个方法,将使得LIST中元素的顺序不可预测,即顺序是随机的,混排的。
    synchronizedList():返回指定列表支持的同步(线程安全的)列表。
    ...

    %6、其他的集合类:concurrenthashmap,treemap,treeset,linkedhashmap等。
    (1)ConcurrentHashMap:http://www.cnblogs.com/ITtangtang/p/3948786.html
    (2)TreeMap:
    (3)TreeSet:
    (4)LinkedHashMap:


    三:异常体系
    !1、Error、Exception和RuntimeException的区别,作用又是什么?列举3个以上的RuntimeException
    Throwable是java语言中所有错误和异常的超类。它有两个子类:Error、Exception。
    Error为错误的意思,是程序无法处理的,如OutOfMemoryErro、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择中止线程。
    Exception是程序可以处理的异常,分为CheckedException(受检异常),另一种是UncheckedException(不受检异常)。其中CheckExpection是发生在编译阶段,必须要使用try...catch(或者throws),否则编译不通过。
    而UnceckedException发生在运行期,具有不确定性,主要是由程序的逻辑问题引起的,难以排查,我们一般需要纵观全局才能够发现这类的异常错误。所以在程序设计中我们需要认真考虑,尽量处理异常,即使产生了异常,也能尽量保证程序朝着有利方向发展。


    !2、Java中的异常处理机制的简单原理和应用
    (1)当Java程序违反了Java的语义规则时,Java虚拟机就会将发生的错误表示为一个异常。
    违反语义规则包括2种情况:
    (a)一种是Java类库内置的语义检查。
    (b)另一种情况就是Java允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。
    (2)所有的异常都是java.lang.Throwable的子类。

    !3、内存溢出和内存泄露
    http://wade6.iteye.com/blog/1842907
    内存溢出:指在指定大小的内存空间,写入了超出大小的数据,从而导致了内存益处。通俗地说,就是内存不够,没办法支持当前程序。
    当发生内存益出时,程序将无法进行,强制终止。
    内存泄露:指某个程序已不再执行,却始终占用着内存,不释放,从而消耗着资源,称其为内存泄露。
    当发生内存泄露,那么可用内存会逐渐减少,从而降低性能。

    (a)对于内存的溢出可能发生的情况,大概有几种:
    1、在程序中存在死循环,或者循环过多,而产生了过多重复的对象的实例。
    2、存在对象的引用,使用完后没有清除,导致Java虚拟机不能回收。
    3、一次操作时,在内存中加载了大量的数据,原则上说,在java中,由于它的自动垃圾回收机制,出现内存溢出的可能性并不是很大。

    (b)对于内存泄露可能发生的情况,大概有几种:
    1、长生命周期的对象持有短生命周期的引用。
    这是内存泄露最常见的场景,也是代码设计中经常出现的问题。
    例如:在全局静态map中缓存局部变量,且没有清空操作,随着时间的推移,这个map会越来越大,造成内存泄露。

    2、修改hashset中对象的参数值,且参数是计算哈希值的字段。

    当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。

    3、机器的连接数和关闭时间设置。
    长时间开启非常耗费资源的连接,也会造成内存泄露。

    解决方案:
    1、尽早释放无用对象的引用
    2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域
    3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收
    4、避免在循环中创建对象
    5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

    四:其他
    !1、String和StringBuffer、StringBuilder的区别
    http://www.cnblogs.com/xudong-bupt/p/3961159.html
    1,可变与不可变化性
    String类中使用字符数组保存字符串,因为有final修饰,所以string是不可变的。
    private final char value[];
    关于不可变性:http://www.hollischuang.com/archives/1230
    StringBUilder和StringBuffer的公共父类是:AbstracStringBuilder类,在AbstracStringBu中也是使用字符数组保存字符串,可知这两种对象都是可变的。
    char[] value;

    2,是否多线程安全
    String中对象是不可变得,也可以理解为常量,显然是线程安全的。
    StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
    StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

    !2、String s = “123”;这个语句有几个对象产生
    创建了一个对象,将“123”存储到常量池中。
    延展:String s = new String("abc")这个语句创建了几个对象?
    这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。


    !3、reader和inputstream区别
    首先要分清reader和inputstream,一个是读取字符流,一个是读取字节流。
    InputStream是表示字节输入流的所有类的超类(实体类),Reader是用于读取字符流的抽象类
    InputStream提供的是字节流的读取,而非文本读取,这是和Reader类的根本区别。
    即用Reader读取出来的是char数组或者String ,使用InputStream读取出来的是byte数组。
    http://blog.sina.com.cn/s/blog_6d3183b50101cri5.html


    !4、==和equals的区别
    1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
    他们之间的比较,应用双等号(==),比较的是他们的值。
    2.复合数据类型(类)
    当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
    JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
    对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

    对于String复写Object的equals方法,只是比较字符串值是否相等:
    String类中的equals()方法:

    1 public boolean equals(Object anObject)

    2 {

    3 //如果是同一个对象

    4 if (this == anObject)

    5 {

    6 return true;

    7 }

    8 //如果传递进来的参数是String类的实例

    9 if (anObject instanceof String)

    10 {

    11 String anotherString = (String)anObject;

    12 int n = count;//字符串长度

    13 if (n == anotherString.count) //如果长度相等就进行比较

    14 {

    15 char v1[] = value;//取每一个位置的字符

    16 char v2[] = anotherString.value;

    17 int i = offset;

    18 int j = anotherString.offset;

    19 while (n-- != 0) //对于每一位置逐一比较

    20 {

    21 if (v1[i++] != v2[j++])

    22 return false;

    23 }

    24 return true;

    25 }

    26 }

    27 return false;

    28 }


    %5、hashCode的作用
    http://www.cnblogs.com/dolphin0520/p/3681042.html
    很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:
    public native int hashCode();
    hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
    考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)
    也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。


    %6、hashCode和equals方法的关系
    在有些情况下,程序设计者在设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。
    也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
    如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
    如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
    如果两个对象的hashcode值相等,则equals方法得到的结果未知。


    ?7、Object类中有哪些方法,列举3个以上(可以引导)
    妈蛋,这个在我校招也是我参加的第一次面试时问到了,当时觉得怎么会问这么简单的问题,但是最后自己还是回答不全。
    Object方法:equals()、toString()、finalize()、hashCode()、getClass()、clone()、wait()、notify()、notifyAll()


    !8、char型变量中能不能存贮一个中文汉字?为什么?
    CHAR类型变量时能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的。
    char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。

    %9、了解过哪些JDK8的新特性,举例描述下相应的特性?
    !10、Input/OutputStream和Reader/Writer有何区别?何为字符,何为字节

     1. Reader、Writer是用来处理16位元的流
      Reader支持16位的Unicode字符输出,InputStream支持8位的字符输出。
      Reader和InputStream分别是I/O库提供的两套平行独立的等级机构,
      InputStream、OutputStream是用来处理8位元的流,
    2.java.io.Reader 和 java.io.InputStream 组成了 Java输入类。Reader 用于读入16位字符,也就是 Unicode编码的字符;而 InputStream 用于读入 ASCII字符和二进制数据。在 Java中,有不同类型的 Reader 输入流对应于不同的数据源
    FileReader 用于从文件输入;
    CharArrayReader 用于从程序中的字符数组输入;
    StringReader 用于从程序中的字符串输入;
    PipedReader 用于读取从另一个线程中的 PipedWriter 写入管道的数据。
    相应的也有不同类型的 InputStream 输入流对应于不同的数据源:FileInputStream,ByteArrayInputStream,StringBufferInputStream,PipedInputStream。另外,还有两种没有对应 Reader 类型的 InputStream 输入流

    !11、如何在字符流和字节流之间转换?
    InputStreamReader是字节流向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符。
    OutputStreamWriter是字符流通向字节流的桥梁,它使用指定的charset将要写入流中的字符编码成字节,它使用的字符集可以由名称指定或显示给定,否则将接受默认的字符集:

    !12、启动一个线程是用run()还是start()?
    start()方法是启动(即开辟)一个线程的方法,因此线程的启动必须通过此方法, 这个是程序员完成的,
    而run()方法其实完全是一个接口回调方法,它是你这个线程对象要完成的具体逻辑.简单说你要做什么就你在run中完成,当start()方法调用后,jvm就会自动调用run()方法已完成需要


    %13、海量数据查询、存储
    !14、switch可以使用那些数据类型
      byte、short、char、int、String、枚举.

    !15、多线程与死锁
    实现多线程的几种方式:
    继承Thread/实现Runnable接口,在多线程中,由于线程是共享资源的,所以注意线程的安全
    所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都等待下去,且不释放资源                   同步概念:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。比如使用synchronized定义的同步方法,块,变量,类等。注意:synchronized关键字给一个类中成员加锁,但其锁在类实例手上,即线程只有拿到类实例手中的锁才能执行该同步方法等,而同步方法等只有返回值或结束后才会释放锁到类实例手中。(每个类实例都有一把锁)

    %16、Java的四种引用:                                                                                                                                                                              强引用,弱引用,软引用,虚引用

    %17.java中关于堆、栈、常量池,方法区中各种数据存储类型
    !17、序列化与反序列化                                                                                                                                                                                这里要注意如何序列化,反序列化,同时序列化后的对象与原对象(没有通过序列类的方法作用过的对象)不同
    !18、自动装箱与拆箱                                                                                                                                                                                       就是A==B的判断问题,即当A是基础类型时,这时就是数值比较,B若是基础对象就需要拆箱,若A是基础对象,而B是基础数据,这时就需要B装箱,进行对象地址比较
    !19、正则表达式

    五:JAVA开发工具、环境的使用
    IDE、maven、svn/git、Linux、Firebug

    第二部分: Java高级

    一:多线程
    !1、多线程的实现方式,有什么区别
    java中可以通过继承Thread和实现Runnable接口来实现多线程。
    两者的区别:
    a,一个类只能继承一个父类,存在局限;一个类中可以实现多个接口。
    b,在实现Runable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnable run, String name)构造方法创建进程时,使用同一个Runnable实例,所以建立的多线程的实例变量是可以共享的。

    public class RunThread implements Runnable{}

    public static void main(String[] args){

    RunThread thread = new RunThread();

    new Thread(thread).start();

    new Thread(thread).start();

    }


    c,Runnable接口和Thread之间的联系:
    public class Thread extends Object implements Runnable


    %2、同步和并发是如何解决的


    3、什么叫守护线程,用什么方法实现守护线程(Thread.setDeamon()的含义)
    在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
    用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
    只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
    JVM内部的实现如果运行的程序只剩下守护线程的话,程序将终止运行,直接结束。所以守护线程是作为辅助线程存在的,主要的作用是提供计数等等辅助的功能。

    %4、如何停止一个线程?
    终止线程的三种方法:
    1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
    在定义退出标志exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值,
    2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
    使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:
    thread.stop();
    虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。
    3. 使用interrupt方法中断线程。
    使用interrupt方法来终止线程可分为两种情况:
    (1)线程处于阻塞状态,如使用了sleep方法。
    (2)使用while(!isInterrupted()){……}来判断线程是否被中断。
    在第一种情况下(线程处于阻塞状态时)使用interrupt方法,sleep方法将抛出一个InterruptedException,然后异常处理机制就会捕捉到异常进行中断处理,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。
    注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他线程是否被中断。因此,while (!isInterrupted())也可以换成while (!Thread.interrupted())。

    4.使用interrupted()和isinterrupt()来中断状态

    5、解释是一下什么是线程安全?举例说明一个线程不安全的例子。解释Synchronized关键字的作用。
    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
    线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
    在多线程的情况下,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
    由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。

    !6、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
    1.(这是指实例方法的层次上)可进入对象的其他非synchronized方法,但是进入其他有加synchronized关键字的方法将处于阻塞状态。这里(加synchronized关键字)类方法(静态方法层次上),所有实例的线程须同步,(无论是同一实例或者不是同一个实例的)线程都要得到类字节的锁才能使用类方法,而(加synchronized关键字)非静态的方法,线程需要得到各自实例的锁才能使用各自实例的方法,所使用的实例方法在实例层次上是独立的,不会影响到其他实例方法的值或使用,但在同一个实例的层次上多个线程是需要同步的

    2.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
    分几种情况:
         1.其他方法前是否加了synchronized关键字,如果没加,则能。
         2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
         3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
         4.如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。


    内存结构,GC

    !1、gc的概念,如果A和B对象循环引用,是否可以被GC?
    两个对象相互引用,是会不会被GC没有直接关系。采用的GC Roots可达性来决定是否会被GC回收。
    现在的GC都采用分代收集的方式,不同的区采用不同的算法,大致有:复制,标记-清扫,标记-压缩,标记-清扫-压缩等。

    %2、Java中的内存溢出是如何造成的总结内存溢出是活的线程造成的,内存泄漏是理论上死的线程造成的
    http://outofmemory.cn/java/OutOfMemoryError/PermGen-space-Java-heap-space-unable-create-new-native-thread
    jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。
    其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。
    堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。
    Java栈区跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation space和Heap space。

    第一种OutOfMemoryError: PermGen space
    发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:
    1,增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行: JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128M" 如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。
    2,清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。
    第二种OutOfMemoryError: Java heap space
    发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:
    1,检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。 我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Java heap space的内存溢出问题,后来通过修改程序得到了解决。
    2,增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

    %3、jvm gc如何判断对象是否需要回收,有哪几种方式?
    (1)、引数记数 (Reference Counting)
    给对象添加一个引用计数其,每有一个地方引用这个对象,计数器值加1,每有一个引用失效则减1。
    优点:实现简单、判断效率高。 缺点:难以解决对象之间的循环引用问题。
    (2)、可达性分析(Reachability Analysis)
    从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。
    JVM使用“可达性分析算法”来判定一个对象是否会可以被回收,有两个细节需要注意:
    1.Java的GC Roots如何定义
    Java中GC Roots包括以下几种对象:
    a.虚拟机栈(帧栈中的本地变量表)中引用的对象
    b.方法区中静态属性引用的对象
    c.方法区中常量引用的对象
    d.本地方法栈中JNI引用的对象

    2.不可达对象一定会被回收吗
    不是。
    执行垃圾回收前JVM会执行不可达对象的finalize方法,如果执行完毕之后该对象变为可达,则不会被回收它。
    但一个对象的finalize方法只会被执行一次。


    !5、引用计数,对象引用遍历;jvm有哪几种垃圾回收机制?讲讲分代回收机制
    引用技术法 Reference Counting:
    引用计数器的实现很简单,对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用计数器就加 1,当引用失效时,引用计数器就减 1。只要对象 A 的引用计数器的值为 0,则对象 A 就不可能再被使用。
    引用计数器的实现也非常简单,只需要为每个对象配置一个整形的计数器即可。但是引用计数器有一个严重的问题,即无法处理循环引用的情况。因此,在 Java 的垃圾回收器中没有使用这种算法。
    分带回收:Generational Collection

    三:CLASSLOADER
    !1、ClassLoader的功能和工作模式
    Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。

    四:NIO
    ?1、IO和NIO本质不同在实际项目使用场景及如何使用

    五:其他
    ?1、hashcode 有哪些算法
    %2、反射,是否可以调用私有方法,在框架中的运用                                                                                                                 反射是可以调用私有方法,你知道ssh这些框架都是基于反射+注解+动态代理等技术实现的。虽然它违反了java某些语法逻辑,但是语法体现的是信号机制,它是人为规定的,所以可以在某些条件下进行特定的设置,相当于方言,或语法习惯
    ?3、知道范型的实现机制吗?                                                                                                                                         泛型好处:

    泛型简单易用

    类型安全 泛型的主要目标是实现Java的类型安全。 泛型可以使编译器知道一个对象的限定类型是什么,这样编译器就可以在一个高的程度上验证这个类型

    消除了强制类型转换 使得代码可读性好,减少了很多出错的机会

    Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

    泛型的实现原理

    泛型的实现是靠类型擦除技术 类型擦除是在编译期完成的 也就是在编译期 编译器会将泛型的类型参数都擦除成它的限定类型,如果没有则擦除为object类型之后在获取的时候再强制类型转换为对应的类型。 在运行期间并没有泛型的任何信息,因此也没有优化。

    泛型不考虑继承

    List 类型 是否 可以 赋值为 List类型 ? 
    答案是不可以的 
    虽然说在赋值之后 String类型可以当做 Object类型使用 但是还是会出现问题 
    假设如下代码

    List<String> strings = new ArrayList<>();
        strings.add("哈哈哈哈");
        List<Object> objs = new ArrayList<>();
        objs = strings;  //编译会出错,这里我们假设成功  
        Object aa = objs.get(0);  //此处可以把String转换为Object使用 
        objs.add("呵呵呵呵");
        objs.add(new Object());  //这时 objs中 既有 String 类型 也有 Object类型 
        objes.get(2);  //这个时候 我们不会知道取出的到底是 Object对象 还是 String类型的对象  

    ?4、Socket编程通常出现的异常有哪些,什么情况下会出现
    ?5、了解JVM启动参数吗?-verbose -Xms -Xmx的意思是什么?                                                                                             verbose: 在输出设备上显示虚拟机运行信息。Xms -Xmx是指jvm堆大小值
    %6、StringBuffer的实现方式,容量如何扩充                                                                                                                      
     相信大家都知道StringBuffer、StringBuilder,但是这两个的用法都差不多,到底有什么区别呢,这也是面试的时候问的比较多的一道题,这里我就来说说,这两个的区别结合String来说~

      String:查看源码得知,String类的声明是:public final,所以可以很清楚的知道,fianl的话是改变不了的,所以,如果我们用String来操作字符串的时候,一旦我们字符串的值改变,就会在内存创建多一个空间来保存新的字符串,可想而知,一旦遇到复杂的操作,用String是多么低效率的事啊!

      所以,一般涉及到字符串操作的,我们一般使用StringBuffer或者StringBuilder,但是这两者都又有什么区别呢,下面我来说说:

      查看源码可以得知:

        StringBuffer和StringBuilder都集成了AbstractStringBuilder,而StringBuffer大部分方法都是synchronized,也就是线程安全的,而StringBuilder就没有,所以,我们查看API可以知道,StringBuilder可以操作StringBuffer,但是StringBuffer不可以操作StringBuilder,这也是线程的原因;

        所以,可想而知,StringBuffer的效率肯定没有StringBuilder,因为StringBuffer要维持同步锁,这肯定要消耗部分资源,下面这个例子就可以充分证明这三者之间的关系:


    %7、代理机制的实现

    第三部分: JavaEE

    一:Servlet的掌握,包括新的异步Servlet
    !1、Servelt的概念。常问http request能获得的参数
    Servlet 是一种基于java技术的web组件,是一种基于java的且与第三方平台无关的类。通常,它可以被Web 服务器编译、加载和运行,最后生成动态的资源内容。

    %2、servlet中,如何定制session的过期时间?
    1.在web.xml中配置:
    <session-config>
    <session-timeout>20</session-timeout>
    </session-config>
    上面这种设置,对整个web应用生效。当客户端20分钟内都没有发起请求时,容器会将session干掉。单位为分钟。

     在servlet.java类中进行设置                                                                                                                                 session.setMaxInactiveInterval(30*60);

    单位是秒。Session设置产生效果的优先循序是,先程序后配置,先局部后整体。

    !3、Servlet中的session工作原理 (禁用cookie如何使用session)
    http://dxz.iteye.com/blog/2193399?utm_source=tuicool&utm_medium=referral
    session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息。
    Cookies是一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术。Cookies是当你浏览某网站时,由Web服务器置于你硬盘上的一个非常小的文本文件,它可以记录你的用户ID、密码、浏览过的网页、停留的时间等信息。
    session: 当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。
    cookie机制:采用的是在客户端保持状态的方案,而session机制采用的是在服务端保持状态的方案。同时我们看到由于服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助cookie机制来达到保存标识的目的。
    session机制:服务器首先检查这个客户端的请求里是否已经包含了一个session标识(session id)如果含有sessionid则说明以前为此客户端创建过session服务器就会把这个按照session Id把session给检索出来(如果检索不到就会重新建立一个session),如果客户端请求不包含session id,
    则为此客户端创建一个session并且生成一个与此对应的session id,session id是一个不会重复的字符串,该id会在本次响应客户端的时候传送给客户端。
    保存这个session id的方式:
    a、可以采用cookie,这样就可以在交互的过程中自动的按照规则把这个表示发送给服务器。一般这个cookie的名字类似于SEEESIONID的,但是cookie可以被人为禁止。
    b、所以也经常使用一种使用一种叫做URL重写的技术,就是把sessionid直接附加在URL路径的后面。
    c、还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单添加一个隐藏的字段,在表单提交的时候就会把这个session id传递会服务器。

    session和cookie的区别:
    cookie数据放在客户端,session数据放在服务器端。
    cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗考虑安全性的话应该使用session。
    session因为在服务器上保存了很多信息,当在访问量比较大的时候会增加服务器的负担,从这方面考虑应该使用cookie
    单个cookie的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie
    有人建议将登录信息等重要信息放在session中,非重要的信息可以放在cookie中。


    !4、servlet中,filter的应用场景有哪些
    1,通过filter进行全站字符编码过滤
    get/post 提交不同。
    post 是表单提交
    get方法提交的话是将数据带在你的url上面传过去的。

    request.setCharacterEncoding("UTF-8");这个是只对post方法有用,如果想对get方法有用就在tomcat配置文件server.xml中Connector元素下设置
    URIEncoding="UTF-8"


    !5、描述JSP和Servlet的区别、共同点(JSP的工作原理)。
    尽管JSP在本质上就是SERVLET,但是两者的创建方式不一样.Servlet完全是JAVA程序代码构成擅长于流程控制和事务处理而通过Servlet
    来生成动态网页很不直观;JSP由HTML代码和JSP标签构成可以方便地编写动态网页因此在实际应用中采用Servlet来控制业务流程,而采
    用JSP来生成动态网页.在struts框架中,JSP位于MVC设计模式的视图层,而Servlet位于控制层。

    服务器在执行jsp的时候,首先把jsp翻译成一个Servlet,所以我们访问jsp时,其实不是在访问jsp,而是在访问jsp翻译过后的那个Servlet

    ?6、JSP的动态include和静态include                                                                                                                             jsp:include是先编译一下included.jsp文件,然后再包含 先编译,后包含                                                                                                        @ include是先把文件包含就来,然后统一编译 先包含,后编译


    !7、Servlet的生命周期
    init service destory


    二:WEB框架的掌握(挑其掌握的一种)
    !1、Struts中请求的实现过程                                                                                                                                         在表单form的各个参数对象,之所以可以传值给Action类,其框架操作是利用struts.xml中的action类生成了actionProxy代理类,然后利用代理类创建InvocationHandler实例,调用了invoke方法,根据invoke方法的映射原理,会调用参数对象所对应的getter/setter方法。


      !2、MVC概念                                                                                                                                                        

    • Model :模型层(用于数据库打交道)
    • View :视图层(用于展示内容给用户看)
    • Controller :控制层(控制业务逻辑)

    MVC模型如下图所示:

    %4、Spring mvc与Struts mvc的区别 (什么是Mvc框架)
    ?5、Service嵌套事务处理,如何回滚

    三:http相关(内部重定向,外部重定向),http返回码
    !1、session和cookie的区别
    !2、HTTP请求中Session实现原理?
    %3、如果客户端禁止Cookie能实现Session吗?
    !4、http get和post区别
    GET在浏览器回退时是无害的,而POST会再次提交请求。
    GET产生的URL地址可以被Bookmark,而POST不可以。
    GET请求会被浏览器主动cache,而POST不会,除非手动设置。
    GET请求只能进行url编码,而POST支持多种编码方式。
    GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    GET请求在URL中传送的参数是有长度限制的,而POST么有。
    对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
    GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
    GET参数通过URL传递,POST放在Request body中。

    !5、在web开发中,用redirect与forward做跳转有什么区别?web应用服务器对用户请求通常返回一些状态码,请描述下分别以4和5开头的状态码
    100-199:表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程。
    200-299:表示成功接收请求并已完成整个处理过程,常用200.
    300-399:为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、307和304
    400-499:客户端的请求有错误,常用404.
    500-599:服务器出现错误,常用500。

    四:spring,ibatis,hibernate相关
    ?1、Hibernate/Ibatis两者的区别
    ?2、OR Mapping的概念
    %3、hibernate一级和二级缓存是否知道
    ?4、使用hibernate实现集群部署,需要注意些什么
    !5、Spring如何实现AOP和IOC的?
    !6、Spring的核心理念是什么?是否了解IOC和AOP
    !7、Spring的事务管理 ,Spring bean注入的几种方式
    !8、Spring AOP解决了什么问题

    五:jboss,tomcat等容器相关
    ?1、Tomcat和weblogic的最根本的区别
    ?2、Jboss端口在哪个配置文件中修改

    六:web安全,SQL注入,XSS, CSRF等
    %1、SQL注入 SQL安全

    七:AJAX相关
    ?1、AJAX感受,有什么缺点?
    %2、你使用的是Ajax的那种框架?
    ?3、Ajax如何解决跨域问题

    八:Web Service
    ?1、简述WebService是怎么实现的

    九:JMS
    ?1、JMS的模式两种模式
    队列模式: 一对一
    主题(订阅)模式: 一对多

    十:其他
    ?1、Js:confirm()方法
    ?2、Iframe的优缺点
    %3、我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?(主要是考量有没有碰到过编码问题,问题是如何解决的)
    ?4、怎么获取到客户端的真实IP?
    ?5、名词解释:jndi,rmi,jms,事务,如果有了解的话可以深入
    ?6、WEB层如何实现Cluster

    第四部分: 数据库相关

    一:关系模型理论:
    !1、范式
    ?2、rownum和rowid的区别与使用

    二:事务相关
    %1、Transaction有哪几种隔离级别?(Isolation Level)
    ?2、Global transaction的原理是什么?
    !3、事务是什么?

    三:并发控制
    %1、乐观锁,悲观锁

    四:ORACLE或MYSQL题目
    !1、分页如何实现(Oracle,MySql)
    !2、Mysql引擎

    五:其他
    %1、数据库操作的性能瓶颈通常在哪里, 1000万级别访问,在数据库和java程序上考虑哪些来进行性能优化
    %2、性能方面。多数结合多线程、同步来问,以提取一张大表数据来作为例子 解决性能的方法
    !3、表关联时,内连接,左连接,右连接怎么理解?
    !4、Statement和PreparedStatement之间的区别
    !5、用JDBC怎样从数据库中查询一条记录
    %6、索引以及索引的实现(B+树介绍、和B树、R树区别

    第五部分: 设计模式

    一:高内聚,低耦合方面的理解
    ?1、在项目中是否采用分层的结构,是怎样划分的,各层之间采用了哪些相关技术? 对哪些设计模式比较熟悉?
    %2、什么是低耦合和高聚合?封装原则又是什么意思?
    %3、类A依赖类B,会产生什么问题?怎样解除这种耦合?

    二:设计模式方面
    %1、谈一下自己了解或者熟悉的设计模式
    !2、Singleton的几种实现方式
    ?3、工厂模式和抽象工厂模式之间的区别
    !4、简述M-V-C模式解决了什么问题?

    三:其他
    %1、说说你所知道的UML图,在项目中是如何运用

  • 相关阅读:
    使用Xcode和Instruments调试解决iOS内存泄露(转载) sansan
    GCD介绍(二): 多核心的性能(转载) sansan
    iphone 常用的<app>info.plist设置(转载) sansan
    GCD介绍(一): 基本概念和Dispatch Queue (转载) sansan
    iOS 证书与签名 解惑详解[转] sansan
    Xcode快捷键 sansan
    GCD介绍(三): Dispatch Sources(转载) sansan
    Apple开发者授权 sansan
    MFC中Spin control的使用
    c++ windows mobile中设置菜单活动与否
  • 原文地址:https://www.cnblogs.com/w-wfy/p/6409807.html
Copyright © 2020-2023  润新知