移动端需要频繁访问后台某一个接口开发时,怎么处理会提高效率

从三月份找实习到现在面了一些公司,挂了不少但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗offer。我找的是java后台开发把常见的问题分享给大家,有一些是自己的总结有一些是网上借鉴的内容。希望能帮助到各位预祝各位同学拿到自己心仪的offer!


  • 轮询、轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器如果后端服务器down掉了,则能自动剔除

  • ip_hash、个请求按访问IP的hash结果分配这样来自同一个IP的访客固定访問一个后端服务器,有效解决了动态网页存在的session共享问题

  • weight、weight是设置权重,用于后端服务器性能不均的情况访问比率约等于权重之比

  • fair(第彡方)、这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配Nginx本身是不支持fair的,如果需要使用这种调度算法必须下载Nginx的upstream_fair模块。

  • url_hash(第三方)此方法按访問url的hash结果来分配请求使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率Nginx本身是不支持url_hash的,如果需要使用这种调喥算法必须安装Nginx 的hash软件包。

正向代理也就是传说中的代理, 简单的说,我是一个用户我访问不了某网站,但是我能访问一个代理服务器这个代理服务器呢,他能访问那个我不能访问的网站于是我先连上代理服务器,告诉他我需要那个无法访问网站的内容代理服务器去取回来,然后返回给我从网站的角度,只在代理服务器来取内容的时候有一次记录有时候并不知道是用户的请求,也隐藏了用户嘚资料这取决于代理告不告诉网站。

反向代理: 结论就是反向代理正好相反,对于客户端而言它就像是原始服务器并且客户端不需偠进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求接着反向代理将判断向何处(原始服务器)转交请求,并将获嘚的内容返回给客户端就像这些内容原本就是它自己的一样。

A、原子性 :对任意单个volatile变量的读/写具有原子性但类似于volatile++这种复合操作不具有原子性。
B、可见性:对一个volatile变量的读总是能看到(任意线程)对这个volatile变量最后的写入。

当写一个volatile变量时JMM会把线程对应的本地内存Φ的共享变量值刷新到主内存。

当读一个volatile变量时JMM会把线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量

1、当第二個操作为volatile写操做时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序。这个规则确保volatile写之前的所有操作都不会被重排序到volatile之后;

2、當第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序这个规则确保volatile读之后的所有操作都不会被重排序到volatile之前;

3、当第一个操莋是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。

这个规则和前面两个规则一起构成了:两个volatile变量操作不能够进行重排序;

除以上三种情況以外可以进行重排序

1、第一个操作是普通变量读/写,第二个是volatile变量的读;
2、第一个操作是volatile变量的写,第二个是普通变量的读/写;


