首页 文章 精选 留言 我的

精选列表

搜索[面试],共4912篇文章
优秀的个人博客,低调大师

Android--面试中遇到的问题总结(一)

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/56479430 一、handler 一、主要涉及到的类有Handler、Thread、Message、Looper、MessageQueue; 二、.异步消息处理机制的作用主要有刷新UI和线程间通信 三、 .Handler主要是发送消息(sendMessage),处理消息(handlerMessage)的类; Message就是在线程之间传递的消息,它可以携带少量信息,在线程间进行信息交换; Looper主要是管理消息队列的,一旦调用Loop()方法之后就会进入到一个无线循环中去,每当发现 MessageQueue 中存在一条消息,就会将其取出,并传递到 handleMessage()方法当中,每个线程中也 只会 有一个Looper对象; MessageQueue消息队列用来存储handler传过来的消息的,每个线程只有一个消息队列。 二、手机屏幕适配 1.布局文件适配:多用match_parent、wrap_content,weight;多套布局文件 2.图片资源适配:根据手机屏幕分辨率的不同,制作几套图片资源;使用自动拉伸位图:Nine-Patch的图片类 型 3.布局控件尺寸适配:尽量使用密度无关像素 dp或独立比例像素 sp单位指定尺寸 4.自定义控件,利用代码根据屏幕的大小动态设置界面的显示大小 使用 “wrap_content”,系统就会将视图的宽度或高度设置成所需的最小尺寸以适应视图中的内容,而 “match_parent”(在低于 API 级别 8 的级别中称为 “fill_parent”)则会展开组件以匹配其父视图的尺寸。 如果使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需的空间或展开以填满可用空间。 此方法可让布局正确适应各种屏幕尺寸和屏幕方向。 weight是线性布局的一个独特的属性,我们可以使用这个属性来按照比例对界面进行分配,完成一些特殊的需求。 三、内存泄漏的原因 1.资源对象没关闭造成的内存泄漏:资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如 SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。 2.构造Adapter时,没有使用缓存的convertView:public View getView(int position, ViewconvertView, ViewGroup parent) 来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,如果我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话。 3.Bitmap对象不在使用时调用recycle()释放内存 4.试着使用关于application的context来替代和activity相关的context:有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长,而不是取决于activity的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。 5.注册没取消造成的内存泄漏 一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。调用registerReceiver后未调用unregisterReceiver。 比如:假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个 PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。 但是如果在释放 LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process 进程挂掉。 6.集合中对象没清理造成的内存泄漏 我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。 四、Android中的动画 Android中动画分别帧动画、补间动画和属性动画(Android 3.0以后的) 帧动画 帧动画是最容易实现的一种动画,这种动画更多的依赖于完善的UI资源,他的原理就是将一张张单独的图片连贯的进行播放,从而在视觉上产生一种动画的效果;有点类似于某些软件制作gif动画的方式。在有些代码中,我们还会看到android:oneshot=”false” ,这个oneshot 的含义就是动画执行一次(true)还是循环执行多次。 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/a_0" android:duration="100" /> <item android:drawable="@drawable/a_1" android:duration="100" /> <item android:drawable="@drawable/a_2" android:duration="100" /> </animation-list> 补间动画 补间动画又可以分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)。 补间动画的实现,一般会采用xml 文件的形式;代码会更容易书写和阅读,同时也更容易复用。Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。pivot 决定了当前动画执行的参考位置 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@[package:]anim/interpolator_resource" android:shareInterpolator=["true" | "false"] > <alpha android:fromAlpha="float" android:toAlpha="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale="float" android:toYScale="float" android:pivotX="float" android:pivotY="float" /> <translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float" /> <rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float" /> <set> ... </set> </set> 属性动画 属性动画,顾名思义它是对于对象属性的动画。因此,所有补间动画的内容,都可以通过属性动画实现。属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。 五、Android中常用的布局 常用的布局: FrameLayout(帧布局):所有东西依次都放在左上角,会重叠 LinearLayout(线性布局):按照水平和垂直进行数据展示 RelativeLayout(相对布局):以某一个元素为参照物,来定位的布局方式 不常用的布局: TableLayout(表格布局): 每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个元素(Android TV上使用) AbsoluteLayout(绝对布局):用X,Y坐标来指定元素的位置,元素多就不适用。(机顶盒上使用) 新增布局: PercentRelativeLayout(百分比相对布局)可以通过百分比控制控件的大小。 PercentFrameLayout(百分比帧布局)可以通过百分比控制控件的大小。 六、Android数据存储 使用SharedPreferences存储数据;它是Android提供的用来存储一些简单配置信息的一种机制,采用了XML格式将数据存储到设备中。只能在同一个包内使用,不能在不同的包之间使用。 文件存储数据;文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。 SQLite数据库存储数据;SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库。 使用ContentProvider存储数据;主要用于应用程序之间进行数据交换,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。 网络存储数据;通过网络上提供给我们的存储空间来上传(存储)和下载(获取)我们存储在网络空间中的数据信息。 七、Android中的ANR ANR的全称application not responding 应用程序未响应。 在android中Activity的最长执行时间是5秒。 BroadcastReceiver的最长执行时间则是10秒。 Service的最长执行时间则是20秒。 超出执行时间就会产生ANR。注意:ANR是系统抛出的异常,程序是捕捉不了这个异常的。 解决方法: 1. 运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法 (如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message 的方式做一些操作,比如更新主线程中的ui等) 2. 应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。 八、 AsyncTask AsyncTask的三个泛型参数说明 1.第一个参数:传入doInBackground()方法的参数类型 2.第二个参数:传入onProgressUpdate()方法的参数类型 3.第三个参数:传入onPostExecute()方法的参数类型,也是doInBackground()方法返回的类型 运行在主线程的方法: onPostExecute() onPreExecute() onProgressUpdate(Progress...) 运行在子线程的方法: doInBackground() 控制AsyncTask停止的方法: cancel(boolean mayInterruptIfRunning) AsyncTask的执行分为四个步骤 1.继承AsyncTask。 2.实现AsyncTask中定义的下面一个或几个方法onPreExecute()、doInBackground(Params…)、onProgressUpdate(Progress…)、onPostExecute(Result)。 3.调用execute方法必须在UI thread中调用。 4.该task只能被执行一次,否则多次调用时将会出现异常,取消任务可调用cancel。 九、Android进程间的通讯 1.activity Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。 在android系统中有很多应用程序提供了可以跨进程访问的Activity,例如,下面的代码可以直接调用拨打电话的Activity。 IntentcallIntent=newIntent(Intent.ACTION_CALL,Uri.parse("tel:12345678"); startActivity(callIntent); 2. Content Provider Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用Content Provider完成下面的工作 1. 查询数据 2. 修改数据 3. 添加数据 4. 删除数据 3.广播(Broadcast) 广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。 4.Service 1.利用AIDL Service实现跨进程通信 这是我个人比较推崇的方式,因为它相比Broadcast而言,虽然实现上稍微麻烦了一点,但是它的优势就是不会像广播那样在手机中的广播较多时会有明显的时延,甚至有广播发送不成功的情况出现。 注意普通的Service并不能实现跨进程操作,实际上普通的Service和它所在的应用处于同一个进程中,而且它也不会专门开一条新的线程,因此如果在普通的Service中实现在耗时的任务,需要新开线程。 要实现跨进程通信,需要借助AIDL(Android Interface Definition Language)。Android中的跨进程服务其实是采用C/S的架构,因而AIDL的目的就是实现通信接口。

