• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

Java基础语法,常用知识复习(3)

开发技术 开发技术 3小时前 4次浏览

目录
  • 7.java常用类
    • 7.1 java.lang.String
    • 7.2 时间相关的常用类
      • 7.2.1 java.util.Date类与java.sql.Date类
      • 7.2.2Calendar类:日历类、抽象类
      • 7.2.3 1.8新特性 时间API
  • 8. 枚举和注解
    • 8.1自定义枚举
    • 8.2jdk 5.0 新增使用enum定义枚举类
    • 8.2jdk 5.0 enum枚举类常用的方法
    • 8.3注解
      • 8.3.1自定义注解
  • 9.集合
    • 9.1集合与数组
    • 9.2Collection接口
    • 9.3Collection接口的子接口List接口
      • 9.3.1ArrayList源码分析
      • 9.3.1.1 jdk7(待补充!!!)
      • 9.3.1.2 jdk8
    • 9.3.2ArrayList源码分析jdk7 和 8相同 这里以8为例
    • 9.3.3Vector的源码分析:(jdk8)
    • 9.4Collection接口的子接口Set接口
      • 9.4.1 TreeSet的使用示例
      • 9.4.2HashSet相关的理解(!)
    • 9.5Map接口
      • 9.5.1HashMap的源码分析
        • 9.5.1.1jdk7
        • 9.5.1.1jdk8
      • 9.5.2HashMap代码测试
    • 9.6Collections工具类的使用

上一篇
下一篇

7.java常用类

7.1 java.lang.String

1.概述
String:字符串,使用一对""引起来表示。
①String声明为final的,不可被继承
②String实现了Serializable接口:表示字符串是支持序列化的。
        实现了Comparable接口:表示String可以比较大小
③String内部定义了final char[] value用于存储字符串数据
④通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
⑤字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。
2.String的不可变性
2.1说明
①当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
②当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
③当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
2.2代码
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");

System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
2.3 面试题
String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"
3.常用的方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
4.String、StringBuffer、StringBuilder三者的对比
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
执行效率:从高到低排列:StringBuilder > StringBuffer > String
StringBuffer与StringBuilder的内存解析
以StringBuffer为例:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
         默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。

        指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)

5. JVM中字符串常量池存放位置说明:
jdk 1.6 (jdk 6.0 ,java 6.0):字符串常量池存储在方法区(永久区)
jdk 1.7:字符串常量池存储在堆空间
jdk 1.8:字符串常量池存储在方法区(元空间)

Java基础语法,常用知识复习(3)

常见算法题目的考查:
1)模拟一个trim方法,去除字符串两端的空格。

2)将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”

3)获取一个字符串在另一个字符串中出现的次数。
      比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数

4)获取两个字符串中最大相同子串。比如:
   str1 = "abcwerthelloyuiodef“;str2 = "cvhellobnm"
   提示:将短的那个串进行长度依次递减的子串与较长的串比较。

5)对字符串中字符进行自然顺序排序。
提示:
1字符串变成字符数组。
2对数组排序,择,冒泡,Arrays.sort();
3将排序后的数组变成字符串。

7.2 时间相关的常用类

7.2.1 java.util.Date类与java.sql.Date类
java.util.DateDate date1 = new java.util.DateDate();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date2 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date2);
7.2.2Calendar类:日历类、抽象类
Calendar calendar = Calendar.getInstance();
int days = calendar.get(Calendar.DAY_OF_MONTH);
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
7.2.3 1.8新特性 时间API
java.time 包含值对象的基础包
java.time.chrono 提供对不同的日历系统的访问
java.time.format 格式化和解析时间和日期
java.time.temporal 包括底层框架和扩展特性
java.time.zone 包含时区支持的类
本地日期、本地时间、本地日期时间的使用:LocalDate / LocalTime / LocalDateTime

public void testZonedDateTime(){    
  // 获取当前时间日期
  ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
  System.out.println("date1: " + date1); 
  ZoneId id = ZoneId.of("Europe/Paris");
  System.out.println("ZoneId: " + id);
        
  ZoneId currentZone = ZoneId.systemDefault();
  System.out.println("当期时区: " + currentZone);
}