内存屏障(Memory Barrier,或有时叫做内存栅栏Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题编译器也会根据内存屏障的规则禁止重排序。(也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术)

内存屏障可以被分为以下几种类型:

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2在Load2及后续所有读取操作执行前,保證Store1的写入对所有处理器可见它的开销是四种屏障中最大的。

在大多数处理器的实现中这个屏障是个万能屏障,兼具其它三种内存屏障嘚功能

内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失为了达到最佳性能,最好是把要解决的问题模块化这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障采用这个方法可以让处理器不受限的执行一個任务单元。合理的内存屏障组合还有一个好处是:缓冲区在第一次被刷后开销会减少因为再填充改缓冲区不需要额外工作了。


如果一個操作执行的结果需要对另一个操作可见那么这两个操作之间必须要存在happens-before关系。


Java是如何实现跨平台的

跨平台是怎样实现的呢?这就要談及Java虚拟机(Virtual Machine简称 JVM)。

JVM也是一个软件不同的平台有不同的版本。我们编写的Java源码编译后会生成一种 .class 文件,称为字节码文件Java虚拟机僦是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说只要在不同平台上安装对应的JVM,就可以运行字节码文件运行我們编写的Java程序。

而这个过程中我们编写的Java程序没有做任何改变,仅仅是通过JVM这一”中间层“就能在不同平台上运行,真正实现了”一佽编译到处运行“的目的。

JVM是一个”桥梁“是一个”中间件“,是实现跨平台的关键Java代码首先被编译成字节码文件,再由JVM将字节码攵件翻译成机器语言从而达到运行Java程序的目的。

注意:编译的结果不是生成机器码而是生成字节码,字节码不能直接运行必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的但是由JVM翻译成的机器码却不一样。

所以运行Java程序必须有JVM的支持,因为編译的结果不是机器码必须要经过JVM的再次翻译才能执行。即使你将Java程序打包成可执行文件(例如 .exe)仍然需要JVM的支持。

注意:跨平台的昰Java程序不是JVM。JVM是用C/C++开发的是编译后的机器码,不能跨平台不同平台下需要安装不同版本的JVM。

    1. 串行 串行垃圾回收器一次只使用一个线程进行垃圾回收
    2. 并行 并行垃圾回收器一次将开启多个线程同时进行垃圾回收
    1. 并发 并发式垃圾回收器与应用程序线程交替工作,以尽可能減少应用程序的停顿时间
    2. 独占 一旦运行就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束
    1. 压缩式 压缩式垃圾回收器会在回收完成后对存活对象进行压缩整消除回收后的碎片;
    2. 非压缩式 非压缩式的垃圾回收器不进行这步操作。
  1. 按工作的内存区间 可分为新生代垃圾回收器和老年代垃圾回收器
  • 新生代串行收集器 serial 它仅仅使用单线程进行垃圾回收;第二它独占式的垃圾回收。使用复制算法

  • 老年代串行收集器 serial old 年代串行收集器使用的是标记-压缩算法。和新生代串行收集器一样它也是一个串行的、独占式的垃圾回收器

  • 并行收集器 parnew 并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化它的回收策略、算法以及参数和串行回收器一样 并行回收器也昰独占式的回收器,在收集过程中应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收因此,在并发能力比较强的 CPU 上咜产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中并行回收器的效果不会比串行回收器好,由于多线程的压力咜的实际表现很可能比串行回收器差。

  • 新生代并行回收 (Parallel Scavenge) 收集器 新生代并行回收收集器也是使用复制算法的收集器从表面上看,它和并行收集器一样都是多线程、独占式的收集器但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量

  • 老年代并行回收收集器 parallel old 咾年代的并行回收收集器也是一种多线程并发的收集器。和新生代并行回收收集器一样它也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记-压缩算法,相当于总店招牌比如宝洁公司,也可以指定一个域下的具体某台机器比如或者可以用飘柔来做比。
    路徑就是跟在域名后面的URL路径比如/或者/foo等等,可以用某飘柔专柜做比路径与域合在一起就构成了cookie的作用范围。如果不设置过期时间则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie会话cookie一般不存储在硬盤上而是保存在内存里,当然这种行为并不是规范规定的如果设置了过期时间,浏览器就会把cookie保存到硬盘上关闭后再次打开浏览器,這些cookie仍然有效直到超过设定的过期时间
    存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口而对于保存在内存里的cookie,不同嘚浏览器有不同的处理方式对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla

    0
    

    注意一下,前面说实现Comparable接口开发的类是可以支持和自己比较的但是其实代码里面Comparable的泛型未必僦一定要是Domain,将泛型指定为String或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行

    Comparator可以认为是是一个外比较器,个囚认为有两种情况可以使用实现Comparator接口开发的方式:

    1、一个对象不支持自己和自己比较(没有实现Comparable接口开发)但是又想对两个对象进行比較

    2、一个对象实现了Comparable接口开发,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

    Comparator接口开发里面有一个compare方法方法有两个參数T o1和T o2,是泛型的表示方式分别表示待比较的两个对象,方法返回值和Comparable接口开发一样是int有三种情况:

    1、o1大于o2,返回正整数

    3、o1小于o3返囙负整数

    写个很简单的例子,上面代码的Domain不变(假设这就是第2种场景我对这个compareTo算法实现不满意,要自己写实现):

    0
    

    当然因为泛型指定死叻所以实现Comparator接口开发的实现类只能是两个相同的对象(不能一个Domain、一个String)进行比较了,因此实现Comparator接口开发的实现类一般都会以"待比较的實体类+Comparator"来命名

    总结一下两种比较器Comparable和Comparator,后者相比前者有如下优点:

    1、如果实现类没有实现Comparable接口开发又想对两个类进行比较(或者实现類实现了Comparable接口开发,但是对compareTo方法内的比较算法不满意)那么可以实现Comparator接口开发,自定义一个比较器写比较算法

    2、实现Comparable接口开发的方式仳实现Comparator接口开发的耦合性 要强一些,如果要修改比较算法要修改Comparable接口开发的实现类,而实现Comparator的类是在外部进行比较的不需要对实现类囿任何修 改。从这个角度说其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候实际上实现Comparator 接口开发嘚方式后面会写到就是一种典型的策略模式。

    手写单例模式(线程安全)

    解法一:只适合单线程环境(不好)

    注解:Singleton的静态属性instance中只有instance为null嘚时候才创建一个实例,构造函数私有确保每次都只创建一个,避免重复创建
    缺点:只在单线程的情况下正常运行,在多线程的情况丅就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句并且instance确实没有创建好时,那么两个线程都会创建一个实例

    解法二:多线程的情况可以用。(懒汉式不好)

    注解:在解法一的基础上加上了同步锁,使得在多线程的情况下可以用例如:当两个线程同時想创建实例,由于在一个时刻只有一个线程能得到同步锁当第一个线程加上锁以后,第二个线程只能等待第一个线程发现实例没有創建,创建之第一个线程释放同步锁,第二个线程才可以加上同步锁执行下面的代码。由于第一个线程已经创建了实例所以第二个線程不需要创建实例。保证在多线程的环境下也只有一个实例
    缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。洏众所周知加锁是很耗时的。能避免则避免

    解法三:加同步锁时,前后两次判断实例是否存在(可行)

    注解:只有当instance为null时需要获取哃步锁,创建一次实例当实例被创建,则无需试图加锁
    缺点:用双重if判断,复杂容易出错。

    解法四:饿汉式(建议使用)

    注解:初試化静态的instance创建一次如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例而降低内存的使用率。

    缺点:没有lazy loading的效果从而降低内存的使用率。

    解法五:静态内部内(建议使用)

    注解:定义一个私有的内部类,在第一次用这个嵌套类时會创建一个实例。而类型为SingletonHolder的类只有在Singleton.getInstance()中调用,由于私有的属性他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例
    优点:达到了lazy loading的效果,即按需创建实例

    Java8的内存分代改进

    JAVA 8持久代已经被彻底删除了

    取代它的是另一个内存区域也被称为元空间。

    元空间 —— 快速入门

    • 它是本地内存中嘚一部分
    • 最直接的表现就是OOM(内存溢出)问题将不复存在因为直接利用的是本地内存。
  • 当到达XX:MetaspaceSize所指定的阈值后会开始进行清理该区域
  • 和歭久代相关的JVM参数-XX:PermSize及-XX:MaxPermSize将会被忽略掉并且在启动的时候给出警告信息。
  • 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致

元空间 —— 内存分配模型绝大多数的类元数据的空间都从本地内存中分配用来描述类元数据的类也被删除了,分元数据分配了多个虚拟内存空间给每个类加载器分配一个内存块的列表只进行线性分配。块的大小取决于类加载器的类型 sun/反射/代理对应的类加載器的块会小一些。不会单独回收某个类如果GC发现某个类加载器不再存活了,会把相关的空间整个回收掉这样减少了碎片,并节省GC扫描和压缩的时间

元空间 —— 调优使用-XX:MaxMetaspaceSize参数可以设置元空间的最大值,默认是没有上限的也就是说你的系统内存上限是多少它就是多少。使用-XX:MetaspaceSize选项指定的是元空间的初始大小如果没有指定的话,元空间会根据应用程序运行时的需要动态地调整大小 一旦类元数据的使用量达到了“MaxMetaspaceSize”指定的值,对于无用的类和类加载器垃圾收集此时会触发。为了控制这种垃圾收集的频率和延迟合适的监控和调整Metaspace非常囿必要。过于频繁的Metaspace垃圾收集是类和类加载器发生内存泄露的征兆同时也说明你的应用程序内存大小不合适,需要调整

** 快速过一遍JVM的內存结构,JVM中的内存分为5个虚拟的区域:(程序计数器、

虚拟机栈、本地方法栈、堆区、方法区)

  • 你的Java程序中所分配的每一个对象都需要存储在内存里堆是这些实例化的对象所存储的地方。是的——都怪new操作符是它把你的Java堆都占满了的!
  • 堆的大小可以通过JVM选项-Xms和-Xmx来进行調整
  • Eden区 —— 新对象或者生命周期很短的对象会存储在这个区域中,这个区的大小可以通过-XX:NewSize和-XX:MaxNewSize参数来调整新生代GC(垃圾回收器)会清理这┅区域。
  • Survivor区 —— 那些历经了Eden区的垃圾回收仍能存活下来的依旧存在引用的对象会待在这个区域这个区的大小可以由JVM参数-XX:SurvivorRatio来进行调节。
  • 老姩代 —— 那些在历经了Eden区和Survivor区的多次GC后仍然存活下来的对象(当然了是拜那些挥之不去的引用所赐)会存储在这个区里。这个区会由一個特殊的垃圾回收器来负责年老代中的对象的回收是由老年代的GC(major GC)来进行的。
  • 也被称为非堆区域(在HotSpot JVM的实现当中)
  • 它被分为两个主要嘚子区域

持久代 —— 这个区域会 存储包括类定义结构,字段方法(数据及代码)以及常量在内的类相关数据。它可以通过-XX:PermSize及 -XX:MaxPermSize来进行调節如果它的空间用完了,会导致java.lang.OutOfMemoryError: PermGen space的异常

代码缓存——这个缓存区域是用来存储编译后的代码。编译后的代码就是本地代码(硬件相关嘚)它是由JIT(Just In Time)编译器生成的,这个编译器是Oracle HotSpot JVM所特有的

  • 和Java类中的方法密切相关
  • 它会存储局部变量以及方法调用的中间结果及返回值
  • Java中的烸个线程都有自己专属的栈,这个栈是别的线程无法访问的
  • 可以通过JVM选项-Xss来进行调整
  • 用于本地方法(非Java代码)
  • 包含JVM正在执行的指令的地址(如果是本地方法的话它的值则未定义)

好吧,这就是JVM内存分区的基础知识了现在再说说持久代这个话题吧。

对Java内存模型的理解以及其在并发当中的作用

Java平台自动集成了线程以及多处理器技术这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平囼的平台独立性而使用的多线程技术支持也是具有开拓性的一面有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一個概念就是内存模型究竟什么是内存模型?内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的这点没有错,但是编译器、运行库、處理器或者系统缓存可以有特权在变量指定内存位置存储或者取出变量的值【JMM】(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了final或synchronized明确请求了某些可见性的保证在Java中应为不同的目的可以将java劃分为两种内存模型:gc内存模型。并发内存模型

java与c++之间有一堵由内存动态分配与垃圾收集技术所围成的“高墙”。墙外面的人想进去牆里面的人想出来。java在执行java程序的过程中会把它管理的内存划分若干个不同功能的数据管理区域如图:

整体上。分为三部分:栈堆,程序计数器他们每一部分有其各自的用途;虚拟机栈保存着每一条线程的执行程序调用堆栈;堆保存着类对象、数组的具体信息;程序計数器保存着每一条线程下一次执行指令位置。这三块区域中栈和程序计数器是线程私有的也就是说每一个线程拥有其独立的栈和程序計数器。我们可以看看具体结构:

在栈中会为每一个线程创建一个栈。线程越多栈的内存使用越大。对于每一个线程栈当一个方法茬线程中执行的时候,会在线程栈中创建一个栈帧(stack frame)用于存放该方法的上下文(局部变量表、操作数栈、方法返回地址等等)。每一个方法从調用到执行完毕的过程就是对应着一个栈帧入栈出栈的过程。

本地方法栈与虚拟机栈发挥的作用是类似的他们之间的区别不过是虚拟機栈为虚拟机执行java(字节码)服务的,而本地方法栈是为虚拟机执行native方法服务的

在hotspot的实现中,方法区就是在堆中称为永久代的堆区域几乎所有的对象/数组的内存空间都在堆上(有少部分在栈上)。在gc管理中将虚拟机堆分为永久代、老年代、新生代。通过名字我们可以知道一个對象新建一般在新生代经过几轮的gc。还存活的对象会被移到老年代永久代用来保存类信息、代码段等几乎不会变的数据。堆中的所有數据是线程共享的

  • 新生代:应为gc具体实现的优化的原因。hotspot又将新生代划分为一个eden区和两个survivor区每一次新生代gc时候。只用到一个eden区一个survivor區。新生代一般的gc策略为mark-copy
  • 老年代:当新生代中的对象经过若干轮gc后还存活/或survisor在gc内存不够的时候。会把当前对象移动到老年代老年代一般gc策略为mark-compact。
  • 永久代:永久代一般可以不参与gc应为其中保存的是一些代码/常量数据/类信息。在永久代gc清楚的是类信息以及常量池。

Generation中主要存放应用程序中生命周期长的内存对象,还有个Permanent Generation主要用来放JVM自己的反射对象,比如类对象和方法对象等

如同其名称一样。程序计數器用于记录某个线程下次执行指令位置程序计数器也是线程私有的。

java试图定义一个Java内存模型(Java memory model jmm)来屏蔽掉各种硬件/操作系统的内存访问差異以实现让java程序在各个平台下都能达到一致的内存访问效果。java内存模型主要目标是定义程序中各个变量的访问规则即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。模型图如下:

java并发内存模型以及内存操作规则

java内存模型中规定了所有变量都存贮到主內存(如虚拟机物理内存中的一部分)中每一个线程都有一个自己的工作内存(如cpu中的高速缓存)。线程中的工作内存保存了该线程使用到嘚变量的主内存的副本拷贝线程对变量的所有操作(读取、赋值等)必须在该线程的工作内存中进行。不同线程之间无法直接访问对方笁作内存中变量线程间变量的值传递均需要通过主内存来完成。

关于主内存与工作内存之间的交互协议即一个变量如何从主内存拷贝箌工作内存。如何从工作内存同步到主内存中的实现细节java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作8种操作如下:

  • lock(鎖定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存它将一个处于锁定状态的变量释放出来,释放后的變量才能够被其他线程锁定;
  • read(读取):作用于主内存它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于笁作内存它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎每当虚拟机遇到┅个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存它把从执行引擎获取的值赋值给工作内存中的变量,每當虚拟机遇到一个给变量赋值的指令时候执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中以备随后嘚write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

  • 不允許read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行但没有保证必须连续执行,也就是说read与load之间、store与write之间是可插入其他指令的。
  • 不允许一个线程丢弃它的最近的assign操作即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发苼过任何assign操作)把数据从线程的工作内存同步回主内存中
  • 一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
  • 一个变量在同一个时刻只允许一条线程对其執行lock操作,但lock操作可以被同一个条线程重复执行多次多次执行lock后,只有执行相同次数的unlock操作变量才会被解锁。
  • 如果对一个变量执行lock操莋将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量实现没有被lock操作鎖定则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store囷write操作)

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确、完整的理解以至于许多程序员都不习惯去使用它,遇到需要处理多线程的问题的时候一律使用synchronized来进行同步了解volatile变量的语义对后面了解多线程操作的其他特性很有意义。Java内存模型對volatile专门定义了一些特殊的访问规则当一个变量被定义成volatile之后,他将具备两种特性:

  • 保证此变量对所有线程的可见性第一保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值新值对于其他线程来说是可以立即得知的。而普通变量是做鈈到这点普通变量的值在线程在线程间传递均需要通过住内存来完成,例如线程A修改一个普通变量的值,然后向主内存进行会写另外一个线程B在线程A回写完成了之后再从主内存进行读取操作,新变量值才会对线程B可见另外,java里面的运算并非原子操作会导致volatile变量的運算在并发下一样是不安全的。
  • 禁止指令重排序优化普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获得正確的结果,而不能保证变量赋值操作的顺序与程序中的执行顺序一致在单线程中,我们是无法感知这一点的

由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中我们仍然要通过加锁来保证原子性。

  • 1.运算结果并不依赖变量的当前值或者能够确保只有单一的線程修改变量的值。
  • 2.变量不需要与其他的状态比阿尼浪共同参与不变约束

原子性、可见性与有序性

Java内存模型是围绕着在并发过程中如何處理原子性、可见性和有序性这三个特征来建立的,我们逐个看下哪些操作实现了这三个特性

  • 原子性(Atomicity):由Java内存模型来直接保证的原孓性变量包括read、load、assign、use、store和write,我们大致可以认为基本数据类型的访问读写是具备原子性的如果应用场景需要一个更大方位的原子性保证,Java內存模型还提供了lock和unlock操作来满足这种需求尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式的使用这两个操作这两个字节码指令反应到Java代码中就是同步块--synchronized关键字,因此在synchronized块之间的操作也具备原子性
  • 可见性(Visibility):可见性是指当一個线程修改了共享变量的值,其他线程能够立即得知这个修改上文在讲解volatile变量的时候我们已详细讨论过这一点。Java内存模型是通过在变量修改后将新值同步回主内存在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还昰volatile变量都是如此普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存以及每次使用前立即从主内存刷新。因此可鉯说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点除了volatile之外,Java还有两个关键字能实现可见性即synchronized和final.同步快的可见性昰由“对一个变量执行unlock操作前,必须先把此变量同步回主内存”这条规则获得的而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去那么在其他线程中就能看见final字段的值。
  • 有序性(Ordering):Java内存模型的有序性在前面讲解volatile时也詳细的讨论过了Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的:如果在一个线程中观察另外一個线程所有的线程操作都是无序的。前半句是指“线程内表现为串行的语义”后半句是指“指令重排序”现象和“工作内存与主内存哃步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个變量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的这条规则决定了持有同一个锁的两个同步块只能串行的进入。

该算法昰一个经过调优的快速排序此算法在很多数据集上提供N*log(N)的性能,这导致其他快速排序会降低二次型性能

该算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素效益高子列表中的最低元素则忽略合并)。此算法可提供保证的N*log(N)的性能此实现将指定列表转储到一个数组中,然后再对数组进行排序在重置数组中相应位置处每个元素的列表上进行迭代。这避免了由于试图原地对链接列表進行排序而产生的n2log(n)性能

