Какие коллекции обычно используются в Java? Какие из них потокобезопасны?

Потокобезопасность: Hashtable, ConcurrentHashMap , Vector, CopyOnWriteArrayList, CopyOnWriteArraySet.

Потоко-небезопасные: HashMap, ArrayList, LinkedList, HashSet, TreeSet , TreeMap

Обычно используемые коллекции включают Set, List и Map. Среди них set и list наследуют интерфейс коллекции.

1. Список

Общие операции

  • add(данные): добавить данные
  • удалить(данные): удалить данные
  • indexOf(data): возвращает значение позиции индекса первого вхождения данных в коллекцию.
  • contains(): используется для определения того, содержит ли коллекция указанный элемент, и возвращает true. Никто не возвращает ложь.
  • clear(): Превратить объекты в списке в мусор и очистить их.

Три метода: удаление, содержание и indexOf по умолчанию вызовут метод равенства.

1.1 Список массивов

  • ArrayList — наиболее часто используемая коллекция. Она реализует интерфейс List, наследует класс AbstractList и реализуется экземпляром Object[]. То есть нижний слой представляет собой массив, и между каждым элементом не может быть пробела.
  • Длина инициализации по умолчанию равна 10, а правило расширения равно 0,5 исходной емкости плюс 1, то есть длина после одного расширения равна 16.
  • По сравнению с LinkedList он имеет высокую скорость выполнения запросов и медленное добавление и удаление. Он пригоден для поиска, но не пригоден для добавления и удаления.При добавлении или удалении со средней позиции стоимость перемещения и копирования массива относительно высока. Когда его размер не удовлетворяет, создается новый массив, а затем данные старого массива копируются в новый массив.
  • Потоки не синхронизированы (небезопасно)

1.2 Связанный список:

Нижний уровень LinkedList представляет собой двусвязный список, поэтому он очень удобен для операций вставки и удаления. LinkedList наследует от AbstractSequentialList. Он также предоставляет методы, отсутствующие в интерфейсе списка, специально используемые для работы с элементами в начале и конце списка. Его можно использовать как стек, очередь или двусвязный список, но он не подходит для чтения. 
 

  • Он реализует интерфейс List, наследует класс AbstractSequentialList, а также реализует интерфейсы Deque и Queue.
  • Он реализован с помощью связанного списка узлов Node.Нижний уровень представляет собой двусторонний связанный список, поэтому он очень подходит для операций вставки и удаления.
  • Поскольку его структура данных представляет собой связанный список, механизм предварительного расширения отсутствует;
  •  Особенности: Добавление и удаление выполняются быстро, а запросы медленнее, чем ArrayList.
  • Потоки не синхронизированы (небезопасно).

Методы, специфичные для LinkindeList

  •     public void addFirst(E e) Вставляет указанный элемент в начало вторичного списка.
  •     public void addLast(E e) Добавляет указанный элемент в конец этого списка.
  •     public E getFirst() Возвращает первый элемент этого списка.
  •     public E getLast() Возвращает последний элемент этого списка.
  •     public E removeFirst() Удаляет и возвращает первый элемент этого списка.
  •     public E removeLast() Удаляет и возвращает последний элемент этого списка.
  •     public E pop() Извлекает элемент из стека, представленного этим списком.
  •     public void push(E e) Поместить элемент в стек, представленный этим списком.

1.3 Вектор:

  • Вектор реализует интерфейс List, наследует класс AbstractList и реализуется экземпляром Object[], то есть нижний уровень представляет собой структуру массива;
  • Длина инициализации по умолчанию равна 10, а коэффициент загрузки расширения равен 1. Когда длина элемента превышает исходную емкость, он расширяется. Значение по умолчанию — 10. После одного расширения емкость равна 20;
  • Особенности: Потокобезопасный, но медленный; метод реализации модифицируется с помощью ключевого слова синхронизированный, то есть в методе реализована блокировка синхронизации.
Перечислите точки удаления, требующие внимания

В реальной разработке мы иногда сталкиваемся с таким сценарием, когда нам нужно удалить определенные элементы в коллекции списка. В этом случае мы можем использовать метод удаления, предоставляемый List, для достижения этого требования.

Параметр, передаваемый методом удаления в List, может быть индексом коллекции, элементом коллекции или коллекцией.
Пример неправильного использования:

@Data
@AllArgsConstructor
public class Person {
    private String id;
    private String name;
 
    public static void main(String[] args) {
        List<Person> lists = new ArrayList<>();
        lists.add(new Person("1", "张三"));
        lists.add(new Person("2", "王五"));
        lists.add(new Person("3", "李六"));
        lists.add(new Person("4", "哈哈"));
 
        for (Person person : lists) {
            if (person.getId().equals("2")) {
                lists.remove(person);
            }
        }
    }
}