public void testLocalDateTime(){
  //获取当前的日期时间
  LocalDateTime currentTime = LocalDateTime.now();//2021-06-07T14:55:38.331
  System.out.println("当前时间: " + currentTime);
  LocalDate date = currentTime.toLocalDate();
  Month month = currentTime.getMonth();
  int year = currentTime.getYear();
  int day = currentTime.getDayOfMonth();
  int seconds = currentTime.getSecond();
  System.out.println(currentTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));//2021-06-07 15:07:35
  System.out.println("月: " + month.getValue() +", 日: " + day +", 秒: " + seconds);

  System.out.println("=============================");
  LocalDate currentDate = LocalDate.now();//2021-06-07
  System.out.println("当前日期: " + currentDate);
  System.out.println("================================");
  LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
  System.out.println("时间间隔" + Duration.between(date2, currentTime).toDays());
  //日期间隔:Period --用于计算两个“日期”间隔,以年、月、日衡量
  //System.out.println("时间间隔" + Period.between(LocalDate, LocalDate).getYears());
  System.out.println("date2: " + date2);

  // 12 december 2014
  LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
  System.out.println("date3: " + date3);

  // 22 小时 15 分钟
  LocalTime date4 = LocalTime.of(22, 15);
  System.out.println("date4: " + date4);

  // 解析字符串
  LocalTime date5 = LocalTime.parse("20:15:30");
  LocalTime date6 = LocalTime.parse("201530",DateTimeFormatter.ofPattern("HHmmss"));
  LocalDate date7 = LocalDate.parse("20200809",DateTimeFormatter.ofPattern("uuuuMMdd"));
  System.out.println("date5: " + date5);
  System.out.println("date6: " + date6);
  System.out.println(date7);
}

8. 枚举和注解

8.1自定义枚举

public class Season{
  //1.声明Season对象的属性
  private final String seasonName;
  private final String seasonDesc;
  //2.构造方法私有化
  private Season(String seasonName, String seasonDesc){
    this.seasonName = seasonName;
    this.seasonDesc = seasonDesc;
  }
  //3.提供当前枚举类的多个对象:public static final的
  public static final Season SPRING = new Season("春天","春暖花开");
  public static final Season SUMMER = new Season("夏天","夏日炎炎");
  public static final Season AUTUMN = new Season("秋天","秋高气爽");
  public static final Season WINTER = new Season("冬天","冰天雪地");
  //4.其他诉求1:获取枚举类对象的属性
  public String getSeasonName() {
    return seasonName;
  }

  public String getSeasonDesc() {
    return seasonDesc;
  }
  //重写toString()方法

}

8.2jdk 5.0 新增使用enum定义枚举类

interface Info{
  void show();
}
enum SeasonEnum implements Info{
  //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
  SPRING("春天","春暖花开") {
    public void show(){
      System.out.println("春天");
    }
  },
  SUMMER("夏天","夏日炎炎"),
  AUTUMN("秋天","秋高气爽"),
  WINTER("冬天","冰天雪地");
  //2.声明Season对象的属性:private final修饰
  private final String seasonName;
  private final String seasonDesc;
  //3.私有化构造器
  private Season1(String seasonName,String seasonDesc){
    this.seasonName = seasonName;
    this.seasonDesc = seasonDesc;
  }
  //4.获取枚举类对象的属性
  public String getSeasonName() {
    return seasonName;
  }
  public String getSeasonDesc() {
    return seasonDesc;
  }
  //实现继承的方法
  public void show(){
    System.out.println(this.seasonName);
  }
}

8.2jdk 5.0 enum枚举类常用的方法

//toString():返回枚举类对象的名称
//static 枚举类型[] values():返回所的枚举类对象构成的数组
//static 枚举类型 valueOf(String objName):返回枚举类中对象名是objName的对象。如果没objName的枚举类对象,则抛异常:IllegalArgumentException

8.3注解

8.3.1自定义注解
/**
 * ① 注解声明为:@interface
 * ② 内部定义成员,通常使用value表示
 * ③ 可以指定成员的默认值,使用default定义
 * ④ 如果自定义注解没成员,表明是一个标识作用。
说明:
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才意义。
自定义注解通过都会指明两个元注解:Retention、Target
jdk 提供的4种元注解:
Retention:指定所修饰的 Annotation 的生命周期:SOURCECLASS(默认行为RUNTIME
       只声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
*******出现的频率较低*******
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具继承性。
JDK8中注解的新特性:可重复注解、类型注解
可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
               ② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
*/
@Inherited//可继承
@Repeatable(MyAnnotations.class)//可重复
@Retention(RetentionPolicy.RUNTIME)//生命周期
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})//可以加的地方
public @interface MyAnnotation {
    String value() default "hello";
}

