阿里架构师讲面试:玩转java数据存储和操作

484 阅读21分钟

数据类型,存储和声明

变量存储方式

参考:zhuanlan.zhihu.com/p/28654272

基本类型专讲

基本类型和包装类型

为什么存在这两种类型呢?

我们都知道在Java语言中,new一个对象存储在堆里,我们通过栈中的引用来使用这些对象;但是对于经常用到的一系列类型如int,如果我们用new将其存储在堆里就不是很有效——特别是简单的小的变量。所以就出现了基本类型,同C++一样,Java采用了相似的做法,对于这些类型不是用new关键字来创建,而是直接将变量的值存储在栈中,因此更加高效。

有了基本类型为什么还要有包装类型呢?

我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。

参考:blog.csdn.net/min99635831…

基本类型自动装箱、拆箱

Java中基础数据类型与它们的包装类进行运算时,编译器会自动帮我们进行转换,转换过程对程序员是透明的,这就是装箱和拆箱,装箱和拆箱可以让我们的代码更简洁易懂。

Java中基础数据类型与它们对应的包装类见下表(共8种):

原始类型包装类型
booleanBoolean
byteByte
charCharacter
floatFloat
intInteger
longLong
shortShort
doubleDouble

我们看一段平常很常见的代码:

public void testAutoBox() {
    List<Float> list = new ArrayList<>();
    list.add(1.0f);
    float firstElement = list.get(0);
}
复制代码

list集合存储的是Float包装类型,我传入的是float基础类型,所以需要进行装箱,而最后的get方法返回的是Float包装类型,我们赋值给float基础类型,所以需要进行拆箱,很简单,安排的明明白白。

字符串专讲

本质为字符数组对象。

String对象的创建及访问

String 对象的创建

  • 通过字符串常量的方式

String str= "pingtouge" 的形式,使用这种形式创建字符串时,JVM 会在字符串常量池中先检查是否存在该对象,如果存在,返回该对象的引用地址,如果不存在,则在字符串常量池中创建该字符串对象并且返回引用。使用这种方式创建的好处是:避免了相同值的字符串重复创建,节约了内存。

  • String()构造函数的方式?

String str = new String("pingtouge") 的形式,使用这种方式创建字符串对象过程就比较复杂,分成两个阶段,首先在编译时,字符串 pingtouge 会被加入到常量结构中,类加载时候就会在常量池中创建该字符串。然后就是在调用new()时,JVM 将会调用String的构造函数,同时引用常量池中的pingtouge字符串,在堆内存中创建一个String对象并且返回堆中的引用地址。

String对象的拼接

  • "+"

“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

+拼接的时候底层会使用StringBuilder,如果是非循环体和使用StringBuilder相差无几,如果是循环体,就会在循环体内每次循环的时候new一个StringBuilder对象,创建很多次对象,造成运行效率下降。

前面提到过,使用+拼接字符串,其实只是Java提供的一个语法糖。我们把他生成的字节码进行反编译,看看结果。

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;

反编译后的内容如下,反编译工具为jad。

String wechat = "Hollis";
String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();

通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。

public class Main {

    public static void main(String[] args) {
        String string = "";
        for(int i=0;i<10000;i++){
            string += "hello";
        }
    }
}

这句 string += "hello";的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。也就是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。string+="hello"的操作事实上会自动被JVM优化成:

  StringBuilder str = new StringBuilder(string);

  str.append("hello");

  str.toString();

  • stringbuilder

在原有对象上修改。因此在循环了10000次之后,这段代码所占的资源要比上面小得多。但是在字符串常量池中仍然会创建10000个字符串常量。

public class Main {

    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        for(int i=0;i<10000;i++){
            stringBuilder.append("hello");
        }
    }
}
  • StringBuffer

那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

  下面摘了2段代码分别来自StringBuilder和StringBuffer,insert方法的具体实现:

  StringBuilder的insert方法

public StringBuilder insert(int index, char str[], int offset,
                              int len)
  {
      super.insert(index, str, offset, len);
  return this;
  }

  StringBuffer的insert方法:

public synchronized StringBuffer insert(int index, char str[], int offset,
                                            int len)
    {
        super.insert(index, str, offset, len);
        return this;
    }

参考:www.cnblogs.com/dolphin0520…

  • String对象操作实例

直接使用双引号声明出来的 String 对象会直接存储在常量池中。

          String str1 = "str";
          String str2 = "ing";

          String str3 = "str" + "ing";//在堆上创建的新的对象,底层调用stringbuilder
          String str4 = str1 + str2; //在堆上创建的新的对象,底层调用stringbuilder 
          String str5 = "string";//常量池中的对象
          System.out.println(str3 == str4);//false
          System.out.println(str3 == str5);//flase
          System.out.println(str4 == str5);//false

