Java工作笔记/Java面试题/Java八股文/Java常用API
码农工具包
hutool工具
hutool工具类判断各种类型数据
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.8</version>
</dependency>
============================1.判断数组是否为空============================
String[] strArr = new String[]{};// 判断数组不为null,且素组长度不为0
if (ArrayUtil.isNotEmpty(strArr)) {System.out.println("数组不为null,且素组长度不为0");
}// 判断数组为null或数组长度为0
if (ArrayUtil.isEmpty(strArr)) {System.out.println("数组为null或数组长度为0");
}============================2.判断集合是否为空============================
List<String> list = new ArrayList();// 判断集合list是否为空,同时判断list为null,为空集合
if (CollectionUtil.isEmpty(list)) {System.out.println("集合list是否为空,同时判断list为null,为空集合");
}// 判断集合list是否为空,同时判断list不为null,不为空集合
if (CollectionUtil.isNotEmpty(list)) {System.out.println("集合list是否为空,同时判断list不为null,不为空集合");
}if (CollUtil.isNotEmpty(list)) {System.out.println("集合list是否为空,同时判断list不为null,不为空集合");
}============================3.判断字符串是否为空============================
String str = null;System.out.println("判断字符串是否为空:" + StrUtil.isNotBlank(str));
// 判断string不为"null"、""、" "
if (StrUtil.isNotBlank(str)) {}// 判断string为"null"、""、" "
if (StrUtil.isBlank(str)) {}============================4.判断两个字符串是否相等(内容相等)============================
String str1 = null;
String str2 = null;System.out.println("判断两个字符串是否相等:" + ObjectUtil.equals(str1, str2));// 判断两个字符串是否相等,此方法可以避免空指针异常
if (ObjectUtil.equals(str1, str2)) {/* 如果 string1 = null && string1 = null 返回true如果 string1 = null || string1 = null 返回false*/
}
hutool工具类时间字符串转换
============================获取当前时间============================
Date date = DateUtil.date(); //当前时间
Date date2 = DateUtil.date(Calendar.getInstance()); //当前时间
Date date3 = DateUtil.date(System.currentTimeMillis()); //当前时间
String now = DateUtil.now(); //当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
String today= DateUtil.today(); //当前日期字符串,格式:yyyy-MM-dd============================字符串转时间============================
eg1:
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd"); //自定义日期格式转化eg2:
String dateStr = "2015-08-12";
DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //自定义日期格式化的格式
Date classDate = format.parse(dateStr);//把字符串转化成指定格式的日期============================时间转字符串============================
Date date = new Date();
String dateStr = DateUtil.format(date, "yyyy-MM-dd"); // 输出为(yyyy-MM-dd)格式的字符串============================格式化日期输出============================
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);String format = DateUtil.format(date, "yyyy/MM/dd"); //结果 2017/03/01
String formatDate = DateUtil.formatDate(date); //常用格式的格式化,结果:2017-03-01
String formatDateTime = DateUtil.formatDateTime(date); //结果:2017-03-01 00:00:00
String formatTime = DateUtil.formatTime(date); //结果:00:00:00
数据类型互相转换
Convert.toLong(str);
Convert.toInt(num);
开发日常编码
封装树结构
1.封装数据
主表
//value为标识编码,label为前端需要显示的数据
private String label;
private Integer value;
private List<DictData> children;
从表
private String label;
private Integer value;
接口
@GetMapping("/getDictList")
public AjaxResult getDictList(DictType type){List<DictType> list = deptService.selectArchiveTreeList(type);return AjaxResult.success(list);
}@Override
public List<DictType> selectArchiveTreeList(DictType type) {return deptMapper.selectArchiveTreeList(type);
}<resultMap id="dictListMap" type="com.sk.common.core.domain.entity.archivesEntity.DictType"><id column="dict_id" property="value"/><result column="dict_name" property="label"/><collection property="children" ofType="com.sk.common.core.domain.entity.archivesEntity.DictData"><result column="dict_code" property="value"/><result column="dict_label" property="label"/></collection>
</resultMap>
<select id="selectArchiveTreeList" resultMap="dictListMap">SELECTt.dict_id,t.dict_name,d.dict_label,d.dict_codeFROMsys_dict_data AS dLEFT JOIN sys_dict_type AS t ON t.dict_type = d.dict_typeWHEREt.dict_type = 'sys_archives_type';
</select>
2.前端页面
<el-col :span="4" :xs="24"><div class="head-container"><el-tree:data="basicsOptions":props="defaultProps":expand-on-click-node="false":filter-node-method="filterNode"ref="tree"default-expand-allhighlight-current@node-click="handleNodeClick"/></div>
</el-col>basicsOptions: undefined,
defaultProps: {children: "children",label: "label",value: "value"
},// 筛选节点
filterNode(value, data) {if (!value) return true;return data.label.indexOf(value) !== -1;
},
// 节点单击事件
handleNodeClick(data) {
},
/** 查询部门下拉树结构 */
getBasicsTree() {basicsTreeSelect().then(response => {this.basicsOptions = response.data;});
}
通过时间范围查询数据
前端
<el-form-item label="上传时间"><el-date-pickerv-model="form.queryTime" <!-- queryTime: '' -->type="daterange"value-format="yyyy-MM-dd" <!--限制日期格式-->range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item><el-button type="primary" @click="query(form)">查询</el-button>
</el-form-item>
后台
@Override
public List<SkArchiveArchives> selectArchivesList(SkArchiveArchives archives) throws ParseException {//判断集合是否为空if (ArrayUtil.isNotEmpty(archives.getQueryTime())) {String[] queryTime = archives.getQueryTime();archives.setStartTime(queryTime[0]);String endTime = queryTime[1];DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //定义日期格式化的格式Date classDate = format.parse(endTime);//把字符串转化成指定格式的日期Calendar calendar = Calendar.getInstance(); //使用Calendar日历类对日期进行加减calendar.setTime(classDate);calendar.add(Calendar.DAY_OF_MONTH, 1);classDate = calendar.getTime();// 将日期类型转换成指定的字符串类型archives.setEndTime(DateUtil.format(classDate, "yyyy-MM-dd"));
}
java日期类型实现加减天数
String dateStr = "2015-08-12"; //需要加减的字符串型日期DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //定义日期格式化的格式
Date classDate = format.parse(dateStr);//把字符串转化成指定格式的日期Calendar calendar = Calendar.getInstance(); //使用Calendar日历类对日期进行加减
calendar.setTime(classDate);
calendar.add(Calendar.DAY_OF_MONTH, - 1);//-为昨天,+为明天classDate = calendar.getTime();//获取加减以后的Date类型日期dateSStr = DateUtil.format(classDate,"yyyy-MM-dd"); //得到处理以后的字符串
前端分页
<!--分页-->
<paginationbackgroundv-show="total>0":total="total":page.sync="form.pageNum":limit.sync="form.pageSize"@pagination="getList"
/>/** 总条数 */
total: 0,form: {pageNum: 1,/** 每页显示10条数据 */pageSize: 10,
},//获取表单数据的方法
getList(){}
JSON
# 从redis取出token(token包含当前登录用户的信息)
JSONObject jsonObject = JSONObject.parseObject(String.valueOf(redisTemplate.opsForValue().get(token)));
String userName = jsonObject.getString("userName");
将对象转为json,目的使对象称为k,v格式
Data data = new Data();
JSONObject dataToJson = (JSONObject) JSONObject.toJSON(data);
注解
lombak相关注解
//生成所有字段的getter、toString()、hashCode()、equals()、所有非final字段的setter、构造器,相当于设置了 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
@Data@Accessors(chain = true) //链式赋值
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name;private int score;
}
public class Main {public static void main(String[] args) {Student student = new Student().setName("yolo").setScore(98);//链式赋值String s = JSON.toJSONString(student);System.out.println(s);}
}
JSON相关注解、依赖
<!--以下两个依赖完成对象与json的相互转换-->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.3</version>
</dependency>
<dependency><groupId>com.colobu</groupId><artifactId>fastjson-jaxrs-json-provider</artifactId><version>0.3.1</version>
</dependency>
public class Person implements Serializable {//ordinal:自定义字段的输出顺序 name:给字段起别名 serialize = false:表示不参与序列化@JSONField(ordinal = 1)private Integer eid;@JSONField(ordinal = 2,name = "user")private String username;@JSONField(serialize = false)private String sex;@JSONField(ordinal = 4)private Integer age;
}
表示不是数据库字段、时间格式化
@TableField(exist = false):表示该属性不为数据库表字段@TableField(exist = true):表示该属性为数据库表字段。/**
* date
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
/**
* localDate
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;
/**
* localDateTime
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
/**
* localTime
*/
@JsonFormat(pattern = "HH:mm:ss")
private LocalTime localTime;
打包部署
1、上传jar包到服务器响应的文件目录下
2、停掉服务
ps -ef|grep sk-blo
kill -9 1111
3、备份当前jar包
mv 原jar包名 新名
4、修改上传的jar包名称
5、开启服务
nohup java -jar -Xms2g -Xmn1g java.jar >java.out 2>&1 &
java -jar ./java..jar
6、查看日志
tail -f -n 200 java.out
报错
杀进程
1、解决端口占用问题,首先查看端口的启动情况
win+R 输入cmd打开DOS命令框。输入:netstat -ano | findstr 80 其中80是我服务的端口号。
2、接下来我们就来杀死它
根据PID杀死任务: taskkill /F /PID “18560”
Git
idea查看git地址
查看git地址:git remote -v
git拉取代码提示Authentication failed for []
git拉取代码的时候提示Authentication failed for []
解决办法,用管理员身份打开git命令行,执行 git config --global credential.helper store重新clone的时候会提示让输入用户名,然后弹出框让输入密码,就可以了
跑项目时报错
Cannot resolve com.sun:tools:1.8解决方法
引入阿里的**druid **启动器,druid-spring-boot-starter-1.2.6.pom
<!--数据库连接池-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version>
</dependency>
启动器应用的druid,druid-1.2.6.pom
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.6</version>
</dependency>
重点是里面的profiles,会根据系统选择。这里只选择windows的
<profile><id>jconsole</id><activation><!--<activeByDefault>true</activeByDefault>--><file><exists>${env.JAVA_HOME}/lib/jconsole.jar</exists></file></activation><properties><toolsjar>${env.JAVA_HOME}/lib/tools.jar</toolsjar><jconsolejar>${env.JAVA_HOME}/lib/jconsole.jar</jconsolejar>
</properties>
...
</profile>
根据JAVA_HOME获取jar包,所以要看一下环境配置是否正确。
最后解决办法,原因是因为maven版本3.8.2有问题,没有搜索到jar包,换成3.6.3的就解决了
报的警告,但是过了,程序员无视警告就好了
java: You aren’t using a compiler supported by lombok, so lombok will not work and has been disab…
报错问题
java: You`aren't using a compiler supported by lombok, so lombok will not work and has been disabled.Your processor is: com.sun.proxy.$Proxy26Lombok supports: sun/apple javac 1.6, ECJ
解决
方法一
在以下位置加上该配置
-Djps.track.ap.dependencies=false
方法二
更新一下版本到以下版本
<!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.14</version><scope>provided</scope>
</dependency>
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.kimi.service.edu.mapper.EduCourseMapper.getPublishCourseInfo
因为maven默认加载机制,它只会把src-main-java文件夹中的java类型文件进行加载,其它类型文件不会加载。
在target文件夹中也没有加载xml文件夹中的xml文件
解决方式:
1.将xml文件复制到target文件夹中的mapper文件夹里面去;
2.将xml文件写到resources文件下;
3.通过配置文件进行配置,让maven自动加载xml文件:
pom.xml文件中添加如下配置:
<build>
<resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes><filtering>false</filtering></resource>
</resources>
</build>
application.properties文件中添加如下配置:
# 配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/kimi/service/edu/mapper/xml/*xml
com.mongodb.MongoSocketOpenException: Exception opening socket
23:29:11.057 [cluster-ClusterId{value='6318b8c44a60ed674eff44ac', description='null'}-localhost:27017] INFO o.m.driver.cluster - [info,76] - Exception in monitor thread while connecting to server localhost:27017
com.mongodb.MongoSocketOpenException: Exception opening socketat com.mongodb.internal.connection.SocketStream.open(SocketStream.java:70)at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:143)at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.lookupServerDescription(DefaultServerMonitor.java:188)at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:144)at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.ConnectException: Connection refused: connectat java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)at java.net.Socket.connect(Socket.java:589)at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:107)at com.mongodb.internal.connection.SocketStream.initializeSocket(SocketStream.java:79)at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:65)... 4 common frames omitted
解决方法
springboot自动配置了支持mongodb。在启动springboot时会自动实例化一个mongo实例,需要禁用自动配置 ,增加@SpringBootApplication(exclude = MongoAutoConfiguration.class)这个注解即可
Java基础
八大基本类型
JDK1.8新特性
JDK1.7和JDK1.8的区别
Java8 是 Java发布以来改动最大的一个版本
添加了函数式编程、Stream、全新的日期处理类
函数式编程新加了一些概念:Lambda表达式、函数式接口、函数引用、默认方法、Optional类等
Stream中提供了一些流式处理集合的方法,并提供了一些归约、划分等类的方法
日期中添加了ZoneDateTime、DataFormat等线程安全的方法类
1.JDK1.8接口中除了定义抽象方法以外,还可以定义static和default方法, static和default方法有方法体;
2.JDK1.8中switch语句支持String;
扩展:
基本类型有:byte,short,int,char
包装类型有:Byte,Short,Integer,Character,String,enum
switch实际上只支持int类型,使用其他的类型时是通过转化支持的:
1、基本类型byte char short ,原因:这些基本数字类型可自动向上转为int, 实际还是用的int。
2、包装类型Byte,Short,Character,Integer ,原因:java的自动拆箱机制 可将这些对象自动转为基本类型
3、String 类型 原因:实际switch比较的string.hashCode值,它是一个int类型
4、enum类型 原因 :实际比较的是enum的ordinal值(表示枚举值的顺序),它也是一个int类型
3.Lamdba表达式
4.函数式接口
5.日期中添加了ZoneDateTime、DataFormat等线程安全的方法类
因为JDK1.8新特性比较多,只记得这几个!
final, finally, finalize 的区别
final
final修饰变量时,如果是基本类型的变量,在声明时必须给定初始值,则其数值一旦被初始化后就不能被修改。如果是引用类型的变量,则初始化之后便不能再让其指向另外一个对象,但是它指向对象的内容可以被改变。
final修饰 的方法不能被重写,但是可以重载。
final修饰的类称为终类,该类不能被继承,final不能和abstract一起使用。
finally
finally用在try/catch/finally语句中,表示这段代码最终会被执行,通常被用来释放资源、关闭资源等操作。但是有时候finally语句不会执行,比如当程序执行try语句时,所有线程被结束,finally语句就不会执行。
try{System.out.println("没有异常时执行");System.exit(0);//正常退出 System.exit(0); 会结束整个虚拟机,也就是所有线程// System.exit(-1);
}catch (Exception e){System.out.println("发生异常时执行");
}finally {System.out.println("最终会被执行");
}
延伸:try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,
跳转到catch块中执行。
finalize
finalize是Object类中的一个方法,在GC执行的时候会被调用回收对象的finalize()方法,可以覆盖此方法来实现对其它资源的回收,例如关闭文件等。需要注意的是,一旦GC准备好释放对象的占用空间,将首先调用其finalize()方法,并且在下一次GC回收动作发生时,才会真正回收对象占用的内存。
由于finalize()方法存在很多问题,JDK1.9之后已经弃用了该方法。
栈、堆、方法区
栈:基本数据类型、堆中对象的引用、局部变量
堆:对象、数组、静态变量、字符串常量池
方法区:类信息、运行时常量池
多态
父类引用指向子类对象,同一类的对象收到相同消息时,会得到不同的结果。而这个消息是不可预测的。多态,顾名思义,就是多种状态,也就是多种结果。
反射
运行时拿到类的字节码对象(.class),得到类中的属性和方法。
这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。
项目中Spring框架IOC就使用了反射机制。
notify和notifyAll的区别
notify()和notifyAll()都是用来唤醒调用wait()方法进入等待锁资源队列的线程,区别在于:
notify():唤醒正在等待此对象监视器的单个线程。如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利。
notifyAll():唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争。
sleep和wait的区别
1.sleep是线程类Tread的静态方法,wait是Object类的方法;
2.sleep是使线程休眠,不会释放对象锁;wait是使线程等待,释放锁;
sleep让出的是cpu,如果此时代码是加锁的,那么即使让出了CPU,其他线程也无法运行,因为没有得到锁;
wait是让自己暂时等待,放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)
后本线程才进入对象锁定池准备获得对象锁进入运行状态。
3.调用sleep使线程进入阻塞状态;调用 wait使线程进入就绪状态。
悲观锁和乐观锁
悲观锁:
当多事务/多线程并发执行时,事务总是悲观的认为,在自己访问数据期间,其他事务一定会并发执行,此时会产生线程安全问题,
所以为了保证线程安全,这个事务在访问数据时,立即给数据加锁,从而保证线程安全.
特点:可以保证线程安全,但是并发执行效率低下
synchronized、排它锁都是悲观锁的应用乐观锁:
在多线程/多事务并发执行中,某个事务总是乐观的认为,在自己执行期间,没有其他事务与之并发,认为不会产生线程安全问题,所以不会给数据加锁;但是确实存在其他事务与之并发执行的情况,确实存在线程安全问题,为了保证线程安全,通过版本号机制或CAS来保证线程安全.
CAS:compare and swap 比较并交换
线程篇
线程创建的四种方式,什么地方使用了线程池
创建线程的方式:1.继承Thread类
2.实现Runnable接口
3.实现callable接口
Callable和Runnable的区别:1.Callable重写的是call()方法,Runnable重写的是run()方法2.实现Callable接口有返回值,实现Runnable接口没有返回值
4.线程池创建线程
平时使用那种线程池:
提高一下插入表的性能优化,因为是两张表,先插旧的表,紧接着插新的表,一万多条数据就有点慢了
后面就想到了线程池ThreadPoolExecutor,而用的是Spring Boot项目,
可以用Spring提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接使用注解启用
使用步骤
先创建一个线程池的配置,让Spring Boot加载,用来定义如何创建一个ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync**(/ əˈsɪŋk /)**这两个注解,表示这是个配置类,并且是线程池的配置类;
创建一个Service接口,是异步线程的接口;
实现类;
将Service层的服务异步化,在executeAsync()方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor方法是前面ExecutorConfig.java中的方法名,表明executeAsync方法进入的线程池是asyncServiceExecutor方法创建的。
在Controller里或者是哪里通过注解@Autowired注入这个Service
线程池
为什么要使用线程池
创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。使用线程池可以把创建和销毁的线程的过程去掉。
线程池有什么作用
1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就 分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。
**线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。 **
四种常见线程池
1.newFixedThreadPool
创建一个固定大小线程池,可控制线程最大并发数,超出的线程会在队列中等待。
固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
2.newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
3.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
缓存线程池,缓存的线程默认存活60秒。执行效率最快,线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
4.newScheduledThreadPool(/ ˈskedʒuːld )
创建一个定长线程池,支持定时及周期性任务执行
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。
线程池七大参数
public ThreadPoolExecutor(int corePoolSize, //线程池中常驻核心线程数int maximumPoolSize, //线程池能够容纳同时执行的最大线程数,此值必须大于1//多余空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize为止。long keepAliveTime, TimeUnit unit, //keepAliveTime的单位BlockingQueue<Runnable> workQueue,//:里面放了被提交但是尚未执行的任务ThreadFactory threadFactory, //表示线程池中工作线程的线程工厂,用于创建线程//拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。RejectedExecutionHandler handler)
synchronized(同步锁)
synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块
但是归根结底它上锁的资源只有两类:一个是对象,一个是类。
Synchronized如何避免死锁
Synchronized不要嵌套定义;
synchronized底层实现原理
synchronized具有四个特性:原子性,可见性,有序性,可重入性(可重入锁)
synchronized是可重入锁,每次锁对象会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。
注意!面试时经常会问比较synchronized和volatile(可见性),它们俩特性上最大的区别就在于原子性,volatile不具备原子性。
volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性。
多线程怎么解决线程安全问题
1.用局部变量去取代实例变量和静态变量。
2.如果是实例变量,则可以创建多个实例。
3.用Synchronized加锁。
Java多线程之间的通讯和协作
生产者-消费者模型
可以通过中断和共享变量的方式实现线程间的通讯和协作;比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,消费者通知生产者队列空间有了。同样地,当队列空时,消费者也必须等待,等待生产者通知消费者队列中有商品了。这种互相通信的过程就是先线程间的协作。Java中线程通信协作的最常见三种方式:
1.Syncrhoized加锁线程的Object类中的wait()/notify()/notifyAll();wait:等待让作用在仓库上的线程处于等待状态,会释放锁notify: 唤醒
可以唤醒仓库上等待的线程wait和notify方法是object方法,sleep是Thread方法2.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
线程间直接的数据交换:
通过管道进行线程间通信:1)字节流、2)字符流3.volatile(可见性)
代码解析
public class No4 {public static void main(String[] args) {List list = new ArrayList<>();Thread t1 = new Thread(new Producer(list));Thread t2 = new Thread(new Consumer(list));t1.setName("生产者");t2.setName("消费者");t1.start();t2.start();}
}class Producer implements Runnable {List list;public Producer(List list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (list) {if (list.size() > 0) {try {list.wait();Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}//说明仓库为空,可以生产Object producer = new Object();list.add(·);System.out.println(Thread.currentThread().getName() + "===>" + producer);list.notify();}}}
}class Consumer implements Runnable {List list;public Consumer(List list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (list) {if (list.size() == 0) {try {list.wait();Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}//说明仓库里可以被消费Object remove = list.remove(0);System.out.println(Thread.currentThread().getName() + "===>" + remove);list.notify();}}}
}
线程三大特性
/*** 线程有三个特性:可见性(volatile)、原子性、安全性*/static volatile boolean a = true;//volatile保证多个线程之间可见性,必须至少有两个线程一写一读public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {@Overridepublic void run() {while (a) {// try {// Thread.sleep(1000);
// } catch (InterruptedException e) {// e.printStackTrace();
// }}}}).start();Thread.sleep(1000);a = false; //写入System.out.println(a); //读取}
}
//static int a = 0;static AtomicInteger a = new AtomicInteger(0);//AtomicInteger(/ əˈtɒmɪk /):原子性public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 20000; i++) {new Thread(new Runnable() {@Overridepublic void run() {// synchronized (Demo06.class){// a++;
// }a.getAndAdd(1);}}).start();}Thread.sleep(1000);System.out.println(a);}
线程的5种状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
**3、运行状态(Running):**就绪状态的线程获取了CPU,执行程序代码。
**4、阻塞状态(Blocked):**阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“**等待池”**中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入**“锁池”**中。
(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
**5、死亡状态(Dead):**线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
start()和run()的区别
new一个Thread,线程进入新建状态,调用start()方法,使线程进入就绪状态,当得到cpu时间片后自动调用run(),run()执行结束后才能继续执行下一个run(),所有run()并没有多线程的体现。
start()
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
run()
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
区别:
1、线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程去执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。
2、另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法。
总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码。
还有:
1、start方法用来启动相应的线程;
2、run方法只是thread的一个普通方法,在主线程里执行;
3、需要并行处理的代码放在run方法中,start方法启动线程后自动调用run方法;
4、run方法必去是public的访问权限,返回类型为void。
如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,也就是说,start()方法能够异步地调用run()方法,但是直接调用run()方法却是同步的,因此也就无法达到多线程的目的。
只有通过调用线程类的start()方法才能真正达到多线程的目的。
过滤器和拦截器的区别?
1.拦截器(Interceptor)是基于Java的反射机制(属于面向切面编程(AOP)的一种运用),而过滤器(Filter)是基于函数回调。
2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3.过滤器可以对几乎所有的请求起作用,拦截器只能对action请求起作用。
4.过滤器不能获取IOC容器中的各个bean,而拦截器可以,在拦截器里注入一个service,可以调用业务逻辑。
过滤器(Filter):
它依赖于servlet容器,它可以对几乎所有请求进行过滤。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,
比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或controller进行业务逻辑操作。
通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter[自定义过滤器]),如:过滤低俗文字、危险字符等。
拦截器(Interceptor):
它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java反射机制。
属于面向切面编程(AOP)的一种运用,就是在servlet或一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑操作。
由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。拦截器可以对静态资源的请求进行拦截处理。
两者的本质区别:
拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。
从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。
Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。
迭代器
Iterable接口称为迭代器,主要用于遍历实现Collection接口的集合中的元素。
特点是更加安全,因为它可以确保在当前遍历的集合元素被修改的时候会抛出
ConcurrentModificationException异常
Iterator怎么使用,有什么特点:
1.java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator对象;
2.next()方法获得集合的下一个元素;
3.hasNext()检查集合是否还存在下一个元素;
4.remove()方法将迭代器返回的元素删除。
集合篇
Java集合框架继承图
那些集合是线程安全的
- Vector:就比ArrayList多了个同步化机制(线程安全)
- Stack:栈,也是线程安全,继承与Vector
- HashTable:就比HashMap多了个线程安全
- ConcurrentHashMap:是一种高效但是线程安全的集合
综合面试题
1.List、Set、Queue、Map四者的区别
- List、Set、Queue继承自Collection
- List存储的元素是有序的、可重复的
- Set存储的元素是无序的、不可重复的
- Queue按照特定的排队规则来排序,存储的元素是有序的、可重复的
- Map使用Key-value来进行存储数据,key不可重复、value是无序的、可重复的
2.集合的底层数据结构
List
实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。
ArrayList:底层由**Object[]**数组实现。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。
LinkedList :底层是链表结构。对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。还具有下列方 法:
addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(),
这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Set
简单回答:set集合底层就是一个Map集合,区别是Set是一个单列数据,也就是一个一个的数据,Map是双列数据,就是以键值对的形式存储数据。
复杂回答:Set集合的底层就是数组+分支的结构,简称Hash表(散列表)。
往Set集合中添加元素的算法:
根据对象的地址算出一个hasCode值,如果底层数组中没有对应的hashCode值,则找一个数组空间放进去,如果集合中有了对应的hashCode值对象,则进一步判断两个对象是否为equlas,如果不为equlas,则新加的对象以链表的形式挂在对应的hashCode的值元素下,如果两个对象equlas,则认为对象重复了,就不添加。所以往set集合中添加的元素会
重写hashCode方法和equlas方法。
HashSet(无序、唯一):底层采用HasMap来保存元素。为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet(有序、唯一): 底层基于TreeMap实现,TreeMap底层是红黑树。保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列,TreeSet集合中存放的对象必须实现了comparable接口,因为红黑树要求TreeSet集合元素
必须按照顺序存放。
Queue
- ArrayDeque:Object[]数组+双指针
- PriorityQueue:Object[]数组来实现二叉堆
Map
- HashMap jdk1.8之前由数组+链表组成,数组是HashMap的主体,而链表主要是为了解决哈希冲突。jdk1.8之后引入了红黑树。当链表长度大于等于8且数组长度大于等于64,链表为转换为红黑树。如果只满足一个条件,那么会优先选择数组扩容。
- TreeSet底层基于TreeMap实现
- TreeMap:红黑树(自平衡二叉排序树)
- HashTable:数组+链表组成,数组是HashTable的主体,链表则是主要为了解决哈希冲突而存在的。
3.如何选取集合结构
- 如果我们需要根据键值来获取元素值,就可以选用Map接口下的集合。需要排序就选用
TreeMap
,不需要排序就选用HashMap
,需要保证线程安全就选用ConcurrentHashMap
(Concurrent:/ kənˈkʌrənt /); - 如果只需要保存元素值,就选择实现
Conllection
接口的集合。需要保证元素唯一时选择Set接口下的集合,比如TreeSet
或者是HashSet
,不需要就选用List
接口下的集合,比如ArrayList
或者LinkedList
。
4.为什么要使用集合
当需要保存一组数据类型相同的数据的时候,由于数据的类型是多种多样的,所以我们使用了集合。集合提高了数据存储的灵活性,还可以保存具有映射关系的数据。
List
1、List常用方法
.add():添加元素 .remove(index):通过下标删除元素 .remove(Object o):删除指定的一个元素
.contains(Object o):是否包含某个元素 .set(index,element):将index位置的元素替换为element
.add(index,element):将element元素放到index位置,原来的元素后移一位
.indexOf(Object o):显示集合中元素第一次出现的下标 .lastIndexOf(Object o):显示集合中元素最后一次出现的下标
.size():得到集合中元素的个数 .subList(fromIndex,toIndex):截取集合中的元素生成一个新集合(含头不含尾)
.equals():判断两个集合是否相等 .isEmpty():判断集合是否为空 .toString():集合转为字符串//将多个对象同时添加到一个集合中
Article article1 = new Article();
Article article2 = new Article();
Article article3 = new Article();
ArrayList<Article> list = new ArrayList<>();
Collections.addAll(list,article1,article2,article3);// 迭代器
.iterator():返回Iterator集合对象
Iterator<String> iterator = person.iterator();
while (iterator.hasNext()){System.out.println(iterator.next());
}.toArray():集合转为数组
String[] phones = (String[]) phone.toArray();
System.out.println("将集合转换为数组:"+phones);
2.ArrayList与LinkedList的区别?
(1)是否保证线程安全: ArrayList和LinkedList都是不同步的、线程不安全的。
(2)底层实现: ArrayList底层使用Object[]数组实现,LinkedList底层使用双向链表实现。(jdk1.6之前是双向循环链表,1.6之后取消了循环)。
(3)是否支持快速随机访问: ArrayList底层使用数组实现,支持快速访问。而LinkedList底层是链表实现,不支持快速访问。
3.System.arraycopy()方法和Arrays.copyOf()方法的区别
通过查看源码我们可以知道,Arrays.copyOf()方法内部调用了System.arraycopy()方法。
arraycopy()方法需要目标数组,将原数组拷贝到自定义的数组中或者是原数组,并且可以选择拷贝的起点以及放入新的数组中的位置。
copyOf()时系统自动在内部新建一个数组,并返回该数组。
拓展:
ArrayList实现了RandomAccess接口,只是作为一个标识,说明ArrayList支持快速随机访问功能。
4.ensureCapacity()方法的作用
ensureCapacity()方法并不是在ArrayList内部调用的,而是提供给用户来使用的,在向ArrayList里面添加大量元素之前最好先使用ensureCapacity方法,以减少增量重新分配的次数,提高效率。
5.ArrayList扩容机制
- 先创建的ArrayList的容量为0
- 第一次调用add()方法扩容成10
- 当容量不够需要扩容时按照1.5倍的速度进行扩容
6.ArrayList如何做到并发修改,而不出现并发修改异常?
为解决此问题呢,java引入了一个可以保证读和写都是线程安全的集合(读写分离集合):CopyOnWriteArrayList
所以解决方案就是:
// private static ArrayList<String> list = new ArrayList<>();// 使用读写分离集合替换掉原来的ArrayListprivate static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();static {list.add("Jack");list.add("Amy");list.add("Lucy");}
7.ArrayList和Vector的区别?
Set
1.comparable 和 Comparator 的区别
- comparable接口实际上是出自java.lang包中的一个CompareTo(Object obj)方法用来排序;
- comparator实际上是出自java.util包中有一个compare(Object obj1,Object obj2)方法来排序。
当我们需要对一个集合进行自定义排序后,我们就要重写CompareTo或者是compare方法。
2.无序性和不可重复性的含义
(1)无序性?无序性不等于随机性,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
(2)不可重复性?不可重复性是指添加的元素按照equals判断时,返回false,需要同时重写equals方法和hashCode方法。
3.比较HashSet、LinkedListHashSet、TreeSet的异同
- HashSet、LinkedListHashSet、TreeSet三者都是Set集合的实现类,都能保证元素唯一,并且都不是线程安全的。
- HashSet、LinkedListHashSet、TreeSet的主要区别在于底层数据结构不同。HashSet底层数据结构式HashMap(哈希表);LinkedListHashSet的底层数据结构是链表和哈希表。TreeSet的底层数据结构是红黑树,元素是有序的。
- HashSet用于不需要保证元素插入和去除顺序的场景;LinkedListHashSet用于保证元素的插入和取出满足FIFO的场景;TreeSet用于支持元素自定义排序规则的场景。
Queue
1.Queue与Deque的区别
Queue是单端队列,只能从一端插入元素,另一端删除元素,遵循FIFO的规则。
Queue根据容量问题而导致操作失败后的处理方式不同。一种抛出异常,一种返回null。
Deque是双端队列,在队列的两端均可以插入或者删除元素。
实际上,Deque还提供了其他的方法,可以当作栈来使用。
2.ArrayDeque和LinkedList的区别
ArrayDeque和LinkedList都实现了Deque接口,两者都可以当作队列来使用。
- ArrayDeque是基于可变容量的数组和双指针来实现的,而LinkedList是通过链表来实现的
- ArrayDeque不支持Null数据,而LinkedLisr可以使用Null数据
- ArrayDeque是在jdk1.6引入的,而LinkedList早在jdk1.2都已存在
- ArrayDeque插入时可能存在扩容过程,不过插入的操作时间复杂度仍为O(1),虽然LinkedList不需要扩容,但是每次插入数据时都需要重新申请空间,性能相比更差一点
从性能上来说,ArrayDeque来实现队列要比LinkedList要好一点,另外,ArrayDeque也可以直接用作栈。
3.简单说一下PriorityQueue
PriorityQueue是在jdk1.5被引入的,与Queue的区别在于元素出队顺序是与优先级相关的,即总是优先级高的元素出队列。
- PriorityQueue利用了二叉堆的数据结构来实现,底层使用可变长的数组来存储数据
- PriorityQueue通过堆元素的上浮和下沉,实现了在(logn)的时间复杂度内插入元素和删除堆顶元素
- PriorityQueue是非线程安全的,而且不支持存储NULL对象
- PriorityQueue默认是小顶堆,但是可以接收一个comparator作为构造参数,可以自定义元素优先级的先后
Map
1.HashMap和Hashtable的区别
线程是否安全:HashMap是非线程安全的,而Hashtable是线程安全的。Hashtable内部的方法基本都经过synchronized修饰。
效率:因为线程安全的问题,HashMap要比Hashtable效率高,而且Hashtable已经快要淘汰了。HashMap线程不安全,HashTable线程安全,要使用线程安全的map集合可以使用ConcurrentHashMap;
初始容量和每次扩充容量的大小:
- 创建时如果不指定初始容量,Hashtable默认初始容量为11,每次扩充之后,边缘原来的 2n+1 ;而HashMap默认初始化大小为16,每次扩容会变为原来的2倍。
Hashmap是允许key和value为null值的,用containsValue和containsKey方法判断是否包含对应键值对;
HashTable键值对都不能为空,否则包空指针异常。
底层数据结构:JDK1.8之后HashMap在解决哈希冲突时,当链表长度大于8且数组长度大于64,链表就会转化为红黑树,若有一个条件不满足,那么会优先选择数组扩容。而Hashtable没有这样的机制。
2.HashMap和HashSet的区别
HashSet底层就是基于HashMap实现的。
HashMap实现了Map接口,而HashSet实现了Set接口。
HashMap用于存储键值对,而HashSet用于存储对象。
HashMap不允许有重复的键,可以允许有重复的值。HashSet不允许有重复元素。
HashMap允许有一个键为空,多个值为空,HashSet允许有一个空值。
HashMap中使用put()将元素加入map中,而HashSet使用add()将元素放入set中。
HashMap比较快,因为其使用唯一的键来获取对象。
3.HashMap和TreeMap的区别
HashMap和TreeMap都继承于AbstractMap,但是TreeMap还实现了NavigableMap接口和SortedMap接口。
- 实现Navigable接口让TreeMap对集合内元素有了搜索的功能;
- 实现SortedMap接口让TreeMap有了对集合中的元素根据键排序的能力。
相比来说,TreeMap多了对集合中元素根据键排序的功能和对集合元素进行搜索的能力。
4.HashSet如何检查重复?
当把对象加入到HashSet中时,HashSet会先计算对象的hashCode值来判断对象加入的下标位置,同时也会与其他的对象的hashCode进行比较,如果没有相同的,就直接插入数据;如果有相同的,就进一步使用equals来进行比较对象是否相同,如果相同,就不会加入成功。5.HashCode与equals的相关规定
(1)如果两个对象相等,则hashCode也一定是相等的
(2)两个对象相等,对两个进行equals也会返回true
(3)两个对象有相同的hashCode,他们也不一定是相等的
(4)hashCode和equals方法都必须被同时覆盖
6.HashMap的长度为什么是2的n次方?
为了能让HashMap存取高效,尽量减少哈希碰撞,尽量把数据均匀分配。
7.HashMap多线程下操作导致死循环问题
主要原因是并发下的Rehash会造成元素之间形成一个循环链表。多线程下HashMap会存在数据丢失的问题,并发环境下推荐使用ConcurrentHashMap、
8.ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashmap和Hashtable的区别主要体现在实现线程安全的方式上不同
- 底层数据结构: jdk1.7的ConcurrentHashMap底层采用分段的数组+链表实现,jdk1.8采用的数据结构跟HashMap1.8的结构是一样的,数树。Hashtable和jdk1.8之前的HashMap的底层数据结构类似,都是采用数组+链表的形式, 数组是hashMap的主体,链表则是为了解决哈希冲突。
- 实现线程安全的方法:①在1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),到1.8直接使用数组+链表+红黑树的实现。
9.ConcurrentHasMap底层如何实现分布式锁
将同一个容器的划分为好多Segmet(段),段数就是并发度,某个线程执行某些操作只会获取一个段的锁,
而不会影响其他线程获取其他锁的段。
扩展: ConcurrentHashMap把整个容器分为许多个Segment(段),段数就是并发度,每个Segment类似于一个Hashtable,管理一个HashEntry数组,每个HashEntry是一个链表头,或者红黑树的根,其实Segment的数据结构类似于HashMap。
10.map常用方法
.put(Object key,Object value):将指定key-value添加或修改当前map对象中
.putAll(Map m):将集合m中所有的key-value添加到当前map中
.remove(Object key):删除指定key的key-value对,并返回当前key对应的value
.clear():清空当前map中的所有数据(clear方法只是把元素清除掉,但在堆上开辟的map还在)
.isEmpty():判断集合是否为空(底部是用size==0判断的)
.get(Object key):通过key获取value
.containsKey(Object key)/.containsValue(Object value)是否包含key/value
.size():得到集合键值对个数
.keySet():获取Map中所有的key,返回一个Set集合
.values():获取map中所有的value,返回一个Conllection
HashMap
1.put数据时key与已有数据的HashCode相等时会怎么样?
会产生哈希碰撞。
如果Hash码相同,则会通过equals方法进行比较key是否相同:
如果key值相同,则使用新的value代替旧的value;
如果key值不相同,则会在该节点的链表结构上新增一个节点(如果链表长度>=8并且数组节点数>=64 ,链表结构就会转化成红黑树)。
2.什么时候会产生hash碰撞?如何解决哈希碰撞?
只要通过hash函数计算所得到的两个元素的hash值相同就会产生hash碰撞。
HashMap在jdk8之前采用链表解决哈希碰撞,jdk8之后采用链表+红黑树解决哈希碰撞。
3.如果hashCode相同,那么如何存储key-value对?
当hashCode值相同时,就会进一步使用equals方法进行比较key是否相同。
如果key相同,那么新值覆盖旧值;
如果key不相同,则将新的key-value添加到HashMap中。
4.HashMap如何进行扩容?
当通过put方法不断进行数据添加时,如果元素个数超过了当前阈值就会进行扩容,默认扩容大小是原来的2倍,扩容之后会将原来的数据复制到新的数组中。
5.如果确定了要存储的元素个数n,设置多少的初始容量可以减少扩容导致的性能损失?
应该设置初始容量为 n/0.75 + 1 取整即可减少resize导致的性能损失。
Collections工具类
常用方法:
- 排序
- 查找,替换
- 同步控制(不推荐)
排序操作
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
123456
查找,替换
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
1234567
集合注意事项
集合判空
集合判断是否为空,使用isEmpty()的方法,而不是size==0的方式
因为isEmpty()方法的可读性好,而且时间复杂度为O(1)
集合转化Map
使用Java.util.stream.Collectors类的toMap()方法转化为map集合时,要注释当value为null时会抛出NPE异常。
集合遍历
不要在foreach里面进行元素的remove/add操作,remove请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
fail-fast 机制 :多个线程对 fail-fast 集合进行修改的时候,可能会抛出ConcurrentModificationException
。
集合去重
可以利用Set元素唯一的特性,进行集合去重,避免使用List的contains进行遍历去重或者判断包含操作
集合转数组
使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的类型完全一致,长度为0的空数组。
toArray(T[] array)
方法的参数是一个泛型数组,如果 toArray
方法中没有传递任何参数的话返回的是 Object
类 型数组。
数组转集合
使用工具类Arrays.asList()把数组转化成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
集合知识补充
一, 先来看几个变量、常量、静态变量、静态常量:
1)链表与红黑树互相转化的阀值:
//将链表转化为红黑树的阀值static final int TREEIFY_THRESHOLD = 8;//将红黑树转回为链表的阀值static final int UNTREEIFY_THRESHOLD = 6;//将链表转为红黑树的最小entry数组的长度为 64//当链表的长度 >= 8,且entry数组的长度>= 64时,才会将链表转为红黑树。static final int MIN_TREEIFY_CAPACITY = 64;
2) Map的默认初始化容量以及容量极限:
//HashMap默认的初始容量大小--16,容量必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//HashMap的容量极限,为2的30次幂;
static final int MAXIMUM_CAPACITY = 1 << 30;
3) 默认负载因子、实际元素数量:
//负载因子的默认大小,
//元素的数量/容量得到的实际负载数值与负载因子进行对比,来决定容量的大小以及是否扩容;
static final float DEFAULT_LOAD_FACTOR = 0.75f;//HashMap的实际元素数量
transient int size;
4)table——Entry数组;
//Node是Map.Entry接口的实现类,可以将这个table理解为是一个entry数组;
//每一个Node即entry,本质都是一个单向链表
transient Node<K,V>[] table;
5)Map已经修改的次数、下一次扩容的大小、存储负载因子的常量:
//HashMap已在结构上修改的次数 结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些
transient int modCount;//下一次HashMap扩容的阀值大小,如果尚未扩容,则该字段保存初始entry数组的容量,或用零表示
int threshold;//存储负载因子的常量,初始化的时候将默认的负载因子赋值给它;
final float loadFactor;
5.5)什么是Map已经修改的次数?
modCount用于记录HashMap的修改次数,在HashMap的put(),get(),remove(),Interator()等方法中,都使用了该属性。
由于HashMap不是线程安全的,所以在迭代的时候,会将modCount赋值到迭代器的expectedModCount属性中,然后进行迭代;
如果在迭代的过程中HashMap被其他线程修改了,modCount的数值就会发生变化,这个时候expectedModCount和ModCount不相等,迭代器就会抛出ConcurrentModificationException()并发修改异常。
二,链表转红黑树
当这个链表过长了,查找这个链表上的元素的时候自然会变慢,所以链表如果过长了会影响到HashMap的性能,
所以后来在Java8中,当链表过长时,会将该链表自动转为红黑树,红黑树是一个自平衡二叉树,能够优化查找的性能。
什么时候将链表转换为红黑树
①,当链表的长度 >= 8,并且entry数组的长度>= 64时,才会将链表转为红黑树。
②,当链表的长度 >= 8,但是entry数组的长度< 64时,不转为红黑树,而是调用resize方法进行扩容;
什么时候将红黑树退化回为链表:
在resize()方法扩容的时候,在将原Node数组迁移到扩容后的新Node数组的时候,如果该Node元素是一个红黑树,则对其进行拆分、然后才迁移到新的Node数组中,如果拆分之后的子树的数量小于等于6了,则将该子树转回为链表结构;
如何通过不改变引用的指向来修改内容
String str = new String("abc")在不改变引用指向的前提下输出“abcd”
public class No1 {public static void main(String[] args) throws Exception{String str = new String("abc");.....System.out.println(str); //abcd
}
只要通过String
源码可以看到,它是通过参数赋值给value
属性,value
为一个char[]
数组,
即可通过反射来获取value
字段,将字段值直接修改为abcd
即可:
public class No1 {public static void main(String[] args) throws Exception{String str = new String("abc");//通过反射拿到str里面的value字段Field value = str.getClass().getDeclaredField("value");//修改权限为truevalue.setAccessible(true);//将abc直接修改为abcd并转换为字符数组value.set(str,"abcd".toCharArray());System.out.println(str);}
代码解析:
setAccessible()方法不属于Field,它属于AccessibleObject类,
Field通过extends AccessibleObject来获得setAccessible()方法;getDeclaredField("value"):
java.lang.Class类的getDeclaredField()方法用于获取此类的指定字段。该方法以Field对象的形式返回此类的指定字段。setAccessible(true):
该方式是用来设置获取权限的。
如果 accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的);
如果设置为false,反射对象在使用的时候,会检查Java语言权限控制。
需要注意的是,设置为true会引起安全隐患。
String底层源码解析:
public String(String original) {this.value = original.value;this.hash = original.hash;}
// 调用String类的构造方法,通过源码可以看到,它是把传进去的值赋值给value属性public final class Stringimplements java.io.Serializable, Comparable<java.lang.String>, CharSequenc/** The value is used for character storage. */private final char value[];......
// value属性为一个char数组
冒泡排序、选择排序、插入排序
冒泡排序演示:
public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}
9long t1 = System.currentTimeMillis();/*** 冒泡外层:每一轮都是从第一个元素开始比较,所以起始下标永远都是0* 外层:比较轮数* 内层:元素下标*///冒泡排序 耗时:21327msfor (int i = 0; i < ary.length; i++) {for (int j = 0; j < ary.length-1-i; j++) {if (ary[j]<ary[j+1]){int tmp = ary[j];ary[j]=ary[j+1];ary[j+1]=tmp;}}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}
选择排序演示:
public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}long t1 = System.currentTimeMillis();/*** 选择排序:* 选择排序(Selection sort)是一种简单直观的排序算法。* 它的工作原理是:* 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,* 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。* 以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。**///选择排序 耗时:6139ms//外层:i表示对该位置求最小值for (int i = 0; i < ary.length; i++) {int min = 1;//内层:j表示对i后的每个元素进行遍历,判断其与min指向元素的大小for (int j = i+1; j < ary.length; j++) {if (ary[j]<ary[min]){min = j;}}//将min指向的元素和i位置的元素互换if (min!=i){int tmp = ary[i];ary[i] = ary[min];ary[min] = tmp;}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}
插入排序演示
public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}long t1 = System.currentTimeMillis();/*** 插入排序:* 将序列分为排好序和未排序部分,对未排序部分元素进行遍历,* 将遍历到的元素插入到排好序元素的适当位置,通过比较和交换来实现*///插入排序 耗时:2149msfor (int i = 1; i < ary.length; i++) {for (int j = i; j>0 && ary[j]<ary[j-1]; j--) {int tmp = ary[j];ary[j] = ary[j-1];ary[j-1] = tmp;}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}
Java中八种数据结构
- 哈希表(Hash)
- 队列(Queue)
- 树(Tree)
- 堆(Heap)
- 数组(Array)
- 栈(Stock)
- 链表(Linked List)
- 图(Graph)
哈希表(Hash)
哈希表也叫散列表,是一种可以通过关键码值(Key-Value)直接访问的数据结构,可以实现快速查询、插入、删除。
数组类型的数据结构在插入和删除时时间复杂度高;链表类型的数据结构在查询时时间复杂度高;而哈希表结合了数组与链表的优势。
在jdk8中,Java中经典的HashMap,以数组+链表+红黑树构成。
哈希函数在哈希表中起着关键作用,能够将任意长度的输入转为定长的输出(哈希值)。通过哈希函数,能够快速地对数据元素进行定位。
哈希值并不是具有唯一性,在某些情况下Hash值会冲突,HashMap在Hash冲突时,会将元素在数组的位置上添加为链表元素结点,当链表长度大于8时,链表会转换为红黑树。
队列(Queue)
类比水管,两端放开,一端入水,一端出水。
队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cPzOtJJo-1667037486895)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220802085955709.png)]
树(Tree)
树是一种非线性结构,由n(n>0)个有限结点组成有层次关系的集合。
术语:
二叉树:每个结点最多含有2个子树。
完全二叉树:除了最外层的结点,其他各层结点都达到最大数。
满二叉树:
国内定义:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
国外定义:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。
二叉查找树:
任意结点的左子树不为空,左子树所有结点的值均小于根结点的值。
任意结点的右子树不为空,右子树所有结点的值均大于根节点的值。
任意结点的左右子树也是一颗二叉查找树。
平衡二叉树:也称AVL树,当且仅当任何结点的两棵子树的高度差不大于1的二叉树。Java中HashMap的红黑树就是平衡二叉树!!!
B树:一种对读写优化的自平衡二叉树,在数据库的索引中常见的BTREE就是自平衡二叉树。
B+树:B+树是应文件系统所需而产生的B树的变形树。
所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字
所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。
有m个子树的中间节点包含有m个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引。
Java8中HashMap的红黑树
实质上就是平衡二叉树,通过颜色约束二叉树的平衡:
1)每个节点都只能是红色或者黑色
2)根节点是黑色
3)每个叶节点(NIL 节点,空节点)是黑色的。
4)如果一个节点是红色的,则它两个子节点都是黑色的。也就是说在一条路径上不能出现相邻的两个红色节点。
5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
堆(Heap)
堆可以被看成一个树的数组对象,具有如下特点:
堆是一颗完全二叉树。
最大堆/大根堆:某个结点的值不大于父结点的值。
最小堆/小根堆:某个结点的值不小于父结点的值。
数组(Array)
数组是一种线性表的数据结构,连续的空间存储相同类型的数据。
- 优点:
按照索引查询元素的速度很快;
按照索引遍历数组也很方便。
- 缺点:
数组的大小在创建后就确定了,无法扩容;
数组只能存储一种类型的数据;
添加、删除元素的操作很耗时间,因为要移动其他元素。
栈(Stock)
栈可以类比为水桶,只有一端能够进出,遵循的先进后出的规则。
栈先进的元素进入栈底,读元素的时候从栈顶取元素。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erQRTUNV-1667037486897)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220802085901651.png)]
链表(Linked List)
链表是一种线性表的链式存储方式,链表的内存是不连续的,前一个元素存储地址的下一个地址中存储的不一定是下一个元素。链表通过一个指向下一个元素地址的引用将链表中的元素串起来。
单向链表:单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点(node),每一个节点包含了数据块和指向下一个节点的指针。
双向链表:顾名思义,双向链表就是有两个方向的链表。同单向链表不同,在双向链表中每一个节点不仅存储指向下一个节点的指针,而且存储指向前一个节点的指针。通过这种方式,能够通过在O(1)时间内通过目的节点直接找到前驱节点,但是同时会增加大量的指针存储空间。
循环链表:循环链表与双向链表相似,不同的地方在于:在链表的尾部增加一个指向头结点的指针,头结点也增加一个指向尾节点的指针,以及第一个节点指向头节点的指针,从而更方便索引链表元素。
图(Graph)
一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。
节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。
设计模式
设计模式有哪六大原则?
- 单一职责原则
- 开放封闭原则
- 里氏替换原则
- 依赖倒置原则
- 迪米特原则
- 接口隔离原则
设计模式的三大类
总体来说设计模式分为三大类:
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
根据作用范围来分
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
单例模式
单例模式? 懒汉模式? 饿汉模式? 它们之间的安全问题
单例模式:1.单例类只有一个实例;
2.单例类必须自己创建自己的实例;
3.单例类必须给所有其它对象提供这一实例。
单例模式的实现:1.创建一个私有的构造函数(防止其他类直接new此对象)
2.创建一个私有的属性对象
3.创建一个公共的对外暴露得单例对象
懒汉式 :非线程安全,懒汉比较懒只有当调用getInstance的时候,才会去初始化这个单例。
if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。
public class SingletonLH {/***是否 Lazy 初始化:是*是否多线程安全:否*实现难度:易*描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
*因为没有加锁 synchronized,所以严格意义上它并不算单例模式。*这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。*/private static SingletonLH instance;private SingletonLH (){}public static SingletonLH getInstance() {if (instance == null) {instance = new SingletonLH();}return instance;
}
}
饿汉式:线程安全,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
public class SingletonEH {/***是否 Lazy 初始化:否*是否多线程安全:是*实现难度:易*描述:这种方式比较常用,但容易产生垃圾对象。*优点:没有加锁,执行效率会提高。*缺点:类加载时就初始化,浪费内存。*它基于 classloder 机制避免了多线程的同步问题,* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,* 在单例模式中大多数都是调用 getInstance 方法,* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,* 这时候初始化 instance 显然没有达到 lazy loading 的效果。*/private static SingletonEH instance = new SingletonEH();private SingletonEH (){}public static SingletonEH getInstance() {System.out.println("instance:"+instance);System.out.println("加载饿汉式....");return instance;}
}
保证懒加载的线程安全
我们首先想到的就是使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。
public class Singleton {private static Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
我们把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁
public class Singleton {private static Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
new一个对象的代码是无法保证顺序性的,因此,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。
public class Singleton {private static volatile Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {synchronized (instance) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
到此,我们就保证了懒加载的线程安全。
工厂方法模式
1、工厂方法模式
创建对象的过程不再由当前类实例化,而是由工厂类完成,在工厂类中只需要告知对象类型即可。工厂模式中必须依赖接口
简单工厂模式
简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。不修改代码的话,是无法扩展的。
以生产“电脑”为例,电脑有办公的功能,可以生产一体机或笔记本
代码与静态工厂一样
2.静态工厂模式
//电脑接口
public interface Computer {//电脑办公public void work();
}
//笔记本
public class PersonComputer implements Computer{@Overridepublic void work() {System.out.println("这是笔记本电脑,正在办公");}
}
//一体机
public class WorkComputer implements Computer{@Overridepublic void work() {System.out.println("这是一体机正在办公");}
}
//用于生产电脑的工厂 (这个工厂既可以生产台式机也可以生产笔记本)
public class ComputerFactory {/*** 根据不同的类型 生产不同的产品* @param type* @return*/public Computer produce(String type){Computer computer =null;if(type.equals("personComputer")){computer = new PersonComputer();}else if(type.equals("workComputer")){computer = new WorkComputer();}else{System.out.println("不能生产");}return computer;}
//静态工厂方法模式
public class ComputerFactory2 {/*** 静态工厂方法* @param type* @return*/public static Computer produce(String type){// 定义一个接口的引用 通过接口new 一个实现类的对象// 提高扩展性Computer computer=null;if(type.equals("workComputer")){computer = new WorkComputer();}else if(type.equals("personComputer")){computer = new PersonComputer();}else{System.out.println("不能创建对象");}return computer;}
}
//测试类
public class Test1 {public static void main(String[] args) {// 通过工厂类创建对象ComputerFactory factory = new ComputerFactory();// 要对象 找工厂Computer computer1 = factory.produce("workComputer");computer1.work();// 创建笔记本Computer computer2 = factory.produce("personComputer");computer2.work();Computer computer3 = ComputerFactory2.produce("workComputer");computer3.work();}
}
3.3工厂方法模式
工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。在同一等级结构中,支持增加任意产品。
例如:
//汽车接口
public interface Car {public void showInfo();
}
public class AudiCar implements Car {@Overridepublic void showInfo() {System.out.println("这是一台奥迪汽车。。");}
}
public class BMWCar implements Car {@Overridepublic void showInfo() {System.out.println("这是一台宝马汽车。");}
}
/**
生产汽车的工厂接口
**/
public interface CarFactory {public Car produce();
}
public class AudiCarFactory implements CarFactory {@Overridepublic Car produce() {return new AudiCar();// 这里AudiCar是Car的实现类}
}
public class BMWCarFactory implements CarFactory {@Overridepublic Car produce() {return new BMWCar();// 因为BWMCar是Car的实现类}
}
public class Test1 {public static void main(String[] args) {//先创建 汽车工厂CarFactory bmwFactory = new BMWCarFactory();// 这个工厂生产的汽车就是 宝马Car bmw = bmwFactory.produce();bmw.showInfo();//这个模式对于同一级别的产品,可扩展性高//可以扩展不同品牌的汽车,此时不需要修改代码,只需要增加代码即可// 创建一个新的品牌汽车 大众汽车CarFactory dazhongFactory = new DazhongCarFactory();Car car = dazhongFactory.produce();car.showInfo();}}
抽象工厂模式
对于在工厂方法的基础上,对同一个品牌的产品有不同的分类,并对分类产品创建的过程 ,一个汽车产品 会分为不同的种类(迷你汽车 ,SUV汽车 )
/*** 迷你汽车接口*/
public interface MiniCar {public void showInfo();
}
/*** SUV汽车接口*/
public interface SUVCar {public void showInfo();}
public class AudiMiniCar implements MiniCar {@Overridepublic void showInfo() {System.out.println("这是奥迪迷你汽车 ");}
}
public class BMWMiniCar implements MiniCar {@Overridepublic void showInfo() {System.out.println("这是宝马Cooper迷你汽车");}
}
public class AudiSUVCar implements SUVCar {@Overridepublic void showInfo() {System.out.println("这是一辆 奥迪SUV汽车");}
}
public class BMWSUVCar implements SUVCar {@Overridepublic void showInfo() {System.out.println("这宝马的SUV系列");}
}
public interface CarFactory {//生成不同型号的汽车 ,两条产品线public MiniCar produceMiniCar();public SUVCar produceSUVCar();
}
public class AudiCarFactory implements CarFactory {@Overridepublic MiniCar produceMiniCar() {return new AudiMiniCar();}@Overridepublic SUVCar produceSUVCar() {return new AudiSUVCar();}
}
public class BMWCarFactory implements CarFactory {// 生成迷你汽车的方法,返回MiniCar@Overridepublic MiniCar produceMiniCar() {return new BMWMiniCar();}//生成SUV汽车的方法, 返回SUVCar@Overridepublic SUVCar produceSUVCar() {return new BMWSUVCar();}
}
/*** 测试类*/
public class Test1 {public static void main(String[] args) {//创建宝马迷你汽车 找工厂CarFactory factory = new BMWCarFactory();MiniCar car = factory.produceMiniCar();car.showInfo();}
}
总结
对于简单工厂,工厂方法模式和抽象工厂的区别和用途
★工厂模式中,重要的是工厂类,而不是产品类。产品类可以是多种形式,多层继承或者是单个类都是可以的。但要明确的,工厂模式的接口只会返回一种类型的实例,这是在设计产品类的时候需要注意的,最好是有父类或者共同实现的接口。
★使用工厂模式,返回的实例一定是工厂创建的,而不是从其他对象中获取的。
★工厂模式返回的实例可以不是新创建的,返回由工厂创建好的实例也是可以的。
区别:
1、对于简单工厂,用于生产同一结构中的任意产品,对于新增产品不适用。
2、对于工厂方法,在简单工厂的基础上,生产同一等级结构中笃定产品,可以支持新增产品。
3、抽象工厂,用于生产不同种类(品牌)的相同类型(迷你,SUV),对于新增品牌可以,不支持新增类型
JVM
JVM构成
- 类加载器: 负责加载类到内存中
- 运行时数据区: 储存数据 对象,方法等等
- 执行引擎: 负责解释执行字节码,GC操作等
- 本地库接口: 融合其他语言为Java所用
JVM运行内存如何划分?
- 堆内存
- 栈内存
- 程序计数器
- 方法区
什么是JVM调优
简单理解,JVM调优主要就是为了解决系统运行时慢、卡顿、OOM、死锁等问题。
其实上面所说的问题存在很多方面的原因,比如网络波动导致响应时间慢、数据库查询慢、死锁等,今天我们主要分析JVM层面的,而JVM调优,主要是为了减少Full GC问题,也就是针对堆内存进行优化。
jvm调优涉及到两个很重要的概念:吞吐量和响应时间。jvm调优主要是针对他们进行调整优化,达到一个理想的目标,根据业务确定目标是吞吐量优先还是响应时间优先。
吞吐量:用户代码执行时间/(用户代码执行时间+GC执行时间)。指系统在单位时间内处理请求的数量。对于并发系统,通常需要用吞吐 量作为性能指标。
响应时间:整个接口的响应时间(用户代码执行时间+GC执行时间),stw
时间越短,响应时间越短。指系统对请求作出响应的时间。对 于单用户的系统,响应时间可以很好地度量系统的性能。
Stop-the-World,简称STW
指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,
有点像卡死的感觉,这个停顿称为STW。
一、调优步骤
调优的前提是熟悉业务场景,先判断出当前业务场景是吞吐量优先还是响应时间优先。调优需要建立在监控之上,由压力测试来判断是否达到业务要求和性能要求。
简单描述就是牺牲响应时间来满足内存要求,或者是增加内存来满足响应时间
调优的步骤大致可以分为:
1.熟悉业务场景,了解当前业务系统的要求,是吞吐量优先还是响应时间优先;
2.选择合适的垃圾回收器组合,如果是吞吐量优先,则选择ps+po组合;如果是响应时间优先,在1.8以后选择G1,在1.8之前选择ParNew+CMS组合;
在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加ParallelOld收集器这个组合
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space`这个异常吧,没错,
它就是大名鼎鼎的OOM(堆内存溢出)
一、CMS定义和特点
CMS收集器是一种以获取最短停顿时间为目标的收集器。它的优点是并发收集和低停顿。
二、CMS 基于哪种GC算法实现的
CMS主要是基于标记清除算法实现的
三、标记清除算法详解
算法主要是分为 标记 和 清除 两个阶段实现,在标记阶段 从 GC root出发通过可达性分析将需要被清除的对象标记起来,在清除阶段全部进行清除,这种算法有两个比较显著的一个缺点,一个是效率问题,一个是空间问题,标记-清除算法效率其实并不理想,这与它需要事先将需要清除的对象标记起来有关系,再一个就是空间的问题,该算法在清理的过程中会产生许多的空间碎片,这对内存是一种不小的浪费。为了解决空间碎片的问题 jvm引入了标记-整理算法,这将在之后的文章中介绍。
四、CMS 回收的四个阶段
(1)、初始标记
根据可达性分析从 GC roots 出发标记待清理对象的过程
(2)、并发标记
并发 从 GC roots tracing 的过程
(3)、重新标记
并发表基过程中由于 程序运行而导致标记产生变动的那一部分对象的重新标记
(4)、并发清除
清除标记对象的过程
3.规划内存需求,只能进行大致的规划。
4.CPU选择,在预算之内性能越高越好;
5.根据实际情况设置升级年龄,最大年龄为15;
为了避免这种朝生夕死的对象进入老年代,我们可以加大一下年轻代的容量和减少对象进入老年代的年龄阈值。
6.设定日志参数:-Xloggc:/path/name-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogs=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCauses
-XX:+UseGCLogFileRotation:GC文件循环使用
-XX:NumberOfGCLogs=5:使用5个GC文件
-XX:GCLogFileSize=20M:每个GC文件的大小
上面这三个参数放在一起代表的含义是:5个GC文件循环使用,每个GC文件20M,总共使用100M存储日志文件,当5个GC文件都使用完毕以后,覆盖第一个GC日志文件,生成新的GC文件。
二、CPU使用率飙高问题
当cpu经常飙升到100%的使用率,那么证明有线程长时间占用系统资源不进行释放,需要定位到具体是哪个线程在占用,定位问题的步骤如下(linux系统):
1.使用top命令查看当前服务器中所有进程(jps命令可以查看当前服务器运行java进程),找到当前cpu使用率最高的进程,获取到对应的pid;
2.然后使用top -Hp pid,查看该进程中的各个线程信息的cpu使用,找到占用cpu高的线程pid
3.使用jstack pid打印它的线程信息,需要注意的是,通过jstack命令打印的线程号和通过top -Hp打印的线程号进制不一样,需要进行转换才能进行匹配,jstack中的线程号为16进制,而top -Hp打印的是10进制。
使用jastack命令分析线程信息的时候需要关注线程对应的运行状态:
runnable代表当前线程正在运行,waiting代表当前线程正在等待,该状态需要进行特殊关注wait fot 后面的线程号,因为如果当前处于
waiting状态的程序长时间处于等待状态,那么就需要知道它在等待哪个线程结束,也就是wait for后面的线程号(jdk版本不同,单词可能不一样,总之就是在日志中找到它等待的线程号)然后根据线程号找到对应的线程,去查看当前线程有什么问题。
三、内存标高问题
内存飙高一般都是堆中对象无法回收造成,因为java中的对象大部分存储在堆内存中。
其实也就是常见的oom问题(Out Of Memory)。
指令
1.jinfo pid,可以查看当前进行虚拟机的相关信息列举出来,如下图
2.jstat -gc pid ms,多长毫秒打印一次gc信息,打印信息如下,里面包含gc测试,年轻代/老年带gc信息等:
3.jmap -histo pid head -20,查找当前进程堆中的对象信息,加上管道符后面的信息以后,代表查询对象数量最多的20个:
jmap -dump:format=b,file=xxx pid,可以生成堆信息的文件,但是这个命令不建议在生产环境使用,因为当内存较大时,执行该命令会占用大量系统资源,甚至造成卡顿。建议在项目启动时添加下面的命令,在发生oom时自动生成堆信息文件:-XX:+HeapDumpOnOutOfMemory。如果需要在线上进行堆信息分析,如果当前服务存在多个节点,可以下线一个节点,生成堆信息,或者使用第三方工具,阿里的arthas。
四、jvm调优常用参数
通用GC参数
-Xmn:年轻代大小 -Xms:堆初始大小 -Xmx:堆最大大小 -Xss:栈大小
-XX:+UseTlab:使用tlab,默认打开,涉及到对象分配问题-XX:+PrintTlab:打印tlab使用情况-XX:+TlabSize:设置Tlab大小-XX:+DisabledExplictGC:java代码中的System.gc()不再生效,防止代码中误写,导致频繁触动GC,默认不起用。-XX:+PrintGC(+PrintGCDetails/+PrintGCTimeStamps)打印GC信息(打印GC详细信息/打印GC执行时间)-XX:+PrintHeapAtGC打印GC时的堆信息-XX:+PrintGCApplicationConcurrentTime 打印应用程序的时间-XX:+PrintGCApplicationStopedTime 打印应用程序暂停时间-XX:+PrintReferenceGC 打印回收多少种引用类型的引用-verboss:class 类加载详细过程-XX:+PrintVMOptions 打印JVM运行参数-XX:+PrintFlagsFinal(+PrintFlagsInitial) -version grep 查找想要了解的命令,很重要-X:loggc:/opt/gc/log/path 输出gc信息到文件-XX:MaxTenuringThreshold 设置gc升到年龄,最大值为15parallel常用参数-XX:PreTenureSizeThreshold 多大的对象判定为大对象,直接晋升老年代-XX:+ParallelGCThreads 用于并发垃圾回收的线程-XX:+UseAdaptiveSizePolicy 自动选择各区比例CMS常用参数-XX:+UseConcMarkSweepGC 使用CMS垃圾回收器-XX:parallelCMSThreads CMS线程数量-XX:CMSInitiatingOccupancyFraction 占用多少比例的老年代时开始CMS回收,默认值68%,如果频繁发生serial old,适当调小该比例,降低FGC频率-XX:+UseCMSCompactAtFullCollection 进行压缩整理-XX:CMSFullGCBeforeCompaction 多少次FGC以后进行压缩整理-XX:+CMSClassUnloadingEnabled 回收永久代-XX:+CMSInitiatingPermOccupancyFraction 达到什么比例时进行永久代回收GCTimeTatio 设置GC时间占用程序运行时间的百分比,该参数只能是尽量达到该百分比,不是肯定达到-XX:MaxGCPauseMills GCt停顿时间,该参数也是尽量达到,而不是肯定达到G1常用参数-XX:+UseG1 使用G1垃圾回收器-XX:MaxGCPauseMills GCt停顿时间,该参数也是尽量达到,G1会调整yong区的块数来达到这个值-XX:+G1HeapRegionSize 分区大小,范围为1M~32M,必须是2的n次幂,size越大,GC回收间隔越大,但是GC所用时间越长G1NewSizePercent 新生代所占最小比例,默认5%G1MaxNewSizePercent 新生代所占最大比例,默认60%GCTimeRatio GC时间比例,此值为建议值,G1会调整堆大小来尽量达到这个值ConcGCThreads GC线程数量InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例
五、JVM的作用
首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在
运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
GC的垃圾回收算法?
垃圾回收只涉及到堆内存。
新生代使用复制算法:分配同等大小的内存空间,标记被GC Root引用的对象,将引用的对象连续的复制到新的内存空间,
清除原来的内存空间。
老年代使用:
标记清除法:标记没有被GC Root引用的对象,清除被标记位置的内存。
标记整理法:标记没有被GC Root引用的对象。
双亲委派
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,
一层层的向上传递,如果上级的类加载器都没有加载,自己才会去加载这个类。
向上委派,向下查找
Spring
Spring、SpringBoot、SpringMVC的区别
spring是一个开源的轻量级的java开发框架,主要负责创建对象并管理对象,它的核心功能是IOC和AOP;
SpringBoot是基于Spring的一套快速开发整合包,类似于脚手架,可以快速搭建一个项目;
SpringMVC是基于Spring实现了servlet规范的MVC框架,用于Java Web开发;
Spring是什么,怎么理解它的aop,ioc
spring是一个开源的轻量级的java开发框架,主要负责创建对象并管理对象;
IOC
:控制反转,Spring 的 IOC 的实现原理就是工厂模式加反射机制。把一个类放到spring容器去管理,对象的创建,初始化,销毁的工作都交给spring容器去做。由spring容器控制对象的生命周期。
AOP
:面向切面编程,把系统分为核心关注点与横切关注点。核心主要是处理业务逻辑,横切主要是权限验证,日志,事务处理。
Spring AOP是基于代理实现的,默认标准的JDK 动态代理,这使得任何接口(或者接口的集合)可以被代理。
Spring AOP 也使用 CGLIB 代理。如果业务对象没有实现任何接口那么默认使用CGLIB。
**JDK动态代理:**利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
**CGlib动态代理:**利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将处理对象类的class文件加载进来,通过修改其字节码生成子类来处理
SpringBean的作用域
singleton:默认作用域,单例bean,每个容器只有一个bean的实例
prototype:每一个bean请求创建一个实例
request:为每一个request请求创建一个实例,在请求完成以后,bean会消失并被垃圾回收器回收
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同实例
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享存储变量的话,
那么这全局变量需要存储在global-session中
SpringBean的生命周期
1.Spring启动,查找并加载需要被Spring管理的Bean,进行Bean的实例化;
2.Bean实例化后对将Bean的应用和值注入到Bean的属性中;
3.如果Bean实现了BeanNameAware接口的话,Spring将Bean的ID传递给setBeaName()方法;
4.如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5.如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来;
6.如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforelization()方法;
7.如果Bean实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。如果Bean使用init-method声明了初始化方法,该方法也会被调用;
8.如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterlnitialization()方法;
9.此时,Bean已经准备就绪,可以被开发者使用了,它们会一直驻留在应用上下文中,知道应用上下文被销毁;
10.如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样 如果Bean使用destory-methos声明销毁方法,该方法也会被调用。
Spring三大核心组件
core:Core 组件作为 Spring 的核心组件,他其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式。
作用:访问资源,资源的加载
context:spring运行环境和IOC容器
作用:为Spring提供运行环境,Context是作为Spring的IOC容器。
bean:创建和定义bean
作用:1.Bean的创建 2.Bean的定义
Spring常用的注入方式
1.setter注入:创建applicationContext.xml文件,手动配置Bean对象,property为Bean对象赋值。
2.构造器注入:更改applicationContext.xml文件中的property为construct-arg
3.注解注入(属性注入):使用注解方式的属性注入Bean。加@Component注解,将其标记为组件,并使用@Value注解为各属性赋值;
@Resource和@Autowired
- @Resource和@Autowired都可以用来装配bean,都可以用于字段或setter方法。
- @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false。
- @Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。
注意:如果name属性一旦指定,就只会按照名称进行装配。
@Autowire和@Qualifier配合使用效果和@Resource一样:
@Autowired(required = false) @Qualifier("example")
private Example example;@Resource(name = "example")
private Example example;
- @Resource装配顺序
- 如果同时指定name和type,则从容器中查找唯一匹配的bean装配,找不到则抛出异常
- 如果指定name属性,则从容器中查找名称匹配的bean装配,找不到则抛出异常
- 如果指定type属性,则从容器中查找类型唯一匹配的bean装配,找不到或者找到多个抛出异常
- 如果都不指定,则自动按照byName方式装配,如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配
简要对比表格
注解对比 | @Resource | @Autowire |
---|---|---|
注解来源 | JDK | Spring |
装配方式 | 优先按名称 | 优先按类型 |
属性 | name、type | required |
Spring框架中使用了那些设计模式
(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
(2)单例模式:Bean默认为单例模式
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
(4)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。
比如RestTemplate, JmsTemplate, JpaTemplate
(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,
客户在每次访问中根据需要会去访问不同的数据库
Spring事务
**声明式事务(常用):**建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
编程式事务:控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
Spring事务4个特性(ACID):
**原子性(Atomicity):**一个事务是一个不可分割的工作单位,事务要么全部成功、要么全部失败;
**一致性(Consistency)
Java工作笔记/Java面试题/Java八股文/Java常用API相关推荐
- Java工作笔记-注解的进一步理解
目录 基本概念 代码与实例 基本概念 注解:说明程序,给计算机看的. 注释:给程序员看的. 作用分类: 编写文档:通过代码里标识的注解生成文档javadoc(生成doc文档) 代码分析:通过代码里标识 ...
- Java工作笔记-Java函数参传值传引用问题
目录 基本概念 代码与实例 源码打包下载 基本概念 最近的系统中,Java程序和C++程序混着,很多程序都是用Java搞的,不得不稍微研究下Java的细节,其中很关键的一点,就是如何在Java中传引用 ...
- C++|Java工作笔记-google protobuf基本使用
目录 前言 protoc生成 Java相关 C++相关 前言 这里主要是生成序列号,在我所做的项目中,一般是把数据序列化后扔到消息总线上,消费者读取了,自行解析. 个人感觉这种方式比Json和XML都 ...
- Java工作笔记-使用Maven创建多模块项目
目录 前言 演示 前言 在某些项目中会用到多模块,一般情况下都使用Maven进行操作,然后手动写porn.xml,这样就算是spring boot也是手动写的,并没有使用官方的初始化项目工具. 演示 ...
- java工作笔记018---java中BigDecimal小数位数的四舍五入等操作
技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这个电视购物项目大量用到了BigDecimal,有钱内存大,哈哈 一.简介 Java在java.m ...
- java中级程序员面试题_中级Java程序员常见面试题汇总
下面是一些中级Java程序员常见面试题汇总,你可以用它来好好准备面试. 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器 ...
- java软件网络工程师面试题_170道Java工程师面试题,你敢挑战吗?
1.面向对象的特征有哪些方面? 2.访问修饰符public,private,protected,以及不写(默认)时的区别? 3.String 是最基本的数据类型吗? 4.float f=3.4;是否正 ...
- 【Java书笔记】:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2部分-自动内存管理,第3部分-虚拟机执行子系统,第5部分-高效并发
作者:周志明 整理者GitHub:https://github.com/starjuly/UnderstandingTheJVM 第2部分-自动内存管理 第2章 Java内存区域与内存溢出异常 2.2 ...
- 【Java学习笔记之二十九】Java中的equals和==的用法及区别
Java中的"equals"和"=="的用法及区别 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String(&quo ...
- 【Java学习笔记一】初识计算机和Java语言
目录 (一)计算机的体系结构(常识) 什么是计算机 计算机的基本概念 常见的主要硬件 主要硬件的详解 CPU的概述 内存的概述 硬盘的概述 科普小知识 输入输出设备 常见的主要软件 计算机的体系结构 ...
最新文章
- canvas.width和canvas.style.width区别以及应用
- 简单的XML和JSON数据的处理
- 4次迭代!10w行级别数据的Excel导入优化记录
- php生成格式 word文档,php使用phpword生成word文档
- 评教数据的存储和显示问题
- password textbox setup
- Java集合对象详解
- 树莓派是什么 树莓派能做什么 树莓派的功能用途
- Google Java Style Guide
- 网页提示https“证书错误:导航已阻止”,无法跳转解决办法
- cutoff shader
- 树莓派3B+安装系统(Raspbian)以及配置环境
- 有奖体验 CODING 产品,iPad Pro、HHKB 键盘等超级礼包等你来!
- colorFormat颜色转换插件
- FPGA通信第二篇--UDP
- 2013c语言二级等级考试试题,计算机等级考试二级c语言考试试题
- LeetCode-完成旅途的最少时间
- 计算机病毒手动查杀,电脑中毒了怎么办 如何手动彻底查杀病毒【解决方法】...
- 白话数字签名(3)——Web程序中的数字签名【转】
- 计算机信息管理专业个人简历,计算机信息管理专业个人简历个人技能范文
热门文章
- vsphere5.0环境中win2000虚机无法安装vmtools解决办法。
- 成功人士必备“十商”,一张思维导图让你清晰认识自己
- 为什么使用消息中间件
- 初中毕业学计算机有啥科目,学好哪些科目最容易成为学霸?初中生学好这几门功课很重要...
- 基于STM32、FreeRTOS低功耗设计思路和原理
- RTOS环境下低功耗设计思路
- 停车场系统无法连接服务器,手动挡停车场 联机版无法连接服务器是什么原因...
- 10设置精美的免费网站后台管理系统模板
- 温度传感器DS18B20 ISIS仿真
- java swf pdf_SWFTools pdf2swf 参数详解 及中文乱码问题