9.集合

9.1集合与数组

1. 集合与数组存储数据概述:
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

2. 数组存储的特点:
> 一旦初始化以后,其长度就确定了。
> 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
    比如:String[] arr;int[] arr1;Object[] arr2;
3. 数组存储的弊端:
   > 一旦初始化以后,其长度就不可修改。
   > 数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
   > 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
   > 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
4. 集合存储的优点:
解决数组存储数据方面的弊端。

9.2Collection接口

|----Collection接口:单列集合,用来存储一个一个的对象
          |----List接口:存储序的、可重复的数据。  -->“动态”数组
              |----ArrayList、LinkedList、Vector

          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
              |----HashSet、LinkedHashSet、TreeSet

Collection接口常用方法:
add(Object obj),addAll(Collection coll),size(),isEmpty(),clear();
contains(Object obj),containsAll(Collection coll),remove(Object obj),removeAll(Collection coll),retainsAll(Collection coll),equals(Object obj);
hasCode(),toArray(),iterator();
//拓展:数组 --->集合:调用Arrays类的静态方法asList(T ... t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);

List arr1 = Arrays.asList(new int[]{123, 456});//看作一个元素
System.out.println(arr1.size());//1

List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2

Java基础语法,常用知识复习(3)

Collection接口的遍历
① 使用迭代器Iterator  ② foreach循环(或增强for循环)

Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
  System.out.println(iterator.next());
  Object obj = iterator.next();
  if("Tom".equals(obj)){
    iterator.remove();
    //iterator.remove();
  }
}
//测试Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
//内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
for(Object obj : coll){
  obj = null;//不会对集合产生影响(局部变量)
  System.out.println(obj);
}
//内部仍然是调用了迭代器

Java基础语法,常用知识复习(3)

9.3Collection接口的子接口List接口

//需要重写equels方法
1.存储的数据的特点:存储有序的,可重复的数据.
2.常用的方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
     ② 增强for循环
     ③ 普通的循环
3.常用的实现类
ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
4.面试题:ArrayList、LinkedList、Vector者的异同?
相同:都实现了List接口
不同:见3,以及源码分析
9.3.1ArrayList源码分析
9.3.1.1 jdk7(待补充!!!)
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
9.3.1.2 jdk8
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//底层Object[] elementData初始化为{}.并没创建长度为10的数组
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
private static final int DEFAULT_CAPACITY = 10;
//第一次进来minCapacity = 1;
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code 越界
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}
//minCapacity = 10
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//0
    int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5   0
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;//走到这了
    if (newCapacity - MAX_ARRAY_SIZE > 0)//长度溢出
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

9.3.2ArrayList源码分析jdk7 和 8相同 这里以8为例

public LinkedList() {}
public boolean add(E e) {
    linkLast(e);
    return true;
}
//刚进来时    有数据时
void linkLast(E e) {
    final Node<E> l = last;//null | 0x2345(将`旧链表`的最后一个节点的地址保存起来)
    final Node<E> newNode = new Node<>(l, e, null);//增加要新增的节点(将新节点的前一个保存为`旧链表`的最后一个节点的地址)
    last = newNode;//最后一个节点等于新节点
    if (l == null)
        first = newNode;//走到这了
    else
        l.next = newNode;//有数据走到这了 旧链表的后一个保存为新节点的地址
    size++;//个数加一
    modCount++;//修改的次数? 和期望值判断 不相等会抛出异常ConcurrentModificationException
}
//静态内部类(双向链表)
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

9.3.3Vector的源码分析:(jdk8)

/**
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
*/
public Vector() {
    this(10);
}
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
//capacityIncrement容量增量
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
//synchronized  说明是线程安全的
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//扩容
}
//capacityIncrement容量增量 空参构造器默认为0
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);//2倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

9.4Collection接口的子接口Set接口

1. 存储的数据特点:无序的、不可重复的元素
具体的:
以HashSet为例说明:
①无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
②不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
2.元素添加的过程(以HashSet为例)
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断
数组此位置上是否已经元素:
    如果此位置上没其他元素,则元素a添加成功。 --->情况1
    如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
        如果hash值不相同,则元素a添加成功。--->情况2
        如果hash值相同,进而需要调用元素a所在类的equals()方法:
               equals()返回true,元素a添加失败
               equals()返回false,则元素a添加成功。--->情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,下一个指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。(前提:jdk7)