Здесь я использую расширенный цикл for, и вы обнаружите, что об ошибке сообщается напрямую.

Одной из характеристик коллекции List является то, что элементы в ней упорядочены, а это означает, что индексы элементов основаны на порядке вставки. После удаления элемента в заголовке или в середине индексы последующих элементов будут двигаться вперед. . Проблемы возникают при зацикливании.

Решение 1. Не используйте обход for-each, переключитесь на обход итератора и не используйте метод list.remove() для удаления объектов, используйте метод итератора iterator.remove() для удаления объектов. 

@Data
@AllArgsConstructor
public class Person {
    private String id;
    private String name;
 
    public static void main(String[] args) {
        List<Person> lists = new ArrayList<>();
        lists.add(new Person("1", "张三"));
        lists.add(new Person("2", "王五"));
        lists.add(new Person("3", "李六"));
        lists.add(new Person("4", "哈哈"));
 
        Iterator<Person> iterator = lists.iterator();
        while (iterator.hasNext()){
            Person next = iterator.next();
            if(next.getId().equals("2")){
                iterator.remove();
            }
        }
 
        lists.forEach(item-> System.out.println(item));
    }
}

Решение 2. При использовании метода удаления в цикле (например, цикле for) обязательно начинайте обход с последнего элемента коллекции List.

@Data
@AllArgsConstructor
public class Person {
    private String id;
    private String name;
 
    public static void main(String[] args) {
        List<Person> lists = new ArrayList<>();
        lists.add(new Person("1", "张三"));
        lists.add(new Person("2", "王五"));
        lists.add(new Person("3", "李六"));
        lists.add(new Person("4", "哈哈"));
 
        for (int i = lists.size() - 1; i >= 0; i--) {
            if (lists.get(i).getId().equals("2")) {
                lists.remove(i);
            }
        }
 
        lists.forEach(item-> System.out.println(item));
    }
}

2. Карта

Карта — это интерфейс, в котором хранятся пары ключ-значение. Если ключ, хранящийся в карте, повторяется, значение будет перезаписано. Дублирование означает, что сравниваются методы хеш-кода и равенства. Если оба метода согласованы, это будет считаться дублированием.

  • Нижний слой HashMap в JDK1.8 — это (массив + связанный список + красно-черное дерево).
  • Данные хранятся в соответствии с хэш-кодом ключа.В большинстве случаев его значение можно найти напрямую, поэтому он имеет высокую скорость доступа.
  • Порядок прохождения не определен.
  • HashMap допускает, чтобы ключ одной записи был нулевым, а значение нескольких записей было нулевым.
  • HashMap не является потокобезопасным, то есть несколько потоков могут одновременно писать HashMap в любое время, что может привести к несогласованности данных.
     

Общие методы работы

  • put(): добавить данные.
  • get(key): Получить отдельные данные.
  • keySet(): Получить набор ключей.
  • values(): Получить набор всех значений.
  • enterSet(): получить все объекты записи (набор значений ключей).
  • isEmpty(): определяет, пуста ли коллекция.
  • size(): количество полученных данных.
  • удалить(ключ): удалить значение.


Карта включает в себя: HashMap, LinkedHashMap, TreeMap, Hashtable, ConcurrentHashMap.

Среди них Hashtable и ConcurrentHashMap являются потокобезопасными.

2.1 Хэш-карта:

  1. HashMap реализует интерфейс Map и наследует класс AbstractMap. Структура данных использует массив битовых сегментов, а нижний уровень использует для хранения связанный список или красно-черное дерево; связанный список становится красно-черным деревом.
  2.  Длина инициализации по умолчанию равна 16, а коэффициент загрузки расширения — 0,75. Как только он станет больше 0,75*16, будет вызвана функция resize() для увеличения емкости в 2 раза, то есть в 32;
  3. В JDK1.7 структура данных принимает: массив битовых сегментов + структуру связанного списка;
  4. В JDK1.8 структура данных использует: массив битовых сегментов + (связный список/красно-черное дерево);
  5. Поддерживает клонирование, беспорядок, рассинхронизацию потоков и отсутствие безопасности.

