为什么我输入如下代码,为什么代码对了但是程序无法运行什么也不会输出?

随着三月份的到来,虽然仍受疫情影响,相信还是会有不少Java在职工作者和应届毕业生都在为着疫情过后的工作面试在积极的做着准备。

那么,重头戏来了,这次小编给大家奉上了一份集合BATJ一线大厂面试题集,如果准备面试的朋友千万别错过了,本文分为十九个模块,囊括了大部分Java技术知识点。分别是:Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring

那么,进入正题,上干货!!!

程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。

== 解读:对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

基本类型:比较的是值是否相同;引用类型:比较的是引用是否相同;代码示例:

代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:

输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:

原来 equals 本质上就是 ==。那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:

同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:

原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

这里大家可以关注一下我的个人专栏《Java 进阶集中营》,每天会给大家即时分享一个最新的java技术资讯,有优秀的java技术内容,也欢迎分享在我的专栏。

代码解读:很显然“精彩”和“笔记”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。

final 修饰的类叫最终类,该类不能被继承。final 修饰的方法不能被重写。final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

6.String 属于基础的数据类型吗?

7.Java 中操作字符串都有哪些类?它们之间有什么区别?

存储数据的字符数组没有被final修饰,说明值可以改变,抽象类AbstractStringBuilder内部都提供了一个自动扩容机制,当发现长度不够的时候(初始默认长度是16),会自动进行扩容工作,扩展为原数组长度的2倍加2,创建一个新的数组,并将数组的数据复制到新数组,所以对于拼接字符串效率要比String要高。线程安全性:StringBuffer由于很多方法都被

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

9.如何将字符串反转?

10.String 类的常用方法都有那些?

indexOf():返回指定字符的索引。charAt():返回指定索引处的字符。replace():字符串替换。trim():去除字符串两端空白。split():分割字符串,返回一个分割后的字符串数组。getBytes():返回字符串的 byte

11.抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法;但是包含一个抽象方法的类一定是抽象类。示例代码:

上面代码,抽象类并没有抽象方法但完全可以正常运行。

12.普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法。抽象类是不能被实例化的,就是不能用new调出构造方法创建对象,普通类可以直接实例化。如果一个类继承于抽象类,则该子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract类。13.抽象类能使用 final 修饰吗?不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:

14.接口和抽象类有什么区别?

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。构造函数:抽象类可以有构造函数;接口不能有。实现数量:类可以实现很多个接口;但只能继承一个抽象类【java只支持单继承】。访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的抽象方法可以使用Public和Protected修饰,如果抽象方法修饰符为Private,则报错:The

按功能来分:输入流(input)、输出流(output)。按类型来分:字节流和字符流。字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

17.Files的常用方法都有哪些?

Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示:

Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法:Collections. sort(list)。

List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。三者之间的区别,如下表:

而应该用containsKey()方法来判断,因为使用get的时候,当返回null时,你无法判断到底是不存在这个key,还是这个key就是null,还是key存在但value是null。线程安全性不同:HashMap的方法都没有使用synchronized关键字修饰,都是非线程安全的,而Hashtable的方法几乎都是被synchronized关键字修饰的。但是,当我们需要HashMap是线程安全的时,怎么办呢?我们可以通过Collections.synchronizedMap(hashMap)来进行处理,亦或者我们使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。初始容量大小和每次扩充容量大小的不同:Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。计算hash值的方法不同:为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。

数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

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

相同点:都是返回第一个元素,并在队列中删除返回的对象。不同点:如果没有元素 remove()会直接抛出NoSuchElementException 异常,而 poll()会返回 null。代码示例:

30.哪些集合类是线程安全的?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

34.怎么确保一个集合不能被修改?

35.并行和并发有什么区别?

并行:多个处理器或多核处理器同时处理多个任务。并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。如下图:【并发 = 两个队列和一台咖啡机】 【并行 = 两个队列和两台咖啡机】

36.线程和进程的区别?

一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有多个线程来增加程序的执行速度。

37.守护线程是什么?

守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。

38.多线程有几种实现方式?有4种,分别是:

继承Thread类实现Runnable接口实现Callable接口通过FutureTask包装器来创建Thread线程通过线程池创建线程,使用线程池接口ExecutorService结合Callable、Future实现有返回结果的多线程。前面两种【无返回值】原因:通过重写run方法,run方法的返回值是void,所以没有办法返回结果。后面两种【有返回值】原因:通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中。

40.线程有哪些状态?线程的6种状态:

初始(NEW):新创建了一个线程对象,但还没有调用start()方法。运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。阻塞(BLOCKED):表示线程阻塞于锁。等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。终止(TERMINATED):表示该线程已经执行完毕。

notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。

44.创建线程池有哪几种方式?线程池创建有七种方式,最核心的是最后一种:

1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;newSingleThreadScheduledExecutor():创建单线程池,返回

45.线程池都有哪些状态?

RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法

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

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

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

volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。ReentrantLock 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

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

什么情况下需要序列化?Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。以下情况需要使用 Java 序列化:

想把的内存中的对象状态保存到一个文件中或者数据库中时候;想用套接字在网络上传送对象的时候;想通过RMI(远程方法调用)传输对象的时候。

59.动态代理是什么?

有哪些应用?动态代理是运行时动态生成代理类。动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。

60.怎么实现动态代理?

JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。

61.为什么要使用克隆?

克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。

62.如何实现对象克隆?

实现 Cloneable 接口并重写 Object 类中的 clone() 方法。实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

63.深拷贝和浅拷贝区别是什么?

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

JSP 是 servlet 技术的扩展,本质上就是 servlet 的简易方式。servlet 和 JSP 最主要的不同点在于,servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 html 里分离开来,而 JSP 的情况是 Java 和 html 可以组合成一个扩展名为 JSP 的文件。JSP 侧重于视图,servlet 主要用于控制逻辑。

65.JSP 有哪些内置对象?作用分别是什么?

request:封装客户端的请求,其中包含来自 get 或 post 请求的参数;response:封装服务器对客户端的响应;pageContext:通过该对象可以获取其他对象;session:封装用户会话的对象;application:封装服务器运行环境的对象;out:输出服务器响应的输出流对象;config:Web 应用的配置对象;page:JSP 页面本身(相当于 Java 程序中的 this);exception:封装页面抛出异常的对象。

page:代表与一个页面相关的对象和属性。request:代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。application:代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。

session:是一种将会话状态保存在服务器端的技术。Cookie :是在 HTTP 协议下, Web 服务器保存在用户浏览器(客户端)上的小文本文件,它可以包含有关用户的信息。无论何时用户链接到服务器,Web 站点都可以访问 Cookie 信息 。

存储位置不同:session 存储在服务器端;cookie 存储在浏览器端。安全性不同:cookie 安全性一般,在浏览器存储,可以被伪造和修改。容量和个数限制:cookie 有容量限制,每个站点下的 cookie 也有个数限制。存储的多样性:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中。

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。

拦截级别:struts2 是类级别的拦截;spring mvc 是方法级别的拦截。数据独立性:spring mvc 的方法之间基本上独立的,独享 request 和 response 数据,请求数据通过参数获取,处理结果通过 ModelMap 交回给框架,方法之间不共享变量;而 struts2 虽然方法之间也是独立的,但其所有 action 就可以实现了;而 struts2 一般需要安装插件或者自己写代码才行。

使用预处理 PreparedStatement。使用正则表达式过滤掉字符中的特殊字符。

72.什么是 XSS 攻击,如何避免?

XSS 攻击:即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其他网站等。预防 XSS 的核心是必须对输入的数据做过滤处理。

73.什么是 CSRF 攻击,如何避免?

CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。防御手段:

验证请求来源地址;关键操作添加验证码;在请求地址添加 token 并验证。···

throw:是真实抛出一个异常。throws:是声明可能会抛出一个异常。

final:是修饰符,如果修饰类,此类不能被继承;如果修饰方法和变量,则表示此方法和此变量不能在被改变,只能使用。finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何情况都会执行,finally 部分可以省略,但如果 finally 部分存在,则一定会执行 finally 里面的代码。finalize:是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。

78.常见的异常类有哪些?

301:永久重定向;302:暂时重定向。它们的区别是,301 对搜索引擎优化(SEO)更加有利;302 有被提示为网络拦截的风险。

tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输,而 udp 则常被用于让广播和细节控制交给应用的通信传输。两者的区别大致如下:

tcp 面向连接,udp 面向非连接即发送数据前不需要建立链接;tcp 提供可靠的服务(数据传输),udp 无法保证;tcp 面向字节流,udp 面向报文;tcp 数据传输慢,udp 数据传输快;

82.tcp 为什么要三次握手,两次不行吗?为什么? 

我们假设A和B是通信的双方。我理解的握手实际上就是通信,发一次信息就是进行一次握手。