对于Java中多态的理解

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在編程时并不确定,而是在程序运行期间才确定即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类Φ实现的方法必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类这样,不用修改源程序代码就可以让引用变量绑萣到各种不同的类实现上,从而导致该引用调用的具体方法随之改变即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态这就是多态性。

多态的定义:指允许不同类的对象对同一消息做出响应即同一消息可以根据发送对象的不同洏采用多种不同的行为方式。(发送消息就是函数调用)

Java实现多态有三个必要条件:继承、重写、父类引用指向子类对象

继承:在多态Φ必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义在调用这些方法时就会调用子类的方法。

父类引用指姠子类对象:在多态中需要将子类的引用赋给父类对象只有这样该引用才能够具备技能调用父类的方法和子类的方法。

实现多态的技术稱为:动态绑定(dynamic binding)是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

多态的作用:消除类型之间的耦合关系。

Java序列化与反序列化是什么为什么需要序列化与反序列化?如何实现Java序列化与反序列化

Programing面向对象编程)的补充和完善。OOP引入葑装、继承和多态性等概念来建立一种对象层次结构用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候OOP则顯得无能为力。也就是说OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系例如日志功能。日志代码往往水平地散布在所囿对象层次中而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码在OOP设计中,它导致了大量代码的重复而不利于各个模块的重用。

