Thinking logic of computer programs (51) and thinking 51

Source: Internet
Author: User
Tags addall union of sets

Thinking logic of computer programs (51) and thinking 51

The previous section describes EnumMap. This section also describes the implementation class EnumSet for the Set interface of the enumeration type. Similar to EnumMap, there is a special implementation class for enumeration types, mainly because it can implement the Set interface very efficiently.

The implementation class HashSet/TreeSet of the Set interface described earlier is implemented using the corresponding HashMap/TreeMap internally, but EnumSet is not. Its implementation has nothing to do with EnumMap, it is implemented with a very simplified and efficient bit vector. Bit vector is a common method to solve problems in computer programs. It is necessary for us to understand and master it.

In addition to the implementation mechanism, the usage of EnumSet is also somewhat different. In addition, EnumSet is a powerful tool for processing enumeration data. It is very convenient and efficient in some application fields.

Next, let's first look at the basic usage of EnumSet, then let's look at the EnumSet application in a scenario. Finally, let's analyze the implementation mechanism of EnumSet.

Basic usage

Unlike TreeSet/HashSet, EnumSet is an abstract class and cannot be created directly through new. That is to say, the following code is incorrect:

EnumSet<Size> set = new EnumSet<Size>();

However, EnumSet provides several static factory methods to create EnumSet objects, such:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)

The noneOf method creates an EnumSet with the specified enumeration type, without any elements. The actual type of the created EnumSet object is the subclass of EnumSet. We will analyze its implementation later.

For example, we define an enumeration Day that represents the Day of the week. The value ranges from Monday to Sunday, as shown below:

enum Day {    MONDAY, TUESDAY, WEDNESDAY,    THURSDAY, FRIDAY, SATURDAY, SUNDAY}

The noneOf method can be used as follows:

Set<Day> weekend = EnumSet.noneOf(Day.class);weekend.add(Day.SATURDAY);weekend.add(Day.SUNDAY);System.out.println(weekend);

Weekend indicates the day off, and the Set returned by noneOf is null. It adds Saturday and Sunday, so the output is:

[SATURDAY, SUNDAY]

There are many other static factory methods in EnumSet, as shown below (public static modification is omitted ):

// The initial set includes all enumeration values of the specified enumeration type <E extends Enum <E> EnumSet <E> allOf (Class <E> elementType) // The initial set includes <E extends Enum <E> EnumSet <E> range (E from, E) // The initial set includes the completion set of the specified set <E extends Enum <E> EnumSet <E> complementOf (EnumSet <E> s) // The initial set includes all the elements in the parameter <E extends Enum <E> EnumSet <E> of (E) <E extends Enum <E> EnumSet <E> of (E e1, E e2) <E extends Enum <E> EnumSet <E> of (E e1, E e2, E e3) <E extends Enum <E> EnumSet <E> of (E e1, E e2, E e3, E e4) <E extends Enum <E> EnumSet <E> of (E e1, E e2, E e3, E e4, E e5) <E extends Enum <E> EnumSet <E> of (E first, E... rest) // The initial set includes all elements in the parameter container <E extends Enum <E> EnumSet <E> copyOf (EnumSet <E> s) <E extends Enum <E> EnumSet <E> copyOf (Collection <E> c)

As you can see, EnumSet has a lot of methods in the form of overloading. The last one accepts variable parameters, and other overload methods seem redundant, other overload methods are available because variable parameters are less efficient.

Application scenarios

Next, let's look at the EnumSet application in a scenario.

Imagine a scenario where, in some jobs, such as doctors and customer service, not every staff member is working every day. The working hours for each person are different. For example, Michael may be on Monday or Wednesday, li Si may be on Thursday and Saturday. Given that everyone can work, we may have some questions to answer, such:

  • Will anyone ever come?
  • In which days will at least one person come?
  • In which days will at least two people come?
  • In which days will everyone come for a meeting?
  • Who will come on Monday and Tuesday?

With EnumSet, you can easily and efficiently answer these questions. How can this problem be solved? Let's first define a Worker class that represents the staff, as shown below:

class Worker {    String name;    Set<Day> availableDays;        public Worker(String name, Set<Day> availableDays) {        this.name = name;        this.availableDays = availableDays;    }        public String getName() {        return name;    }        public Set<Day> getAvailableDays() {        return availableDays;    }}

For ease of demonstration, put all staff information in an array workers, as shown below:

Worker [] workers = new Worker [] {new Worker ("James", EnumSet. of (Day. MONDAY, Day. TUESDAY, Day. WEDNESDAY, Day. FRIDAY), new Worker ("Li Si", EnumSet. of (Day. TUESDAY, Day. THURSDAY, Day. SATURDAY), new Worker ("Wang Wu", EnumSet. of (Day. TUESDAY, Day. THURSDAY )),};

Each employee's working time is represented by an EnumSet. With this information, we can answer the above questions.

Which days will one not come? The code can be:

Set<Day> days = EnumSet.allOf(Day.class);for(Worker w : workers){    days.removeAll(w.getAvailableDays());}System.out.println(days);

Days is initialized to all values, and then workers is traversed to delete all the time that can work from days. The rest is the time that no one will come, this is actually a complementary set in the worker time union. The output is:

[SUNDAY]

In which days will at least one person come? Is the union of worker time. The code can be:

Set<Day> days = EnumSet.noneOf(Day.class);for(Worker w : workers){    days.addAll(w.getAvailableDays());}System.out.println(days);

Output:

[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]

In which days will everyone come? Is the intersection of worker time. The code can be:

Set<Day> days = EnumSet.allOf(Day.class);for(Worker w : workers){    days.retainAll(w.getAvailableDays());}System.out.println(days);

Output:

[TUESDAY]

Who will come on Monday and Tuesday? Use the containsAll method. The code can be:

Set<Worker> availableWorkers = new HashSet<Worker>();for(Worker w : workers){    if(w.getAvailableDays().containsAll(            EnumSet.of(Day.MONDAY,Day.TUESDAY))){        availableWorkers.add(w);    }}for(Worker w : availableWorkers){    System.out.println(w.getName());}

Output:

Zhang San

In which days will at least two people come? We first use EnumMap to count the number of people every day, and then find the day with at least two people. The code can be:

Map<Day, Integer> countMap = new EnumMap<>(Day.class);for(Worker w : workers){    for(Day d : w.getAvailableDays()){        Integer count = countMap.get(d);        countMap.put(d, count==null?1:count+1);    }}Set<Day> days = EnumSet.noneOf(Day.class);for(Map.Entry<Day, Integer> entry : countMap.entrySet()){    if(entry.getValue()>=2){        days.add(entry.getKey());    }}System.out.println(days);

Output:

[TUESDAY, THURSDAY]

After understanding how to use EnumSet, let's see how it is implemented.

Implementation Principle

Bit Vector

EnumSet is implemented using bitvectors. What is a bitvector? A single bit represents the state of an element, and a group of BITs represents the state of a set. Each bit corresponds to one element, and there are only two States.

For the previous enumerated class Day, It has seven enumerated values. A set of days can be expressed in one byte. The maximum bit is not used and is set to 0, the rightmost bits correspond to the smallest enumerated values, from right to left, each corresponding to an enumerated value. 1 indicates that this element is included, and 0 indicates that this element is not included.

For example, it indicates a set of values including Day. MONDAY, Day. TUESDAY, Day. WEDNESDAY, and Day. FRIDAY. The bitvector graph structure is as follows:


The corresponding integer is 23.

The number of elements expressed in BITs is related to the vector length. A byte type can represent 8 elements, and a long type can represent 64 elements. What is the length of EnumSet?

EnumSet is an abstract class that does not define the vector length. It has two subclasses: RegularEnumSet and JumboEnumSet. RegularEnumSet uses a long type variable as the bit vector. The length of the long type is 64, and JumboEnumSet uses a long type array. If the number of enumerated values is less than or equal to 64, the RegularEnumSet created in the static factory method is JumboEnumSet.

Internal components

After understanding the basic concepts of bit vectors, let's look at the implementation of EnumSet. Like EnumMap, it also has instance variables that represent type information and all enumeration values, as shown below:

final Class<E> elementType;final Enum[] universe;

ElementType indicates the type information, and universe indicates all enumeration values of the enumeration class.

The EnumSet itself does not record the variable of the number of elements, nor does it have bitvectors. They are maintained by subclasses.

For RegularEnumSet, it uses a long type to represent the bitvector. The code is:

private long elements = 0L;

It does not define a variable that represents the number of elements and is calculated in real time. The calculation code is:

public int size() {    return Long.bitCount(elements);}

For JumboEnumSet, it is represented by a long array and has a separate size variable. The code is:

private long elements[];private int size = 0;

Static factory Method

Let's take a look at the static factory method noneOf of EnumSet. The code is:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {    Enum[] universe = getUniverse(elementType);    if (universe == null)        throw new ClassCastException(elementType + " not an enum");    if (universe.length <= 64)        return new RegularEnumSet<>(elementType, universe);    else        return new JumboEnumSet<>(elementType, universe);}

The code for getUniverse is the same as the EnumMap described in the previous section. If the number of elements cannot exceed 64, create RegularEnumSet. Otherwise, create JumboEnumSet.

RegularEnumSet and JumboEnumSet are constructed as follows:

RegularEnumSet(Class<E>elementType, Enum[] universe) {    super(elementType, universe);}JumboEnumSet(Class<E>elementType, Enum[] universe) {    super(elementType, universe);    elements = new long[(universe.length + 63) >>> 6];}

They all call the constructor of the parent class EnumSet. The code is:

EnumSet(Class<E>elementType, Enum[] universe) {    this.elementType = elementType;    this.universe    = universe;}

Assign a value to the instance variable. JumboEnumSet allocates a long array of sufficient length based on the number of elements.

Other Factory Methods call noneOf to construct an empty set and then call the add method. Let's look at the add method.

Add Element

The code for the add method of RegularEnumSet is:

public boolean add(E e) {    typeCheck(e);    long oldElements = elements;    elements |= (1L << ((Enum)e).ordinal());    return elements != oldElements;}

The main code is bitwise OR operation:

elements |= (1L << ((Enum)e).ordinal());

(1L <(Enum) e). ordinal () sets the bits corresponding to element e to 1, which is equal to or from the existing bit vector elements, indicating that e is added. From the perspective of set theory, this is the union of sets.

The code for the add method of JumboEnumSet is:

public boolean add(E e) {    typeCheck(e);    int eOrdinal = e.ordinal();    int eWordNum = eOrdinal >>> 6;    long oldElements = elements[eWordNum];    elements[eWordNum] |= (1L << eOrdinal);    boolean result = (elements[eWordNum] != oldElements);    if (result)        size++;    return result;}

The difference between the add method and RegularEnumSet is that it first finds the corresponding array location. eOrdinal >>> 6 is the eOrdinal divided by 64, and eWordNum indicates the array index. With the index, other operations are similar to RegularEnumSet.

JumboEnumSet has a similar idea for other operations. The main algorithm is the same as RegularEnumSet. It mainly adds operations to search for the corresponding long-bit vector or some cyclic processing, the logic is also relatively simple. Later, we will only introduce the implementation of RegularEnumSet.

The code for the addAll method of RegularEnumSet is:

public boolean addAll(Collection<? extends E> c) {    if (!(c instanceof RegularEnumSet))        return super.addAll(c);    RegularEnumSet es = (RegularEnumSet)c;    if (es.elementType != elementType) {        if (es.isEmpty())            return false;        else            throw new ClassCastException(                es.elementType + " != " + elementType);    }    long oldElements = elements;    elements |= es.elements;    return elements != oldElements;}

If the type is correct, it is bitwise OR operation.

Delete Element

The code of the remove Method is:

public boolean remove(Object e) {    if (e == null)        return false;    Class eClass = e.getClass();    if (eClass != elementType && eClass.getSuperclass() != elementType)        return false;    long oldElements = elements;    elements &= ~(1L << ((Enum)e).ordinal());    return elements != oldElements;}

The main code is:

elements &= ~(1L << ((Enum)e).ordinal());

~ This Code sets the bitwise of Element e to 0, so that the deletion is completed.

From the point of view of the set theory, remove is to find the difference of the set, the A-B is equivalent to A between B ', B' represents the complement set of B. In the code, elements is equivalent to A, (1L <(Enum) e). ordinal () is equivalent to B ,~ (1L <(Enum) e). ordinal () is equivalent to B ', elements & = ~ (1L <(Enum) e). ordinal () is equivalent to A between B ', that is, A-B.

Check whether an element exists.

The contains method code is:

public boolean contains(Object e) {    if (e == null)        return false;    Class eClass = e.getClass();    if (eClass != elementType && eClass.getSuperclass() != elementType)        return false;    return (elements & (1L << ((Enum)e).ordinal())) != 0;}

The Code is also very simple. bitwise AND operation. If it is not 0, it indicates inclusion.

Check whether all elements in the set are included

The containsAll method code is:

public boolean containsAll(Collection<?> c) {    if (!(c instanceof RegularEnumSet))        return super.containsAll(c);    RegularEnumSet es = (RegularEnumSet)c;    if (es.elementType != elementType)        return es.isEmpty();    return (es.elements & ~elements) == 0;}

The last bit operation is a bit obscure. From the perspective of set theory, containsAll is to check whether the set represented by parameter c is a subset of the current set. Generally, Set B is the subset of set A, that is, B Branch A. equivalent to a' Branch B is an empty set, and A' represents the complement set of A, as shown in:


In the code above, elements is equivalent to A and es. elements is equivalent to B ,~ Elements is equivalent to the complement set of A, (es. elements &~ Elements) = 0; it is to verify that a' primary B is an empty set, that is, whether B is A subset of.

Only elements in the parameter set are retained.

The code of the retainAll method is:

public boolean retainAll(Collection<?> c) {    if (!(c instanceof RegularEnumSet))        return super.retainAll(c);    RegularEnumSet<?> es = (RegularEnumSet<?>)c;    if (es.elementType != elementType) {        boolean changed = (elements != 0);        elements = 0;        return changed;    }    long oldElements = elements;    elements &= es.elements;    return elements != oldElements;}

From the perspective of set theory, this is the intersection of sets. Therefore, the main code is bitwise AND operation, which is easy to understand.

Complement

The static factory method complementOf of EnumSet is to find the complementary set, and the code called is:

void complement() {    if (universe.length != 0) {        elements = ~elements;        elements &= -1L >>> -universe.length;  // Mask unused bits    }}

This code is a bit obscure, elements = ~ Elements is easier to understand, that is, bitwise inversion, which is equivalent to the complement set. But we know that elements is 64-bit, And the enumeration class may not use so many digits at present, after the result is reversed, the high part is changed to 1 and the universe is exceeded. length is set to 0. The following code is used to do this:

elements &= -1L >>> -universe.length; 

-1L is a 64-bit binary with a full value of 1. In the Integer Analysis Section, we introduced the situation where the number of digits to be moved is negative. The above code is equivalent:

elements &= -1L >>> (64-universe.length); 

If universe. if the length is 7,-1L >>> (64-7) is the binary 1111111. The elements and elements are beyond the universe. the 57 digits on the right of the length part are all changed to 0.

Implementation principle Summary

The above is the basic implementation principle of EnumSet. The bit vector is used internally, which indicates that it is concise and space-saving. Most operations are bitwise operations with high efficiency.

Summary

This section describes the usage and implementation principles of EnumSet. In terms of usage, EnumSet is a powerful tool for processing enumeration-type data. It is simple and convenient. In terms of implementation principle, it uses bitvectors to simplify and improve efficiency.

For data in only two States that require a set operation, bit vectors are used for representation and bit operations. This is a common way of thinking in computer programs.

So far, we have finished introducing the specific container class. There are some outdated container classes in the Java container class, as well as some less commonly used classes, we will not introduce them.

During the introduction of specific container classes, we ignored the implementation details, that is, all the container classes are not actually built from scratch, and they all inherit some abstract container classes. These abstract classes provide partial implementation of container interfaces, facilitating the implementation of specific Java container classes. If we need to implement a custom container class, we should also consider inheriting from these abstract classes.

So, What abstract classes are specific? What basic functions do they provide? How can we scale it out? Let's discuss it in the next section.

----------------

For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.