优秀的个人博客,低调大师

Android--面试中遇到的问题总结(二)

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/56480392 一、开发中都使用过哪些框架、平台 EventBus(事件处理) xUtils(网络、图片、ORM) JPush(推送平台) 友盟(统计平台) 有米(优米)(广告平台) 百度地图 bmob(服务器平台、短信验证、邮箱验证、第三方支付) 阿里云 OSS(云存储) ShareSDK(分享平台、第三方登录) Gson(解析 json 数据框架) imageLoader (图片处理框架) zxing (二维码扫描) anroid-asyn-http(网络通讯) DiskLruCache(硬盘缓存框架) Viatimo(多媒体播放框架) universal-image-loader(图片缓存框架) 讯飞语音(语音识别) 二、怎样对 android 进行优化? 对 listview 的优化。 对图片的优化。 对内存的优化。 具体一些措施 尽量不要使用过多的静态类 static 数据库使用完成后要记得关闭 cursor 广播使用完之后要注销 三、即时通讯是是怎么做的? 使用asmark 开源框架实现的即时通讯功能.该框架基于开源的 XMPP 即时通信协议,采用 C/S 体系结构,通过 GPRS 无线网络用 TCP 协议连接到服务器, 以架设开源的Openfn’e 服务器作为即时通讯平台。 客户端基于 Android 平台进行开发。负责初始化通信过程,进行即时通信时,由客户端负责向服务器发起创建连接请求。系统通过 GPRS 无线网络与 Internet 网络建立连接,通过服务器实现与Android 客户端的即时通信脚。 服务器端则采用 Openfire 作为服务器。 允许多个客户端同时登录并且并发的连接到一个服务器上。服务器对每个客户端的连接进行认证,对认证通过的客户端创建 会话,客户端与服务器端之间的通信就在该会话的上下文中进行。 四、 android fragment和activity的区别 Fragment是到Android3.0+ 以后,Android新增了Fragments,在没有 Fragment 之前,一个屏幕只能放一个 Activity。这是一个起源时间大家要知道是什么时候开始引入的。 .Activity 代表了一个屏幕的主体,而Fragment可以作为Activity的一个组成元素。一个Activity可以有若干个(0或n)Fragment构成。你可以把Fragment想象成Activity中的一个控件,只不过相对于一般控件,Fragment与Activity联系更为紧密,随着Activity的生命周期变化,Fragment也随之相应不同的生命周期函数。Fragment 从功能上讲相当于一个子活动(Activity),它可以让多个活动放到同一个屏幕上,也就是对用户界面和功能的重用,因为对于大屏设备来说,纯粹的 Activity 有些力不从心。 Fragment 像是一个子活动,但是 Fragment 不是 Activity 的扩展,因为 Fragment 扩展自 android.app 中的 Object,而 Activity 是 Context 的子类。Fragment 有自己的视图层级结构,有自己的活动周期,还可以像活动一样响应后退按钮,Fragment 还有一个用作其初始化参数的包(Bundle),类似 Activity,Fragment 也可由系统自动保存并在以后还原。当系统还原 Fragment 时,它调用默认的构造函数(没有参数),然后将此Bundle还原到新创建的 Fragment 中,所以无论新建还是还原 Fragment,都要经过两个步骤:(1)调用默认构造函数(2)传入新的或者保存起来的Bundle。 一个Activity可以运行多个 Fragment,Fragment 切换时,由 FragmentTransaction 执行,切换时,上一个 Fragment 可以保存在后退栈中(Back Stack),这里的后退栈由 FragmentManager 来管理,注意 Fragment 和 Activity 的后退栈是有区别的:Activity 的后退栈由系统管理,而 Fragment 的后退栈由所在的Activity 管理。 Fragment不能脱离Activity而存在,只有Activity才能作为接收intent的载体。其实两者基本上是载体和组成元素的关系。 Fragment用来描述一些行为或一部分用户界面在一个Activity中,你可以合并多个fragment在一个单独的activity中建立多个UI面板,同时重用fragment在多个activity中.你可以认为fragment作为一个activity中的一节模块,fragment有自己的生命周期,接收自己的输入事件,你可以添加或移除从运行中的activity.一个fragment必须总是嵌入在一个activity中,同时fragment的生命周期受activity而影响,举个例子吧,当activity暂停,那么所有在这个activity的fragments将被destroy释放。然而当一个activity在运行比如resume时,你可以单独的操控每个fragment,比如添加或删除。不过因为Fragment和Activity的生命周期都比较复杂,我们分别对比下:创建一个fragment你必须创建一个Fragment的子类或存在的子类,比如类似下面的代码 public static classAndroidFragmentextendsFragment{ @Override publicView onCreateView(LayoutInflaterinflater, ViewGroupContainer, Bundle savedInstanceState) {returninflater.inflate(R.layout.android_fragment,container,false); } } Fragment类的一些代码看起来有些像Activity为了让大家了解清楚,Android开发网给大家整理下 Fragment的生命周期大家可以参考一下网上关于生命周期的介绍 http://www.cnblogs.com/purediy/p/3276545.html,部分类似Activity的,我们详细解释 onCreate()当fragment创建时被调用,你应该初始化一些实用的组件,比如在fragment暂停或停止时需要恢复的 onCreateView()当系统调用fragment在首次绘制用户界面时,如果画一个UI在你的fragment你必须返回一个View当然了你可以返回null代表这个fragment没有UI. 那么如何添加一个Fragment到Activity中呢? Activity的布局可以这样写 <?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragmentandroid:name="com.android.cwj.ArticleListFragment"android:id="@+id/list"android:layout_weight="1"android:layout_width="0dp"android:layout_height="match_parent"/><fragmentandroid:name="com.android.cwj.ArticleReaderFragment"android:id="@+id/viewer"android:layout_weight="2"android:layout_width="0dp"android:layout_height="match_parent"/></LinearLayout>