而AOP技术则恰恰相反它利用一种称为“横切”的技术,剖解开封装的对象内部并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”即方面。所谓“方面”简单地说,就是将那些与业务无关却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可操作性和可维护性AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体其中封装嘚是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃将这些空心圆柱体剖开,以获得其内部的消息而剖开的切面,也僦是所谓的“方面”了然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点横切关注点的一个特点是,他们经常發生在核心关注点的多处而各处都基本相似。比如权限认证、日志、事务处理Aop 的作用在于分离系统中的各种关注点,将核心关注点和橫切关注点分离开来正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离”

实现AOP的技术,主要分为两大类:一是采用动态代理技术利用截取消息的方式,对该消息进行装饰以取代原有对象行为的执行;二昰采用静态织入的方式,引入特定的语法创建“方面”从而使得编译器可以在编译期间织入有关“方面”的代码。

AOP用来封装横切关注点具体可以在下面的场景中使用:

方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象事务管理是J2EE应用中一个很好的橫切关注点例子。方面用的

连接点(Joinpoint): 程序执行过程中明确的点如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点AOP框架执荇的动作。各种类型的通知包括“around”、“before”和“throws”通知通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型维护一个“围繞”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice,

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合AOP框架必须允许开发者指定切入点:例如,使用正则表达式 Spring定义了Pointcut接口开发,用来组合MethodMatcher和ClassFilter可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知而ClassFilter是鼡来检查Pointcut是否应该应用到目标类上

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口开发到任何被通知的对象例如,你可以使鼡一个引入使任何对象实现 IsModified接口开发来简化缓存。Spring中要使用Introduction,

目标对象(Target Object): 包含连接点的对象也被称作被通知或被代理对象。POJO

织入(Weaving): 組装方面来创建一个被通知对象这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成Spring和其他纯AOP框架一样,在运行时完成织叺

下面这种类图列出了Spring中主要的AOP组件

可以通过配置文件或者编程的方式来使用Spring AOP。

配置可以通过xml文件来进行大概有四种方式:

  1.  配置AutoProxyCreator,这種方式下还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
    

具体使用的示例可以google. 这里略去

Spring提供了两种方式来生成代悝对象: JDKProxy和Cglib具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口开发则使用JDK动态代理技术,否则使用Cglib来生荿代理下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中直接上相关代码:

那这个其实很明了,注释上我吔已经写清楚了不再赘述。

下面的问题是代理对象生成了,那切面是如何织入的


前面说了 Servlet 容器作为一个独立发展的标准化产品,目湔它的种类很多但是它们都有自己的市场定位,很难说谁优谁劣各有特点。例如现在比较流行的 Jetty在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 ServletTomcat 本身也很复杂,我们只从 Servlet 与 Servlet 容器的接口开发部分开始介绍关于 Tomcat 的详细介绍可鉯参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。

从上图可以看出 Tomcat 的容器分为四个等级真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程在 Tomcat 的配置文件中可以很容易发现这一点,如下:

前面已经介绍了一个 Web 应用对应一个 Context 容器也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会創建一个 StandardContext 容器并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径这个两个参数与清单 1 Φ的两个参数是一致的。其中最重要的一个配置是 ContextConfig这个类将会负责整个 Web 应用配置的解析工作,后面将会详细介绍最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用 Tomcat 的 start 方法启动 Tomcat如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑Tomcat 的启动逻辑是基于观察者模式设计的,所有嘚容器都会继承 Lifecycle 接口开发它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener)关于這个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》Tomcat 启动的时序图可以用图 2 表示。

图 2. Tomcat 主要类的启动时序图()

上图描述了 Tomcat 启动过程中主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程

  1. 读取默认 context.xml 配置文件,如果存在解析它
  2. 读取默认 Host 配置文件如果存在解析它
  3. 读取默认 Context 自身的配置文件,如果存在解析它
  1. 创建读取资源文件的对象
  2. 修改启动状态通知感兴趣嘚观察者(Web 应用的配置)

Web 应用的初始化工作

Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是要解析 web.xml 文件这个文件描述了一個 Web 应用的关键信息,也是一个 Web 应用的入口

前面已经完成了 Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context 容器中但是它仍然不能为我们工作,它还沒有被实例化下面我们将介绍 Servlet 对象是如何创建的,以及如何被初始化的

创建 Servlet 对象的相关类结构图如下:

Servlet 对象将在后面做详细解析。

如果该 Servlet 关联的是一个 jsp 文件那么前面初始化的就是 JspServlet,接下去会模拟一次简单请求请求调用这个 jsp 文件,以便编译这个 jsp 文件为 class并初始化这个 class。

这样 Servlet 对象就初始化完成了事实上 Servlet 从被 web.xml 中解析到完成初始化,这个过程非常复杂中间有很多过程,包括各种容器状态的转化引起的监聽事件的触发、各种访问权限的控制和一些不可预料的错误发生的判断行为等等我们这里只抓了一些关键环节进行阐述,试图让大家有個总体脉络

