容器【容器介绍、Set接口介绍、 HashSet容器的使用、TreeSet容器的使用】(三)-全面详解(学习总结---从入门到深化)

简介: 容器【容器介绍、Set接口介绍、 HashSet容器的使用、TreeSet容器的使用】(三)-全面详解(学习总结---从入门到深化)

LinkedList容器介绍


LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。 双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两 个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中 的任意一个节点开始,都可以很方便地找到所有节点。


LinkedList的存储结构图


每个节点都应该有3部分内容:

class  Node<E> {
    Node<E>  previous;   //前一个节点
    E  element;          //本节点保存的数据
    Node<E> next;        //后一个节点
}


List实现类的选用规则


如何选用ArrayList、LinkedList、Vector?

1 需要线程安全时,用Vector。

2 不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)

3 不存在线程安全问题时,增加或删除元素较多用LinkedList


LinkedList容器的使用(List标准)


LinkedList实现了List接口,所以LinkedList是具备List的存储特征的 (有序,元素有重复)。

public class LinkedListTest {
    public static void main(String[] args) {
        //实例化LinkedList容器
        List<String> list = new LinkedList<>();
        //添加元素
        boolean a = list.add("a");
        boolean b = list.add("b");
        boolean c = list.add("c");
        list.add(3,"a");
        System.out.println(a+"\t"+b+"\t"+c);
        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
       }
   }
}


LinkedList容器的使用(非List标准)


public class LinkedListTest2 {
    public static void main(String[] args) {
        System.out.println("-------LinkedList-------------");
        //将指定元素插入到链表开头
        LinkedList<String> linkedList1 = new LinkedList<>();
        linkedList1.addFirst("a");
        linkedList1.addFirst("b");
        linkedList1.addFirst("c");
        for (String str:linkedList1){
            System.out.println(str);
       }
        System.out.println("----------------------");
        //将指定元素插入到链表结尾
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.addLast("a");
        linkedList.addLast("b");
        linkedList.addLast("c");
        for (String str:linkedList){
            System.out.println(str);
       }
        System.out.println("---------------------------");
        //返回此链表的第一个元素
        System.out.println(linkedList.getFirst());
        //返回此链表的最后一个元素
        System.out.println(linkedList.getLast());
        System.out.println("-----------------------");
        //移除此链表中的第一个元素,并返回这个元素
        linkedList.removeFirst();
        //移除此链表中的最后一个元素,并返回这个元素
        linkedList.removeLast();
        for (String str:linkedList){
            System.out.println(str);
       }
        System.out.println("-----------------------");
        linkedList.addLast("c");
        //从此链表所表示的堆栈处弹出一个元素,等效于removeFirst
        linkedList.pop();
        for (String str:linkedList){
            System.out.println(str);
       }
        System.out.println("-------------------");
        //将元素推入此链表所表示的堆栈 这个等效于addFisrt(E e)
        linkedList.push("h");
        for (String str:linkedList){
            System.out.println(str);
       }
   }
}


LinkedList的源码分析


添加元素

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;
   }
}


成员变量

transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
*           (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
*            (last.next == null && last.item != null)
*/
transient Node<E> last;


添加元素

/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
    linkLast(e);
    return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}


头尾添加元素


addFirst

/**
* Inserts the specified element at the beginning of this list.
*
* @param e the element to add
*/
public void addFirst(E e) {
    linkFirst(e);
}
/**
* Links e as first element.
*/
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
    last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}


addLast

/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #add}.
*
* @param e the element to add
*/
public void addLast(E e) {
    linkLast(e);
}
/**
* Links e as last element.
*/
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
    first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}


获取元素

/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Tells if the argument is the index of an existing element.
*/
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}   
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
    // assert isElementIndex(index);
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
   } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
            return x;
   }
}


Set接口介绍



Set接口继承自Collection接口,Set接口中没有新增方法,它和 Collection接口保持完全一致。我们在前面学习List接口的使用方 式,在Set中仍然适用。因此,学习Set的使用将没有任何难度。


Set接口特点


Set特点:无序、不可重复。无序指Set中的元素没有索引,我们只 能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新 元素如果和Set中某个元素通过equals()方法对比为true,则只能保 留一个。

Set常用的实现类有:HashSet、TreeSet等,我们一般使用 HashSet。


HashSet容器的使用


HashSet是Set接口的实现类。是Set存储特征的具体实现。

public class HashSetTest {
    public static void main(String[] args) {
        //实例化HashSet
        Set<String> set = new HashSet<>();
        //添加元素
        set.add("a");
        set.add("b1");
        set.add("c2");
        set.add("d");
        set.add("a");
        //获取元素,在Set容器中没有索引,所以没有对应的get(int index)方法
        for(String str: set){
            System.out.println(str);
       }
        System.out.println("--------------------");
        //删除元素
        boolean flag = set.remove("c2");
        System.out.println(flag);
        for(String str: set){
            System.out.println(str);
       }
        System.out.println("------------------------");
        int size = set.size();
        System.out.println(size);
   }
}


HashSet存储特征分析


HashSet 是一个不保证元素的顺序且没有重复元素的集合,是线程 不安全的。HashSet允许有null 元素。


无序:


在HashSet中底层是使用HashMap存储元素的。HashMap底层使 用的是数组与链表实现元素的存储。元素在数组中存放时,并不是 有序存放的也不是随机存放的,而是对元素的哈希值进行运算决定 元素在数组中的位置。


不重复:


当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会 调用元素的equals()方法判断两个元素是否相同。如果元素相同则 不会添加该元素,如果不相同则会使用单向链表保存该元素。


通过HashSet存储自定义对象


创建Users对象

public class Users {
    private String username;
    private int userage;
    public Users(String username, int userage) {
    this.username = username;
        this.userage = userage;
   }
    public Users() { }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Users users = (Users) o;
        if (userage != users.userage) return false;
        return username != null ? username.equals(users.username) : users.username == null;
   }
    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + userage;
        return result;
       }
    public String getUsername() {
        return username;
      }
    public void setUsername(String username)
      {
        this.username = username;
      }
    public int getUserage() {
        return userage;
      }
    public void setUserage(int userage) {
        this.userage = userage;
      }
    @Override
    public String toString() {
        return "Users{" +
                "username='" + username + '\'' +
                ", userage=" + userage +
               '}';
   }
}


在HashSet中存储Users对象

public class HashSetTest2 {
    public static void main(String[] args) {
        //实例化HashSet
        Set<Users> set = new HashSet<>();
        Users u = new Users("oldlu",18);
        Users u1 = new Users("oldlu",18);
        set.add(u);
        set.add(u1);
        System.out.println(u.hashCode());
        System.out.println(u1.hashCode());
        for(Users users:set){
            System.out.println(users);
       }
   }
}


HashSet底层源码分析


成员变量

private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();


添加元素

/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null&nbsp;? &nbsp;e2==null&nbsp;:&nbsp;e.equals(e2)) </tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}


TreeSet容器的使用



TreeSet实现了Set接口,它是一个可以对元素进行排序的容器。底 层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap, 通过key来存储元素。 TreeSet内部需要对存储的元素进行排序,因 此,我们需要给定排序规则。


排序规则实现方式:


1、通过元素自身实现比较规则。

2、通过比较器指定比较规则。

public class TreeSetTest {
    public static void main(String[] args) {
        //实例化TreeSet
        Set<String> set = new TreeSet<>();
        //添加元素
        set.add("c");
        set.add("a");
        set.add("d");
        set.add("b");
        set.add("a");
        //获取元素
        for(String str :set){
            System.out.println(str);
       }
   }
}
目录
相关文章
|
19天前
|
安全 Java 容器
第一篇:并发容器学习开篇介绍
第一篇:并发容器学习开篇介绍
18 4
|
20天前
|
存储 C++ 索引
|
20天前
|
存储 C++ 容器
|
13天前
|
弹性计算 Kubernetes Linux
主流容器工具对比以及重点推荐学习的企业级工具
主流容器工具对比以及重点推荐学习的企业级工具
|
20天前
|
存储 语音技术 Python
语音识别,函数综合案例,黑马ATM,/t/t一个对不齐,用两个/t,数据容器入门,数据容器可以分为列表(list)、元组(tuple)、字符串(str)、集合(set)、字典(dict)
语音识别,函数综合案例,黑马ATM,/t/t一个对不齐,用两个/t,数据容器入门,数据容器可以分为列表(list)、元组(tuple)、字符串(str)、集合(set)、字典(dict)
|
6天前
|
Shell Linux Docker
docker常用命令大全(基础、镜像、容器、数据卷)
这些命令仅仅是 Docker 命令行工具的冰山一角,但对于日常操作来说已经非常全面。通过熟练地使用这些基础命令,用户可以有效地管理 Docker 的镜像、容器、数据卷和网络。随着用户对 Docker 的深入使用,更高级的命令和选项将会变得必需,但上面列出的命令已经为用户提供了一个坚实的起点。对于初学者来说,理解和掌握这些常用命令是深入学习 Docker 的基础。
76 5
docker常用命令大全(基础、镜像、容器、数据卷)
|
3天前
|
Ubuntu Devops 云计算
ubuntu docker-compose编排容器并且设置自启动
使用Docker Compose编排容器并设置为Ubuntu系统的自启动服务,不仅优化了应用的部署流程,也提升了运行时的可管理性和可靠性。通过上述步骤,您可以轻松实现这一目标。维护此类服务时,记得定期检查和更新您的 `docker-compose.yml`文件,确保所有的服务都符合当前的业务需求和技术标准。在云计算和微服务架构不断演进的今天,掌握Docker Compose等工具对于DevOps和软件工程师来说,变得尤为重要。
14 3
|
8天前
|
Docker Python 容器
容器化技术,特别是Docker,已经成为现代软件开发和部署的重要工具。
容器化技术,特别是Docker,已经成为现代软件开发和部署的重要工具。
|
8天前
|
Kubernetes 持续交付 开发者
微软Azure云上部署Docker容器的技术实践
【7月更文挑战第15天】Docker与Azure的集成,为开发者提供了一种快速、可扩展和可维护的应用部署方式。通过在Azure平台上部署Docker容器,开发者可以充分利用Azure丰富的云服务和强大的基础设施,实现应用的快速迭代和高效运行。未来,随着Docker和Azure的不断发展和集成,我们可以期待更加便捷和高效的容器化应用部署体验。