数据操作

Float,Double和BigDecimal

为什么是BigDecimal ?

在Java中,我们通常使用 BigDecimal 类型来表示金额,特别是在金融,财务系统中,使用的特别多。例如:转账金额,手续费等等。今天就一起来认识下BigDecimal。

在此之前,我们先来讲讲为什么要使用 BigDecimal ?而不是Float,Double类型?其实光从表现形式来看,Float,Double,BigDecimal 类型都能表示小数。其区别在于精确计算时,Float 与 Double 类型都会损失精度,当然了,BigDecimal 使用不正确时,也会损失精度。在金融系统中,金额计算是最基本的运算,精度的丢失是绝对不能容忍的。接下来,我们来看看下面的例子:

Float 类型:

public void testFloat(){
    float a = 1.1f;
    float b = 0.8f;
    System.out.println("a-b = "+(a-b));
    System.out.println("a+b = "+(a+b));
    System.out.println("a*b = "+(a*b));
    System.out.println("a/b = "+(a/b));
}

结果如下:

a-b = 0.3
a+b = 1.9000001
a*b = 0.88000005
a/b = 1.375

Double 类型:

public void testDouble(){
    double a = 1.1;
    double b = 0.8;
    System.out.println("a-b = "+(a-b));
    System.out.println("a+b = "+(a+b));
    System.out.println("a*b = "+(a*b));
    System.out.println("a/b = "+(a/b));
}

结果如下:

a-b = 0.30000000000000004
a+b = 1.9000000000000001
a*b = 0.8800000000000001
a/b = 1.375

BigDecmial 错误使用:

public void testBigDecimal(){
    BigDecimal a = new BigDecimal(1.1);
    BigDecimal b = new BigDecimal(0.8);
    System.out.println("a-b = "+(a.subtract(b)));
    System.out.println("a+b = "+(a.add(b)));
    System.out.println("a*b = "+(a.multiply(b)));
    System.out.println("a/b = "+(a.divide(b)));
}

结果如下:

a-b = 0.3000000000000000444089209850062616169452667236328125
a+b = 1.9000000000000001332267629550187848508358001708984375
a*b = 0.8800000000000001199040866595169103100567462588676208086428264139311483660321755451150238513946533203125
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

正确使用方法:

public void testBigDecimalNormal(){
    BigDecimal a = new BigDecimal("1.1");
    BigDecimal b = new BigDecimal("0.8");
    System.out.println("a-b = "+(a.subtract(b)));
    System.out.println("a+b = "+(a.add(b)));
    System.out.println("a*b = "+(a.multiply(b)));
    System.out.println("a/b = "+(a.divide(b)));
}

结果如下:

a-b = 0.3
a+b = 1.9
a*b = 0.88
a/b = 1.375

通过上面的例子,我们可以清晰的看出。除了正确使用BigDecimal类型外,其余的在计算过程中,均损失精度。因此我们可以得出以下结论:

  1. 在需要精度计算数值时,不应该使用float,double 类型进行计算。
  2. BigDecimal 应该使用 String 构造函数,禁止使用double构造函数。

使用细节

其实,在使用BigDecimal过程,也有许多需要注意的细节。

  • 科学计数法问题
@Test
    public void testBigDecimalResult(){
        BigDecimal b = new BigDecimal("0.0000001");
        System.out.println(b.toString());
        System.out.println(b.toPlainString());
    }

执行结果:

1E-7
0.0000001

结论:当 BigDecimal的值 小于一定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可以使用 toPlainString()方法显示原来的值。

  • 去除多余的 0
@Test
    public void testBigDecimalStripZeros(){
        BigDecimal b = new BigDecimal("0.000000100000000");
        System.out.println(b.stripTrailingZeros().toString());
        System.out.println(b.stripTrailingZeros().toPlainString());
    }

使用场景:去除多余的0,当金额有小数位限制时,使用该方法能够去除掉无效的0,从而达到自动修复无效参数的目的。

结论:stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,同样的在使用时需要注意科学技术法的问题。

  • 保留小数位和四舍五入

末位进位方式一定要设置。否则报异常。

new BigDecimal("0.2").setScale(2, BigDecimal.ROUND_HALF_UP);
  • 小结

通过上面的例子,现在我们已经知道了BigDecimal的一些使用细节。其实呀,这些都是血淋淋的教训换来的经验,每一个小细节对应的都是一个个事故,记忆犹新。这里推荐大家都抽时间看看《Java开发手册》,就能避免掉很多坑。

上面的问题,在《Java开发手册》中同样有写到:

【强制】为了防止精度损失,禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。
说明:BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149
正例:优先推荐入参为String 的构造函数,或使用BigDecimal的valueOf方法。

  • Bigdecimal和String互转