第一次握手:A给B打电话说,你可以听到我说话吗?第二次握手:B收到了A的信息,然后对A说:我可以听得到你说话啊,你能听得到我说话吗?第三次握手:A收到了B的信息,然后说可以的,我要给你发信息啦!在三次握手之后,A和B都能确定这么一件事:我说的话,你能听到;你说的话,我也能听到。这样,就可以开始正常通信了。注意:HTTP是基于TCP协议的,所以每次都是客户端发送请求,服务器应答,但是TCP还可以给其他应用层提供服务,即可能A、B在建立链接之后,谁都可能先开始通信。

如果采用两次握手,那么只要服务器发出确认数据包就会建立连接,但由于客户端此时并未响应服务器端的请求,那此时服务器端就会一直在等待客户端,这样服务器端就白白浪费了一定的资源。若采用三次握手,服务器端没有收到来自客户端的再此确认,则就会知道客户端并没有要求建立请求,就不会浪费服务器的资源。

83.说一下 tcp 粘包是怎么产生的?

tcp 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:

发送端粘包:发送端需要等缓冲区满才发送出去,造成粘包;接收方粘包:接收方不及时接收缓冲区的包,造成多个包接收。

84.OSI 的七层模型都有哪些?

物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。数据链路层:负责建立和管理节点间的链路。网络层:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。会话层:向两个实体的表示层提供建立和使用连接的方法。表示层:处理用户信息的表示问题,如编码、数据格式转换和加密解密等。应用层:直接向用户提供服务,完成用户希望在网络上完成的各种工作。

get 请求会被浏览器主动缓存,而 post 不会。get 传递参数有大小限制,而 post 没有。post 参数传输更安全,get 的参数会明文限制在 url 上,post 不会。

86.如何实现跨域?实现跨域有以下几种方案:

服务器端运行跨域 设置 CORS 等于 *;在单个接口使用注解 @CrossOrigin 运行跨域;使用 jsonp 跨域;

jsonp:JSON with Padding,它是利用script标签的 src 连接可以访问不同源的特性,加载远程返回的“JS 函数”来执行的。

88.说一下你熟悉的设计模式?

单例模式:保证被创建一次,节省系统开销。工厂模式(简单工厂、抽象工厂):解耦代码。观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

89.简单工厂和抽象工厂有什么区别?

简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。···

spring 提供 ioc 技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,更轻松的实现了程序的解耦。spring 提供了事务支持,使得事务操作变的更加方便。spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。更方便的框架集成,spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等。

91.解释一下什么是 aop?

aop 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。简单来说就是统一处理某一“切面”(类)的问题的编程思想,比如统一处理日志、异常等。

92.解释一下什么是 ioc?

ioc:Inversionof Control(中文:控制反转)是 spring 的核心,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。简单来说,控制指的是当前对象对内部成员的控制权;控制反转指的是,这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。

94.spring 常用的注入方式有哪些?

setter 属性注入构造方法注入注解方式注入

spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

有状态就是有数据存储功能。无状态就是不会保存数据。

no:默认值,表示没有自动装配,应使用显式 bean 引用进行装配。byName:它根据 bean 的名称注入对象依赖项。byType:它根据类型注入对象依赖项。构造函数:通过构造函数来注入依赖项,需要设置大量的参数。autodetect:容器首先通过构造函数使用 autowire 装配,如果不能,则通过 byType 自动装配。

声明式事务:声明式事务也有两种实现方式,基于 xml 配置文件的方式和注解方式(在类上添加 @Transaction 注解)。编码方式:提供编码的形式管理和维护事务。

spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;ISOLATIONREADUNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);ISOLATIONREADCOMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;ISOLATIONREPEATABLEREAD:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。不可重复读 :是指在一个事务内,多次读同一数据。幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。视图对象负责渲染返回给客户端。

将 http 请求映射到相应的类/方法上。

@Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作,通过@Autowired 的使用来消除 set/get 方法。

配置简单独立运行自动装配无代码生成和 xml 配置提供应用监控易上手提升开发效率

107.spring boot 配置文件有哪几种类型?它们有什么区别?

持久化接口规范,hibernate 属于 jpa 的具体实现。

spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

Eureka:服务注册于发现。Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。···

hibernate 是对 jdbc 的封装,大大简化了数据访问层的繁琐的重复性代码。hibernate 是一个优秀的 ORM 实现,很多程度上简化了 DAO 层的编码功能。可以很方便的进行数据库的移植工作。提供了缓存机制,是程序执行更改的高效。