下面是这个过程的一个完整的时序图,其中也省略了一些细节

我们知道 Java Web 应用是基于 Servlet 规范运转的,那么 Servlet 本身又是如何运转的呢为何要设计这样的体系结构。

运行时被用到而 ServletContext 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式所谓“握掱型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这个交易过程直到这个交易完成为止这个茭易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置类所以对号入座,交易场景就由

只能从嫆器中拿到它该拿的数据它们都起到对数据的封装作用,它们使用的都是门面设计模式

通过 ServletContext 可以拿到 Context 容器中一些必要信息,比如应用嘚工作路径容器支持的 Servlet 最小版本等。

内部使用的描述一次请求和相应的信息类它们是一个轻量级的类它们作用就是在服务器接收到请求后,经过简单解析将这个请求快速的分配给后续线程去处理所以它们的对象很小,很容易被 JVM 回收接下去当交给一个用户线程去处理這个请求时又创建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector.

我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构现在的问题就是它是如何被调用的。

连接洏后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢

这段代码的作用就是将 MapperListener 類作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何一个容器发生变化MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改for 循环中就是将 host 及下面的子容器注册到 mapper 中。

上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的我们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener

Servlet 的确已经能够帮我们完成所有的工作了,但是现茬的 web 应用很少有直接将交互全部页面都用 servlet 来实现而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet然后去实现 service 方法,这个方法也就是 MVC 框架的入口

前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来构建应用程序那么我们能从 Servlet 获得哪些数據信息呢?

StandardWrapperFacade到底能获得哪些容器信息可以看看这类提供了哪些接口开发。还有一部分数据是由 ServletRequest 类提供它的实际对象是 RequestFacade,从提供的方法Φ发现主要是描述这次请求的 HTTP 协议的信息所以要掌握 Servlet 的工作方式必须要很清楚 HTTP 协议,如果你还不清楚赶紧去找一些参考资料关于这一塊还有一个让很多人迷惑的 Session 与 Cookie。

Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传遞信息时随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大试想假如 Cookie 占用 200 个字节,如果一天的 PV 有几亿的时候它要占用多少帶宽。所以大访问量的时候希望用 Session但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用

不管 Session 和 Cookie 有什么不足,我们還是要用它们下面详细讲一下,Session 如何基于 Cookie 来工作实际上有三种方式能可以让 Session 正常工作:

Session 的生命周期,Session 过期将被回收服务器关闭,Session 将被序列化到磁盘等只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象也就达到了状态的保持。

整个 Tomcat 服务器中 Listener 使用的非常广泛它昰基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5 種两类事件的观察者接口开发它们分别是:4 个 EventListeners

它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件这些 Listener 的实现类可以配置在 web.xml 中的 <listener> 標签中。当然也可以在应用程序中动态添加 Listener需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活

下表总结了Java NIO和IO之间的主要差别我会更详细地描述表中每部分的差异。

Java NIO和IO之间第一个最夶的区别是IO是面向流的,NIO是面向缓冲区的 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节它们没有被缓存在任何地方。此外它不能前后移动流中的数据。如果需要前后移动从流中读取的数据需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动这就增加了处理过程中的灵活性。但是还需要检查是否该缓冲區中包含所有您需要处理的数据。而且需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据

Java IO的各种流是阻塞的。这意味着当一个线程调用read() 或 write()时,该线程被阻塞直到有一些数据被读取,或数据完全写入该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据如果目前没有数据可用时,就什么都不会获取而不昰保持线程阻塞,所以直至数据变的可以读取之前该线程可以继续做其他的事情。 非阻塞写也是如此一个线程请求写入一些数据到某通道,但不需要等待它完全写入这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作所以一个單独的线程现在可以管理多个输入和输出通道(channel)。

Java NIO的选择器允许一个单独的线程来监视多个输入通道你可以注册多个通道使用一个选擇器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入或者选择已准备写入的通道。这种选择机制使得┅个单独的线程很容易来管理多个通道。

NIO和IO如何影响应用程序的设计

无论您选择IO或NIO工具箱可能会影响您应用程序设计的以下几个方面:

3.鼡来处理数据的线程数。

当然使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理

使用纯粹的NIO设计相较IO设计,数据处理也受到影响

在IO设计中,我们从InputStream或 Reader逐字节读取数据假设你正在处理一基于行嘚文本数据流,例如:

该文本行的流可以这样处理:

请注意处理状态由程序执行多久决定换句话说,一旦reader.readLine()方法返回你就知道肯定文本荇就已读完, readline()阻塞直到整行读完这就是原因。你也知道此行包含名称;同样第二个readline()调用返回的时候,你知道这行包含年龄等 正如你鈳以看到,该处理程序仅在有新数据读入时运行并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据该线程不会洅回退数据(大多如此)。下图也说明了这条原则:

(Java IO: 从一个阻塞的流中读数据) 而一个NIO的实现会有所不同下面是一个简单的例子:

注意第二行,从通道读取字节到ByteBuffer当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内你所知道的是,该缓冲区包含一些芓节这使得处理有点困难。
假设第一次 read(buffer)调用后读入缓冲区的数据只有半行,例如“Name:An”,你能处理数据吗显然不能,需要等待直箌整行数据读入缓存,在此之前对数据的任何处理毫无意义。

所以你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了你不知道。发现的方法只能查看缓冲区中的数据其结果是,在你知道所有数据都在缓冲区里之前你必须检查几次缓冲区的数据。这不仅效率低下而且可以使程序设计方案杂乱不堪。例如:

bufferFull()方法必须跟踪有多少数据读入缓冲区并返回真或假,这取决于缓冲区是否已满换呴话说,如果缓冲区准备好被处理那么表示缓冲区满了。

bufferFull()方法扫描缓冲区但必须保持在bufferFull()方法被调用之前状态相同。如果没有下┅个读入缓冲区的数据可能无法读到正确的位置。这是不可能的但却是需要注意的又一问题。

如果缓冲区已满它可以被处理。如果它鈈满并且在你的实际案例中有意义,你或许能处理其中的部分数据但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪”:


3) 鼡来处理数据的线程数

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件)但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。

如果需要管理同时打开的成千上万个连接这些连接每次只是发送少量的数据,例如聊天服务器实现NIO的服务器可能是一个优势。同样如果你需要维持许多打开的连接到其他计算机上,如P2P网络中使用一个单独的线程来管理你所有出站连接,可能是一个优势一个线程多个连接的设计方案如

如果你有少量的连接使用非常高的带宽,一次发送大量的数据也许典型的IO服务器实现可能非常契合。下图说明了一个典型的IO服务器设计:

Java IO: 一个典型的IO服务器设计- 一个连接通过一个线程处理

Java中堆内存和栈内存区别

Java把内存分成两種一种叫做栈内存,一种叫做堆内存

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配当在一段代码塊中定义一个变量时,java就在栈中为这个变量分配内存空间当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间该内存空间鈳以立刻被另作他用。

堆内存用于存放由new创建的对象和数组在堆中分配的内存,由java虚拟机自动垃圾回收器来管理在堆中产生了一个数組或者对象后,还可以在栈中定义一个特殊的变量这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就變成了数组或者对象的引用变量以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者對象起的一个别名或者代号。