String StrBd="1048576.1024"; 
//构造以字符串内容为值的BigDecimal类型的变量bd 
BigDecimal bd=new BigDecimal(StrBd); 
//设置小数位数,第一个变量是小数位数,第二个变量是取舍方法(四舍五入) 
bd=bd.setScale(2, BigDecimal.ROUND_HALF_UP); 
//转化为字符串输出 
String OutString=bd.toString();
  • Bigdecimal****和double及float的转换
@Test
    public void testbigDecimal() {
        // BigDecimal转Double
        BigDecimal bigDecimal = new BigDecimal("123.123456789");
        Double double1 = bigDecimal.doubleValue();
        System.out.println(double1);
        // BigDecimal转float
        BigDecimal bigDecimal2 = new BigDecimal("123.123456789");
        float f = bigDecimal.floatValue();
        System.out.println(f);
    }
  • int,double,float与string的转换
String s = Integer.toString(int i);
String s = Double.toString(double d);
String s = Float.toString(float f);
String s = Long.toString(long L);

Integer i = Integer.valueOf(String num);
Double d = Double.valueOf(String num);
Float f = Float.valueOf(String num);
Long l = Long.valueOf(String num);

集合操作

集合:按照特定数据结构批量存储和操作数据。关注:使用(特性,场景和方法),原理,历史发展。

集合特性与选用

主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap。

当我们只需要存放元素值时,就选择实现Collection接口的集合。需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。

List:对付顺序的好帮手
List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象

Set:注重独一无二的性质
不允许重复的集合。不会有多个元素引用相同的对象。

用于存储不含重复元素的集合。几乎所有的Set实现都是基于同类型Map的,简单地说,Set是阉割版的Map。每一个Set内都有一个同类型的Map实例(CopyOnWriteArraySet除外,它内置的是CopyOnWriteArrayList实例),Set把元素作为key存储在自己的Map实例中,value则是一个空的Object。

Map:用Key来搜索的专家
使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

Collection

List

  • Arraylist 与 LinkedList 区别(查询,插入,删除效率)

Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList。一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。

  • ArrayList 与 Vector 区别(并发安全,处理效率)

Vector类的所有方法都是同步的。两个线程可以安全地访问一个Vector对象,但是一个线程访问Vector ,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。

为什么要用Arraylist取代Vector呢?(jdk1.2中引入)

Set

  • HashSet,LinkedHashSet,TreeSet

和Map接口类似,区别为只存储键值,且键值唯一。hashset无序,linkedhashset按照键值大小排序(可自定义比较大小方法),treeset按照键值插入顺序排序。

  • HashSet 和 HashMap 区别

  • HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值(然后根据hashcode值来做取余)来判断对象加入的桶的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。因此,放入hashset的对象要覆写object类中的equals()方法。

hashCode()与equals()的相关规定:

  1. 如果两个对象相等,则hashcode一定也是相同的
  2. 两个对象相等,对两个equals方法返回true
  3. 两个对象有相同的hashcode值,它们也不一定是相等的
  4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

Map

HashTable

java1.0引入。该类的所有方法都加上了Synchronized标记,因此并发效率较低。目前已经不推荐使用。

HashMap

java1.2引入,HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表)实现。里边最重要的两个方法put、get,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。

HashMap内部实现是一个桶数组,每个桶中存放着一个单链表的头结点。其中每个结点存储的是一个键值对整体(Entry),HashMap采用拉链法解决哈希冲突。

  • 底层数据结构

Map 的 key 和 value 都不允许是基本数据类型。

JDK1.8之前,HashMap底层是数组和链表结合在一起使用也就是链表散列。HashMap通过key的hashCode来计算hash值(先根据key计算hashcode,然后根据hashcode计算hash值,映射到某个桶编号),当hashCode相同时,通过“拉链法”解决冲突。所谓“拉链法”就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

简单来说,JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,仅需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过key对象的equals方法逐一比对查找。

相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。

  • hashmap put方法执行流程
  1. 对key的hashCode做hash操作,然后再计算在bucket中的index;
  2. 如果没碰撞直接放到bucket里;
  3. 如果碰撞了,以链表的形式存在buckets后(插入链表首节点,因此时间复杂度为O(1));
  4. 如果节点已经存在就替换old value(保证key的唯一性)
  5. 如果bucket满了(超过阈值,阈值=loadfactor*current capacity,load factor默认0.75),就要resize。
  • hashmap get方法执行流程

通过对key的hashCode()进行hashing,计算出桶下标,从而获得buckets的位置。如果存在碰撞,则利用key.equals()方法去链表或树中查找对应的节点。因此,对于key对象的类,需要覆写object类中的equals()方法做对象比较。

  • hashmap自动扩容

