Обновить список из другого списка - PullRequest
3 голосов
/ 12 марта 2010

У меня есть список пользователей в локальном хранилище, которые мне нужно обновлять из удаленного списка пользователей время от времени. В основном:

  1. Если удаленный пользователь уже существует локально, обновите его поля.
  2. Если удаленный пользователь еще не существует локально, добавьте пользователя.
  3. Если локальный пользователь не отображается в удаленном списке, отключите его или удалите.
  4. Если локальный пользователь также появляется в удаленном списке, обновите его поля. (То же, что 1)

Например. Удаленный список: Пользователь (1, правда), Пользователь (2, правда), Пользователь (4, правда), Пользователь (5, правда)

Локальный список: Пользователь (1, истина), Пользователь (2, ложь), Пользователь (3, истина), Пользователь (6, истина)

Новый локальный список: Пользователь (1, истина), Пользователь (2, истина), Пользователь (3, ложь), Пользователь (4, истина), Пользователь (5, истина), Пользователь (6, ложь),

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

public class User {
    Integer id;
    String email;
    boolean active;

    //Getters and Setters.......

    public User(Integer id, String email, boolean active) {
        this.id = id;
        this.email = email;
        this.active = active;
    }

    @Override 
    public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof User) {
            User that = (User) other;
            result = (this.getId() == that.getId());
        }
        return result;
    }

}




public static void main(String[] args) {

    //From 3rd party
    List<User> remoteUsers = getRemoteUsers();

    //From Local store
    List<User> localUsers =getLocalUsers();     

    for (User remoteUser : remoteUsers) {
        boolean found = false;
        for (User localUser : localUsers) {
            if (remoteUser.equals(localUser)) {
                found = true;
                localUser.setActive(remoteUser.isActive());
                localUser.setEmail(remoteUser.getEmail());
                //update
            } 
            break;
        }
        if (!found) {
            User user = new User(remoteUser.getId(), remoteUser.getEmail(), remoteUser.isActive());
            //Save
        }
    }

    for(User localUser : localUsers ) {
        boolean found = false;
        for(User remoteUser : remoteUsers) {
            if(localUser.equals(remoteUser)) {
                found = true;
                localUser.setActive(remoteUser.isActive());
                localUser.setEmail(remoteUser.getEmail());
                //Update
            }
            break;
        }
        if(!found) {
            localUser.setActive(false);
            // Deactivate
        }
    }
}

Ответы [ 3 ]

7 голосов
/ 12 марта 2010

Лучший способ - это переключиться на другую структуру данных. A Map<Integer, User> будет лучшим, потому что предположительно пользователи имеют уникальные идентификаторы. Вы можете выбрать реализацию Map: HashMap (ожидается O(1) для основных операций) или TreeMap (O(log N)).

ВАЖНО: Вы @Override equals(Object) без @Override hashCode() !!! Это опасно ! Вы должны всегда иметь привычку переопределять ни то, ни другое! (см .: Переопределение equals и hashCode в Java )

Итак, допустим, у вас есть Map<Integer, User> remoteUsers и Map<Integer, User> localUsers.

1.) Если удаленный пользователь уже существует локально, обновите его поля.
4.) Если в удаленном списке также появляется локальный пользователь, обновите его поля. (аналогично 1)
2.) Если удаленный пользователь еще не существует локально, добавьте пользователя.

Нахождение, если User из remoteUsers находится в localUsers, можно ответить в O(1) или O(log N) с помощью простых containsKey и get.

for (int id : remoteUsers.keys()) {
   User local;
   if (localUsers.containsKey(id)) {
      local = localUsers.get(id);
   else {
      localUsers.put(id, local = new User(id));
   }
   local.updateFrom(remoteUsers.get(id));
}

3.) Если локальный пользователь не отображается в удаленном списке, отключите его или удалите.

Следующее решение показывает, насколько мощными могут быть эти более сложные структуры данных:

Set<Integer> toDeactivate = new TreeSet<Integer>();
toDeactivate.addAll(localUsers.keySet());
toDeactivate.removeAll(remoteUsers.keySet());

for (int id : toDeactivate) {
   User local = localUsers.get(id);
   local.deactivate();
   localUsers.remove(id);
}

Кроме того, если вы застряли с List<User>, вы все равно можете использовать Map<Integer, User> в качестве промежуточной структуры данных для этой обработки (в основном преобразуйте List<User> в Map<Integer, User> и затем обратно в List<User>). Это будет все еще быстрее, так как это O(N log N) или O(N), по сравнению с O(N^2), который у вас есть прямо сейчас.

Если вы настаиваете на использовании только списков, то вы можете захотеть сделать из этого список Collections.sort, так что вы можете сделать с ним Collections.binarySearch. Вам нужно будет предоставить Comparator<User> или сделать User implements Comparable<User>, естественно заказывая по id. Это тоже будет O(N log N).

1 голос
/ 13 марта 2010

Langali:

Предполагая, что идентификатор уникально идентифицирует пользователя, у меня есть несколько предложений для вас:

  • Создайте класс User.Key (внутренний класс вашего класса User) и переместите туда поле id. Сделай это окончательно. Переопределите метод hashcode и equals в классе User.Key, просто используя идентификатор:
    public User {
       private final Key key;
       ... other variables

       public static class Key {
       private final int id;
          public Key(final int id) {

          }
          // hashcode (can be the id)
          // equals (as you had implemented)
       }
    }
  • Создайте карту для размещения ваших пользователей.
    Map<User.Key, User>
    ;
  • Используйте эту карту для удержания ваших пользователей, а затем используйте методы get и containsKey, чтобы найти то, что вы ищете.

Проблема со списком List.contains заключается в том, что в ArrayList он выполняет полное сканирование содержимого списка. Если вы делаете это для каждого элемента второго списка, ваша производительность равна O (n ^ 2), что означает, что, когда вы удваиваете элементы, вы умножаете на четыре время, необходимое для запуска вашего метода. HashMap имеет производительность O (log (n)), что означает, что при использовании 1000 объектов время, необходимое для его запуска, будет всего в 10 раз медленнее (приблизительно).

1 голос
/ 12 марта 2010

Вы можете использовать List.indexOf() вместо итерации по списку:

for (User remoteUser : remoteUsers) {
    int index = localUsers.indexOf(remoteUser);
    if (index >= 0) {
        User localUser = localUsers.get(index);
        localUser.setActive(remoteUser.isActive());
        localUser.setEmail(remoteUser.getEmail());
        //update
    } else {
        User user = new User(remoteUser.getId(), remoteUser.getEmail(), remoteUser.isActive());
        //Save
    }
}
...