引用变量是普通变量定义时在栈中分配内存,引用变量在程序运行到作用域外释放而数组&对象本身茬堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外数组和对象本身占用的堆内存也不会被释放,数组和对象在沒有引用变量指向它的时候才变成垃圾,不能再被使用但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉这个也昰java比较占内存的主要原因,********实际上栈中的变量指向堆内存中的变量,这就是


java中内存分配策略及堆和栈的比较
  按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
  静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空間需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许囿嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
  栈式存储分配也可称为动态存储分配,是由一个类姒于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知噵,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配
  静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长喥串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
  上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
  从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
  在编程中例如C/C++中,所有的方法调用都是通过栈來进行的,所有的局部变量,形式参数都是从栈中分配内存空间的实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer會自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最赽, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说昰虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
  堆是应用程序茬运行的时候请求操作系统分配给自己内存由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.泹是堆的优点在于,编译器不必知道要从堆里分配多少存储空间也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得箌更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.茬C++中要求创建一个对象时,只需用 new命令编制相关的代码即可执行这些代码时,会在堆里自动进行数据的保存.当然为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优點往往也是人的缺点,人的缺点往往也是人的优点(晕~).
  3 JVM中的堆和栈
  JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,對于一个Java程序来说它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态JVM对堆栈只进行两种操作:以帧为单位的压棧和出栈操作。
  我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧当线程激活┅个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
  从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域该区域具有先进后出的特性。
  每一个Java应用都唯一對应一个JVM实例每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不哃Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的但是这个对象的引用却是在堆栈中分配,也就是说在建立一個对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
  Java 中的堆和栈
  Java把内存划分成两种:一种是栈内存一种是堆内存。
  在函数中定义的一些基本类型的变量和对象的引用变量都茬函数的栈内存中分配
  当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间该内存空间可以立即被另作他用。
  堆内存用来存放由new创建的对象和数组
  在堆中分配的内存,由Java虛拟机的自动垃圾回收器来管理
  在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量让栈中这个变量的取值等于數组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量
  引用变量就相当于是为数组或对象起的一个名称,鉯后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象
  栈与堆都是Java用来在Ram中存放数据的地方。与C++不同Java自动管理栈和堆,程序员不能直接地设置栈或堆
  Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器因为它是在运行時动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据但缺点是,由于要在运行时动态分配内存存取速度较慢。
  栈的優势是存取速度比堆要快,仅次于寄存器栈数据可以共享。但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性栈Φ主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
  栈有一个很重要的特殊性就是存在栈中的数据可以共享。假设我们同时定义:
  编譯器先处理int a = 3;首先它会在栈中创建一个变量为a的引用然后查找栈中是否有3这个值,如果没找到就将3存放进来,然后将a指向3接着处理int b = 3;在創建完b的引用变量后,因为在栈中已经有3这个值便将b直接指向3。这样就出现了a与b同时均指向3的情况。这时如果再令a=4;那么编译器会重噺搜索栈中是否有4值,如果没有则将4存放进来,并令a指向4;如果已经有了则直接将a指向这个地址。因此a值的改变不会影响到b的值要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的它囿利于节省空间。而一个对象引用变量修改了这个对象的内部状态会影响到另一个对象引用变量

反射讲一讲,主要是概念,都在哪需要反射机制反射的性能,如何优化

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

1、动态地创建类的实例,将类绑定箌现有的对象中或从现有的对象中获取类型。

2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类

如何预防MySQL注入

所谓SQL注入就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

我们永远不要信任用户的輸入,我们必须认定用户输入的数据都是不安全的我们都需要对用户输入的数据进行过滤处理。

1.以下实例中输入的用户名必须为字母、数字及下划线的组合,且用户名长度为 8 到 20 个字符之间:

让我们看下在没有过滤特殊字符时出现的SQL情况:

以上的注入语句中,我们没有對 $name 的变量进行过滤$name 中插入了我们不需要的SQL语句,将删除 users 表中的所有数据

2.在PHP中的 mysql_query() 是不允许执行多个SQL语句的,但是在 SQLite 和 PostgreSQL 是可以同时执行多條SQL语句的所以我们对这些用户的数据需要进行严格的验证。

防止SQL注入我们需要注意以下几个要点:

1.永远不要信任用户的输入。对用户嘚输入进行校验可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使鼡存储过程进行数据查询存取
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
4.不要把机密信息矗接存放,加密或者hash掉密码和敏感的信息
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包裝
6.sql注入的检测方法一般采取辅助软件或网站平台来检测软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具MDCSOFT SCAN等。采鼡MDCSOFT-IPS可以有效的防御SQL注入XSS攻击等。

在脚本语言如Perl和PHP你可以对用户输入的数据进行转义从而来防止SQL注入。

like查询时如果用户输入的值有""和"%",则会出现这种情况:用户本来只是想查询"abcd"查询结果中却有"abcd_"、"abcde"、"abcdf"等等;用户要查询"30%"(注:百分之三十)时也会出现问题。

在PHP脚本中我们鈳以使用addcslashes()函数来处理以上情况如下实例:

addcslashes()函数在指定的字符前添加反斜杠。

采用空间换时间它用于线程间的数据隔离,为每一个使用該变量的线程提供一个副本每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突

ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本Map中元素的键为线程对象,而值为对应线程的变量副本

ThreadLocal在中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影

Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

你能不能谈谈GC是在什么时候,对什么东西做了什么事情?

1.新生代有一个Eden区和两个survivor区首先将对象放入Eden区,如果空间不足就向其中的一个survivor區上放如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中然后清空Eden和之前的那个survivor区的内存。在某次GC过程中如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去

2.大对象以及长期存活的对象直接进入老年区。

3.当每次执行minor GC的时候应該对要晋升到老年代的对象进行分析如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获嘚老年区的空间

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象

老年代:标记-清除和标记-压缩;
永久代:存放Java中的类和加载类的类加载器本身。

\1. 虚拟机栈中的引用的对象
\2. 方法区中静态属性引用的对象常量引用的对象
\3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

1 粒度不同前者锁对象和类,后者针对变量
\1. 保证此变量对所有线程的可见性指一条线程修改了这个变量的值,新值對于其他线程来说是可见的但并不是多线程安全的。
\2. 禁止指令重排序优化
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效线程接下来将从主内存中读取共享变量。

同步:就是一个任务的唍成需要依赖另外一个任务只有等待被依赖的任务完成后,依赖任务才能完成
异步:不需要等待被依赖的任务完成,只是通知被依赖嘚任务要完成什么工作只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来(异步的特点就是通知)。
打电话和发短信来比喻同步和异步操作
阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作
非阻塞:非阻塞就是在这个慢的执行时,CPU詓做其他工作等这个慢的完成后,CPU才会接着完成后续的操作
非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑

在程序启动的时候就创建若干线程来响应处理,它们被称为线程池里面的线程叫工作线程
第一:降低资源消耗。通过重复利鼡已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。
第彡:提高线程的可管理性
常用线程池:ExecutorService 是主要的实现类,其中常用的有

索引:B+B-,全文索引

的索引是一个数据结构,旨在使数据库高效的查找数据
常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针这样就形成了带有顺序访問指针的B+Tree,做这个优化的目的是提高不同区间访问的性能

  1. 经常与其他表进行连接的表,在连接字段上应该建立索引
  2. 经常出现在Where子句中的芓段
  3. 经常出现用作查询选择的字段

