JAVA 核心基础功能介绍
JAVA是一个面向对象的编程语言,面向对象就是一种抽象开发的思想包含封装、继承、多态,去掉这些概念也就是面向过程。应该是最初很多程序员使用面向过程编程,总结各种经验,对编程过程进行各种改进抽象出的更接近现实世界的最佳实鉴。通过面向对象有助与复杂系统的开发,有效提高编程效率。
- 抽象 将一类实体的共同特性抽象出来,封装在一个抽象类中
- 封装 将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
- 继承 在一个现有类型的基础上,通过增加新的方法或者重定义已有方法的方式(重载/重写),产生一个新的类型。
- 多态 通过传递给父类对象引用不同的子类对象从而表现出不同的行为,多态可为程序提供更好的可扩展性,同样也可以代码重用。
类直接的关系:
- 继承
- 实现
- 依赖 一个类A使用到了类B,属于非常弱的关系。
a.append(B b)
。UML中虚线箭头 - 关联 强依赖,长期的关系。B是A的成员变量
public class A { private B b = new B();}
。UML中实线箭头 - 聚合 整体与部分的关系,has-a,是可以分离的,各自有各自的生命周期。比如公司与员工。UML中空心菱形加实线箭头
- 组合 是contains-a的关系,比关系比聚合强就是强聚合。整体和部分不可分割。比如人和手。UML中实心菱形加实线箭头
浏览器访问IPV6地址的后端程序,ip地址需要[]
括起来。
基础写法
循环
for循环目前有4种写法
1 | for (int i=0; i<100; i++) {} |
1 | for (int i=0; i<100; i++) { |
try catch return
任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
try catch后的语句还会继续执行,所以在后面进行return false 那么函数返回的就是false。try{} catch(){}finally{} return;
按顺序执行try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;再执行finally块,最后执行try中return;finally块之后的语句return,因为程序在try中已经return所以不再执行。
常用函数
空值判断,最常见的问题之一
1 | String[] photo = new String[0]; // 初始化 |
类型转换
String.valueOf()
Integer.parseInt(String s)
- 错误用法:强制转换
(String)data //Cannot cast from Integer to String
split
注意: . 、 | 和 * 等转义字符,必须得加 \\
。
注意:多个分隔符,可以用 | 作为连字符。
public String[] split(String regex, int limit)
regex – 正则表达式分隔符。
limit – 分割的份数
split(" ", -1)
:
- 如果字符串最后一位有值则没有区别
- 如果最后N位都是分隔符,
split(" ")
不会继续切分,split(" ", -1)
会继续切分。 split(" ")
不会保留空值,但是split(" ")
会保留空值- 具体说明可以参看java API文档说明,通过开发工具可以直接查看注释
1 | public class StringTest { |
反射
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
- Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
- Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
- Constructor :可以用 Constructor 创建新的对象。
Method invoke()调用的例子:
1 | // 1.获取字节码对象 |
List
LinkedList
- jdk1.6 双向循环列表
- jdk1.7 去掉了Head节点 变变成双向链表 但是有first last指针方便对头尾插入删除操作,节省了header节点空间
- 适合频繁插入删除
ArrayList
- 基于动态数组数组
- 可以通过下标定位
- 随机访问 get set优于LinkedList,LinkedList需要移动指针
- 插入效率没有LinkedList高
demo
1 | List<TreeNode> tmpList = new ArrayList<>(treeNodeMap.values()); // Map转List values() |
Set
具有去重功能,无序。
需要重写equals()与hashCode()方法,Set集合判断对象是否先等用到这两个方法(相当于通过hashcode生成箱子的编号),可以让工具自动生成
Object的代码:
1 | public String toString () { |
hashCode() 方法用于返回字符串的哈希码。
字符串对象的哈希码根据以下公式计算:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
从实现来说,一般的HashCode方法会这样:return Attribute1.HashCode() + Attribute2.HashCode()…[+super.HashCode()]
1 |
|
TreeMap
- 存入TreeMap的键值对的key是要能自然排序的(实现了Comparable接口),否则就要自定义一个比较器Comparator作为参数传入构造函数。
- TreeMap是以红黑树将数据组织在一起,在添加或者删除节点的时候有可能将红黑树的结构破坏了,所以需要判断是否对红黑树进行修复。
- 由于底层是红黑树结构(一种自平衡二叉查找树),所以TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是O(log n)。
- 由于TreeMap实现了NavigableMap,所以TreeMap有一系列的导航方法。
时间复杂度对比
TreeMap基于红黑树(一种自平衡二叉查找树)实现的是一个有序集合,时间复杂度平均能达到O(log n)。
hashSet(底层通过HashMap实现),hashtable,HashMap是基于散列表实现的,时间复杂度平均能达到O(1),最差O(n)。
ConcurrentSkipListMap是基于跳表实现的,时间复杂度平均能达到O(log n)。
数组 查询的时间复杂度 O(n)
链表 查询的时间复杂度 O(n)
TreeSet O(log(n)) TreeSet是基于TreeMap实现的是一个有序集合,同HashSet、LinkedHashSet一样,它使用TreeMap的键。TreeSet 搜索都是基于TreeMap红黑树搜索。TreeSet中的元素要实现Comparable接口
给定值的比较次数等于给定值节点在二叉排序树中的层数。如果二叉排序树是平衡的,则n个节点的二叉排序树的高度为Log2(n+1),其查找效率为O(Log2n),近似于折半查找。如果二叉排序树完全不平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。一般的,二叉排序树的查找性能在O(Log2n)到O(n)之间。因此,为了获得较好的查找性能,就要构造一棵平衡的二叉排序树。平衡二叉树 查找O(logn) ,二叉查找树 查找最坏O(n)
关键字
- transient关键字 实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化.transient关键字只能修饰变量,而不能修饰方法和类
- legth size() length是数组的一个属性,在获取值的时候是按属性的方法获取。而size()是链表的一个方法,用于获取链表的长度
<<左移动 >>右移动相当于除以2 ~ 取反
- static 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化,按照定义的顺序,顺序初始化,调用内部类的静态成员变量,会先执行静态代码块。静态代码块:全局,不管new多少次,只会初始化1次
- assert 断言,不符合条件会报错抛出异常
选择单例还是static
只有加载资源/需要初始化并比较慢的类,才去使用单例。单例:多方法,有状态。静态方法:工具,无状态(也能支持有状态),纯函数。其实全局静态变量(类)就是单例模式在语言层面上的一种实现。static变量使用的时候是可以多个类定义相同的对象的。static是在容器加载的时候就已经加载到内存中,所以static方法和变量不宜过度使用,有选择的使用,如果一个方法和他所在类的实例对象无关,那么它就应该是静态的,否则就应该是非静态。因此像工具类,一般都是静态的。如果我们确实应该使用非静态的方法,但是在创建类时又确实只需要维护一份实例时,就需要用单例模式了。从功能上讲:单例模式可以控制单例数量;可以进行有意义的派生;对实例的创建有更自由的控制;比如加载的配置信息,从面向对象的角度考虑应该使用单例模式。
连接池可以使用单例,是说连接池只有一个,池里面的链接可以有多个比如30个。还有Service层与dao层,service层要频繁调用dao层,但是不需要每次都new,dao层没有跟对象相关的值变量,可以用单例实现dao,spring就是这么管理的,dao可以去连接池里取一个链接进行访问数据库,单例支持多线程访问要实现同步得加synchronized
具体使用可以参考:Springboot、Mybatis、Netty等开业框架源码
Netty:ByteToMessageDecoder.MERGE_CUMULATOR
Mybatisplus:
1 | SqlCondition |
注意:new一个对象里面的成员变量也会被new一份出来,虽然里面成员变量使用了单例创建但是外层对象new一下还是会导致创建多个不同成员对象。线程比较多的程序可用通过dump查看内存状态
static test
A:随着类的加载而加载
B:优先与对象存在
C:被类的所有对象共享
D:可以通过类名调用
static 随着类的初始化而初始化,随着类的销毁而销毁。 静态变量:方法区的静态区, 成员变量:堆内存。静态变量:属于类,类变量, 成员变量:属于对象,对象变量,实例变量。
修改static值后其他对象获取该值也一样跟着修改:
1 | public class StaticTest { |
递归
递归边界,递归公式
递归次数过多容易造成栈溢出
尾递归可以复用栈帧
回调的写法
1 | public interface Callback { |
正则表达式
匹配出.数字.部分内容
1 | String str = "a.b.32.cd.323.f"; |
IO
写文件最靠谱的做法
1 | public static void saveStreamToFile(InputStream in, File file) throws IOException { |
java WEB 部署
注意: 部署多个tomcat 8080访问端口跟关闭端口都要改。
队列
如何批量消费队列中数据
1 | /** |
1 | <dependency> |
概念理解
值传递与引用传递
新建Test.java
1 | /** |
重写
子类重写父类方法的时候还可以继续调用父类的方法
1 |
|
开发中可能会遇到的处理
用整数表示下拉框多个值
比如 3
的二进制11
代表 1,2
。每个二进制位可以代表一个下拉框输入项
1 | // 处理方式循环二进制字符串位数,把各个位数非0的数值计算出来. 1*2 + 1*1 → '2,1' |
运行
jar包运行方式
java -jar xxx.jar
。 需要jar包中manifest.mf
文件包含入口类内容Main-Class: xxx.xx.XxxMainClass
.sh
.bat
执行脚本。可以通过脚本配置各种启动参数环境变量,方便引用第三方包。例子:创建test.sh,lib目录,下面放入a.jar,b.jar,c.jar,启动AppMain.class可以放在其中jar中运行结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17!/bin/bash
dir=`dirname $0`
workpath=`cd $dir;pwd`
JAVA_OPTS="-server"
JAVA_OPTS="$JAVA_OPTS -Xmx1024m"
JAVA_OPTS="$JAVA_OPTS -Duser.dir=$workpath"
for oneJar in ./lib/*.jar
do cp=$cp:$oneJar
done
java $JAVA_OPTS -classpath $cp vip.infotech.AppMain
echo " =============================================================================="
echo $dir
echo $0
echo $JAVA_OPTS
echo $cp
echo $oneJar
echo "java" $JAVA_OPTS "-classpath" $cp "vip.infotech.AppMain"1
2
3
4
5
6
7==============================================================================
.
./test.sh
-server -Xmx1024m -Duser.dir=/root
:./lib/a.jar:./lib/b.jar:./lib/c.jar
./lib/c.jar
java -server -Xmx1024m -Duser.dir=/root -classpath :./lib/a.jar:./lib/b.jar:./lib/c.jar vip.infotech.AppMain
war
web项目tomcat下用的是war包。可以用7zip
压缩包工具更新war包中的文件。
新特性
声明式编程
类似sql语句。
1 | int count = students.stream() |
其他功能
Runtime.getRuntime().addShutdownHook(shutdownHook);
当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作- 实体类类型统一,比如对于数据库bigint使用Long类型存储,其他整型都统一使用Integer
JNI
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。
JNI调用姿势:Java —> JNI —> C/C++(SO库)
如果用eclipse,需将dll或so文件放在项目下,而不是src及其子目录下。
如果用命令行编译,把dll文件放在该包的同目录下。
常见代码有:
1 | public class Object { |
1、在Java中声明native()方法,然后编译;
2、用javah产生一个.h文件;
3、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件);
4、将第三步的.cpp文件编译成动态链接库文件;
5、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。
JAVA本地方法适用的情况:
1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API访问
2.为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用JAVA编写的
3.为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。
4.也就是与Java程序外交互、与操作系统交互
异常可能原因
栈溢出
调用层级过深或者递归调用没有正常退出,函数无限循环调用自己等
gc overhead limit exceeded
由于数据库问题,业务积压也可能导致内存不够用。也就是本来业务处理完内存是要释放的,由于某些原因导致业务无法完全执行完毕,新业务有过来处理。
while循环创建对象?
读取大文件-XX:-UseGCOverheadLimit
延后出错时间
java.net.SocketTimeoutException: Read timed out
增加超时时长
删掉超时时长配置
tomcat配置,增加disableUploadTimeout="true"
或者keepAliveTimeout="100000"
1 | <!--disableUploadTimeout默认值是false--> |
ParseException 程序可能共用一个异常
ParseException 时间格式错误是这个异常,Cron表达式格式错误也是这个异常,然后内容里面体现出是哪里报错与报错原因。
SimpleDateFormat会有线程安全问题
因为内部实现中calendar.setTime(date)这条语句改变了成员变量calendar。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解,无状态的好处。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。所以得保证成员变量每个线程使用都是新new
出来的而且更不能做成static,不然会有线程安全问题,也就是说这种成员变量问题可以通过new对象解决。
可以线程各自new谨慎做成公共属性。使用同步:同步SimpleDateFormat对象、使用ThreadLocal、抛弃JDK,使用其他类库中的时间格式化类Joda-Time
systemctl stop 关闭失败
可能开启了远程调试导致问题,根据具体情况,考虑是否直接使用kill。
1 | 为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪 |
Failed to copy full contents from
使用FileUtils
, this could be due to the fact that the file i am trying to copy is consistantly updating so i might catch it mid update。
Well as you said the file might get updated during your copy process, so you should be required to get a file lock on the file you want to copy.
有建议用 Google guava API Files Class的。或者读写文件加锁的。
用了框架了还得自己加锁用,这个算不算框架BUG呢?
集合工具类
1 | /** |
类的选择
String StringBuffer StringBuilder
如果是固定值的拼接可以使用String
如果是带循环的字符串不确定的使用StringBuffer或者StringBuilder
内存分析与排查
1 | ps -ef|grep java #获取进程号 |
问题
Unable to open socket file: target process not responding or HotSpot VM not loaded
需要使用启动虚拟机相同的用户执行jstack命令sudo -u xxx_user jstack 26814 > a.txt
导入一个文件中查看
扩展
EOF
End Of File的缩写JDK
下载地址https://jdk.java.net/archive/
代码性能优化
减少数据库范围,需要访问多次数据库的情况考虑是否可以在重复调用之前一次性调用。也就是获取数据可以在用的时候调用也可以预先调用。哪种效率高用哪种,比如主表与关联附加信息表,调用方法的时候是只传输主表信息进去还是把主表关联表一起获取出来传递到目标方法内给线程池执行呢?,如果这个信息会调用多个任务分别执行的话这个信息提前全部获取好比较合适,如果只会调用一个任务那么可以提前获取,也可以用的时候再获取附加信息影响不大。如果想让任务执行的职责比较单一就是处理数据减少交互那么最好提前准备好数据。还可以考虑这部分附加信息被用到的概率,附加数据是不是有很多份,用到的是哪一份,会不会不同子任务用到的是不同份的附加数据,全部获取容易吗性能消耗怎样。一般情况可能事先获取全部会比较合理一点,用到的时候在获取会简单点比较符合正常思维逻辑。其实都是可以的,有些情况写在一起可能性能更高,有些情况分开性能更高。
代码结构优化
if else优化
- 可以把结果提前使用一层if else。有的时候使用一层好一点,有点时候还是使用多层好一点。
- 使用枚举
- 使用反射+枚举+策略模式
- 某些场景可以使用责任链,责任链模式其实就是一个灵活版的if…else…语句,它就是将这些判定条件的语句放到了各个处理类中,这样做的优点是比较灵活了,但同样也带来了风险,比如设置处理类前后关系时,一定要特别仔细,搞对处理类前后逻辑的条件判断关系,并且注意不要在链中出现循环引用的问题
- 模板方法判断,利用
pplicationContext.getBeansOfType(xx.class);
实现循环判断是否符合条件,需要在具体需要执行的多个实现类中添加是否符合当前类执行的验证方法,会导致需要先创建对象进行判断操作,当然判断操作可以放在静态方法。 - 通过反射获取实现某接口的所有实现类名称,调用实现类的某静态方法看是否符合条件,如果符合就通过反射创建该对象进行执行操作。
- 条件非常多非常长,而且重复出现,可以定义成一个参数复用。
否定的逻辑,防御式写法,减少嵌套层级,提前return
尽量减少层级提前return,根据情况长语句改成多条短语句执行。
try{}catch 需要赋值字段可以预先定义不进行赋值,在使用的时候在包含到try里面不需要整个代码块都包含进去。
性能分析
List 的length(),直接读取size属性不会重新计算,String的存储结构是数组,调用数组的length也不需要重新计算。首先数组是一个对象类型,数组一旦创建成功它的长度就固定了,所以它可以定义一个变量类型length属性来存储数组的长度,也就是说每次调用lengh属性的时候不会重新的计算数组的长度,也就不会过多的消耗资源;数组在java里是一种特殊类型,有别于普通的“类的实例”对象,java里数组不是类,所以也就没有对应的class文件,数组类型是由jvm从元素类型合成出来的;在jvm中获取数组的长度是用arraylength这个专门的字节码指令的;在数组的对象头里有一个_length字段,记录数组长度,arraylength的实现只需要去读_length字段就可以了,数组还是不可变类型,length大小不能修改,所以获取数组长度也不需要重新计算
。
其他
可以共用的属性在父类创建定义,当然要避免多线程处理同一个成员变量,继承关系的话new出来,后续没有其他线程进行修改是可以考虑的。
其他API学习
百度SDK API。token有效期是30天,避免重复获取,采取的策略是每次调用接口前做预判断,把当前时间+1天判断是否超过过期时间。
注意事项
主要还是线程的问题,资源分配,有没线程等待读写资源导致程序跟不上节奏,线程池资源全部被吃光。定时任务要能够在周期内执行完才行,不然就得建立队列,一个个执行,最终就很难保障时间了。
定时任务是个线程池,FTP上传又是一个线程池,定时任务线程数得比FTP线程多,FTP线程池得比需要连接的连接数多。任务资源执行的超时时间可以调小一点,比如ftp超时时间等。
还可以通过jstack排查是哪个线程业务一直执行着跑不完。特别是扫描目录上传,第一个线程还没扫描执行完第二个线程又进来扫描执行了,可以通过对象锁、或者类锁或者常量参数等进行判断退出根据具体情况进行加锁,避免周期性进入无法执行完的任务,导致业务积压越来越严重。经过分析好像quartz任务全部被吃满了,全部qz线程都在io或者数据库操作。quartz可以用@DisallowConcurrentExecution注解避免同jobkey的任务并发执行。
参考
- 关于Java Lambda表达式看这一篇就够了
- java本地方法 hashcode是怎样生成的?hashcode与地址有关系吗?
- 消除if…else判断的技巧,帮你实现更优雅的编程!
- java.lang.OutOfMemoryError:GC overhead limit exceeded
- GC overhead limit exceeded 问题分析与解决
- java中Static内存图解
- 单例模式与多线程之间的关系总结
- JVM即时编译(JIT)
- Java Cipher.doFinal方法代码示例
- 使用枚举代替常量,简化工作
- 使用 Stream API 高逼格 优化 Java 代码!
- 有return的情况下try catch finally的执行顺序
- try catch finally 中包含return的几种情况,及返回结果
- 深入理解Java:SimpleDateFormat安全的时间格式化
- for循环每次都通过list.size、str.length()、length()获取数组或者字符串的长度是否消耗资源
- 《java性能调优》1.字符串性能优化
- JAVA使用for循环会重复调用list.size()吗?
- 各种经纬度坐标系转换-百度坐标系、火星坐标系、国际坐标系
- 百度人脸客户端SDK学习
- Copying a file using FileUtils.copyFile
- How does FileLock work?
- How can I lock a file using java (if possible)
- WAITING at sun.misc.Unsafe.park(Native Method)
- 关于quartz任务调度问题,若一个任务在下一个任务触发前,还未执行完.
- 关于BufferedReader中readLine读取最后一行的问题