logo资料库

2017年阿里Java基础面试题文档 Java知识分享.docx

第1页 / 共14页
第2页 / 共14页
第3页 / 共14页
第4页 / 共14页
第5页 / 共14页
第6页 / 共14页
第7页 / 共14页
第8页 / 共14页
资料共14页,剩余部分请下载后查看
1. 自我介绍 2. 讲一件你印象最深刻的事没事怎么解决的 3. JAVA 垃圾回收机制怎么工作的 4. AOP 是怎么实现的 5. TCP/UDP 的区别是什么 6. MAP 讲一下你对 MAP 的认识,然后谈一下 HsahMap 的性能,主要谈一下哈希冲撞 7. 创造线程的几种方式,Thread 和 Runnable 接口有什么区别 8. 简单说一下你参加的中兴算法大赛 9. 说一下你做的聊天工具,如果遇到服务突然卡顿,怎么解决 10. 讲一下你对于 spring 框架的了解 11. Mysql 数据库,索引是怎么使用的。 12. 你对分布式是否有所了解 13. 你对数据结构了解么 14. 你为什么想转行做软件呢 15. String 和 StringBuffer 16. 浅析 HashMap 与 ConcurrentHashMap 的线程安全性 17. 堆排序,快速排序 18. 数据库的四大特征,数据库的隔离级别, 19. 线程同步机制用的什么?当初为什么选择这个机制? 20. 互斥和条件变量区别?互斥是阻塞的吗?当初为什么选择阻塞的机制? 21.消息队列 22.分布式 Java 中多态性的实现 什么是多态 1. 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是 为多态而准备的。这是我们最后一个概念,也是最重要的知识点。 2. 多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送 对象的不同而采用多种不同的行为方式。(发送消息就是函数调用) 3. 实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用 对象的实际类型,根据其实际的类型调用其相应的方法。 4. 多态的作用:消除类型之间的耦合关系。 5. 现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮 助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的 对象上会产生不同的结果。 下面是多态存在的三个必要条件,要求大家做梦时都能背出来! 多态存在的三个必要条件 一、要有继承; 二、要有重写; 三、父类引用指向子类对象。
Java 中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。 String 与 StringBuffer 的区别 简单地说,就是一个变量和常量的关系。StringBuffer 对象的内容可以修改;而 String 对象 一旦产生后就不可以被修改,重新赋值其实是两个对象。 StringBuffer 的内部实现方式和 String 不同,StringBuffer 在进行字符串处理时,不生成新的 对象,在内存使用上要优于 String 类。所以在实际使用时,如果经常需要对一个字符串进行 修改,例如插入、删除等操作,使用 StringBuffer 要更加适合一些。 String:在 String 类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个 Java 字符串中的某个单独字符,所以在 JDK 文档中称 String 类的对象是不可改变的。然而,不 可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。 StringBuffer:StringBuffer 类属于一种辅助类,可预先分配指定长度的内存块建立一个字符串 缓冲区。这样使用 StringBuffer 类的 append 方法追加字符 比 String 使用 + 操作符添加字 符 到 一个已经存在的字符串后面有效率得多。因为使用 + 操作符每一次将字符添加到一 个字符串中去时,字符串对象都需要寻找一个新的内存空间来容纳更大的字符串,这无凝是 一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。 使用 StringBuffer 类就避免了这个问题。 StringBuffer 是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效 率相对来说就要稍微慢一些。 StringBuffer 的常用方法 StringBuffer 类中的方法要偏重于对字符串的变化例如追加、插入和删除等,这个也是 StringBuffer 和 String 类的主要区别。 String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因 此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新 的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产 生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作, 而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串 对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是 以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的: String S1 = “This is only a” + “ simple” + “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); 你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根 本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个 String S1 = “This is only a” + “ simple” + “test”; 其实就是: String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的 是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如: String S2 = “This is only a”; String S3 = “ simple”;
String S4 = “ test”; String S1 = S2 +S3 + S4; 这时候 JVM 会规规矩矩的按照原来的方式去做 在大部分情况下 StringBuffer > String StringBuffer Java.lang.StringBuffer 线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。 虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。 可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有 操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。 StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个 方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。 例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使 字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。 在大部分情况下 StringBuilder > StringBuffer java.lang.StringBuilde java.lang.StringBuilder 一个可变的字符序列是 5.0 新增的。此类提供一个与 StringBuffer 兼容的 API, 但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时 候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。 两者的方法基本相同。 在数据结构中有一种称为哈希表的数据结构,它实际上是数组的推广。如果有一个数组,要最有效的查找 某个元素的位置,如果存储空间足够大,那么可以对每个元素和内存中的某个地址对应起来,然后把每个 元素的地址用一个数组(这个数组也称为哈希表)存储起来,然后通过数组下标就可以直接找到某个元素 了。这种方法术语叫做直接寻址法。这种方法的关键是要把每个元素和某个地址对应起来,所以如果当一 组数据的取值范围很大的时候,而地址的空间又有限,那么必然会有多个映射到同一个地址,术语上称为 哈希冲突,这时映射到同一个地址的元素称为同义词。毕竟,存储空间有限,所以冲突是不可避免的,但 是可以尽量做到减少冲突。目前有两种比较有效的方法来解决哈希冲突: 链地址法 开放地址法 这里简要说明一下开放地址法,顾名思义,就是哈希表中的每个位置要么存储了一个元素要么为 NULL。当 数据比较多的时候,查找一个元素挺费事的,但是可以使用探测的方法进行查找。这个话题与本主题关系 不大,感兴趣的小伙伴可以自行研究。 从这段注释中可以发现每次执行 ConcurrentHashMap 的 put 方法都是调用 s.put()方法的,而 Segments 对 象是一个继承了 ReentrantLock 锁对象的子类,那么剩下的就很清晰了,每一个 Segments 都有一个锁,只 有执行完上面 try 语句块中的代码才会释放锁,从而保证了多线程并发访问的安全性。 什么是线程同步? 当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数 据不一致),所以我们用同步机制来解决这些问题。
实现同步机制有两个方法: 1.同步代码块: synchronized(同一个数据){} 同一个数据:就是 N 条线程同时访问一个数据。 2.同步方法: public synchronized 数据返回类型 方法名(){} 就是使用 synchronized 来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显示指定同 步监视器,同步方法的同步监视器是 this 也就是该对象的本身(这里指的对象本身有点含糊,其实就 是调用该同步方法的对象)通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征: 1,该类的对象可以被多个线程安全的访问。 2,每个线程调用该对象的任意方法之后,都将得到正确的结果。 3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。 注:synchronized 关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。 实现同步机制注意以下几点: 安全性高,性能低,在多线程用。性能高,安全性低,在单线程用。 1,不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。 2,如果可变类有两种运行环境,当线程环境和多线程环境则应该为该可变类提供两种版本:线程安全版本 和线程不安全版本(没有同步方法和同步块)。在单线程中环境中,使用线程不安全版本以保证性能,在多 线程中使用线程安全版本. 一:进程与线程 概述:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程。 当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程。 进程:进程是指处于运行过程中的程序,并且具有一定的独立功能。进程是系统进行资源分配和调度的一 个单位。当程序进入内存运行时,即为进程。 进程的三个特点: 1:独立性:进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间, 没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间。 2:动态性:进程和程序的区别在于进程是动态的,进程中有时间的概念,进程具有自己的生命周期和各种 不同的状态。 3:并发性:多个进程可以在单个处理器上并发执行,互不影响。 并发性和并行性是不同的概念:并行是指同一时刻,多个命令在多个处理器上同时执行;并发是指在同一 时刻,只有一条命令是在处理器上执行的,但多个进程命令被快速轮换执行,使得在宏观上具有多个进程 同时执行的效果 线程: 线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有自 己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源。它与父进程的其他线程共享该进 程的所有资源。 线程的特点: 线程可以完成一定任务,可以和其它线程共享父进程的共享变量和部分环境,相互协作来完成任务。 线程是独立运行的,其不知道进程中是否还有其他线程存在。
线程的执行是抢占式的,也就是说,当前执行的线程随时可能被挂起,以便运行另一个线程。 一个线程可以创建或撤销另一个线程,一个进程中的多个线程可以并发执行。 1.进程的三种基本状态 进程在运行中不断地改变其运行状态。通常,一个运行进程必须具有以下三种基本状态。 (1)就绪(Ready)状态 当进程已分配到除 CPU 以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪 状态。 (2)执行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状 态。 (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻 塞状态。引起进程阻塞的事件可有多种,例如,等待 I/O 完成、申请缓冲区不能满足、等待信件(信号)等。 阻塞的情况分三种: (1)、等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待 池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()或 notifyAll()方法才能 被唤醒, (2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入 “锁池”中。 (3)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻 塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪 状态。
同步机制: 1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 2、互斥量:为协调共同对一个共享资源的单独访问而设计的。 3、信号量:为控制一个具有有限数量用户资源而设计。 4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。 临界区(Critical Section) 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。 如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂 起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原 子方式操作共享资源的目的。 互斥量(Mutex) 互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此 就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将 拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅 能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的 安全共享。 信号量(Semaphores) 信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统 中的 PV 操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源, 但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时 指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加 一个线程对共享资源的访问,当前可用资源计数就会减 1,只要当前可用资源计数是大于 0 的,就可以发 出信号量信号。但是当前可用计数减小到 0 时则说明当前占用资源的线程数已经达到了所允许的最大数目, 不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时 通过 ReleaseSemaphore()函数将当前可用资源计数加 1。在任何时候当前可用资源计数决不可能大于最 大资源计数。 事件(Event) 事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。 总结: 1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建 互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少 资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。 2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作, 而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,
在退出后为有信号状态。所以可以使用 WaitForSingleObject 来等待进程和线程退出。 3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现 在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少 个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说 是一种资源计数器。 在上一篇关于 Serlvet 框架和 Servlet 生命周期的学习中,我们已经知道了在多线程的情况下 Servlet 是 线程不安全的。Servlet 体系是建立在 Java 多线程的基础之上的,它的生命周期是由 Tomcat 来维护的。当 客户端第一次请求 Servlet 的时候,tomcat 会根据 web.xml 配置文件实例化 servlet,当又有一个客户端访 问该 servlet 的时候,不会再实例化该 servlet,也就是多个线程在使用这个实例。Servlet 线程池 serlvet 采用多线程来处理多个请求同时访问,Tomcat 容器维护了一个线程池来服务请求。 线程池实际上是等待执行代码的一组线程叫做工作组线程(Worker Thread),Tomcat 容器使用一个调度线 程来管理工作组线程(Dispatcher Thead)。 当容器收到一个 Servlet 请求,Dispatcher 线程从线程池中选出一个工作组线程,将请求传递给该线 程,然后由该线程来执行 Servlet 的 service 方法。当这个线程正在执行的时候,容器收到另一个请求, 调度者线程将从线程池中选出另外一个工作组线程来服务则个新的请求,容器并不关心这个请求是否访问 的是同一个 Servlet 还是另一个 Servlet。当容器收到对同一个 Servlet 的多个请求的时候,那这个 servlet 的 service 方法将在多线程中并发的执行。 在浏览器中输入网址访问资源都是通过 GET 方式;在 FORM 提交中,可以通过 Method 指定提交方式为 GET 或 者 POST,默认为 GET 提交。 HTTP 定义了与服务器交互的不同方法,最常用的有 4 种,Put(增),Delete(删),Post(改),Get(查),即增 删改查: 1)Get,
它用于获取信息,注意,他只是获取、查询数据,也就是说它不会修改服务器上的数据,从这点来讲,它 是数据安全的,而稍后会提到的 Post 它是可以修改数据的,所以这也是两者差别之一了。 2) Post,它是可以向服务器发送修改请求,从而修改服务器的,比方说,我们要在论坛上回贴、在博客上评 论,这就要用到 Post 了,当然它也是可以仅仅获取数据的。 3)Delete 删除数据。可以通过 Get/Post 来实现。 4)Put,增加、放置数据,可以通过 Get/Post 来实现。 根据 HTTP 规范,GET 用于信息获取,而且应该是安全的和幂等的 。 1.所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是 说,仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。(注意:这 里安全的含义仅仅是指是非修改信息。) 根据 HTTP 规范,POST 表示可能修改变服务器上的资源的请求 。继续引用上面的例子:还是新闻以网站为例,读者对新闻发表自己的评论应该通过 POST 实现,因为在评 论提交后站点的资源已经不同了,或者说资源被修改了。 表现形式区别: HTTP 请求:在 HTTP 请求中,第一行必须是一个请求行(request line),用来说明请求类型、要访问的资源以及使用的 HTTP 版本。紧接着是一个首部(header)小节,用来 说明服务器要使用的附加信息。在首部之后是一个空行,再此之后可以添加任意的其他数据[称之为主体 (body)]。 两种提交方式的区别: (1)GET 提交,请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据, 多个参数用&连接。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符, 则直接把字符串用 BASE64 加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX 中的 XX 为该符号以 16 进制表示的 ASCII。 POST 提交:把提交的数据放置在是 HTTP 包的包体中。上文示例中红色字体标明的就是实际的传输数据 因此,GET 提交的数据会在地址栏中显示出来,而 POST 提交,地址栏不会改变 (2)传输数据的大小:首先声明:HTTP 协议没有对传输的数据大小进行限制,HTTP 协议规范也没有对 URL 长度进行限制。 而在实际开发中存在的限制主要有: GET:特定浏览器和服务器对 URL 长度有限制,例如 IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏 览器,如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。 因此对于 GET 提交时,传输数据就会受到 URL 长度的限制。 POST:由于不是通过 URL 传值,理论上数据不受限。但实际各个 WEB 服务器会规定对 post 提交数据大小进 行限制,Apache、IIS6 都有各自的配置。 伴随网站业务规模和访问量的逐步发展,原本由单台服务器、单个域名的迷你网站架构已经无法满足发展 需要。 此时我们可能会购买更多服务器,并且启用多个二级子域名以频道化的方式,根据业务功能将网站分布部 署在独立的服务器上;或通过负载均衡技术(如:DNS 轮询、Radware、F5、LVS 等)让多个频道共享一组 服务器。
分享到:
收藏