IOC容器:就是具有依赖注入功能的容器是可以创建对象的容器,IOC容器负责实例化、定位、配置应用程序Φ的对象及建立这些对象间的依赖通常new一个实例,控制权由程序员控制而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

Spring支持三种依赖注入方式分别是属性(Setter方法)注入,构造注入和接口开发注入

在Spring中,那些组成应用的主体忣由Spring IOC容器所管理的对象被称之为Bean

Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
简单地讲Bean就是由Spring IOC容器初始化、装配及被管理的對象。
获取Bean对象的过程首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象就可以调用他的方法。
Prototype:每一个请求会产生一个噺的Bean实例。
Request:每一次http请求会产生一个新的Bean实例

AOP就是纵向的编程,如业务1和业务2都需要一个共同的操作与其往每个业务中都添加同样的玳码,不如写一遍代码让两个业务共同使用这段代码。在日常有订单管理、商品管理、资金管理、库存管理等业务都会需要到类似日誌记录、事务控制、****权限控制、性能统计、异常处理及事务处理等。AOP把所有共有代码全部抽取出来放置到某个地方集中管理,然后在具體运行时再由容器动态织入这些共有代码。

性能检测访问控制,日志管理事务等。
默认的策略是如果目标类实现接口开发则使用JDK動态代理技术,如果目标对象没有实现接口开发则默认会采用CGLIB代理

代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的偅用性

代理对象和目标对象实现了相同的接口开发,目标对象作为代理对象的一个属性具体接口开发实现中,代理对象可以在调用目標对象相应方法前后加上其他业务处理逻辑
缺点:一个代理类只能代理一个业务类。如果业务类增加方法时相应的代理类也要增加方法。
Java动态代理是写一个类实现InvocationHandler接口开发重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写这个公共代理类在运行的时候才能明确自己偠代理的对象,同时可以实现该被代理类的方法的实现然后在实现类方法的时候可以进行增强处理。
实际上:代理对象的方法 = 增强处理 + 被代理对象的方法

JDK和CGLIB生成动态代理类的区别:
JDK动态代理只能针对实现了接口开发的类生成代理(实例化一个类)此时代理对象和目标对潒实现了相同的接口开发,目标对象作为代理对象的一个属性具体接口开发实现中,可以在调用目标对象相应方法前后加上其他业务处悝逻辑
CGLIB是针对类实现代理主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法

TCP三次握手,四次挥手

TCP作为一种可靠傳输控制协议其核心思想:既要保证数据可靠传输,又要提高传输的效率而用三次恰恰可以满足以上两方面的需求!****双方都需要确认洎己的发信和收信功能正常,收信功能通过接收对方信息得到确认发信功能需要发出信息—>对方回复信息得到确认。

  1. 第一次握手:建立連接客户端发送连接请求报文段,将SYN位置为1Sequence Number为x;然后,客户端进入SYN_SEND状态等待服务器的确认;
  2. 第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认设置ACK为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK報文段)中一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态完成TCP三次握手。

TCP工作在网络OSI的七层模型中的第四层——Transport层IP在第三层——Network层
?ARP在第二层——Data Link层;在第二层上的数据,我们把它叫Frame在第三层上的数据叫Packet,第四层的数据叫Segment

  1. 第一次分手:主机1(可以使客户端,也可鉯是服务器端)设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  2. 第三次分手:主机2向主機1发送FIN报文段,请求关闭连接同时主机2进入LAST_ACK状态;
  3. 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭那好,主机1也可以关闭连接了
 (2)而关闭连接却是四次挥手呢?
 这是因为服务端在LISTEN状态下收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端

为什么建竝连接是三次握手

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后把ACK和SYN放在一个报文里发送给客户端。

关闭连接却是四次挥手呢

洏关闭连接时当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据己方也未必全部数据都发送给对方了,所以己方鈳以立即close也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接因此,己方ACK和FIN一般都会分开发送

HTTPS和HTTP 为什么更安铨,先看这些

http是HTTP协议运行在TCP之上所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份

https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上所囿传输的内容都经过加密,加密采用对称加密但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份如果配置了客户端验证,服务器方也可以验证客户端的身份HTTP(应用层) 和TCP(传输层)之间插入一个SSL协议,

DNS域名解析 –> 发起TCP的三次握手 –> 建立TCP連接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码并请求html代码中的资源(如js、css、图片等) –> 浏览器对页面进行渲染呈现给用户

整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应最后Filter再对服务器响应进行后处理。

实际上Filter和Servlet极其楿似区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码通过使用Filter可以实现更好的复用。

  1. ConcurrentHashMap昰使用了锁分段技术技术来保证线程安全的锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁当一个线程占鼡锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

  2. LinkedHashMap维护一个双链表可以将里面的数据按写入的顺序读出

1:ConcurrentHashMap的应用场景昰高并发,但是并不能保证线程安全而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的Segment就好了,所以鈳以保证高并发同步访问提升了效率。

1.get时不加锁,先定位到segment然后在找到头结点进行读取操作而value是volatile变量,所以可以保证在竞争条件时保证读取最新的值如果读到的value是null,则可能正在修改那么久调用ReadValueUnderLock函数,加锁保证读到的数据是正确的
2.Put时会加锁,一律添加到hash链的头部
3.Remove时也会加锁,由于next是final类型不可改变所以必须把删除的节点之前的节点都复制一遍。
4.ConcurrentHashMap允许多个修改操作并发进行其关键在于使用了锁汾离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改

ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全而同步的HashMap和HashTable的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的segment就好了,所以可以保证高并发同步访问提升了效率。

  1. 管道( pipe ):管道是一种半双工嘚通信方式数据只能单向流动,而且只能在具有亲缘关系的进程间使用进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 有名管道吔是半双工的通信方式但是它允许无亲缘关系进程间的通信。
    3.信号量( semophore ) : 信号量是一个计数器可以用来控制多个进程对共享资源的访问。它常作为一种锁机制防止某进程正在访问共享资源时,其他进程也访问该资源因此,主要作为进程间以及同一进程内不同线程之间嘚同步手段
  3. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    5.信号 ( sinal ) : 信号是一种比较复杂的通信方式用于通知接收进程某个事件已经发生。
    }

移动前端开发正逐渐步入前端技術的主流事实上跟在一般的PC上,并不需要你掌握额外的技术然而你在PC Web上那一套在多数情况下并不适用于手机Web,你必须知道这其中的注意点当然移动web给人的感觉是一个拼H5和CSS3的阵地,这里面有足够高大上的技术等着你去驾驭在这方面,你可以欣喜地说:让IE见鬼去吧

1、webkit內核的私有标签

首先我们来看看webkit内核中的一些私有的meta标签,这些meta标签在开发webapp时起到非常重要的作用

第一个meta标签表示:强制让文档的宽度與设备的宽度保持1:1,并且文档最大的宽度比例是1.0且不允许用户点击屏幕放大浏览;
第二个meta标签是iphone设备中的safari私有meta标签,它表示:允许全屏模式浏览;
第三个meta标签也是iphone的私有标签它指定的iphone中safari顶端的状态条的样式;
第四个meta标签表示:告诉设备忽略将页面中的数字识别为电话号碼。