优秀的个人博客,低调大师

9个常用的Shell脚本,面试也常问!

1、Dos 攻击防范(自动屏蔽攻击 IP) #!/bin/bash DATE=$(date+%d/%b/%Y:%H:%M) LOG_FILE=/usr/local/nginx/logs/demo2.access.log ABNORMAL_IP=$(tail-n5000$LOG_FILE|grep$DATE|awk'{a[$1]++}END{for(iina)if(a[i]>10)printi}') forIPin$ABNORMAL_IP;do if[$(iptables-vnL|grep-c"$IP")-eq0];then iptables-IINPUT-s$IP-jDROP echo"$(date+'%F_%T')$IP">>/tmp/drop_ip.log fi done 2、Linux 系统发送告警脚本 #yuminstallmailx #vi/etc/mail.rc setfrom=baojingtongzhi@163.comsmtp=smtp.163.com setsmtp-auth-user=baojingtongzhi@163.comsmtp-auth-password=123456 setsmtp-auth=login 3、MySQL 数据库备份单循环 #!/bin/bash DATE=$(date+%F_%H-%M-%S) HOST=localhost USER=backup PASS=123.com BACKUP_DIR=/data/db_backup DB_LIST=$(mysql-h$HOST-u$USER-p$PASS-s-e"showdatabases;"2>/dev/null|egrep-v"Database|information_schema|mysql|performance_schema|sys") forDBin$DB_LIST;do BACKUP_NAME=$BACKUP_DIR/${DB}_${DATE}.sql if!mysqldump-h$HOST-u$USER-p$PASS-B$DB>$BACKUP_NAME2>/dev/null;then echo"$BACKUP_NAME备份失败!" fi done 4、MySQL 数据库备份多循环 #!/bin/bash DATE=$(date+%F_%H-%M-%S) HOST=localhost USER=backup PASS=123.com BACKUP_DIR=/data/db_backup DB_LIST=$(mysql-h$HOST-u$USER-p$PASS-s-e"showdatabases;"2>/dev/null|egrep-v"Database|information_schema|mysql|performance_schema|sys") forDBin$DB_LIST;do BACKUP_DB_DIR=$BACKUP_DIR/${DB}_${DATE} [!-d$BACKUP_DB_DIR]&&mkdir-p$BACKUP_DB_DIR&>/dev/null TABLE_LIST=$(mysql-h$HOST-u$USER-p$PASS-s-e"use$DB;showtables;"2>/dev/null) forTABLEin$TABLE_LIST;do BACKUP_NAME=$BACKUP_DB_DIR/${TABLE}.sql if!mysqldump-h$HOST-u$USER-p$PASS$DB$TABLE>$BACKUP_NAME2>/dev/null;then echo"$BACKUP_NAME备份失败!" fi done done 5、Nginx 访问访问日志按天切割 #!/bin/bash LOG_DIR=/usr/local/nginx/logs YESTERDAY_TIME=$(date-d"yesterday"+%F) LOG_MONTH_DIR=$LOG_DIR/$(date+"%Y-%m") LOG_FILE_LIST="default.access.log" forLOG_FILEin$LOG_FILE_LIST;do [!-d$LOG_MONTH_DIR]&&mkdir-p$LOG_MONTH_DIR mv$LOG_DIR/$LOG_FILE$LOG_MONTH_DIR/${LOG_FILE}_${YESTERDAY_TIME} done kill-USR1$(cat/var/run/nginx.pid) 6、Nginx 访问日志分析脚本 #!/bin/bash #日志格式:$remote_addr-$remote_user[$time_local]"$request"$status$body_bytes_sent"$http_referer""$http_user_agent""$http_x_forwarded_for" LOG_FILE=$1 echo"统计访问最多的10个IP" awk'{a[$1]++}END{print"UV:",length(a);for(vina)printv,a[v]}'$LOG_FILE|sort-k2-nr|head-10 echo"----------------------" echo"统计时间段访问最多的IP" awk'$4>="[01/Dec/2018:13:20:25"&&$4<="[27/Nov/2018:16:20:49"{a[$1]++}END{for(vina)printv,a[v]}'$LOG_FILE|sort-k2-nr|head-10 echo"----------------------" echo"统计访问最多的10个页面" awk'{a[$7]++}END{print"PV:",length(a);for(vina){if(a[v]>10)printv,a[v]}}'$LOG_FILE|sort-k2-nr echo"----------------------" echo"统计访问页面状态码数量" awk'{a[$7""$9]++}END{for(vina){if(a[v]>5)printv,a[v]}}' 7、查看网卡实时流量脚本 #!/bin/bash NIC=$1 echo-e"In------Out" whiletrue;do OLD_IN=$(awk'$0~"'$NIC'"{print$2}'/proc/net/dev) OLD_OUT=$(awk'$0~"'$NIC'"{print$10}'/proc/net/dev) sleep1 NEW_IN=$(awk'$0~"'$NIC'"{print$2}'/proc/net/dev) NEW_OUT=$(awk'$0~"'$NIC'"{print$10}'/proc/net/dev) IN=$(printf"%.1f%s""$((($NEW_IN-$OLD_IN)/1024))""KB/s") OUT=$(printf"%.1f%s""$((($NEW_OUT-$OLD_OUT)/1024))""KB/s") echo"$IN$OUT" sleep1 done 8、服务器系统配置初始化脚本 #/bin/bash #设置时区并同步时间 ln-s/usr/share/zoneinfo/Asia/Shanghai/etc/localtime if!crontab-l|grepntpdate&>/dev/null;then (echo"*1***ntpdatetime.windows.com>/dev/null2>&1";crontab-l)|crontab fi #禁用selinux sed-i'/SELINUX/{s/permissive/disabled/}'/etc/selinux/config #关闭防火墙 ifegrep"7.[0-9]"/etc/redhat-release&>/dev/null;then systemctlstopfirewalld systemctldisablefirewalld elifegrep"6.[0-9]"/etc/redhat-release&>/dev/null;then serviceiptablesstop chkconfigiptablesoff fi #历史命令显示操作时间 if!grepHISTTIMEFORMAT/etc/bashrc;then echo'exportHISTTIMEFORMAT="%F%T`whoami`"'>>/etc/bashrc fi #SSH超时时间 if!grep"TMOUT=600"/etc/profile&>/dev/null;then echo"exportTMOUT=600">>/etc/profile fi #禁止root远程登录 sed-i's/#PermitRootLoginyes/PermitRootLoginno/'/etc/ssh/sshd_config #禁止定时任务向发送邮件 sed-i's/^MAILTO=root/MAILTO=""/'/etc/crontab #设置最大打开文件数 if!grep"*softnofile65535"/etc/security/limits.conf&>/dev/null;then cat>>/etc/security/limits.conf<<EOF *softnofile65535 *hardnofile65535 EOF fi #系统内核优化 cat>>/etc/sysctl.conf<<EOF net.ipv4.tcp_syncookies=1 net.ipv4.tcp_max_tw_buckets=20480 net.ipv4.tcp_max_syn_backlog=20480 net.core.netdev_max_backlog=262144 net.ipv4.tcp_fin_timeout=20 EOF #减少SWAP使用 echo"0">/proc/sys/vm/swappiness #安装系统性能分析工具及其他 yuminstallgccmakeautoconfvimsysstatnet-toolsiostatif 9、监控 100 台服务器磁盘利用率脚本 #!/bin/bash HOST_INFO=host.info forIPin$(awk'/^[^#]/{print$1}'$HOST_INFO);do USER=$(awk-vip=$IP'ip==$1{print$2}'$HOST_INFO) PORT=$(awk-vip=$IP'ip==$1{print$3}'$HOST_INFO) TMP_FILE=/tmp/disk.tmp ssh-p$PORT$USER@$IP'df-h'>$TMP_FILE USE_RATE_LIST=$(awk'BEGIN{OFS="="}/^\/dev/{print$NF,int($5)}'$TMP_FILE) forUSE_RATEin$USE_RATE_LIST;do PART_NAME=${USE_RATE%=*} USE_RATE=${USE_RATE#*=} if[$USE_RATE-ge80];then echo"Warning:$PART_NAMEPartitionusage$USE_RATE%!" fi done done