3.常用的实现类
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
    |--LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
TreeSet:可以照添加对象的指定属性,进行排序
4.存储对象所在类的要求:
HashSet/LinkedHashSet:
要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
     重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
TreeSet:
1.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
2.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
5.TreeSet的使用
向TreeSet中添加的数据,要求是相同类的对象。
两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)具体查看以下代码
9.4.1 TreeSet的使用示例
class MyData implements Comparable<MyData>{
    String name;
    String age;
    @Override
    public int compareTo(Employee employee) {
        return this.name.compareTo(employee.name);
    }
    @Override
    public String toString() {
        return "MyData{" +
                "name='" + name + ''' +
                ", age=" + age +
                "}";
    }
}
class TestTreeSet{
    //自然排序
    public static void main(String[] args){
        TreeSet set = new TreeSet();
        set.add(new MyData("xiaoming",11));
        set.add(new MyData("xiaonan",17));
        set.add(new MyData("lisi",15));
        set.add(new MyData("zhangsan",19));
        Iterator iterator = set.iterator();
        if(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        TreeSet<MyData> set = new TreeSet(new Comparator<MyData>() {
            @Override
            public int compare(MyData data1, MyData data2) {
                //return data1.compareTo(data2);//方式二
                return data1.name.compareTo(data2.name);//方式一
            }
        });
    }
}
9.4.2HashSet相关的理解(!)
/**
未重写hashCode()时 运行结果
849460928
849460928
重写后运行结果(重写hashCode()时尽量要和重写equals的时候用到的属性要一致)
33111
3571191
*/
public void test1(){
    Person p1 = new Person(1001,"AA");
    System.out.println(p1.hashCode());
    p1.name = "ssss";
    System.out.println(p1.hashCode());
}
/**
此时Person已经重写了equals()和hashCode()
运行结果
[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
*/
public void test2(){
    HashSet set = new HashSet();
    Person p1 = new Person(1001,"AA");
    Person p2 = new Person(1002,"BB");
    set.add(p1);
    set.add(p2);
    System.out.println(set);//输出两个值

    p1.name = "CC";//修改p1的属性
    set.remove(p1);//首先调用p1的hashCode方法,由于重写了hashCode(),属性值变了,hashCode值就会变,因此调用某种算法
//计算存放的数组位置也会变,此时该位置中并没有数据,或者掉hashCode() equals()时 与该数组所有元素的位置都不一样,因此不会移除
    System.out.println(set);//输出两个值
    set.add(new Person(1001,"CC"));//新增
    System.out.println(set);//输出三个值
    set.add(new Person(1001,"AA"));//新增  计算该对象的hashCode()方法 由于重写了hashCode(),属性值变了,hashCode值就会变,因此调用某种算法
//计算存放的数组位置也会变,此时该位置中并没有数据,或者掉hashCode() equals()时 与该数组所有元素的位置都不一样,因此新增
    System.out.println(set);//输出4个值
}
public class Person {
    int id;
    String name;
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Person() {
    }
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + ''' +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }
    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}

9.5Map接口

Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
1.实现类
    HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
        LinkedHashMap:HashMap的子类,保证在遍历map元素时,可以照添加的顺序实现遍历。原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
    对于频繁的遍历操作,此类执行效率高于HashMap。
    TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序  底层使用红黑树
    Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
        Properties Hashtable的子类常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表  (jdk7及之前)
              数组+链表+红黑树 (jdk 8)
