集合、Spring--IOC和DI
集合
概念
- 对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
- 位置:java.util.*
- 和数组区别
- 数组长度固定,集合长度不固定。
- 数组可以存储基本类型和引用类型,集合只能存储引用类型。
Collection体系集合
Collection父接口
- 特点:代表一组任意类型的对象,无序、无下标。
- 常用方法:
方法 | 描述 |
---|---|
boolean add(Object obj) | 添加一个对象数据 |
boolean addAll(Collection c) | 将一个集合中的所有对象添加到此集合中 |
void clear() | 清空此集合中的所有对象 |
boolean contains(Object o) | 检查此集合中是否包含o对象 |
boolean equals(Object o) | 比较此集合是否与指定对象相等 |
boolean isEmpty() | 判断此集合是否为空 |
boolean remove(Object o) | 在此集合中移除o对象 |
int size() | 返回此集合中的元素个数 |
Object[] toArray() | 将此集合转换成数组 |
注:遍历同时不能使用集合删除方法,否则出现并发修改异常,可使用迭代器的删除方法。
遍历方法:
1、增强for循环
2、迭代器
public class TestCollection2 {
public static void main(String[] args) {
//新建Collection对象
Collection collection=new ArrayList();
Student s1=new Student("张三", 20);
Student s2=new Student("张无忌", 18);
Student s3=new Student("王二", 22);
//1添加数据
collection.add(s1);
collection.add(s2);
collection.add(s3);
System.out.println("元素个数:"+collection.size());
System.out.println(collection.toString());
//2删除
//collection.remove(s1);
//collection.remove(new Student("王二", 22));
// collection.clear();
// System.out.println("删除之后:"+collection.size());
//3遍历
//3.1 增强for
System.out.println("-------增强for----");
for (Object object : collection) {
Student s=(Student)object;
System.out.println(s.toString());
}
//3.2迭代器: hasNext() next(); remove(); 迭代过程中不能使用使用collection的删除方法
System.out.println("-------迭代器----");
Iterator it=collection.iterator();
while(it.hasNext()) {
Student s=(Student)it.next();
System.out.println(s.toString());
}
//4判断
System.out.println(collection.contains(s1));
System.out.println(collection.isEmpty());
}
}
List接口及其实现类
List接口
- 特点:有序、有下标、元素可以重复。
- 常用方法:
方法 | 描述 |
---|---|
void add(int index, Object o) | 在index位置插入对象o。 |
boolean addAll(int index, Collection c) | 将一个集合中的元素添加到此集合中的index位置。 |
Object get(int index) | 返回集合中指定位置的元素。 |
List subList(int fromIndex, int toIndex) | 返回fromIndex和toIndex之间的集合元素。 |
- 遍历方法:
1、普通for循环
2、增强for循环
3、迭代器
案例
public class TestList {
public static void main(String[] args) {
//先创建集合对象
List list=new ArrayList();
//1添加元素
list.add("苹果");
list.add("小米");
list.add(0, "华为");
System.out.println("元素个数:"+list.size());
System.out.println(list.toString());
//2删除元素
//list.remove("苹果");
// list.remove(0);
// System.out.println("删除之后:"+list.size());
// System.out.println(list.toString());
//3遍历
//3.1使用for遍历
System.out.println("-----3.1使用for遍历-----");
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i));
}
//3.2使用增强for
System.out.println("-----3.2使用增强for-----");
for (Object object : list) {
System.out.println(object);
}
//3.3使用迭代器
Iterator it=list.iterator();
System.out.println("-----3.3使用迭代器-----");
while(it.hasNext()) {
System.out.println(it.next());
}
//3.4使用列表迭代器 ,和Iterator的区别,ListIterator可以向前或向后遍历,添加、删除、修改元素
ListIterator lit=list.listIterator();
System.out.println("------使用列表迭代器从前往后-------");
while(lit.hasNext()) {
System.out.println(lit.nextIndex()+":"+lit.next());
}
System.out.println("------使用列表迭代器后往前-------");
while(lit.hasPrevious()) {
System.out.println(lit.previousIndex()+":"+lit.previous());
}
//4判断
System.out.println(list.contains("苹果"));
System.out.println(list.isEmpty());
//5获取位置
System.out.println(list.indexOf("华为"));
}
}
List实现类
ArrayList
-
数组结构实现,查询快、增删慢。
-
JDK1.2版本、线程不安全。
-
遍历方式
1、普通for循环
2、增强for循环
3、迭代器
LinkedList
-
链表结构实现,增删快,查询慢。
-
JDK1.2版本、线程不安全。
-
遍历方式
1、普通for循环
2、增强for循环
3、迭代器
Vector(了解)
数组结构实现,查询快、增删慢。
JDK1.0版本,线程安全、运行效率比ArrayList较慢。
List集合体系总结
-
List:一个有序容器,所以可以允许重复的对象,可以插入多个null
-
ArrayList:底层是动态数组,默认是空数组,若存值则扩容至10,如果不够则按照1.5倍进行扩容
-
LinkedList:底层是采用双向链表
-
Vector:线程安全。底层是可扩展的对象数组,可以使用整数索引访问,并且Vector的大小可以根据需要增长或缩小
ArrayList、LinkedList、Vector的区别
- ArrayList和Vector是基于数组实现,ArrayList线程不安全,速度快,Vector线程安全,速度慢。因为底层是数组所以查询、修改快,增删慢。
- LinkedList底层是双向链表,线程不安全,增删快,查询修改慢
- ArrayList和Vector是使用Object类型的数组存储,ArrayList扩容是1.5倍和Vector扩容是2倍,扩容实际上是将数组拷贝复制到另一个数组中
Set接口及其实现类
Set接口
- 特点:无序、无下标、元素不可重复。
- 方法:全部继承自Collection中的方法。
- 遍历方法
1、增强for循环
2、迭代器
实现类
HashSet
- 基于HashCode实现元素不重复。
- 当存入元素的哈希码相同时,会用equals进行确认,结果为true,拒绝后者存入。
- 遍历方法
1、增强for循环
2、迭代器
LinkedHashSet
- 链表实现的HashSet,按照链表进行存储,即可保留元素的插入顺序。
TreeSet
- 基于排列顺序实现元素不重复。
- 实现了SortedSet接口,对集合元素自动排序。
- 元素对象的类型必须实现Comparable接口,指定排序规则(自然排序)。
- 通过CompareTo方法确定是否为重复元素。
- 遍历方法
1、增强for
2、迭代器
注:元素必须要实现Comparable接口,compareTo()方法返回值为0,认为是重复元素。
Comparable接口CompareTo方法
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
//先按姓名比,然后再按年龄比
@Override
public int compareTo(Person o) {
int n1=this.getName().compareTo(o.getName());
int n2=this.age-o.getAge();
return n1==0?n2:n1;
}
}
Comparator接口
public class TestComparator {
public static void main(String[] args) {
//创建集合,并指定比较规则
TreeSet<Person> persons=new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int n1=o1.getAge()-o2.getAge();
int n2=o1.getName().compareTo(o2.getName());
return n1==0?n2:n1;
}
});
Person p1=new Person("xyz", 20);
Person p2=new Person("hello", 22);
Person p3=new Person("zhangsan", 25);
Person p4=new Person("lisi", 25);
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
System.out.println(persons.toString());
}
}
set体系总结
-
不允许重复对象。无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。因为是无序的所以只允许一个 null 元素。
-
TreeSet保证对象的有序性
- 数据会按自然排序(可以理解为从小到大排序)
- 不可存储null
- 数据不可重复
- 非线程安全
- TreeSet底层依赖于TreeMap,TreeMap的数据结构是二叉树(红黑树),由底层数据结构决定了
- TreeSet中元素有序且唯一。
- 使用TreeSet时,如果添加对象的话,就必须去实现Comparable接口,当然也可以实现Comparator接口来保证对象的有序性。
-
HashSet 的实现原理
- HashSet底层使用了哈希表来支持的,特点:存储快, 底层由HashMap实现
- 往HashSet添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置。
- 如果算出的元素存储的位置目前没有任何元素存储,那么该元素可以直接存储在该位置上
- 如果算出的元素的存储位置目前已经存在有其他的元素了,那么还会调用该元素的equals方法与该位置的元素再比较一次,如果equals方法返回的是true,那么该位置上的元素视为重复元素,不允许添加,
如果返回的是false,则允许添加 。
-
Hash碰撞以及常见的解决方案
Hash碰撞 : 对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个不同对象计算值一样时,这就发生了碰撞冲突。
解决方案:
- 开放地址法(再散列法)开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1)其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。
如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。 - 再哈希法Rehash当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。
缺点:计算时间增加。
比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止.这种方法不易产生聚集,但增加了计算时间。 - 链地址法(拉链法)将所有关键字为同义词的记录存储在同一线性链表中.基本思想:将所有哈希地址为i的元素构成一个称为同义词
链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
Map体系集合
Map接口及其实现类
Map接口
- 特点
- 用于存储任意键值对(Key-Value)。
- 键:无下标、不可以重复(唯一)。
- 值:无下标、可以重复 。
- 常用方法
方法名 | 描述 |
---|---|
V put(K key,V value) | 将对象存入到集合中,关联键值。key重复则覆盖原值。 |
Object get(Object key) | 根据键获取对应的值。 |
Set keySet() | 返回所有key。 |
Collection values() | 返回包含所有值的Collection集合。 |
Set<Map.Entry<K,V>> entrySet() | 键值匹配的Set集合。 |
- 遍历方法
1、entrySet()方法
2、keySet()
例子
public class TestMap {
public static void main(String[] args) {
//创建Map集合
Map<String, String> map=new HashMap<>();
//1添加元素
map.put("cn", "中国");
map.put("uk", "英国");
map.put("usa", "美国");
map.put("cn", "zhongguo");
System.out.println("元素个数:"+map.size());
System.out.println(map.toString());
//2删除
// map.remove("usa");
// System.out.println("删除之后:"+map.size());
//3遍历
//3.1使用keySet();
System.out.println("------keySet()--------");
//Set<String> keyset=map.keySet();
for (String key : map.keySet()) {
System.out.println(key+"-----"+map.get(key));
}
//3.2使用entrySet()方法
System.out.println("------entrySet()-----");
//Set<Map.Entry<String, String>> entries=map.entrySet();
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey()+"---------"+entry.getValue());
}
//4判断
System.out.println(map.containsKey("cn"));
System.out.println(map.containsValue("泰国"));
}
}
实现类
HashMap
- JDK1.2版本,线程不安全,运行效率快。
- 允许用null 作为key或是value。
- 存储结构:哈希表。
- 遍历方法
1、使用keySet()
2、使用entrySet()
TreeMap
- 实现了SortedMap接口(Map的子接口),可以对key自动排序,Key需实现Comparable接口。
- 遍历方法
1、使用keyset
Hashtable :
- JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。
Properties :
- Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。
Map体系总结
- Map: 与Collection并列,用来存储具有映射关系(key-value)的数据
- HashMap:哈希表结构存储数据,key值可以为null
- TreeMap:红黑树算法的实现
- HashTable:哈希表实现,key值不可以为null
- Properties:HashTable的子类,键值对存储数据均为String类型,主要用来操作以.properties
结尾的配置文件。
- 存储数据是以key,value对进行存储,其中key值是以Set集合形式存储,唯一且不可重复。
value是以Collection形式存储,可以重复。 - 优缺点
- Map不是collection的子接口或者实现类。Map是一个接口。
- Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
- Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
- Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
- HashTable、HashMap、TreeMap的区别
- Hashtable、HashMap、TreeMap都实现了Map接口,使用键值对的形式存储数据和操作数据。
- Hashtable是java早期提供的,方法是同步的(加了synchronized)。key和value都不能是null值。
- HashMap的方法不是同步的,支持key和value为null的情况。行为上基本和Hashtable一致。由于Hashtable是同步的,性能开销比较大,一般不推荐使用Hashtable。通常会选择使用HashMap。
- HashMap进行put和get操作,基本上可以达到常数时间的性能
- TreeMap是基于红黑树的一种提供顺序访问的Map,和HashMap不同,它的get或put操作的时间复杂度是O(log(n))。具体的顺序由指定的Comparator来决定,或者根据键key的具体顺序来决定。
- HashMap的底层数据存储结构
JDK1.7采用 : 数组 + 链表结构
JDK1.8采用 : 数组 + 链表 + 红黑树
红黑树:jdk1.8最重要的就是引入了红黑树的设计,当hash表的单一链表长度超过 8 个的时候,链表结构就会转为红黑树结构。
- HashMap的底层数据存储结构为哈希表加红黑树。实现过程如下
- 调用HashMap的无参构造方法,加载因子loadFactor赋值0.75,table数组是空。
- 当添加第一个元素时,创建长度16的数组,threshold=12。
- 当‘链表长度大于等于8时,并且数组长度大于等于64时,链表调整红黑树
- 当红黑树的节点个数小于6时,调整为链表
- 当HashMap的容量超出阈值时,扩容为原来大小的2倍,减少元素的移动,提高效率。
- HashMap链表和红黑树转化时机
当链表长度超过8且哈希表容量大于64,会将链表转化为红黑树。而当红黑树的大小小于6,
则由树转化为链表。
Spring—IOC和DI
Spring主要是三种方式
1、xml配置文件
2、注解开发
3、xml+注解开发
概念:Spring是分层的轻量级开源框架。以IOC(控制反转)和 AOP(面向切面编程)为内核。
IOC
是一种设计思想。它的目的是指导我们设计出更加松耦合的程序。
一句话来说就是:bean的创建和销毁交给spring容器管理。
xml配置文件方式
步骤分析及代码实现
- 导入依赖(spring-context)
<!--导入spring的ioc核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
- 编写UserDao接口及其实现类
- 编写一个spring的配置文件(名字任意)
- 通过bean标签配置dao
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="cn.dao.impl.UserDaoImpl"/>
</beans>
- 编写测试代码
- 获取spring容器(工厂)对象
- 通过工厂对象获取dao的对象
- 调用save方法
public class TestHelloIOC {
@Test
public void testIocHello(){
//- 获取spring容器(工厂)对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//- 通过工厂对象获取dao的对象
UserDao userDao = (UserDao) context.getBean("userDao");
//- 调用save方法
userDao.save();
}
}
Spring的API体系
主要关注两个BeanFactory
和ApplicationContext
BeanFactory和ApplicationContext的关系
- BeanFactory是ApplicationContext的父接口
- BeanFactory初始化配置文件中单实例对象的时机:每次调用getBean的时候才创建对应的对象
- ApplicationContext初始化配置文件中单实例对象的时机:在工厂对象初始化完成的时候已经创建好了
实现类
* FileSystemXmlApplicationContext【了解】
功能:从指定磁盘目录加载xml配置文件,创建spring的ioc容器
* ClassPathXmlApplicationContext
功能:从类路径下加载xml配置文件,创建spring的ioc容器
* AnnotationConfigApplicationContext
功能:加载注解配置类,创建spring的ioc容器
Spring配置文件
配置文件的名字可以放在任意位置,名字任意
Bean标签
<!--
bean:将配置的对象加入spring容器管理
id属性(name属性):给bean起个名字,要保证当前项目中唯一
class属性:配置对象对应的全限定类名(类且有无参构造器)
其他的配置:
scope属性:配置对象的作用范围(单实例还是多实例等)
常见俩值:
singleton:默认值,单实例对象
prototype:多实例对象,一般用不上,struts2框架中action是多实例
init-method属性:指定对象创建成功之后要执行的方法(初始化操作),我们一般用不上,框架底层使用
destroy-method属性:指定对象销毁之前要执行的方法(销毁操作),我们一般用不上,框架底层使用
-->
<bean id="userDao" class="cn.dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="dest"/>
DI依赖注入
set方法注入
<!--依赖注入:set-->
<bean id="userService" class="cn.service.impl.UserServiceImpl">
<!--property:使用set方式给属性进行赋值操作
name属性:需要赋值的属性名称
ref属性:属性类型为引用类型且在spring容器中存在,值就是在spring容器中bean对象的名字
value属性:属性类型为简单类型(包含基本类型、包装类及string类型)
-->
<property name="userDao" ref="userDao"/>
</bean>
构造方法注入
<!--依赖注入:构造器方法-->
<bean id="userService1" class="cn.service.impl.UserServiceImpl">
<!--
constructor-arg:使用构造器进行赋值操作
name属相:构造方法中形参的名字
value属性:给简单类型进行赋值
ref属性:给引用类型进行赋值操作,值就是spring容器中对象的id值
-->
<constructor-arg name="age" value="12"/>
<constructor-arg name="name" value="tom"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
p命名空间注入(了解)
本质上也是set方法,在此基础上进行了简化
步骤:
- 需要在spring的配置文件中引入p名称空间
- 在需要注入的bean标签中通过 p:属性=“” 或者 p:属性-ref=""方式注入
<!--p名称空间注入-->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource"
p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql:///travel"
p:username="root" p:password="1234"/>
依赖注入的数据类型
-
普通数据类型
通过标签的value属性注入数据 -
引用数据类型
通过标签的ref属性注入数据 -
集合数据类型
注解开发
个人对Spring理解看法
Spring是一个轻量级框架,使用Spring极大的方便了我们对程序的开发,原因:
- 不用我们自己手动频繁创建对象和管理对象
- 因为spring替我们创建和管理了对象,方便我们更好的关心逻辑代码方面的开发
- spring增加了横向的开发(AOP)
- 对于oop来说我们更多地是一种纵向的开发,相当于是一层叠一层的开发
- 但是对于很多时候,比如说要运行日志记录这些,需要横向抽取代码的时候,oop就无法满足,这个时候需要AOP来帮助实现
- spring的核心就是IOC和AOP
- 配置文件和注解是可以相互替换,相等价的
spring对于我个人来说极大地方便了开发,减少了代码的耦合,但是他也带来了一个很麻烦的事情就是配饰文件太多,太麻烦了。对于spring的开发最好的方法就是使用配置文件+注解的方式开发
两周学习出现的问题
导入jdk8的源码包到idea中失败
下两周计划
1、解决导入jdk8的源码包到idea中失败
2、复习spring框架AOP、mybatis
3、spring注解学习
若上面完成可完成下面的
3、mybatis源码阅读
4、springmvc框架