2.2 LinkedHashMap:

  1. LinkedHashMap реализует интерфейс Map и наследует класс HashMap;
  2.  Порядок итерации определяется атрибутом accessOrder. По умолчанию установлено значение false, что означает, что доступ осуществляется в порядке вставки; если установлено значение true, доступ к нему осуществляется в порядке последнего чтения (каждый раз, когда осуществляется доступ к элементу, элемент будет перемещен в конец связанного списка для удобства доступа в следующий раз, а структура будет все время меняться (то есть после get она переместится в конец).
  3.  Длина инициализации по умолчанию — 16, а коэффициент загрузки расширения — 0,75. Как только значение >0,75*16, будет вызвана функция resize() для расширения, что соответствует HashMap;
  4.  Поддерживает клонирование, упорядочивание, асинхронность потоков и небезопасность.

 2.3 Древовидная карта

TreeMap может сортировать ключи в коллекции.Во-первых, сами элементы сравниваются. Если элементы не являются сравнительными, вам необходимо сделать контейнер сравнительным.

Вам необходимо определить класс, реализующий интерфейс Comparator, переопределить метод сравнения и передать объект экземпляра подкласса интерфейса в качестве параметра конструктору коллекции TreeMap.

Примечание. Когда метод сравнения «Сравнительный» и метод сравнения «Компаратор» существуют одновременно, метод сравнения «Компаратор» является доминирующим.

  1. TreeMap реализует интерфейс NavigableMap и наследует класс AbstractMap;
  2.  Структура данных реализована на основе красно-черных деревьев;
  3. Карта сортируется в соответствии с естественным порядком ее ключей или в соответствии с компаратором, предоставленным при создании карты, в зависимости от используемого конструктора;
  4. Нет длины инициализации.
  5. Поддерживает клонирование, упорядочивание, асинхронность потоков и небезопасность.

2.4 Хэш-таблицы

  1. Hashtable реализует интерфейс Map и наследует класс Dictionary;
  2. Структура данных: это также хеш-таблица. Структура данных такая же, как у HashMap. Ни ключ, ни значение не могут быть нулевыми;
  3. Карта сортируется в соответствии с естественным порядком ее ключей или в соответствии с компаратором, предоставленным при создании карты, в зависимости от используемого конструктора;
  4. Длина инициализации по умолчанию равна 11, а коэффициент загрузки расширения равен 0,75. Как только >0,75*11, он будет расширен до 2n+1, что равно 23;
  5. Поддерживает клонирование, беспорядок, синхронизацию потоков и безопасность . Реализованный метод модифицируется ключевым словом Synchronized, то есть в методе реализована блокировка синхронизации .
  6. Поддерживает режим обхода перечисления.

2.5 ConcurrentHashMap

  • ConcurrentHashMap реализует интерфейс ConcurrentMap и наследует класс AbstractMap;
  • Длина инициализации по умолчанию равна 16, а коэффициент загрузки расширения — 0,75. Как только он станет больше 0,75*16, будет вызвана функция resize() для увеличения емкости в 2 раза, то есть в 32;
  • Структура данных: состоит из структуры массива сегментов и структуры массива HashEntry. ConcurrentHashMap содержит массив сегментов.
  • Структура Segment аналогична HashMap, который представляет собой структуру массива и связанного списка.
  • Используется технология сегментации блокировок.Сегмент представляет собой реентерабельную блокировку.Каждый сегмент имеет блокировку.При изменении данных массива HashEntry сначала необходимо получить соответствующую блокировку сегмента.
  • Клонирование, нарушение порядка, синхронизация потоков и безопасность не поддерживаются.

concurrentHashmap является потокобезопасным во всех версиях, но версия 1.8 внесла относительно большие изменения в свою реализацию, а именно: ConcurrentHashMap отменяет блокировку сегмента и использует CAS и синхронизируется для обеспечения безопасности параллелизма. Структура данных реализована в виде массива + связанного списка/красно-черного двоичного дерева. Когда количество узлов в связанном списке (корзине) превышает 8, оно будет преобразовано в красно-черную древовидную структуру данных для хранения.Цель этой конструкции — снизить эффективность чтения, когда один и тот же связанный список слишком сильно конфликтует. Synchronized блокирует только первый узел текущего связанного списка или красно-черного двоичного дерева. Таким образом, пока хэш не конфликтует, параллелизма не будет, а эффективность будет увеличена в N раз.

3. Установить

  1. Set — неупорядоченная коллекция.
  2. Повторяющиеся данные в наборе можно добавлять только один раз. JVM использует методы хеш-кода и равенства, чтобы определить, повторяются ли они. Если два метода возвращают один и тот же результат, он считается дубликатом. Сначала вызовите метод хеш-кода. Если возвращается несогласованы, метод Equals не будет вызываться. Если результаты согласованы, вызовите метод Equals. Затем метод Equals определяет, являются ли они согласованными. Следовательно, если вы добавляете объект сущности при его использовании, то объект сущности не должен иметь повторяющихся данных и должен перезаписать равные значения и хэш-код.
  3. Обход коллекции Set аналогичен обходу коллекции List, за исключением того, что он неупорядочен и его нельзя пройти с помощью обычного цикла for.

Итератор Итератор
— это не коллекция, а метод доступа к коллекциям List и Set.

Основные операции Iterator:

  • hasNext(): возвращает true, если еще есть элементы, которые можно повторить.
  • next(): возвращает следующий элемент итерации.
  • Remove(): удалить элемент, возвращенный итератором.

3.1 Хэшсет

  • Порядок, в котором HashSet хранит элементы, определяется не тем порядком, в котором они хранятся, а хеш-значением.
  • HashSet — типичная реализация интерфейса Set. Этот класс реализации используется большую часть времени при использовании коллекции Set.
  • HashSet хранит элементы в наборе в соответствии с алгоритмом Hash, поэтому имеет хороший доступ и производительность поиска. Базовая структура данных представляет собой хеш-таблицу (массив, элементы которого представляют собой связанные списки, сочетающие в себе преимущества массивов и связанных списков).
  • HashSet не синхронизирован;
  • Значение элемента коллекции может быть нулевым;


внутренний механизм хранения

  Когда элемент сохраняется в коллекции HashSet, HashSet вызывает метод hashCode объекта, чтобы получить значение hashCode объекта, а затем определяет место хранения объекта в HashSet на основе значения hashCode. Если есть два элемента, которые сравниваются с true с помощью метода Equals, но значения, возвращаемые их методами hashCode, не равны, HashSet сохранит их в разных местах и ​​их все равно можно будет успешно добавить.
То есть. Стандартом оценки двух элементов в коллекции HashSet является то, что два объекта равны с помощью метода Equals, а возвращаемые значения метода hashCode двух объектов также равны.

Положитесь на элемент, чтобы переопределить метод hashCode и метод Equals, чтобы определить, равны ли два элемента. Если они равны, исходный элемент будет перезаписан, чтобы гарантировать уникальность элемента.

3.2 Набор деревьев

Упорядоченный набор, который сортирует вновь добавленные элементы в указанном порядке. Объекты Integer и String можно сортировать по умолчанию, а пользовательские объекты должны реализовывать Comparable и переопределять соответствующий метод ComapreTo.

Два способа реализации сортировки:

1. Реализуйте интерфейс Comparable<T> в классе Student.


import java.util.Objects;
 
public class Student {
    private  String name;
    private Integer age;
 
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Если вы хотите изменить порядок, измените метод CompareTo на

 @Override
    public int compareTo(Student o) {
        //判断如果两个年龄都一样排序 如果不判断会缺失数据 如果年龄相同数据会直接丢失 感兴趣可以注释下面代码测试
        if(this.age -o.age==0){
            //要么返回正数要么返回负数 如果有id也可以根据id排序
            return  -1;
        }
        
        //按照年龄进行排序
        return o.age -this.age;
    }

2. Переопределить метод Compareto в интерфейсе Comparable.Этот метод переопределит (переопределит) метод CompareTo объекта сортировки Comparator, реализованного в сущности.


 
import java.util.Comparator;
import java.util.TreeSet;
 
public class TreeSetDemo02 {
	public static void main(String[] args) {
		TreeSet<Student> ts=new TreeSet<Student>(new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				//判断如果两个年龄都一样排序 如果不判断会缺失数据 如果年龄相同数据会直接丢失 感兴趣可以注释下面代码测试
				if(o2.getAge() -o1.getAge()==0){
					//要么返回正数要么返回负数 如果有id也可以根据id排序
					return  -1;
				}
 
				//按照年龄进行排序
				return o2.getAge() -o1.getAge();
			}
		});
		//创建元素对象
		Student s1=new Student("zhangsan",20);
		Student s2=new Student("lis",22);
		Student s3=new Student("wangwu",24);
		Student s4=new Student("chenliu",26);
		Student s5=new Student("zhangsan",22);
		Student s6=new Student("qianqi",24);
		
		//将元素对象添加到集合对象中
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		
		//遍历
		for(Student s:ts){
			System.out.println(s.getName()+"-----------"+s.getAge());
		}
	}
}

4. Как вы используете коллекции в сценариях с высоким уровнем параллелизма?

Потокобезопасные коллекции с высоким уровнем параллелизма в основном используют CAS (основа для реализации спин-блокировок), а нижний уровень в основном использует AQS для достижения потокобезопасной синхронизации.

AQS: полное имя — AbstractQueuedSynchronized. Это базовая реализация Lock в пакете JUC. AQS можно использовать для реализации многопоточных синхронизаторов.

  • Коллекция List использует классы JUC для обеспечения безопасности коллекции в ситуациях с высоким уровнем параллелизма. CopyOnWriteArrayList
  • Коллекция Set использует классы JUC для обеспечения безопасности коллекции в условиях высокого параллелизма. CopyOnWriteArraySet
  • Коллекции карт используют классы JUC для реализации безопасного для коллекций ConcurrenthashMap в условиях высокого параллелизма. Например: Map<String, String > map = new ConcurrenthashMap<>() 

おすすめ

転載: blog.csdn.net/ddwangbin520/article/details/131495795