ORM(Object Relation Mapping)对象关系映射,是把数据库中的关系数据映射成为程序中的对象。使用 ORM 的优点:提高了开发效率降低了开发成本、开发更简单更对象化、可移植更强。

实体类可以定义为 final 类,但这样的话就不能使用 hibernate 代理模式下的延迟关联提供性能了,所以不建议定义实体类为 final。

Integer 类型为对象,它的值允许为 null,而 int 属于基础数据类型,值不能为 null。

读取并解析配置文件。读取并解析映射文件,创建 SessionFactory。打开 Session。创建事务。进行持久化操作。提交事务。关闭 Session。关闭 SessionFactory。

数据查询时,没有 OID 指定的对象,get() 返回 null;load() 返回一个代理对象。load()支持延迟加载;get() 不支持延迟加载。

hibernate 常用的缓存有一级缓存和二级缓存:一级缓存:也叫 Session 缓存,只在 Session 作用范围内有效,不需要用户干涉,由 hibernate 自身维护,可以通过:evict(object)清除 object 的缓存;clear()清除一级缓存中的所有缓存;flush()刷出缓存;二级缓存:应用级别的缓存,在所有 Session 中都有效,支持配置第三方的缓存,如:EhCache。

临时/瞬时状态:直接 new 出来的对象,该对象还没被持久化(没保存在数据库中),不受 Session 管理。持久化状态:当调用 Session 的 save/saveOrupdate/get/load/list 等方法的时候,对象就是持久化状态。游离状态:Session 关闭之后对象就是游离状态。

124.hibernate 实体类必须要有无参构造函数吗?为什么?

hibernate 中每个实体类必须提供一个无参构造函数,因为 hibernate 框架要使用 reflection api,通过调用 ClassnewInstance() 来创建实体类的实例,如果没有无参的构造函数就会抛出异常。


#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来运行,即:使用哪一种 shell。#!被称为,例如使用

新建一个 test.sh 的文件,内容如下:

第一种方式:作为可执行程序

1、当前 test.sh 是没有可执行权限的,首先使脚本文件具有执行权限。

# 使脚本文件具有执行权限
 


# 执行脚本,需注意要加目录的标识
# 也可以用 source 来执行脚本,跟上面的写法是等价的,但是不需要脚本具有执行权限
 
是找不到命令的,要用./test.sh 告诉系统,就在当前目录找。



通过这种方式运行 bash 脚本,第一行一定要写对,好让系统(Shell 程序)查找到正确的解释器。如果是使用标准默认的 shell,可以省去第一行。

第二种方式:作为解释器参数

 
直接运行解释器,其参数就是 Shell 脚本的文件名。
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
 
 
  • 单行注释:以 # 开头,到行尾结束。
 
如果有段代码要频繁的注释和取消注释,可以用花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
 
 
  • 局部变量:局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
  • 环境变量:环境变量是从父进程中继承而来的变量,对当前 Shell 会话内所有的程序和脚本都可见。创建它们跟创建局部变量类似,但使用的是 export 关键字,shell 脚本也可以定义环境变量。
  • shell 变量(系统变量):shell 变量是由 shell 程序设置的特殊变量。shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 shell 的正常运行。
 
 

可以使用等号操作符为变量赋值:varName=value,varName 是变量名,value 是赋值给变量的值。
  • 首字母必须为字母(a-z,A-Z),剩下的部分只能使用英文字母,数字下划线
  • 中间不能有空格,可以使用下划线,如果有空格,必须使用单引号或双引号
 
注意:varName=value的等号两边没有空格,变量值如果有空格,需要用引号包住。

访问变量的语法形式为:${varName}$varName,变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界(推荐加花括号)。
因为 Shell 使用空白字符来分隔单词,所以上面的例子中需要加上花括号来告诉 Shell 这里的变量名是 fruit,而不是 fruits
注意:使用单引号时,变量不会被扩展(expand),仍依照原样显示。这意味着 echo '$var'会显示 $var。使用双引号时,如果$var 已经定义过,那么 echo "$var"会显示出该变量的值,如果没有定义过,则什么都不显示。

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变

使用 unset 命令可以删除变量,变量被删除后不能再次使用。unset 命令不能删除只读变量