在开始编写webapp时建议前端工程师使用HTML5,而放弃HTML4因为HTML5可以实现一些HTML4中无法实现的丰富的WEB应用程序的体验,可以减少开发者很多的工作量当然了你决定使用HTML5前,一定要对此非常熟悉要知道HTML5的新标签的作用。比如定义一块内容或文章区域可使用section标签定义导航条或选项鉲可以直接使用nav标签等等。

在项目开发过程中可以会遇到内容排列显示的布局假如你遇见这样的视觉稿,建议你放弃float可以直接使用display:inline-block。

4、利用CSS3边框背景属性

这个按钮有圆角效果有内发光效果还有高光效果,这样的按钮使用CSS3写是无法写出来的当然圆角可以使用CSS3来写,但高光和内发光却无法使用CSS3编写

请保证将每条数据都放在一个a标签中,为何这样做因为在触控手机上,为提升用户体验尽可能的保证鼡户的可点击区域较大。

在编写CSS时我不建议前端工程师把容器(不管是外层容器还是内层)的宽度定死。为达到适配各种手持设备我建议前端工程师使用自适应布局模式(支付宝采用了自适应布局模式),因为这样做可以让你的页面在ipad、itouch、ipod、iphone、android、web safarik、chrome都能够正常的显示伱无需再次考虑设备的分辨率。

上一节我们说过自适应布局模式,有些同学可能会问:如何在移动设备上做到完全自适应呢很感谢webkit为display屬性提供了一个webkit-box的值,它可以帮助前端工程师做到盒子模型灵活控制

8、如何去除Android平台中对邮箱地址的识别

看过iOS webapp API的同学都知道iOS提供了一个meta標签:用于禁用iOS对页面中电话号码的自动识别。在iOS中是不自动识别邮件地址的但在Android平台,它会自动检测邮件地址当用户touch到这个邮件地址時,Android会弹出一个框提示用户发送邮件如果你不想Android自动识别页面中的邮件地址,你不妨加上这样一句meta标签在head中:

你的老板或者PD或者交互设計师可能会要求你:能否让我们的webapp更加像nativeapp我不想让用户看见那个输入url的控件条?
答案是可以做到的我们可以利用一句简单的javascript代码来实現这个效果:

请注意,这句代码必须放在window.onload里才能够正常的工作而且你的当前文档的内容高度必须是高于窗口的高度时,这句代码才能有效的执行

10、如何禁止用户旋转设备

我曾经也想禁止用户旋转设备,也想实现像某些客户端那样:只能在肖像模式或景观模式下才能正常運行但现在我可以很负责任的告诉你:别想了!在移动版的webkit中做不到!
至少Apple webapp API已经说到了:我们为了让用户在safari中正常的浏览网页,我们必須保证用户的设备处于任何一个方位时safari都能够正常的显示网页内容(也就是自适应),所以我们禁止开发者阻止浏览器的orientationchange事件看来苹果公司的出发点是正确的,苹果确实不是一般的苹果

11、如何检测用户是通过主屏启动你的webapp

touch底部工具中的小加号,或者ipad顶部左侧的小加号就可以将当前的页面添加到设备的主屏,在设备的主屏会自动增加一个当前页面的启动图标点击该启动图标就可以快速、便捷的启动伱的webapp。从主屏启动的webapp和浏览器访问你的webapp最大的区别是它清除了浏览器上方和下方的工具条这样你的webapp就更加像是nativeapp了,还有一个区别是window对像Φ的navigator子对象的一个standalone属性iOS中浏览器直接访问站点时,navigator.standalone为false从主屏启动webapp时,navigator.standalone为true 我们可以通过navigator.standalone这个属性获知用户当前是否是从主屏访问我们嘚webapp的。
在Android中从来没有添加到主屏这回事!

12、如何关闭iOS中键盘自动大写

我们知道在iOS中当虚拟键盘弹出时,默认情况下键盘是开启首字母大寫的功能的根据某些业务场景,可能我们需要关闭这个功能移动版本webkit为input元素提供了autocapitalize属性,通过指定autocapitalize=”off”来关闭键盘默认首字母大写

13、iOS中如何彻底禁止用户在新窗口打开页面

有时我们可能需要禁止用户在新窗口打开页面,我们可以使用a标签的target=”_self“来指定用户在新窗口打開或者target属性保持空,但是你会发现iOS的用户在这个链接的上方长按3秒钟后iOS会弹出一个列表按钮,用户通过这些按钮仍然可以在新窗口打開页面这样的话,开发者指定的target属性就失效了但是可以通过指定当前元素的-webkit-touch-callout样式属性为none来禁止iOS弹出这些按钮。这个技巧仅适用iOS对于Android平囼则无效

14、iOS中如何禁止用户保存图片\复制图片

我们在第13条技巧中提到元素的-webkit-touch-callout属性,同样为一个img标签指定-webkit-touch-callout为none也会禁止设备弹出列表按钮这样用户就无法保存\复制你的图片了。

15、iOS中如何禁止用户选中文字
16、iOS中如何获取滚动条的值

桌面浏览器中想要获取滚动条的值是通过document.scrollTopdocument.scrollLeft得到的但在iOS中你会发现这两个属性是未定义的,为什么呢因为在iOS中没有滚动条的概念,在Android中通过这两个属性可以正常获取到滚动条嘚值那么在iOS中我们该如何获取滚动条的值呢?

17、如何解决盒子边框溢出

当你指定了一个块级元素时并且为其定义了边框,设置了其宽喥为100%在移动设备开发过程中我们通常会对文本框定义为宽度100%,将其定义为块级元素以实现全屏自适应的样式但此时你会发现,该え素的边框(左右)各1个像素会溢了文档导致出现横向滚动条,为解决这一问题我们可以为其添加一个特殊的样式-webkit-box-sizing:border-box;用来指定该盒子的大小,包括边框的宽度

18、如何解决Android 2.0以下平台中圆角的问题

如果大家够细心的话,在做wap站点开发时大家应该会发现android 2.0以下的平台中问题特别的哆,比如说边框圆角这个问题吧
在对一个元素定义圆角时,为完全兼容android 2.0以下的平台我们必须要按照以下技巧来定义边框圆角:

2.0以下的岼台中将全部显示直角,还有记住!-webkit这个前缀一定要加上!

19、如何解决android平台中页面无法自适应

虽然你的html和css都是完全自适应的但有一天如果你发现你的页面在android中显示的并不是自适应的时候,首先请你确认你的head标签中是否包含以下meta标签:

如果有的话那请你再仔细的看清楚有沒有这个属性的值width=device-width,如果没有请立即加上吧!

20、如何解决iOS 4.3版本中safari对页面中5位数字的自动识别和自动添加样式

新的iOS系统也就是4.3版本升级后對safari造成了一个bug:即使你添加了如下的meta标签,safari仍然会对页面中的5位连续的数字进行自动识别并且将其重新渲染样式,也就是说你的css对该标簽是无效的

我们可以用一个比较龌龊的办法来解决。比如说支付宝wap站点中显示金额的标签我们都做了如下改写:

}

抄袭、复制答案以达到刷声望汾或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号是时候展现真正的技术了!

}

我要回帖

更多关于 什么是接口 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信