HashMap 中比较核心的几个成员变量

  1. 初始化桶大小,因为底层是数组,所以这是数组默认的大小。桶大小,可在初始化时显式指定。
  2. 桶最大值。
  3. 默认的负载因子(0.75)
  4. table 真正存放数据的数组。
  5. Map 存放数量的大小。
  6. 负载因子,可在初始化时显式指定。

什么是resize?

resize就是重新计算容量;当我们不断的向HashMap对象里不停的添加元素时,HashMap对象内部的数组就会出现无法装载更多的元素,这是对象就需要扩大数组的长度,以便能装入更多的元素;当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

什么时候需要resize()?

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值——即当前数组的长度乘以加载因子的值的时候,就要自动扩容。

比如给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。因此通常建议能提前预估 HashMap 的大小最好,尽量的减少扩容带来的性能损耗。

扩容:(源码 661-662)

计算阀值:

1.第一次创建Hash表时:

2.对HashMap进行扩容时:

  • hashMap中容量为什么是2的n次方?

hashmap进行hash散列的算法是hash&(length-1),而hash的容量建议都是取2的n次方。

首先我们先说说这个算法,算法的目的是为了得到小于length的更加均匀的数,如果不均匀容易产生hash碰撞,换句话说只有全是1,进行按位与才是最均匀的,因为1与上任何数都等于任何数本身

为什么是length-1不是length?

16是10000 15是01111。16与任何数只能是0或者16。15与任何数等于小于16的任何数本身。

为什么容量是2的n次方呢?

2的n次方一定是最高位1其它低位是0,

这样减1的时候才能得到01111这样都是1的二进制。

  • hashmap并发死循环问题

hashmap不支持并发,所以日常开发中不要应用到并发场景。可以用concurrenthashmap代替。

juejin.cn/post/684490…

  • HashMap 和 Hashtable 的区别
  1. HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。
  2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。
  3. HashMap键值允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧。

  • HashMap 和 ConcurrentHashMap 的区别
  1. ConcurrentHashMap对整个桶数组进行了分割分段(16个Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。
  2. JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,使用CAS + synchronized 来保证并发安全性。
  3. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

LinkedHashMap

JDK1.4 以后引入,按照键值对插入顺序排序。LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。LinkedHashMap是HashMap子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。

TreeMap

TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进行排序。

TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。

segmentfault.com/a/119000001…

blog.csdn.net/ThinkWon/ar…

集合常用方法

如何求ArrayList集合的交集 并集 差集 去重复并集

需要用到List接口中定义的几个方法:

  • addAll(Collection<? extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾 实例代码:
  • retainAll(Collection<?> c): 仅保留此列表中包含在指定集合中的元素。
  • removeAll(Collection<?> c) :从此列表中删除指定集合中包含的所有元素。
package list;
import java.util.ArrayList;
import java.util.List;
/**
 *TODO 两个集合之间求交集 并集 差集 去重复并集
 * @author 寇爽
 * @date 2017年11月21日
 * @version 1.8
 */
public class MethodDemo {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<Integer> list1 = new ArrayList<Integer>();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        List<Integer> list2 = new ArrayList<Integer>();
        list2.add(2);
        list2.add(3);
        list2.add(4);
        list2.add(5);
        // 并集
        // list1.addAll(list2);
        // 交集
        //list1.retainAll(list2);
        // 差集
        // list1.removeAll(list2);
        // 无重复并集
        list2.removeAll(list1);
        list1.addAll(list2);
        for (Integer i : list1) {
            System.out.println(i);
        }
    }
}

如何实现数组与List的相互转换?

List转数组:toArray(arraylist.size()方法;数组转List:Arrays的asList(a)方法

List<String> arrayList = new ArrayList<String>();
        arrayList.add("s");
        arrayList.add("e");
        arrayList.add("n");
        /**
         * ArrayList转数组
         */
        int size=arrayList.size();
        String[] a = arrayList.toArray(new String[size]);
        //输出第二个元素
        System.out.println(a[1]);//结果:e
        //输出整个数组
        System.out.println(Arrays.toString(a));//结果:[s, e, n]
        /**
         * 数组转list
         */
        List<String> list=Arrays.asList(a);
        /**
         * list转Arraylist
         */
        List<String> arrayList2 = new ArrayList<String>();
        arrayList2.addAll(list);
        System.out.println(list);

Hashmap遍历

//for each map.entrySet()
Map<String, String> map = new HashMap<String, String>();
for (Entry<String, String> entry : map.entrySet()) {
    entry.getKey();
    entry.getValue();
}

//map.entrySet().iterator()
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next();
    entry.getKey();
    entry.getValue();
}

//for each map.keySet(),再调用get获取
Map<String, String> map = new HashMap<String, String>();
for (String key : map.keySet()) {
    map.get(key);
}

//for each map.entrySet()
Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
    entry.getKey();
    entry.getValue();
}