Shell 特殊变量(系统变量)

 
上面讲过变量名的命名规则,但是还有一些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是\$1
传递给脚本或函数的参数个数
传递给脚本或函数的所有参数
传递给脚本或函数的所有参数,被双引号("")包含时,与\$\*稍有不同
函数名称(仅在函数内值)
上个命令的退出状态,或函数的返值
显示 shell 使用的当前选项(flag),后面扩展中检测是否为交互模式时会用到
当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID
最后一个后台运行的进程 ID 号
命令行参数:运行脚本时传递给脚本的参数成为命令行参数,命令行参数用 $n 表示。
第一个命令行参数:Linux 第二个命令行参数:Shell

$? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败会返回 1。$? 也可以表示函数的返回值。

shell 字符串可以使用单引号 '' ,也可以使用双引号"" , 也可以不用引号。

  • 单引号:不识别变量,单引号中间不能出现单独的单引号(使用转义字符转义也不行),可以成对出现实现字符串拼接。
  • 双引号:可以识别变量,双引号中可以出现用转义字符转义的双引号
设置一个字符串变量,下面的都是对这个变量的操作

${#var}:获得变量值的长度

${varx}:通过索引位置截取子字符串

#此时 var 还是没有定义,所以输出:var is

${var:?message}:如果变量 var 为空、没有定义或者已被删除,那么将消息 message 送到标准错误输出。

可以用来检测变量 var 是否可以被正常赋值。若此替换出现在 shell 脚本中,那么脚本将停止运行。

数组是可以存储多个值的变量,这些值可以单独引用,也可以作为整个数组来引用。数组的下标从 0 开始,下标可以是整数或算数表达式,其值应该大于等于 0。

${colors[*]}${colors[@]}有些细微的差别,在将数组中的每个元素单独一行输出的时候,有没有被引号包住会有不同的差别,在引号内,${colors[@]}将数组中的每个元素扩展为一个单独的参数,数组元素中的空格得以保留。

# :0:2 去除数组中从0开始,长度为2的数组元素
 
 
 
 
 
 
Shell 中有很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试符。
 
原生 bash 不支持简单的数学运算,较为常用的是借助 expr 来实现数学运算。
算数运算符列表,变量 a 是 10 变量 b 是 50
a=$b 就是正常的变量赋值
  1. 表达式和运算符之间要有空格,例如1+1是错误的,必须写成1 + 1
  2. 完整的表达式要用反引号 ` 包住
  3. 条件表达式要放在方括号之间,并且要有空格,例如 [${a}==${b}]是错误的,必须写成 [ ${a} == ${b} ]

条件运算符(关系运算符)

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

关系运算符列表,变量 a 是 10 变量 b 是 50

检测两个数是否相等,相等返回 true
检测两个数是否不相等,不相等返回 true
检测左边的数是否大于右边的数,如果是,返回 true
跟 -gt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中使用
检测左边的数是否小于右边的数,如果是,返回 true
跟 -lt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中使用
检测左边的数是否大于等于右边的数,如果是,返回 true
检测左边的数是否小于等于右边的数,如果是,返回 true

条件运算符(布尔运算符、逻辑运算符、字符串运算符)

跟-a 类似,逻辑的 AND,不过需要使用 [[]] 表达式
检测两个数字或字符串是否相等,相等返回 true
检测两个数字或字符串是否相等,不相等返回 true
相等。比较两个数字或字符串,如果相等返回 true(不推荐使用,有兼容性问题)
检测字符串长度是否为 0,为 0 返回 true
检测字符串长度是否为 0,不为 0 返回 true
检测变量是否存在或不为空,存在或不为空返回 true
#输出:-z abc:字符串长度不为0 #输出:-z edf:字符串长度不为0 #输出:abc:不是空字符串 #输出:${s}:不存在

文件目录判断运算符列表

判断文件是否存在,当 filename 存在且是正规文件时(既不是目录,也不是设备文件)返回 true
判断目录是否存在,当 pathname 存在且是目录时返回 true
判断【某个东西】是否存在,当 pathname 指定的文件或目录存在时返回 true
同上,已经过时,而且使用的时候还有另外一个与的逻辑,容易混淆
判断是否是一个非空文件,当 filename 存在并且文件大小大于 0 时返回 true
判断是否可读,当 pathname 指定的文件或目录存在并且可读时返回 true
判断是否可执行,当 pathname 指定的文件或目录存在并且可执行时返回 true
判断是否可写,当 pathname 指定的文件或目录存在并且可写时返回 true
判断是否是一个块文件,当 filename 存在且是块文件时返回 true
判断是否是一个字符文件,当 filename 存在且是字符文件时返回 true
判断是否是一个符号链接,当 filename 存在且是符号链接时返回 true

在条件语句中,由 [][[]] 包起来的表达式被称为检测命令基元

if...else 经常跟 test 命令结合使用,test命令用于检查某个条件是否成立,与方括号[]类似(它们两个在/usr/bin 中是用软连接指向的)。

#输出:a 不等于 b

case 语句匹配一个值或一个模式,如果匹配成功,执行想匹配的命令。适用于需要面对很多情况,分别要采取不同的措施的情况。

echo "你输入的数字是:" #运行后可以自己输入数字体验

注意:可以在 ) 前用 | 分割多个模式。

语法中的列表是一组值(数字、字符串)组成的序列,每个值通过空格分隔。这些值还可以是通配符或大括号扩展,例如 *.sh{1..5}

while 循环会不断的检测一个条件,只要这个条件返回 true,就执行一段命令。被检测的条件跟 if 中的一样。while 也可用于从输入文件中读取数据。

# do 也跟条件写在一行,前面需要加分号 ;

until 循环是检测一个条件,只要条件是 false 就会一直执行循环,直到条件条件为 true 时停止。它跟 while 正好相反。

select 循环的语法跟 for 循环基本一致。它帮助我们组织一个用户菜单。

select 会打印列表的值以及他们的序列号到屏幕上,之后会提示用户选择,用户通常看到的提示是\$?,用户输入相应的信号,选择的结果会被保存到变量中。

PS3="选择你要安装的包管理工具,输入序号:"

break 命令允许跳出所有循环(终止执行后面的所有循环)。在嵌套循环中 break 命令后面还可以跟一个整数,表示跳出几层循环。

# 当 x 等于 2,并且 y 等于 0,就跳出循环

continue 命令跟 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。同样,continue 后面也可以跟一个数字,表示跳出第几层循环。

# 当 x 等于 2,并且 y 等于 0,就跳出循环
  • shell 函数必须先定义后使用,调用函数仅使用其函数名即可。
  • 函数定义时,function关键字可有可无
  • 函数返回值:可以显式的使用 return 语句,返回值类型只能为整数(0-255)。如果不加 return 语句,会默认将最后一条命令运行结果作为返回值。
  • 函数返回值在调用该函数后,通过 $? 获得。
语法:中括号内表示可选
 
 
位置参数是在调用一个函数并传给它参数时创建的变量,见上文 Shell 特殊变量。 # $10 不能获取第10个参数,需要用 ${10},当 n>=10 时,需要使用 ${n} 获取参数。(其中有兼容性,某些Shell解释器两种都能获取到)
 
Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是显示器。
 
shell 接收输入,并以字符序列或字符流的形式产生输出。这些流能被重定向到文件或另一个流中。
一般情况下,每个 Unix/Linux 命令都会打开三个文件:标准输入文件、标准输出文件、标准错误文件,三个文件描述符:
0

重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。

输出重定向:命令的输出不仅可以是显示器,还可以很容的转义到文件,这被称为输出重定向。

输入重定向:使 Unix 命令也可以从文件获取输入,这样本来要从键盘获取输入的命令会转移到文件读取内容。

有一个文件是 test.sh,用两种方式输出文件的行数

#输出:14 没有文件名

第一个例子会输出文件名,第二个不会,因为它仅仅知道从标准输入读取的内容。

以下操作符在控制流的重定向时会被用到:

将输出已追加的方式重定向
Here 文档语法(见下文扩展),将开始标记 tag 和结束标记 tag 之间的内容作为输入

如果希望 stderr 重定向到 file,可以这样写:

#&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出 &-代表关闭与它绑定的描述符
 
如果希望 stdin 和 stdout 都重定向,可以这样写:

#提一下 << 这个连续两个小符号, 他代表的是『结束的输入字符』的意思。这样当空行输入eof字符,输入自动结束,不用ctrl+D
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null。


/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃,如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。

#test1.sh 没有的情况下,将错误输出信息关闭掉
#将错误输出2 绑定给 正确输出 1,然后将 正确输出 发送给 /dev/null设备 这种常用
#& 代表标准输出 ,错误输出 将所有标准输出与错误输出 输入到/dev/null文件
 
 
像其他语言一样,Shell 也可以加载外部脚本,将外部脚本的内容合并到当前脚本。shell 中加载外部脚本有两种写法。
两种方式效果相同,简单起见,一般使用点号(.),但是!注意点号(.)和文件名中间有一个空格
 
 
shell 提供了用于 debug 脚本的工具。如果想采用 debug 模式运行某脚本,可以在其 shebang 中使用一个特殊的选项。(有些 shell 不支持)
或者在执行 Bash 脚本的时候,从命令行传入这些参数
 
有时我们只需要 debug 脚本的一部分。这种情况下,使用 set 命令会很方便。这个命令可以启用或禁用选项。 使用 - 启用选项,使用 + 禁用选项。
1、用来在运行结果之前,先输出执行的那一行命令
2、执行脚本时,如果遇到不存在的变量会报错,并停止执行。(默认是忽略报错的)
顺便说一下,如果命令行下不带任何参数,直接运行set,会显示所有的环境变量和 Shell 函数。
3、执行脚本时,发生错误就终止执行。(默认是继续执行的)
set -e 根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭 set -e,该命令执行结束后,再重新打开 set -e。
4、管道命令执行失败,脚本终止执行
管道命令就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。最后一个子命令不失败,管道命令就总是会执行成功的,因此 set -e 会失效,后面的命令会继续执行。
set -o pipefail 用来解决这个情况,只要一个子命令失败,整个管道命令就会失败,脚本就会终止执行。
上面的命令可以放在一起使用:
 

脚本解释器在环境变量中指定

 
除了比较常见的用路径指定脚本解释器的方式,还有一种是指定环境变量中的脚本解释器。 指定环境变量中的脚本解释器
这样做的好处是,系统会自动在 PATH 环境变量中查找指定的程序(如例子中的 bash)。因为程序的路径是不确定的,比如安装完新版本的 bash 后,我们有可能会把这个新的路径添加到PATH中,来“隐藏”老版本的 bash。所以操作系统的PATH变量有可能被配置为指向程序的另一个版本,如果还是直接用
 
所有的程序,包括 Shell 启动的程序运行时都可以访问的变量就是环境变量。在 shell 脚本中使用 export 可以定义环境变量,但是只在当前运行的 shell 进程中有效,结束进程就没了。如果想持久化,需要将环境变量定义在一些列配置文件中。
 
  • Interactive 模式:通常是指读写数据都是从用户的命令行终端(terminal),用户输入命令,并在回车后立即执行的 shell。
 
 
 

进入 bash 交互模式时也可以用 --login 参数来决定是否是登录模式:

配置文件(启动文件)加载顺序

 


  • Non-Interactive 模式:通常就是执行脚本(script)的时候,此时配置项是从环境变量中读取和执行的,也就是 env 或者 printenv 命令输出的配置项。
 



如上图所示,开启一个 Shell 进程时,有一些参数的值也会影响到配置文件的加载。如--rcfile,--norc 等。
常用的 shell 环境变量:
命令搜索路径,以冒号为分隔符
用户主目录的路径名,是 cd 命令的默认参数
当前运行的 Shell 的全路径名
#输出个别的环境变量值的两种方式
 
全局变量是对所有用户都需要使用的变量,可以将新的变量或修改过的变量设置放在/etc/profile文件中,但升级了发行版该文件也会更新,所以这点要注意 (对所有用户)。


最好是在/etc/profile.d目录中创建一个以.sh结尾的文件,把所有新的变量或修改过的变量全部放在此文件中(对所有用户)。


对于存储个人用户永久性bash shell变量的地方是$HOME/.bashrc文件。这一点适用于所有类型的shell进程(仅对当前用户)。

 
$*$@ 都表示传递给函数或脚本的所有参数,不被双引号("")包含时,都是以"$1" "$2" ... "\\$n"形式把所有参数一个一个单独输出。
但是当他们被双引号包含是,"$*" 会将所有的参数作为一个整体,以"$1 $2 ... $n"的形式输出所有参数。"$@" 还是跟之前一样,把所有参数分开,一个一个的输出。

#输出:打印出没有引号的 $*
#输出:打印出有引号的 "$*"
#输出:打印出没有引号的 $@
#输出:打印出有引号的 "$@"
 
 
 
使用 echo 命令时,使用 -e 可以对转义字符进行替换。使用 -E 可以禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。
换页(FF),将当前位置移到下页开头
水平制表符(tab 键)

命令替换是指 Shell 可以先执行命令,将输出结果暂时保存,在适当的地方输出。

命令替换的语法是:反引号 ``。


在 bash 中,\$()与 ``(反引号)都是用来作命令替换的。先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。

相同点:\$() 与 `` 在操作上,这两者都是达到相应的效果

不同点:`` 很容易与''搞混乱,尤其对初学者来说,而\$( )比较直观;不过 \$() 有兼容性问题,有些类 Unix 系统不支持。

1、(()) 可直接用于整数计算

2、(()) 可重新定义变量值,用于判断条件或计算等

3、(()) 可用于进制转换

\$(())可以将其他进制转成十进制数显示出来。语法:$((N#xx)),其中,N 为进制,xx 为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值。

从上面可以看出,test[属于 Shell 的内置命令,[[属于 Shell 的保留关键字。

在使用上,test[是等价的,因为是命令,所以需要跟它的参数使用空格隔开。

因为 ] 作为最后一个参数表示条件结束,而像<>符号会被理解为重定向,导致错误

[[是关键字,能够按照常规的语义理解其中的内容,双中括号中的表达式看作一个单独的语句,并返回其状态码。

推荐使用[[ 来进行各种判断,可以避免很多错误。

如下展示单中括号会引发的错误

#例如$a为空,就会报语法错误,因为 [ 命令拿到的实际上只有 ==、1、] 三个参数

可以理解为“嵌入文档”。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本形式如下:


  • 结尾的 delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
  • 开始的 delimiter 前后的空格会被忽略掉。

  Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的对象过多时, 会导致 heap 内存不足, 进而引发 OutOfMemoryError 异常。

  上面是一个引发 OutOfMemoryError 异常的代码, 我们可以看到, 它就是通过不断地创建对象, 并将对象保存在 list 中防止其被垃圾回收, 因此当对象过多时, 就会使堆内存溢出。

  编译运行上述代码后, 会有如下输出:

  堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryError:java heap space,出现此种情况的时候,我们需要根据内存溢出的时候产生的dump文件来具体分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm启动参数)。出现此种问题的时候有可能是内存泄露,也有可能是内存溢出了。

  如果内存泄露,我们要找出泄露的对象是怎么被GC ROOT引用起来,然后通过引用链来具体分析泄露的原因。

  如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。

  下面我们通过如下的代码来演示一下此种情况的溢出:

  我们通过如下的命令运行上面的代码:

  程序输入如下的信息:

  从运行结果可以看出,JVM进行了一次Minor gc和两次的Major gc,从Major gc的输出可以看出,gc以后old区使用率为134K,而字节数组为10M,加起来大于了old generation的空间,所以抛出了异常,如果调整-Xms21M,-Xmx21M,那么就不会触发gc操作也不会出现异常了。

  我们知道, JVM 的运行时数据区中有一个叫做 虚拟机栈 的内存区域, 此区域的作用是: 每个方法在执行时都会创建一个栈帧, 用于存储局部变量表, 操作数栈, 方法出口等信息。

  因此我们可以创建一个无限递归的递归调用, 当递归深度过大时, 就会耗尽栈空间, 进而导致了 StackOverflowError 异常。

  下面是具体的代码:

  当编译运行上述的代码后, 会输出如下异常信息:

  栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。

  出现这种情况,一般情况下是程序错误所致的,比如写了一个死递归,就有可能造成此种情况。 下面我们通过一段代码来模拟一下此种情况的内存溢出。

  运行上面的代码,会抛出如下的异常:

  通过上面的实验其实也从侧面验证了一个结论:当对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存还是无法放下的时候,出发垃圾收集,收集后还是不能放下就会抛出内存溢出异常了

  我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space。

  我在工作可能在如下几种场景下出现此问题。

  使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。

  如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。

  一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。

  我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么我们就可以通过String.intern方法来模拟一下运行时常量区的溢出。下面我们通过如下的代码来模拟此种情况:

  我们通过如下的命令运行上面代码:

  运行后的输入如下图所示:

  通过上面的代码,我们成功模拟了运行时常量池溢出的情况,从输出中的PermGen space可以看出确实是持久带发生了溢出,这也验证了,我们前面说的Hotspot jvm通过持久带来实现方法区的说法。

  程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。

  给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的。

  JVM内存区域组成

  简单的说java中的堆和栈

  java把内存分两种:一种是栈内存,另一种是堆内存

  1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;

  2。堆内存用来存放由new创建的对象和数组

  在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理

  堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;

  栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。

  新创建的对象被分配到New区,当该区被填满时会被GC辅助线程移到Old区,当Old区也填满了会触发GC主线程遍历堆内存里的所有对象。Old区的大小等于Xmx减去-Xmn

  每个线程都有他自己的Stack

我要回帖

更多关于 软件编程代码 的文章

 

随机推荐