优秀的个人博客,低调大师

面试官常问的HTTP知识点

互联网中的数据是通过数据包来传输的,IP通过DNS查询IP地址进而把数据包送达目的主机,UDP接过数据包,通过端口号把数据包送往具体的应用,而使用TCP可保证数据的完整性 当传输层TCP/IP协议将数据传输到网络上时,浏览器可通过HTTP协议进行文本传输,物联网可通过MQTT协议进行交互 一、UDP UDP传输非常快,适合在线视频、互动游戏这类强交互的场景 对于数据可靠性有要求的场景则不太适合,它有个大缺点:不能保证数据可靠性 不提供重发机制,直接丢弃当前的包 发送之后不售后,无法确认是否到达目的地 无法还原数据包成完整的文件 但它的兄弟TCP可以代劳 二、TCP TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。解决了丢失数据包的问题,并且提供了组装数据包的能力。这不得不感谢发送端不仅给它提供了源端口号和目标端口号,还提供了序列号,引入了数据包排列机制 1、连接过程 1.1、建立连接 TCP是面向连接的,在数据通信之前就做好两端的准备工作,在客户端和服务端通过三个数据包来确认连接的建立 一开始,客户端和服务端都处于 close状态,接着服务端主动监听某个端口,处于listen状态 客户端生成初始序列号client_isn置于TCP首部的序列号中,同时更改SYN标志为1向服务器发起连接,之后处于SYN-SENT状态 服务端收到SYN报文同样生成初始序列号server_isn置于TCP首部的序列号中,并将客户端序列号+1置于TCP首部的确认应答号中,同时更改SYN和ACK标志为1,发送SYN + ACK报文并更改状态为SYN_RCVD 客户端收到SYN + ACK报文,将服务端序列号+1填入确认应答号并回复ACK应答报文变更状态为established 服务器收到应答后也进入established状态 只有第三次握手可以携带数据,前面两次是不可携带数据的 1.2、为什么三次握手? 通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号 两次握手无法仿制历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号 四次握手:三次握手就已经理论上最少可靠连接建立,因此不需要使用更多的通信次数 2、传输数据 TCP是可靠的,接收端必须对每个数据包进行确认操作,即重发机制 当发送端发送了一个数据包之后,在规定时间内没有接收到反馈的确认信息,则判断数据包丢失,触发重发机制 TCP是基于字节流的,接收端可通过TCP提供的序号进行排序,进而保证数据的完整性,即排列机制 3、断开过程 3.1、断开连接 客户端主动关闭连接,发送FIN报文,即更改FIN标志为1同时进入FIN_WAIT_1状态 服务端收到报文后发出ACK应答报文,接着进入CLOSED_WAIT状态 客户端收到报文后,进入FIN_WAIT_2状态 服务器处理完成后发出FIN报文,并进入LAST_ACK状态 客户端收到报文后发出ACK应答报文,接着进入TIME_WAIT状态 服务器收到ACK应答报文后完成连接关闭 客户端 等待两倍报文最大生存时间(MSL)后 自动进入close状态,完成连接关闭 主动关闭连接的,才有TIME_WAIT状态 3.2、为什么四次挥手? 关闭连接时,客户端向服务端发送FIN时,仅代表客户端不再发送数据但能接收数据 服务端接收到FIN时,回复一个ACK,仅代表收到报文,但服务器可能还有数据需要处理和发送,确保不再发送数据时才发送FIN给到客户端表示同意现在关闭连接 三、HTTP HTTP 协议以 ASCII 码传输,构建于 TCP/IP 协议之上的应用层协议,默认端口号是 80,它是无连接无状态的超文本传输协议 1、HTTP 报文 1.1、请求报文 规范把 HTTP 请求分为三个部分:请求行、请求头 和 消息主体 [method] [url] [version] [headers] [body] HTTP 中的GET、POST、PUT、DELETE对应着资源的查、增、改、删4个操作 1.1.1、GET 只读操作,是安全且幂等的 安全:请求方法不会破坏服务器上的资源 幂等:多次执行相同的操作,结果都是相同的 1.1.2、POST 读写操作,是不安全且不幂等的 1.1.3、PUT 不同于POST,PUT是幂等的 1.1.4、OPTIONS 用以从服务器获取更多信息 1.2、响应报文 同样HTTP响应分为三个部分:状态行、响应头 和 响应正文 [version] [status code] [status msg] [headers] [body] 常见的状态码有 状态码 状态描述 备注 206 Partial Content 范围响应,主体包含所请求的数据区间<br/>断点续传时通过 Range 指定区间 301 Moved Permanently 请求永久重定向 302 Moved Temporarily 请求临时重定向 304 Not Modified 未修改,使用缓存文件(协商缓存) 400 Bad Request 客户端请求有语法错误 401 Unauthorized 请求未经授权(同WWW-Authenticate一起使用)<br/>在后续请求中携带 Authorization用于验证用户代理身份的凭证 403 Forbidden 服务器拒绝提供服务,通常在响应正文给出原因 404 Not Found 请求资源不存在 500 Internal Server Error 服务器发生不可预期的错误 503 Service Unavailable 服务器当前无法处理请求,需等待服务器恢复正常 2、HTTP 演变 版本 核心诉求 新增特性 HTTP/1.0 支持多种类型的文件下载 引入请求头、响应头、状态码 HTTP/1.1 提高对带宽的利用率 1、持久连接(每个域名最多同时维护 6 个 TCP 持久连接) 2、使用 CDN 实现域名分片机制 3、提供虚拟主机的支持(Host 字段) 4、增加缓存策略 5、安全机制(CORS) HTTP/2.0 提升网络速度 1、多路复用 2、设置请求的优先级 3、服务器推送 4、头部压缩 5、二进制格式 HTTP/3.0 构建高效网络 1、甩掉TCP、TLS 的包袱,使用UDP协议 2、QUIC协议 HTTPS 构建安全HTTP 引入SSL 、混合加密、摘要算法 、数字证书 2.1、持久连接 HTTP/1.1 中增加了持久连接的方法,即在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持,提升了整体 HTTP 的请求时长。目前浏览器中对于同一个域名,默认允许同时建立 6 个 TCP 持久连接 Connection: Keep-Alive; // HTTP/1.1默认使用持久连接,如需关闭,请求头Connection设置为close Keep-Alive: timeout=5, max=100; // HTTP 长连接不可能一直保持,timeout=5 表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开 2.2、使用 CDN 实现域名分片机制 2.3、提供虚拟主机的支持 Host表示当前的域名地址,服务器可以根据不同的 Host 值做不同的处理 Host: <host>:<port>; // host: 服务器的域名(用于虚拟主机) port: 服务器监听的 TCP 端口号 2.4、缓存策略 2.5、安全机制 2.5.1、会话跟踪 HTTP是无状态协议,即浏览器对于事务的处理没有记忆能力,可通过Cookie和JWT机制来进行会话跟踪 Cookie机制 服务端第一次收到请求时创建session对象生成对应的sessionID,将其放进Set-Cookie发送给客户端,下一次访问时,客户端携带sessionID请求服务端,服务端可通过sessionID识别用户信息 Cookie 的过期时间、域、路径、有效期、适用站点都可以根据需要来指定 功能 属性 例子 补充说明 定义 Cookie 的生命周期 Expires <br/> Max-Age Set-Cookie: key=value; Expires=Wed, 21 Oct 2022 07:28:00 GMT 设定的日期和时间只与客户端相关,会话期 Cookie 仅在会话期内有效 限制访问 Cookie HttpOnly<br/>Secure Set-Cookie: key=value; Secure; HttpOnly HttpOnly:仅作用于服务器<br/>Secure仅适用于 HTTPS 协议加密过的请求 Cookie 的作用域 Domain<br/>Path Set-Cookie:Domain=mozilla.org;Path=/docs Domain 指定了哪些主机可以接受 Cookie<br/>Path 指定了主机下的哪些路径可以接受 Cookie SameSite None<br/>Strict<br/>Lax Set-Cookie: key=value; SameSite=Strict None:浏览器会在同站请求、跨站请求下继续发送 cookies(旧版本浏览器默认选项)<br/>Strict:浏览器将只在访问相同站点时发送 cookie<br/>Lax:与 Strict 类似,但用户从外部站点导航至URL时除外(新版本浏览器默认选项) JWT机制 (JSON Web Token) Cookies 只适用于单节点的域 或 节点的子域,若通过第三个节点访问会被禁止。而JWT机制则支持跨域认证,可通过多个节点进行用户认证 服务端第一次收到请求时,进行认证后生成一个 Token(签名后的JSON 对象)发送给客户端。客户端可将收到的jwt存储在Cookie或localStorage上,之后每次与服务端通信都携带上,可通过Cookie自动发送,但这种方式不能跨域,比较推荐通过 POST 请求的数据体 或 Authorization进行传递 注意喔,JWT 的 Cookie 信息存储在客户端,即服务端是无状态的 2.5.2、跨源资源共享(CORS) 规范要求那些可能产生副作用的请求,浏览器必须首先使用OPTIONS方法发起一个预检请求,从而获知服务端是否允许跨域请求。服务器确认允许后才发起实际的HTTP请求。在预检请求的返回中,服务端可通知客户端是否需要携带身份凭证 简单请求 若请求满足下述所有条件,则称之为简单请求,它不会触发预检请求 1、使用GET、HEAD和POST请求方法 2、Content-Type的值仅限于text/plain、multipart/form-data和application/x-www-form-urlencoded 3、请求中没有注册任何事件监听器,没有使用 ReadableStream 对象 // 附带身份凭证的简单请求 withCredentials:true; // 向服务器发送 Cookies Access-Control-Allow-Credentials: true; // 服务端允许附带身份凭证 复杂请求 响应头 例子 说明 Access-Control-Allow-Origin Access-Control-Allow-Origin: <origin>/* Vary: Origin origin:指定允许访问该资源的URI<br/>若指定了具体的域名,则Vary的值必须包含Origin,表明服务端按URI返回对应内容 Access-Control-Expose-Headers Access-Control-Expose-Headers: X-My-Custom-Header 服务器把允许浏览器访问的头放入白名单 Access-Control-Allow-Credentials Access-Control-Allow-Credentials: true 指定了credentials:true时是否允许浏览器读取 response 的内容<br/>在预检请求的响应时,指定实际的请求是否可以使用 credentials Access-Control-Max-Age Access-Control-Max-Age: 86400 预检请求的结果在多少秒内有效 Access-Control-Allow-Methods Access-Control-Allow-Methods: <method>[, <method>]* 预检请求的响应,指明了实际请求所允许使用的 HTTP 方法 Access-Control-Allow-Headers Access-Control-Allow-Headers: <field-name>[, <field-name>]* 预检请求的响应,指明了实际请求中允许携带的首部字段 2.6、支持动态生成内容 服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志,因此对于下载请求来说,是没有办法实现进度的 Transfer-Encoding: gzip, chunked; // 分块:chunked 压缩算法:compress、deflate、gzip 2.7、多路复用 一个域名只使用一个TCP长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,避免了多个 TCP 连接竞争带宽的问题。移除了串行请求,顺应的解决了队头阻塞问题 2.8、设置请求的优先级 每个数据流都标记着独一无二的编号,客户端可以指定数据流的优先级 2.9、服务器推送 服务端主动向客户端发送消息,即:当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,大大提升了页面首次渲染速度 2.10、头部压缩 HTTP/2.0引入HPACK算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表生成一个索引号,相同字段只发送对应的索引号,即:同时发出多个请求,请求头一样或相似,则协议会将重复部分消除 2.11、二进制格式 HTTP/2.0 全面采用二进制格式,头信息和数据体都是二进制,统称为「帧」提高了数据传输的效率 2.12、QUIC协议 实现了类似 TCP 的流量控制、传输可靠性的功能 集成了 TLS 加密功能,减少了握手所花费的 RTT 个数 实现了 HTTP/2 中的多路复用功能 不同于 TCP,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,避免了 TCP 中队头阻塞的问题 实现了快速握手功能,基于 UDP 的 QUIC 可使用 0-RTT|1-RTT 来建立连接 3、HTTPS HTTPS在HTTP和TCP之间加了一层用于加解密的SSL/TLS协议,通过信息加密、校验机制 和 身份证书 保证通信的安全性 客户端 发送 对称加密套件列表、非对称加密套件列表 和 客户端随机数 给到服务端 服务端 保存 客户端随机数 和 私钥,回复 选中的对称加密套件、非对称加密套件 和 服务端随机数 以及 数字证书 客户端向 CA机构验证数字证书,证实服务端身份并获取公钥 客户端利用两端的随机数计算出pre-master,并用获取到的公钥进行加密,发送加密后的pre-master 服务端拿出私钥进行解密,得到pre-master 服务端和客户端使用这三组随机数生成会话密钥,并返回确认消息 之后使用对称加密进行通讯 3.1、混合加密 HTTPS通过非对称加密交换「会话密钥」后续通信使用对称加密,这是由于 非对称加密使用两个密钥:公钥和私钥,公钥可保存在CA机构同时保存私钥。可以安全的进行密钥交换,但速度慢 对称加密只使用一个密钥,无法做到安全的密钥交换,但速度快 3.2、摘要算法 摘要算法通过生成唯一的指纹,用于校验数据的完整性。客户端在进行通信前会通过摘要算法得出明文的指纹,请求时将指纹和明文一并加密,服务端收到密文后进行解密,比对携带的指纹和当前计算的指纹是否一致,一致则说明数据完整 3.3、数字证书 权威机构CA签发认证的数字证书【包含了公钥、组织信息、CA信息、有效时间、证书序列号、CA生成的数字签名等】这些信息是明文的,同时可向浏览器证明服务器的身份 四、MQTT MQTT是基于二进制消息的发布/订阅编程模式的消息协议,非常适合需要低功耗和网络带宽有限的IoT场景 设备连接 设备通过MQTT协议连接到物联网云服务,进而可以进行设备管理及数据管理 消息类型 MQTT拥有14种不同的消息类型,比如CONNECT表示客户端连接到MQTT代理,CONNACK表示连接确认 主题 MQTT提供了主题对消息进行分类,消息是一个UTF-8的字符串,通过类似正则的规则进行匹配分类,比如:+可以过滤一个层级,*可以过滤任意级别的层级(必须在主题最后) 服务质量 MQTT提供级别0、级别1和级别2三种服务质量 服务质量 消息可靠性 解释 级别0 尽力而为 不提供重发 级别1 至少一次 提供重发,并发可能造成重复消息 级别2 恰好一次 不丢失不重复,但增加延时减少并发

