概览:
java基础
数组:
一组数据的集合,是一种引用类型,元素可以是几倍呢类型也可以是引用类型但是只能是同一类型。数组作为对象,数组中的元素作为对象的属性。数组还有一个成员属性length,在创建时确定,无法修改。数组元素有下标[0 - n-1],可以通过下标访问元素。
数组的声明方式:
数组元素类型[] 变量名称:int[] a; Student[] stu;
使用new操作符创建静态数组:
type[] 变量名 = new type[数组中的元素个数],这种方式创建的数组已经动态初始化了,预先在堆内存中分配空间,每一个元素都有默认值(boolean为false,其余为0)。
数组重点:
数组是引用类型,它在堆中分配内存空间,传入的是地址,会改变原值。数组通过下标读取修改,从0开始。
数据的内容比较可以使用equals()方法吗?
数组的比较equals使用Object的比较实现,没有重写,不能比较。一种解决方案是自己实现,另一种是用java.util.Arrays工具类。
public staitc boolean isEquals(int[] a,int[] b){
if(a == null || b == null){ return false;}
if(a.length != b,length){retrun false;}
for(int i = 0;i<a.length;i++){
if(a[i]!=b[i]){return false;}
}
return true;
}
数组扩容:
先新建新的数组,将原来的数据拷贝,原数组GC回收,数组拷贝会消耗大量资源。
public static void arrayCopy(int[] src,int srcPos,int[] destPos,int length){
for(int i = srcPos;i<srcPos+length;i++){
dest[destPos++] = src[i];
}
}
数组怎么存放不同类型的数据类型?
数组排序冒泡:
public int[] bubbleSort(int[] array){
int len = array.length;
for(int i = array.length-1;i>0;i++){
for(int j = 0;j<i;j++){
if(array[j] > array[j+1]){
swap(array,i,j);
}
}
}
return array;
}
数组排序选择排序:
public int[] selectSort(int[] array){
for(int i = 0;i<array.length-1;i++){
int min = i; //记录最小标记位
for(int j = i+1;j<array.length;j++){
if(array[i] < min){
min = j; //找出最小位置
}
}
swap(array,i,j); //第一次循环找出第一个最小后交换位置即可
}
return array;
}
数字数组二分法查找:
public int binarySearch(int[] array,int target){
int left = 0;
int right = array.length;
Arrays.sort(array); //二分前一定保证数组是排好序的。
while(left <= right){
int mid = (left + right) / 2;
if(target > array[mid]){
left = mid+1;
}eles if(target < array[mid]){
right = mid-1;
}else if(target == array[mid]){
return arrat[mid];
}
return null;
}
}
常用类:
String:字符串,引用类型,不属于基本数据类型,被final修饰无法继承,声明后不可修改。
public final class String implements java.io.Serializable,Comparable<String>,CharSequebce{}
以上内存图可以看到,String对象赋值后不能再修改,如果修改,就会创建新的对象。
注意:只要双引号赋值的字符串,在编译期.class时将会放在方法区中的字符串常量池中,字符串相加减会放在堆中(放前先验证是否有相同的字符串常量,存在,返回地址,不存在,先将字符串常量放在池中,然后再返回该对象的地址。)
String s1 = "abc"; //方法区常量池
String s2 = "abc"; //方法区常量池
String s3 = new String("abc"); //new时存在双引号,会在常量池中找,所以常量池不再放置,而new会在堆中分配内存,所以堆中会创建一个abc,s3指向abc
sout(s1 == s2); //true
sout(s2 == s3); //true
sout(s2.equals(s3)); //s2和s3比较必须用equals,String类已经重写了equals方法
String s1 = "abc"
s1 = "abcd"; //底层创建两个对象,分别是“abc”和“abcd”都放在字符串常量池中。
String s1 = new String("abc");
String s2 = new String("abc");
//创建三个对象,堆2个,方法区1个
记住:堆区中是运行期分配的,常量池是编译期分配的。
String常用方法:endsWith,equals,indexOf,lastIndexOf,length,split,substring,trim,valueOf。
使用String时的注意事项:因为String是不可变对象,如果多个字符串进行拼接,将会形成多个对象,这样会造成内存溢出,会给垃圾回收带来工作量,如下面应用最好不要用String。
StringBuffer和StringBuilder:
可变长对象,底层char数组,都有同一个父类AbstractStringBuilder,定义了一些常用方法(expandCapacity,append,insert,indexOf)。StringBuffer所有方法上有sybchronized关键字,StringBuilder类的方法上没有synchronized关键字,两者初始容量都是16,扩容原则是value.length*2+2。
StringBuffer:字符串缓冲区,预先申请一块内存,存放字符序列,如果字符系列满了,会重新改变缓冲区的大小,以容纳更多的字符序列。StiringBuffer是可变对象,这个是String最大的不同。
StringBuilder:用法同StringBuffer,StringBuilder和StringBuffer的区别是StringBuffer中所有的方法都是同步的,是线程安全的,但速度慢。Builder相反。
String和StringBuffer,StringBuiler异同点:
1.可变性:String为final不可变,SBuffer和SBuilder继承AbstractStringBuilder可变,其中字符数组保存字符串char[] value,但是没有final关键字。
2.线程安全性:String不可变为常量,安全。SBuffer对方法加同步锁,安全,SBuiler没加不安全。
3.性能:String会创建新的对象,SBuffer不生成新对象,SBuilder能提升,但是不安全。
总结:操作少量数据=String;单线程大量=StringBuilder;多线程大量=StringBuffer。
基本类型对象包装类:
包装类提供更多的操作,且都是final的,不能创建子类,都是不可变对象.(int-Integer,char-character)。
除了boolean和Character外,其他的包装类都有valueOf()和parseXXX方法。
自动拆装箱:JDK5前,包装类和基本类运算时,必须将包装类转换成基本类才可以,而JDK5提供了Auto-boxing,属于编译阶段功能,和运行期无关。
String,int和Integer之间的转换:
int a = 10;
Stirng str = "10";
Integer b = new Integer(10);
//String和Integer转换
new Integer(str); //String->Integer
b.toString(); //Integer->String
//String和int之间的转换
Integer.parseInt(str); //String->int
a+""; //int->String
//Integer和int之间的转换
b.intValue(); //Integer->int
new Integer(a); //int->Integer
数字类:
java.text.DecimalFormat:DecimalFormat用来数字格式化的,如#(任意数字),(千分位).(小数点)0(不够补0)
//加入千分位,保留2位
DecamalFormat df = new DecimalFormat("###,###.##");
//加入千分位保留4位,补0
new DecimalFormat("###,###.0000").format(12345.12);
java.math.BigDecimal:使用BigDecimal可以精确计算,特别是财务数据。如果是电商项目,或者存储价格字段就要用BigDecimal。或者四舍五入问题等。
java.util.Random:Random类实现随机算法的伪随机,就是有规则地随机,起源数字称为种子数(seed),在其基础上进行一定的变化,产生需要的随机数字。相同种子数的Random对象,相同次数生成的随机数字是完全相同的,在生成多个随机数字时需要注意。
Random对象的生成:Random包含两种构造方法:public Random():使用当前系统时间对应的数字作为种子数 和public Random(long seed):使用自定义种子数。
Rendom常用方法:nextBoolean();nextDouble();setSeed(long seed);
Enum:
枚举:java5新增,允许常量来表示数据片段,而且全部都以类型安全的形式来表示。本质是类,频闭了枚举值的类型信息,不像用public static final定义变量都必须指定类型。用来构建常量数据结构的模板,可扩展,增强程序健壮性。
枚举自定义函数:枚举构造器只是在构造枚举值的时候被调用,构造器只能私有private,绝不允许public,这样可以保证外部代码无法新构造枚举类的实例,但枚举类的方法和数据域可以允许外部访问。
public enum Color{
RED("red",1),GREEN("green",2);
private String name;
private int index;
private Color(String name,int index){
this.name = name;
this.index = index;
}
//values()方法:静态,返回一个包含全部枚举值的数组
for(Color color : Color.calues()){
sout(color+""+color.getIndex());
}
}
容器集合:
List:有序集合,可以放重复的数据。
Set:无序集合,不允许放重复的数据。
Map:无序集合,集合中包含一个键对象,值对象,键对象不允许重复,值对象可重复(身份证)
Collection,Iterator,Collections:
Collection接口:是List和Set的父接口,在Collections中定义了一些主要的方法。add(),clear(),contains(Object o),isEmpty(),iterator(),remove(),size(),toArray()。
Iterator接口:迭代接口,遍历集合中的数据,主要方法hasNext(),next(),remove()。
List()接口:
主要实现ArrayList和LinkedList,都是有序的线性存储,可以看作是一个可变数组。1.ArrayList:查询快,添加删除慢(基于可变数组)2.LinkedList:查询慢,添加和删除快(基于链表数据结构)3.Vector:同步,效率慢被ArrayList(但不同步)取代 4.Stack是继承Vector实现了一个栈,被LinkedList取代。
ArrayList:底层是数组队列,相当于动态数组,与java数组相比,它的容量能动态增长,在添加大量元素前,应用程序可以使用‘ensureCapacity’来增加ArrayList实例的容量。可以减少递增式再分配的数量。ArrayList默认容量是10。每次扩容为原来的1.5倍。若新增超过这个量,则为超过最小值,如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量与限制的容量进行判断,超过则指定为Integer的最大值,否则指定为限制容量的大小。然后通过数组的复制将原数据复制到一个更大的数组。
public void grow(int minCapacity){
int oldCapacity = elementData.length;
int newCapacity = oldCapacity+(oldCatacity>>1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if(Capacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
element = Arrays.copyOf(elementData,newCapacity);
}
private static int hugeCapacity(int minCapacity){
if(minCapacity < 0)
throw new OutOfMemoryError();
retiurn (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAXVALUE:MAX_ARRAY_SIZE;
}
LinkList:链表,增删快,查找访问慢,提供List接口中没有定义的方法,用于操作表头和表尾,可以当作堆,栈,队列和双向队列使用。LinkedList实现了List接口和Deque接口的双端链表。线程不安全,想要安全可以调用静态类Collection类中的synchronizedList方法:javaList list = Collections.synchronizedList(new LinkedList(...))
ArrayList和LinkedList异同:
1.是否线程安全:都不安全
2.底层数据结构:ArrayList底层使用Object数组;LinkedLis底层使用双向链表数据结构
3.插入和删除是否受元素位置的影响:ArrayLsit数组,中间插入删除有影响,尾插影响,LinkedList链表,插入删除不受元素位置影响。
4.是否支持快速随机访问:LinkedList不支持高效的随机元素访问,ArrayList支持。
5.占用内存:ArrayList空间浪费为list列表结尾预留一定的容量空间,而LinkedList空间花费在每一个元素都需要消耗比ArrayList多的空间(放置前驱和后继指针)。
ArrayList和Vector区别:线程同步区别(ArrayList使用CopyOnWriteArrayList同步)。
System.arraycopy()和Arrays.copyOf():
相同:都调用了‘System.arraycopy()’方法。
不同:arraycopy()需要目标数组,将原数组拷贝到自己定义的数组,可选拷贝起点和长度。copyOf()是系统自动在内部新建一个数组,并返回该数组。
set接口:
哈希表:基于数组,不能扩展。数组中的元素值和下标存在明确的对应关系,通过元素的值就能换算出数据元素的下标,通过下表就可以定位数组元素。这样的数组就是哈希表。
hashSet:无序不可重复,按照哈希算法存储数据,当向HashSet插入数据的时候,会调用对象的hashCode得到该对象的哈希码,然后根据哈希码计算出该对象的插入到集合中的位置。
hashCode()与equals()的相关规定:
1.如果两个对象相等,则hashCode一定也是相同的。
2.两个对象相等,对两个对象分别调用equals方法返回true
3.两个对象有相同的hashcode值,他们也不一定是相等的。
4.equals方法被覆盖过,则hashcode方法也必须被覆盖。
5.hashCode()的默认行为是对堆上的对象产生独特值,如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使两个对象指向相同的数据)
hashCode()(相当于hash算法):该方法是Object中定义的方法,返回int类型,在Object中缺省实现:该方法执行结束后返回值可以“等同”看作是一个java对象的内存地址。这个哈希码的作用是确定该对象在哈希表中的索引位置。
先由hashCode方法确定定位在数组中的哪一个hash桶里面,在由equals方法确定是否同一个对象,如果equals比较相同将不把此元素加入到Set集合中,但equals比较不相等会重新根据hashCode换算位置仍然将该元素加进去。
特别强调:向HashSet和HashMap中加入元素时必须同时覆盖equals和hashCode方法。
java要求:
两个对象equals相等,那么他的hashCode相等。
两个对象equals不相等,那么他的hashCode并不要求它不相等,但一般建议不相等。
hashCode相等不代表两个对象相等(采用equals比较)。
TreeSet:可以对Set集合进行排序,默认自然排序,但也可做客户化排序。基本类型的包装类和String类都可以排序,因为他们实现了Comparable接口,但是自定义引用类型必须是可排序的类。
1.实现Comparable接口完成排序:
class Person implements Comparable{
Stirng name;
int age;
//覆盖equals
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj instanceof Person){
Person p = (Person)obj;
return this.name.equals(p.name);
}
return false;
}
//覆盖hashCode
public int hashCode(){
return (name == null)?0:name.hashCode();
}
//如果覆盖了equals,最好保证equals和Compareto在相等情况下的比较规则是一致的
public int compareTo(Object o){
if(o instanceof Person){
Person p = (Person)o;
//升序
return (this.age - p.age);
//降序
return (p.age - this.age);
}
throw new IllegaArugmentException("非法参数,o=" +o);
}
}
2.实现Comparator接口:
Set set = new TreeSet(new PersonComparator());
class PersonComparator implements Comparator{
@Override
public int compare(Onject o1,Object o2){
if(!(o1 instanceof Person) || !(o2 instanceof Person)){
throw new RuntimeException("参数格式错误");
}
return ((Person)o1).age - ((Person)o2).age;
}
}
3.匿名内部类:
Set set = new TreeSet(new Comparator(){
@Override
public int compare(Onject o1,Object o2){
if(!(o1 instanceof Person) || !(o2 instanceof Person)){
throw new RuntimeException("参数格式错误");
}
return ((Person)o1).age - ((Person)o2).age;
}
});
Map接口:
Map中存放键值对,较常见的实现为HashMap,对键值对的存取和HashSet一样,采用哈希算法,所以必须重写equals和hashCode方法。
HashMap(数组+链表+红黑树):根据键的hashCode值存储数据,具有快速访问,但遍历顺序不确定,hashMap最多只允许一条记录的键为null,允许多条记录的值为null。hashMap非线程安全,可以用Collections的synchronizedMap方法使其具有线程安全的能力。或者使用ConcurrentHanMap。
HashMap是一个数组,然后数组中每个元素都是一个单向链表,Entry包含四个属性:key,value,hash值和单链表的next。java8中链表超过8个元素会转换为红黑树,可将时间复杂度降为O(logN)。
1.capacity:当前数组容量(16),始终保持2^n,可以扩容,扩容为当前的2倍。
2.loadFactor:负载因子,默认为0.75
3.threshold:扩容的阈值,等于capacity*loadFactor
java7和java8最大不同是树化,当bin被映射到同一个桶时,如果这个桶中的bin的数量小于等于TREEIFY_THRESHOLD(默认8)不会转化为树形存储,如果bin大于,但是capacity小于MIN——TREEIFY_CAPACITY(默认64),依然是链式存储,此时只会对HashMap进行扩容,如果capacity对于64,才会树化。
hashMap put元素图:
1.判断键值对数组是否为null,否则执行resize扩容
2.根据键值key计算hash值得到插入数组的索引i,如果table[i]==null,直接新建节点,转向6,如果不为空,转向3
3.判断table[i]的首个元素是否和key一样,如果(hashCode和eqalsl)相同直接覆盖value,否则转4
4.判断table[i]是否为TreeNode,即是否是红黑树,是直接插入键值对,否则转5
5.遍历table,判断链表长度是否大于8,大于8转红黑树插入,否则链表插入,遍历发现key已经存在直接覆盖value即可。
6.插入成功后,判断实际存在的键值对数量size是否查过了最大容量threshold,超过扩容。
resize扩容操作:初始化为16,put时当检查到size超过threshold执行resize,如果capacity已经大于最大值,便把threshold置为int最大值,否则对capacity,threshold进行扩容操作。发生扩容,必须Map中的所有的数进行再散列,重新装入。
jdk7和jdk8中关于HashMap的对比:
1.树化
2.hash值得计算方式不同
3.7中table创建hashMap时分配空间,8中put的时候分配,如果table为空,则为table分配空间,在发生冲突,插入链表中时,7是头插,8是尾插。
4.在resize操作中,7需要重新进行index计算,8不需要,通过判断相应的位是0还是1,要么依旧是原index,要么是oldCap+原index。
HashMap遍历方式:
public static void main(){
Map<String,String> map = new HashMap<>();
map.put("1","value1");
map.put("2","value2");
map.put("3","value3");
//第一种,普通方式通过ketSet遍历key和value
for(String key:map.keySet()){
Sout(key+map.get(key));
}
//第二种,通过map.entrySet()使用Iterator遍历key和value
Iterator<Map.Entry<String,String>> ite = map.entrySet().iterator();
while(ite.hasNext()){
Map.Entry<String,String> entry = ite.next();
Sout(entry.getKey(),entry.getValue());
}
//第三种,推荐尤其是容量大的时候,map.entrySet遍历key和value
for(Map.Entry<String,String> entry:map.entrySet()){
Sout(entry.getKey(),entry.getValue());
}
//第四种,使用map.value()遍历所有的value但不能遍历key
for(String v:map.values()){
Sout(v);
}
}
HashMap自定义类作为Key:
public void main(){
IdCard idCard = new IdCard();
map.put(idCard,person);
//加入重复数据,因为HashMap底层实现采用hash表,所以Map的key必须覆盖hashCode和equals方法
for(Iterator ite = map.entrySet().iterator();ite.hasNext()){
Map.Entry entry = (Map.Entry)ite.next();
IdCard idCard = (IdCard)entry.getKey();
Person person = (Person)entry.getValue();
Sout(idCard.cardNo,person.name);
}
}
HashMap覆盖key的equals和hashCode方法:
public boolean equals(Object obj){
if(obj==this){return true;}
if(obj instance IdCard){
IdCard idCard = (IdCard)obj;
if(this.cardNo == idCard.cardNo){return true;}
}
return false;
}
public int hashCode(){
return new Long(cardNo).hashCode();
}
TreeMap:对Map中的key进行排序,如果map中的key采用的是自定义类那么需要实现Comparable或Comparator接口完成排序:Map map = new TreeMap();
LinkedHashMap:是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序
HashMap和HashTable的区别:
1.线程是否安全:HashMap不安全,HashTable安全,内部方法都是synchroized修饰,或者使用ConcurrentHashMap线程安全类。
2.效率:hashMap效率高,HashTable基本被淘汰。
3.对null key和null value支持:HashMap中,null可以作为键,这样键只有一个,可以有多个键对应值为null。但是HashTable中put进的键值只要有一个null,直接抛出空指针异常。
4.初始容量大小和每次扩容容量大小的不同:
*创建时不指定容量初始值,Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量为原来的2倍。
*创建时如果给定了容量初始值,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小(HashMap中的tableSizeFo()方法保证,下面给出源代码)。也就是说HashMap总是使用2的幂作为哈希表的大小,后面会介绍为什么是2的幂次方。
5.底层数据结构:JDK8以后的HashMap在解决哈希冲突时有了较大变化,hashTable没有。
HashSet和HashMap区别:
HashSet底层基于HashMap实现,HashSet就是HashMap的key。
ConcurrentHashMap实现原理:由于HashMap是一个线程不安全的容器,主要体现在通量大于总量*负载因子发生扩容时会出现环形链表从而导致死循环。因此需要ConcurrentHashMap。
Segmen段:ConcurrentHashMap由一个个Segment组成。Segment继承ReentrantLock加锁,每次需要锁住Segment,就实现了全局的线程安全。
Collections工具类:
排序:反转reverse(List list),随机排序shuffle(List list),自然排序升序sort(List list),定制排序sort(List list,Comparator c),交换两个索引位置的元素swap(List list,int i,int j),旋转,当distance为正数时,将list后distance个元素移到前面,反之亦然rotate(List list,int distance)
查找,替换:对List进行二分查找,返回索引,注意List必须是有序的binarySearch(List list,Object key),根据元素自然序返回最大max(Collection coll),min(Collection coll),max(Collection coll,根据定制排序返回最大值Comparator c),min(Collection coll,Comparator c),用指定元素代替指定list中所有元素fill(List list,Object obj),统计元素出现次数frequency(Collection c,Object o),用新元素替换旧元素replaceAll(List list,Object oldVal,Object newVal),统计target在list中第一次出现的索引,找不带返回-1indexOfSubList(List list,List target),lastIndexOfSubList(List source,list target)
同步控制:返回支持同步的各个方法sychronizedCollection(Collection<T> c);sychronizedList(List<T> list);sychronizedMap(Map<K,V> m); sychronizedSet(Set<T> s);
Collrctions设置不可变集合:返回一个空的,不可变的集合对象emptyXxx();返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,singletonXxx();返回指定集合对象的不可变视图unmodifiableXxx();