[面试题](百度吧 有时间会再来补充2021-06-09 14:02:11 星期三)
*  1. HashMap的底层实现原理?
*  2. HashMap 和 Hashtable的异同?
*  3. CurrentHashMap 与 Hashtable的异同?
2.存储结构的理解:
①Map中的key:无序的、不可重复的,使用Set存储所的key  ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
②Map中的value:无序的、可重复的,使用Collection存储所的value ---value所在的类要重写equals()
③一个键值对:key-value构成了一个Entry对象。Map中的entry:无序的、不可重复的,使用Set存储所有的entry
9.5.1HashMap的源码分析
9.5.1.1jdk7
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子
static final int MAXIMUM_CAPACITY = 1 << 30;最大容量 2^30
transient Entry<K,V>[] table;//容器
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);//16 0.75f
}
//initialCapacity = 16,loadFactor = 0.75
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;//限制最大容量为 2^30
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

    // Find a power of 2 >= initialCapacity
    int capacity = 1;//真正要创建的容量(总是为2的次幂)
    while (capacity < initialCapacity)//假如initialCapacity的值为[9,15]的话, 默认创建的容量还是16
        capacity <<= 1;//左移 相当于*2

    this.loadFactor = loadFactor;//加载因子赋值
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//阈值赋值 扩容的临界值,
    //(真正的容量*加载因子)与(最大限制容量+1)进行比较,取最小值
    table = new Entry[capacity];//创建容量为16的静态内部类的Entry数组实现了Map类中的内部接口 Entry 结构见下
    useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    init();//可以在这边初始化参数
}
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
}
//新增键值对
public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);//如果key为null 也会成功,见下面方法(给索引为0的数组中增加节点)
    int hash = hash(key);//计算hash值(会调用该类的hashCode() 需要重写)见下面hash(Object key)
    int i = indexFor(hash, table.length);//通过计算的hash值确定数组索引位置
    //获取数组容器中第i个索引的值 遍历 该位置的Entry单链表
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //先判断 hash(存放的时候已经保存到entry中了)是否相同 (在判断 地址 或者 equals比较)
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            //代表key 是相同的  做覆盖操作 并返回旧值
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);//记录 没有做任何操作
            return oldValue;
        }
    }
    //代表没有 相同的key
    modCount++;
    addEntry(hash, key, value, i);//新增节点 返回null
    return null;
}
private V putForNullKey(V value) {
    //获取数组容器中第0个索引的值 遍历 该位置的Entry单链表
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        //判断当前节点的key是否==null  等于的话覆盖老值,返回旧值
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);//未找到的时候 addEntry(int hash, K key, V value, int bucketIndex)
    return null;
}
//添加节点的方法(新增节点的时候新节点总是放到第一个位置,next属性指向原数组索引位置的节点)
//0 null value 0
//hash key value i
void addEntry(int hash, K key, V value, int bucketIndex) {
    //判断是否 越界 并且 该容器的索引所在的位置 是否包含元素
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);//扩容,默认扩容2倍
        hash = (null != key) ? hash(key) : 0;//重新计算hash值
        bucketIndex = indexFor(hash, table.length);//计算存放数组容器的索引
    }

    createEntry(hash, key, value, bucketIndex);
}
//容器中size增加
//0 null value 0
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];//获取该索引位置的值
    table[bucketIndex] = new Entry<>(hash, key, value, e);//Entry(int h, K k, V v, Entry<K,V> next) {
    size++;
}
final int hash(Object k) {
    int h = 0;
    if (useAltHashing) {
        if (k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h = hashSeed;
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    return h & (length-1);
}
9.5.1.1jdk8
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
transient Node<K,V>[] table;//数组容器
int threshold; 临界值
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//初始化长度 16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
static final int TREEIFY_THRESHOLD = 8;//树的阈值
static final int MIN_TREEIFY_CAPACITY = 64;//最小树的容量
public HashMap() {
    //只做了加载因子的赋值,类似与ArrayList的源码(jdk8 和jdk7的区别)
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//如果key 为空 hash值返回0
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hash key value false true
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    //Node 实现了MAp.Entry类 见下
    Node<K,V>[] tab; Node<K,V> p; int n, i;//n 表示数组容器的长度 tab 表示数组容器
    //数组容器为空或者 数组的长度==0
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //根据hash值计算索引 并且判断该位置的node值是否为空
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);//是空直接新增
    else {
        //不是空的话
        Node<K,V> e; K k;
        //先判断hash是否相同,并且 判断(地址是否相同,|| 判断equals是否相同)
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {//代表没有 及e == null了
                    //放到最后一个
                    p.next = newNode(hash, key, value, null);
                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//7
                    treeifyBin(tab, hash);//链表个数大于等于8时
                    break;
                }
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;//代表有相同的key映射
                p = e;
            }
        }
        if (e != null) { // existing mapping for key 存在key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;//覆盖并返回旧值
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();//扩容
    afterNodeInsertion(evict);
    return null;//返回空
}
//转红黑树的方法
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //扩容  如果数组容器的长度小于64 选择扩容
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {//转红黑树
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}
//扩容
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;//获取原来的数组容器
    int oldCap = (oldTab == null) ? 0 : oldTab.length;//数组长度
    int oldThr = threshold;//新增第一个元素时默认是0
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults 进入这里
        newCap = DEFAULT_INITIAL_CAPACITY;//赋值默认长度
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//临界值 16 * 0.75
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);//如果超过最大容量 就取整形的最大值
    }
    threshold = newThr;//赋值临界值
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//创建容器
    table = newTab;//给真正的容器赋值
    if (oldTab != null) {//刚进来的时候 是空  直接返回新容器
        //不为空的话 需要把旧容器的 值赋值到新容器中 懒得看了!!!
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

内存结构说明:(难点)HashMap实现原理:
3.1 HashMap在jdk7中实现原理:
      HashMap map = new HashMap():
      在实例化以后,底层创建了长度是16的一维数组Entry[] table。
      ...可能已经执行过多次put...
      map.put(key1,value1):
      首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
      如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
              如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
              如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
                      如果equals()返回false:此时key1-value1添加成功。----情况3
                      如果equals()返回true:使用value1替换value2。

      补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
      在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
3.2 HashMap在jdk8中相较于jdk7在底层实现方面的不同:
    1. new HashMap():底层没创建一个长度为16的数组
    2. jdk 8底层的数组是:Node[],而非Entry[]
    3. 首次调用put()方法时,底层创建长度为16的数组
    4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 >= 8 且当前数组的长度 >= 64时,此时此索引位置上的所数据改为使用红黑树存储。

3.3 HashMap底层典型属性的属性的说明:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

3.4 LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
HashMap中的内部类  Node
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}
LinkedHashMap中的内部类: Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}
9.5.2HashMap代码测试
//运行结果null=null  可以存放k为null的值,但是只能放一个,再次存放时会覆盖
public void test1(){
    Map map = new HashMap();
//    map = new Hashtable();
    map.put(null,123);
    map.put(null,null);
    map.forEach((k,v)->{
        System.out.println(k + "=" + v);
    });
}
/**
运行结果
123=AA
345=BB
null=CC
LinkedHashMap 遍历时是按照存储的顺序遍历的,添加时维护了`指针`
*/
public void test2(){
    Map map = new HashMap();
    map = new LinkedHashMap();
    map.put(123,"AA");
    map.put(345,"BB");
    map.put(null,"CC");
    map.forEach((k,v)->{
        System.out.println(k + "=" + v);
    });
}
public void test3(){
    Map map = new HashMap();
    //添加
    map.put("AA",123);
    map.put(null,123);
    map.put("BB",56);
    //修改
    map.put("AA",87);
    System.out.println(map);//{AA=87, BB=56, 45=123}
    Map map1 = new HashMap();
    map1.put("CC",123);
    map1.put("DD",123);
    map.putAll(map1);
    System.out.println(map);//{AA=87, BB=56, CC=123, DD=123, 45=123}
    //remove(Object key)
    Object value = map.remove(null);
    System.out.println(value);//123  返回移除key对应的value
    System.out.println(map);//{AA=87, BB=56, DD=123, 45=123}
    boolean isExist = map.containsKey("BB");
    System.out.println(isExist);//true
    isExist = map.containsValue(123);
    System.out.println(isExist);//true
    //clear()
    map.clear();//与map = null操作不同
    System.out.println(map.size());//0
    System.out.println(map);//{}
}
//map的遍历
//不加泛型遍历
//方式一:
public void test1(){
    Map map = new HashMap();
    map.put("AA",123);
    map.put(45,1234);
    map.put("BB",56);
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while(iterator.hasNext()){
        Object o = iterator.next();
        System.out.println(o + " : " + map.get(o));
    }
}
//方式二
public void test2(){
    Map map = new HashMap();
    map.put("AA",123);
    map.put(45,1234);
    map.put("BB",56);
    Set set = map.entrySet();
    Iterator iterator = entrySet.iterator();
    while (iterator.hasNext()){
        Object obj = iterator.next();
        //entrySet集合中的元素都是entry
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey() + "---->" + entry.getValue());
    }

}

9.6Collections工具类的使用

reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)

Java基础语法,常用知识复习(3)


程序员灯塔
转载请注明原文链接:Java基础语法,常用知识复习(3)
喜欢 (0)