优秀的个人博客,低调大师

5 分钟搞懂面试官必问 React 题

说说对 React Hooks 的理解?解决了什么问题? 一、是什么 Hook是 React 16.8 的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性 至于为什么引入hook,官方给出的动机是解决长时间使用和维护react过程中常遇到的问题,例如: 难以重用和共享组件中的与状态相关的逻辑 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面 类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题 由于业务变动,函数组件不得不改为类组件等等 在以前,函数组件也被称为无状态的组件,只负责渲染的一些工作 因此,现在的函数组件也可以是有状态的组件,内部也可以维护自身的状态以及做一些逻辑方面的处理 二、有哪些 上面讲到,Hooks让我们的函数组件拥有了类组件的特性,例如组件内的状态、生命周期 最常见的hooks有如下: useState useEffect 其他 useState 首先给出一个例子,如下: import React, { useState } from 'react'; function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p > <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 复制代码 在函数组件中通过useState实现函数内部维护state,参数为state默认的值,返回值是一个数组,第一个值为当前的state,第二个值为更新state的函数 该函数组件等价于的类组件如下: class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p > <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } 复制代码 从上述两种代码分析,可以看出两者区别: state声明方式:在函数组件中通过 useState 直接获取,类组件通过constructor 构造函数中设置 state读取方式:在函数组件中直接使用变量,类组件通过this.state.count的方式获取 state更新方式:在函数组件中通过 setCount 更新,类组件通过this.setState() 总的来讲,useState 使用起来更为简洁,减少了this指向不明确的情况 useEffect useEffect可以让我们在函数组件中进行一些带有副作用的操作 同样给出一个计时器示例: class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p > <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } 复制代码 从上面可以看见,组件在加载和更新阶段都执行同样操作 而如果使用useEffect后,则能够将相同的逻辑抽离出来,这是类组件不具备的方法 对应的useEffect示例如下: import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p > <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 复制代码 useEffect第一个参数接受一个回调函数,默认情况下,useEffect会在第一次渲染和更新之后都会执行,相当于在componentDidMount和componentDidUpdate两个生命周期函数中执行回调 如果某些特定值在两次重渲染之间没有发生变化,你可以跳过对 effect 的调用,这时候只需要传入第二个参数,如下: useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 仅在 count 更改时更新 复制代码 上述传入第二个参数后,如果count的值是5,而且我们的组件重渲染的时候count还是等于5,React 将对前一次渲染的[5]和后一次渲染的[5]进行比较,如果是相等则跳过effects执行 回调函数中可以返回一个清除函数,这是effect可选的清除机制,相当于类组件中componentwillUnmount生命周期函数,可做一些清除副作用的操作,如下: useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); 复制代码 所以,useEffect相当于componentDidMount,componentDidUpdate和componentWillUnmount这三个生命周期函数的组合 其它 hooks 在组件通信过程中可以使用useContext,refs学习中我们也用到了useRef获取DOM结构...... 还有很多额外的hooks,如: useReducer useCallback useMemo useRef 三、解决什么 通过对上面的初步认识,可以看到hooks能够更容易解决状态相关的重用的问题: 每调用useHook一次都会生成一份独立的状态 通过自定义hook能够更好的封装我们的功能 编写hooks为函数式编程,每个功能都包裹在函数中,整体风格更清爽,更优雅 hooks的出现,使函数组件的功能得到了扩充,拥有了类组件相似的功能,在我们日常使用中,使用hooks能够解决大多数问题,并且还拥有代码复用机制,因此优先考虑hooks 结语 创作不易,如果对大家有所帮助,希望大家点赞支持,有什么问题也可以在评论区里讨论😄~ 如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star: http://github.crmeb.net/u/defu 不胜感激 ! 来自 “开源独尊 ” ,链接: https://ym.baisou.ltd/post/852.html

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。